徐汇区“一网统管”——12345热线分析
项目介绍——2020年6月-2020年9月
业务:数据可视化大屏看板,将百姓通过12345市长热线投诉的问题,借助图、表、地图等形式以可视化的方式分不同维度展示,帮助政府部门作出分析研判。
技术:基于Vue2+TypeScript+iView+Webpack开发的单页面应用
我的职责
- 后期的前端责任人,9月驻场开发
- 用最小的成本,处理现场频繁变更的需求(忘记具体问题)
- 与GIS现场调优,对业主提出的热力聚合效果进行打磨
- 与第三方公司研发沟通,处理项目间通信
- 针对多分辨率,对内嵌的移动端位置做响应式处理
- pdf主题色,css滤镜
- 大屏适配方案
我做了哪些
大屏适配方案
Situation背景
一般大屏适配是这样处理的,设计稿的宽高是40961080。取当前屏幕分辨率下根元素documentElement的宽高(clientWidth、 client Hei ght )。以根元素的高度为基准,求根元素高度与设计稿高度的比值。设置根元素的height为clientHeight,设置根元素的width为设计稿width乘以比值,以保持宽高比不变。设置根元素fontSize值为100比值作为rem的参考值。此时如果在40961080,页面刚好铺满。如果在小屏上,则高度不变,横向会出现滚动条。
然而部署到现场有两个问题,一是现场演示所用的屏幕不全是40961080的,有的屏幕比这个尺寸大的多,因此会在个别分辨率的屏幕上出现滚动条;二是现场要求在笔记本这种小屏上也不能出现滚动条,需要保持大屏尺寸类似于图片的object-fit:contain的效果,上下两侧留白。
Task任务
- 要求适配其他分辨率的大屏,不能出现滚动条
- 对于小屏,应该保持宽高比,居中显示,上下两侧留白
Action行动
- 忽略设计稿尺寸,当文档元素的clientWidth大于1920时,文档元素、body元素、#app元素的宽高都为100%。文档元素的fontSize的计算逻辑不变
- 当文档元素的clientWidth小于1920时,此时将#app元素的宽高设置为设计稿的宽高。为了保证大屏能完整展示,以宽度为基准,求根元素clientWidth与设计稿宽度的比值。并以该比值作为transform: scale的值对大屏进行整体缩放。注意此时根元素的fontSize值始终为100px。在body中使用flex布局,将#app在body中垂直居中。
Result结果
监听文档视图调整的resize事件,当自适应功能开启后,根据当前窗口下的文档元素clientWidth展示不同类型的视角。
技术要点
- resize事件
- addEventListener方法,都有接受哪些参数,有什么区别,DOM事件的捕获与冒泡
- clientWidth offsetWidth scrollWidth都有啥区别
- flex布局
- Echarts中的文字使用的是px,超大分辨率下如何保证文字正常显示?
- rem px em % 的含义
项目间通信
:::info 重点体现做事的主观能动性。关键词:任务优先级、主动推进
一个需要第三方协助的需求,从提出到对接完成只用0.5天。 ::: Situation背景
项目大屏是使用iframe嵌在第三方公司产品(监督指挥)上的。其中项目大屏菜单中有两个模块是第三方公司提供,我们需要在点击菜单时与对方通信。因此有两个问题:
- 跨iframe通信
- 如何联调
Task任务
- 跨iframe与父页面通信
- 与第三方公司研发沟通联调功能
Action行动
- 梳理需求,明确采用postMessage的技术方式向父级页面通信
- 由于是在现场开发,主动找到第三方公司的研发负责人,描述需要协助处理的内容及处理方式。与他指定的研发进一步沟通,明确通信内容
Result结果
从接到需求,到明确解决方案完成对接总共花了不到半天时间。技术方面并不复杂,但是涉及到与第三方公司,越是这种需要第三方配合的工作,越要尽早提出,主动推进。不然拖到最后加班的还是自己。
技术要点
- postMessage
- iframe
- 跨域的几种方式
- 安全与隐私
- CSP(Content Security Policy)
- CORS
- XSS
- CSRF
- MITM
- Samesite
PDF加载与羊皮卷效果实现(不说了这个)
Situation背景
PDF文件使用iframe加载,业主觉得白色太刺眼,需要把文件背景调整偏黄色的这种暖色。
Task任务
- 将iframe背景改成暖黄色
Action行动
- 使用css中的filter滤镜
Result结果
PDF文件直接使用iframe加载,羊皮卷效果通过css的filter属性实现filter: invert(.2) sepia(60%) saturate(100%);
技术要点
- filter
兼容性
- Can I Use
- polyfill
- shim
- browserslist
- Autoprefixer
城市运行中心
项目介绍——2019年7月——2021年2月
业务:城市运行数据大屏看板,将城市中城管、市政、环卫、园林、执法、道桥等场景中的相关数据借助图表、地图等形式以可视化的方式分不同维度展示,帮助政府部门作出分析研判。
技术栈:Vue+TypeScript+Axios+iView+Webpack 多应用模式,单页面形式打包,可视化图表以Echarts为主我的职责
常规的大屏卡片组件开发
地图相关功能开发主要包括:图层控制菜单、图层搜索、人车轨迹面板等我做了哪些
地图图层搜索
:::info 任务描述:
表层事实:(用了哪些新技能或工具)
为了完成该任务,在根据不同图层类型获取图层搜索相关数据方面,为了减少工作量,使匹配策略易维护、易扩展。我使用了Map结构,以正则作为key去匹配对应图层数据。为了解决不同图层的搜索结果样式相同字段不同的问题,固定了搜索结果列表组件的HTML和CSS样式,引入字段映射配置文件,利用字段解析工具函数解析映射后的数据。
关键词:Map、正则表达式、字段映射、字段解析
深度细节:
如何完成任务的
定义了一个返回一个Map结构的函数,Map中每一项元素的key为正则形式,value是一个接收图层code和关键字参数的函数。调用函数后,过滤出符合正则规则的项。然后再调用value函数根据传参获取搜索项的label、placeholder、keyword等数据。定义不同图层的字段映射文件该文件导出一个对象,对象中是图层code与图层字段映射对象组成的键值对,在HTML中根据图层code去匹配对应的字段映射对象。给Object增加解析字段方法Object.getValue(),该方法接受一个对象,以及一个映射字段字符串(a.b.c),返回映射字段对应的值。
- 同事间如何沟通协作的
感受和观点:
- (成就感)简化了图层搜索功能的开发流程
对于新增的A图层,给它加图层搜索功能只需要两步,一是增加该图层的字段映射对象。二是在Map中增加新的策略以供搜索使用。如果该图层是某个图层的子类,比如改图层是渣土车图层,如果已经有车辆图层,就只需要增加字段映射对象1步即可。
(完成任务的关键)平时看博客与学习组长的代码
会看一些文章,使用Map这种形式就是从文章中学习到的。统一数据结构,解析映射字段的灵感来自组长的代码,他在别的场景中用到了,当时我还去调试了字段解析方法的逻辑。 ::: Situtaion背景
GIS地图图层数据搜索功能,不同项目、不同专题都有不同的图层。图层数据量往往很大,分布在城市的各个地方,用户想精确查找某个数据时很不方便,因此需要在大屏中增加图层数据搜索功能。这里存在两个问题:
- 选择搜索不同的图层数据有三个地方不同:一是搜索的关键字,比如人员是根据 name字段搜索,车辆是根据车牌号搜索等;二是placeholder不同,必须选择了搜索人员图层,placeholder应该提示“请输入人员姓名”;三是label标签不同,选择了人员图层,label中会显示”人员“二字。因此,需要根据不同图层类型去匹配不同的数据。使用什么样的模式能让代码更加清晰、方便接入,易维护呢?(可用 ifelse switch object Map)
- 搜索结果列表的视图展示。不同图层搜索出的结果不同,但是HTML结构和CSS样式相同。单是因为图层字段不同,给每个图层都写一遍同样的HTML显然不太合理。那么,如何在保证使用同一套HTML和CSS结构的情况下,展示不同图层的不同数据呢?
Task任务
- 贴合业务选择合适的策略模式,根据不同图层类型,匹配不同的搜索相关数据(label placeholder searchKey);
- 统一HTML和CSS结构,展示不同图层数据;
Action行动
- 对于问题1。使用Map数据结构,使用正则当做key。这样同类型图层,比如车辆图层、渣土车、环卫车都可以使用同一套逻辑。同时,value返回一个函数,可以根据传入的key获取值,也可以根据layerCode做其他逻辑。
```javascript
function actions() {
const vehicleSearch = (layerCode,key) => {
// 可根据layerCode做不同处理,比如都是车,
// 有的label需要显示具体类型的车辆,比如消防车,则可以根据layerCode单独处理
const vehicle = {
}; if (!vehicle.hasOwnProperty(key)) {label: "车辆",
placeholder: "请输入车牌号",
key: "plateNo",
} return vehicle[key]; }; return new Map([[/^layervehicle(|\w+)$/g, vehicleSearch]]); }console.error("对象中不存在" + key + "属性,请检查!");
return;
function getSearchConfig(layerCode, configKey) { const action = […actions()].filter(([key, value]) => key.test(layerCode)); return action.map(([key, value]) => value(layerCode, configKey)).join(“”); } const label = getSearchConfig(“layer_vehicle_fire”, “label”); console.log(label); // 车辆
- 对于问题2。固定HTML结构和CSS样式,增加数据映射配置映射功能,根据图层的code从配置文件中找到展示信息对应的配置。开发配置解析工具,对配置进行解析。
- 数据映射文件
```javascript
export const configData: any = {
layer_records: {
icon: "icon-file",
title: {
name: "taskNo",
text: "案件"
},
status: {
name: "pageStatus",
text: ""
},
data: [
{
name: "subType.text",
text: "类别"
},
{
name: "address",
text: "地点"
},
{
name: "timestamp",
text: "时间"
}
]
}
};
- 固定的HTML和CSS
```html
{{configData[layerCode].title.text}}
{{Object.getValue(item, configData[layerCode].title.name)}}
- 开发getValue方法,取出映射字段的值。支持深度取值,比如"a.b.c.d"。大概思路就是将需要解析的name根据"."拆分成数组。然后循环从对象中寻找值。
**Result结果**
1. 将搜索功能开发精简到1步。对于同种类型的图层比如渣土车图层,开发搜索功能只需要新增数据映射即可。
2. 如果遇到新增的类型,比如视频图层,只需要在Map中新增它的key和value。不会对其他类型造成影响。
3. 配置化思想,低代码雏形。日后配置文件可以在平台生成
<a name="mS02k"></a>
### 人车轨迹面板
:::info
以前:用户选日期 时间段查询轨迹。两个问题:
1. 使用麻烦;有时候这个时间段没有轨迹数据,只能换下个时间段去查,没有就再换时间段
2. 排查麻烦;现场看到没有数据直接找前端排查,前端看接口发现没有轨迹数据,再反馈给现场
过程:排查该类问题出现多次,再加上用户使用确实麻烦。主动找产品,看是否出一个新的轨迹面板。该面板能将一天内的车轨迹数据可视展示,用户在选择时间时能做到有的放矢。<br />现在:轨迹面板中存在一个带时间刻度的面积图,面积图时间刻度下方有个slider双向滑块。用户根据面积图中轨迹数据,使用slider选择时间段。
:::
**Situation背景**<br />人车轨迹数据不是每个时间段都有,以前用户只能盲选时间,如果没有轨迹数据就换其他时间,在使用上很不方便。因此,需要一个可视化的面板,将一天中人车轨迹数据展示出来,让用户知道哪个时间段有数据,配合slider滑块,选择对应时间段的时间查询轨迹。<br />**Task任务**
1. 实现一个带时间刻度的面积图,展示一天中包含轨迹数据的时间段
2. 使用slider双向滑块选择时间段查询
**Action行动**
1. 实现面积图
- 后台数据只返回有轨迹数据的时间段,不包含所有时间。因此需要先生成一个 时间数组,这个数组包含 一天 24个小时的原始值。此外为了保证时间刻度的完整性,最后一个数据是第二天零点的原始值。
- 新建一个type为line的series,将时间数组中的时间原始值作为x轴的数据。将时间数组的值作为data赋值给series。这样就能在echarts中展示出一个包含 24个小时的时间轴,只是数据为空。
- 新建一个type为line的series,将后台返回的数据进行处理,将日期处理成 时间原始值。可以用moment也可以用Date.prototype.valueOf()。将处理后的值赋值给该series的data。此时就实现了一个能展示一天中轨迹数据的echarts图。
```javascript
time25 = [];
function time25Format() {
const date = new Date();
const year = date.getFullYear();
const month = date.getMonth() + 1;
const day = date.getDate();
const formatDate = `${year}-${month}-${day}`;
for (let i = 0; i < 24; i++) {
const curTimestamp = new Date(`${formatDate} ${i}:00:00`).valueOf();
time25.push({
name: curTimestamp,
value: [curTimestamp, '']
});
}
// 3600000 是一个小时的毫秒数
time25.push({
name: new Date(`${formatDate} 23:00:00`).valueOf() + 3600000,
value: [new Date(`${formatDate} 23:00:00`).valueOf() + 3600000, '']
});
}
time25Format();
- 实现刻度(这个亮点gg了,特么的echart自带次刻度线
** minorTick **
就能实现,绝了,不复盘的结果就是用复杂的方法实现了简单效果,还沾沾自喜。)不过这个次刻度线是4.6.0版本提出的,如果没有这个功能,我的方法倒是可以polyfill一下(算是心里安慰吧)
- x轴增加一个类目轴
- 新增一个type为bar的series。利用柱状图实现时间刻度。只是柱状图中的数据均为负值。
- 那一共有多少根柱子呢?根据设计图,整点的刻度长一点,整点之间的分钟刻度短一点。一天有24个小时,就是有24个整点,为了保证刻度完成,还需要追加一个第二天的零点作为最后一根柱子。因此 一共有25个整点。两个整点之间的分钟刻度有三个,表示每隔15分钟显示一根柱子 xx:15 \xx:30 \xx:45 因此25个整点之间存在24个区块,每块有3个分钟刻度,因此一共有 24*3=72个分钟刻度。加上整点刻度就是97个。
- 整点和分钟刻度的长度如何确定?采用负值,比如整点刻度为-10,分钟刻度为-5
Result结果let timeScale = [];
for (let i = 0; i < 97; i++) {
const value = i % 4 === 0 ? -10 : -5;
timeScale.push(value);
}
就是这个样子,挺好用的。