恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
大屏数据可视化实战:疫情监控系统的地图下钻与实时刷新方案
首页
资讯中心
/
大屏数据可视化实战:疫情监控系统的地图下钻与实时刷新方案
大屏数据可视化实战:疫情监控系统的地图下钻与实时刷新方案
发布时间:2026/10/15 17:06:28
接到一个“大屏数据可视化-疫情监控系统2-web程序开发”的项目我第一反应是这活儿看着不复杂但坑是真不少。如果你马上就要接手类似的大屏可视化开发或者正在纠结怎么从零搭一个能实时刷新、有地图下钻、还有动态轮播的监控面板这篇东西应该能帮你省下至少两天的摸索时间。我会按照实际动手的顺序把我踩过的坑、验证过的方案、以及最终跑通的代码逻辑一一写出来。内容有点长但保证每一节都是可以直接拿到自己项目里用的干货。2. 项目整体拆解这类“监控大屏”到底在做什么先说结论大屏数据可视化的核心不在“屏”而在“数据怎么流动、图表怎么不卡、布局怎么不变形”。疫情监控系统这类项目尤其典型因为它既要有全局态势全国/各省总览又要有时序变化趋势曲线还要有空间分布地图着色更要有实时感刷新频率、动态滚动。从技术栈选型开始我最终定了这套方案前端Vue 3 Vite ECharts 5外加一个用于水平滚动的自定义组件。状态管理Pinia用来统一管理全局筛选条件、时间范围、模拟数据源。大屏适配以 1920x1080 为设计基准用 vw/vh 为单位做整体缩放关键图表用百分比配合 rem 做自适应。数据模拟Node.js 写了一套简单的 Mock 服务能模拟指标变化、按省份返回数据支持随机扰动和局部爆发模拟。构建部署Nginx 托管静态资源接口单独跑在 8080 端口通过反向代理解决跨域。有的项目喜欢用 DataV 或 Sugar 这类低代码平台确实省事但一旦需求是“高度定制化的交互 复杂联动刷新”还是自己写更可控。特别是像疫情监控这种数据维度和筛选条件都要灵活变化的地图下钻场景低代码平台做起来反而别扭。这个项目的核心难点有三个地图下钻与返回、实时数据刷新不闪烁、大屏在不同分辨率下的不变形。把这三个问题解决掉整个面板就立住了。3. 数据来源与服务层设计3.1 接口结构怎么定前后端才好联调大屏项目最忌讳的事情就是等到后端接口返回的字段不对才去改前端页面。我习惯在项目动工的第一天就把接口契约定死哪怕用的是 Mock 数据。这套疫情监控系统我设计了下面几个核心接口前端所有的图表都基于这几个接口的数据形状来做渲染// GET /api/overview 顶部汇总指标 { code: 0, data: { confirmed: 10245, suspected: 863, cured: 9201, dead: 104, timestamp: 1712052000000 } } // GET /api/trend?days30 时序趋势 { code: 0, data: { days: [04-01, 04-02, ...], confirmed: [102, 115, ...], suspected: [20, 18, ...], cured: [8, 12, ...], dead: [1, 0, ...] } } // GET /api/province 地图数据 { code: 0, data: [ { name: 北京市, confirmed: 235, cured: 210, dead: 2 }, { name: 广东省, confirmed: 1890, cured: 1800, dead: 8 } ] } // GET /api/city?province广东省 下钻到城市 { code: 0, data: [ { name: 广州市, confirmed: 620, cured: 590, dead: 3 }, { name: 深圳市, confirmed: 710, cured: 690, dead: 2 } ] }接口字段一定要提前统一时间戳用毫秒还是秒、字符串还是 number、空值怎么处理这些细节最容易在联调时炸雷。尤其疫情数据这种字段很多的项目建议文档里直接写明“null 表示暂未统计0 表示无病例”避免前端拿到 null 渲染出 undefined。3.2 Mock 服务怎么写才够真实Mock 数据最怕的就是“一眼假”。疫情数据有几个天然特征新增大致呈现某种波动曲线而非直线、局部地区会出现聚集性上升、治愈和死亡通常滞后于确诊。我是用 Node.js Express 写的模拟服务核心逻辑是每次请求都基于上一次的基准值加上一个随机偏移量。实测下来这样最接近真实系统间的数据波动const express require(express); const app express(); let base { confirmed: 10200, cured: 9150, dead: 100 }; function walk(base, flutuation) { const confirmDelta Math.floor(Math.random() * flutuation) - 5; const cureDelta Math.floor(Math.random() * flutuation * 0.7) 3; base.confirmed Math.max(confirmDelta, 5); base.cured Math.min(base.cured cureDelta, base.confirmed); base.dead Math.random() 0.3 ? 1 : 0; return base; } setInterval(() { base walk(base, 18); }, 3000); app.get(/api/overview, (req, res) { res.json({ code: 0, data: { ...base, timestamp: Date.now() } }); }); app.listen(8080, () console.log(mock server running at :8080));这里有个细节值得注意治愈数不应该超过确诊数这是符合实际数据逻辑的约束。写 Mock 服务时数据约束和关联关系一定要建模出来不然前端拿到一堆自相矛盾的数据整个大屏的严谨感就没了。4. 大屏布局与视觉设计落地4.1 大屏布局思路三个“层次”缺一不可大屏项目在动手写代码前我建议先花半天时间做布局稿。不需要用专业设计工具手绘或者随便一个画板都行。布局要解决三个层次的问题第一层是信息结构层。最上方是标题和全局时间两边是核心汇总指标中间主体区域放地图左右两翼放趋势图和排行表底部放滚动信息和辅助图表。这是大屏通用的“总-分-分”结构用户一眼扫过去就能理解主次关系。第二层是网格系统层。整个屏幕按 1920x1080 划分为 12 列网格顶部高度约 10%左右侧栏各占 20%中间地图区域占 60%。用栅格思维来排版后续开发时每个区块的尺寸和间距才有据可依。第三层是视觉焦点层。颜色上深蓝色背景#0a1128配高亮荧光色#00f0ff、#ffd700、#ff4d6d中性的白灰色用于文字和辅助元素。整体思路是暗底亮色数据指标用暖色地图区域用冷色渐变形成视觉层级。有的同事习惯直接拿 Bootstrap 的栅格来套这在大屏上是行不通的。大屏的特殊之处在于既要“整屏缩放”又要“局部滚动”推荐的做法是根容器固定 1920x1080然后利用 CSS 的 transform: scale 来做整屏适配这个细节在下一章节展开。4.2 图表配色与字体决定大屏“质感”的细节很多项目图表功能都实现了但看起来就是“土”。问题往往出在配色和字体上。背景色统一使用接近黑色的深蓝营造空间感。主色板建议不超过 5 个主色调我用的是荧光青、金黄、玫红、紫罗兰、浅白。荧光青代表已确诊金黄代表治愈玫红代表死亡辅助元素用紫罗兰文字用浅白。图表网格线用 rgba(255,255,255,0.08) 这种低透明度线条不要用默认的灰色网格线否则跟暗色背景非常违和。字体数字部分强烈建议用等宽数字字体比如 DINPro、Barlow Condensed 这类数字跳变时不会因为宽度变化而“抖动”。中文字体用系统默认的“思源黑体”或者“微软雅黑”注意正文不要低于 14px否则大屏上会糊。ECharts 里设置字体和网格线透明度有一个比较核心的配置项是textStyle和splitLine示例如下const baseOptions { textStyle: { fontFamily: DINPro, Microsoft YaHei, sans-serif, color: #d4e0ff }, grid: { borderColor: rgba(255,255,255,0.1), splitLine: { lineStyle: { color: rgba(255,255,255,0.08) } } } };这些细节看起来不起眼但放到大屏上对比非常明显。没有高质量的视觉基底再复杂的功能也会显得廉价。5. 屏幕适配与图表自适应的实战方案5.1 1920 设计稿如何适配不同分辨率大屏项目最常遇到的尴尬情况是在自己电脑上完美换到大屏电视上要么拉伸变形、要么四周留黑边。这里我强烈推荐 transform: scale 方案而不是盲目使用 rem 或 vw/vh。思路是这样的页面按照 1920x1080 开发CSS 根字号固定为 10px方便 1rem 10px 计算然后在最外层包裹一个容器通过 JavaScript 实时计算当前窗口宽高与 1920x1080 的比例用 scale 缩放整屏function resizeScreen() { const scaleX window.innerWidth / 1920; const scaleY window.innerHeight / 1080; const scale Math.min(scaleX, scaleY); // 一般用等比缩放避免变形 document.getElementById(app).style.transform scale(${scale}); document.getElementById(app).style.transformOrigin left top; } window.addEventListener(resize, resizeScreen); resizeScreen();用等比例缩放方案画面不会变形但两边可能会有留白。如果想充满全屏可以改成 scaleX 和 scaleY 分别缩放即拉伸这样就会铺满没有任何空隙但图形会轻微变形。具体取舍看项目要求大多数监控大屏项目接受等比缩放 留白因为观感更自然。内部所有像素值我都统一用 rem 为单位例如某个区块宽度是 80px就写 8rem。整个项目只在根容器上做了一次 scale 变化内部布局就像在固定分辨率下开发一样省去了大量兼容性处理。这个方案实测下来是最稳、最快的。5.2 ECharts 图表如何随容器自动调整大屏面板通常由十几个图表组成如果每个图表都要手动维护 resize 回调代码会非常冗余。我在实际项目中封装了一个核心工具函数export function useChartAutoResize(chartInstance) { const observer new ResizeObserver(() { chartInstance chartInstance.resize(); }); const bind (el) el observer.observe(el); const unbind (el) el observer.unobserve(el); return { bind, unbind }; }ResizeObserver 比 window.resize 事件更可靠因为图表所在区块可能在导航切换时被隐藏或改变尺寸只有监听具体 DOM 容器的变化才能真正触发图表重绘。有一点要特别提醒ECharts 实例的 resize 不能太频繁调用。如果某个区块宽高变化频繁建议加一层 200ms 的防抖。否则大屏在切屏或者切换筛选条件时偶发卡顿。6. 地图下钻与联动高亮实现6.1 中国地图如何注册到 ECharts 中ECharts 5 升级后地图数据不再内置需要自己准备 GeoJSON。我推荐直接到相关开源仓库下载 Albers 等值线地图数据china.json版本要选 5.x 可用的那种。在使用前需要用 ECharts 的 registerMap 方法全局注册import * as echarts from echarts; import chinaJson from ./assets/china.json; echarts.registerMap(china, chinaJson);项目中所有使用中国地图的图表只要数组里填map: china即可共享这份地图数据。中央区域地图不再需要自己拼坐标能省下大量时间。地图下钻到省份之后我是这样处理返回逻辑的维护一个变量currentLevel默认值是 china。当用户点击某个省份时把省份名称赋给currentLevel同时动态加载该省对应的 GeoJSON 并重新注册最后更新 series 数据。点击返回按钮时重新注册全国地图并重置数据。整个过程大概十几行代码但交互效果直接提升了一个档次。6.2 地图气泡与飞线特效的处理细节疫情监控大屏除了常规地图着色之外经常还需要在重点地区做“强调”——比如新增病例较多的城市上方要展示气泡和光效。ECharts 里可以用effectScatter系列叠加在map系列上实现。实现时最容易踩的坑是effectScatter 里的坐标数据必须对应地图上的经纬度如果数据里只有城市名需要再用 geocoding 结果每个城市的经纬度做一次映射。我在项目里维护了一个常见城市经纬度映射表数据量不大但非常管用。const cityGeo [ { name: 武汉, value: [114.3, 30.6] }, { name: 广州, value: [113.3, 23.1] }, { name: 北京, value: [116.4, 39.9] } ]; // series 配置 { type: effectScatter, coordinateSystem: geo, data: cityGeo.map(item ({ name: item.name, value: [...item.value, 10] // 最后一位是气泡大小 })), symbolSize: val Math.min(30, val[2] * 1.2), rippleEffect: { brushType: stroke } }飞线效果则建议用lines系列在中高风险城市之间连线配合动画效果。看起来炫酷但要注意性能线数量大于 30 条时动画帧率会明显下降。我的做法是只展示前 10 条重点飞线其余用静态线表示。7. 实时刷新与动态数据更新机制7.1 setInterval 轮询还是 WebSocket 推送监控大屏数据更新的方式有两种主流选择普通轮询和 WebSocket 推送。如果项目后端只是提供 REST 接口前端轮询是最容易上手的方案。每 5 秒或每 10 秒调用一次接口刷新数据代码逻辑简单可靠。坏处是如果接口响应慢或用户量大会对服务器造成一定压力。如果是真正实时的疫情数据或类似监控场景WebSocket 推送是更正确的选择但会增加后端实现成本。我的建议是演示项目用轮询就够了生产项目强调实时性就上 WebSocket。这次项目我采用的是“轮询为主、WebSocket 预留”的过渡方案。封装了一个核心函数在轮询的同时检测 WebSocket 连接状态一旦可用就自动切换。具体实现思路如下const useDataStream (fetchFn, interval 5000) { let timer null; let ws null; function startPolling() { timer setInterval(async () { const data await fetchFn(); updateCharts(data); }, interval); } function startWebSocket(url) { ws new WebSocket(url); ws.onmessage (e) { const data JSON.parse(e.data); updateCharts(data); }; } return { startPolling, startWebSocket }; };7.2 数据更新时的闪烁问题与过渡动画大屏数据刷新时最吊诡的问题就是“数字跳变太突兀”。比如 200 突然跳到 800看的人会吓一跳在监控系统里也容易造成误读。我推荐所有数字型指标都加上“数字滚动动画”效果。核心思路是对旧值和新值做插值在限定时间内从旧值渐变到新值。实现方式有很多种最简单的是 requestAnimationFrame 配合缓动函数function animateNumber(el, from, to, duration 800) { const start performance.now(); const easeOutCubic (t) 1 - Math.pow(1 - t, 3); function update(now) { const progress Math.min((now - start) / duration, 1); const value Math.floor(from (to - from) * easeOutCubic(progress)); el.textContent value.toLocaleString(); if (progress 1) requestAnimationFrame(update); } requestAnimationFrame(update); }折线图和时间序列图也会遇到数据点追加时整体“跳动”的问题。我的经验是启用 ECharts 自带的渐进动画配合animationDurationUpdate: 400这样新旧数据的切换就有一种平滑过渡的感觉不会突兀地整条线重绘。轮询间隔也有讲究不能无限压缩。实测 3 秒轮询是最小安全阈值间隔小于 2 秒时页面会明显卡顿尤其是地图场景。建议演示项目默认 5 秒生产项目根据后端压力调节到 10~15 秒。8. 大数据量的性能优化实践8.1 首屏加载性能从 3MB 压缩到 800KB大屏项目最怕的是加载慢。客户通常站在大屏前几秒内画面出不来就会觉得系统“不行”。我拆解了一下首屏资源发现主要瓶颈是 ECharts 全量引入。ECharts 5 完整包约 1MB 以上如果 only 使用地图、折线、饼图、效果散点、滚动表格这几个组件完全可以按需加载// 原来是 import * as echarts from echarts // 改成按需引入 import { use } from echarts/core; import { CanvasRenderer } from echarts/renderers; import { LineChart, PieChart, BarChart, MapChart, EffectScatterChart, LinesChart } from echarts/charts; import { GridComponent, TooltipComponent, LegendComponent, VisualMapComponent, GeoComponent } from echarts/components; use([ CanvasRenderer, LineChart, PieChart, BarChart, MapChart, EffectScatterChart, LinesChart, GridComponent, TooltipComponent, LegendComponent, VisualMapComponent, GeoComponent ]);这样处理之后打包体积能减少一半左右。再配合路由级懒加载首屏实际加载资源从 3MB 降到了 800KB 上下大屏从打开到出数据大约 1.5 秒。构建层面还要开启 gzip。Vite 打包时用 vite-plugin-compression 插件Nginx 配置gzip_static on体积能再压缩 60% 以上。如果公司服务器不支持 gzip_static至少保证 Nginx 的gzip on开启。8.2 大数据量图表的渲染卡顿排查大屏上级联了很多图表随着时间的推移数据点会越来越多。特别是 30 天逐小时数据折线图会有 720 个点。这时候 ECharts 渲染压力就开始大了。我的优化套路是用sampling: lttb对折线图降采样LTTB 算法能保留峰值特征视觉上几乎无损。dataZoom 只显示近 7 天数据其余数据拖拽查看。排行榜表格用虚拟滚动只渲染可视区域内的行。我封装了一个虚拟列表渲染时间从每次 200ms 降到 10ms 以内。图表离开视口比如切换到其他 Tab 页面时用chart.dispose()释放实例避免内存泄漏。对超过 10 个图表的页面我用一个统一的“仪表盘调度器”来管理所有图表的创建、更新、注销。每个图表实例都注册在 Map 中页面卸载时统一 dispose。这比在单个组件里写onUnmounted(() chart.dispose())要安全得多不会漏。9. 企业级部署与跨域/接口联调避坑9.1 Nginx 部署与跨域配置要点大屏系统前后端分离部署时最烦人的就是跨域。开发环境 Vite 已经处理了代理问题但生产环境必须靠 Nginx。我的 Nginx 配置长这样server { listen 80; server_name bigscreen.example.com; gzip on; gzip_types application/javascript text/css application/json image/svgxml; root /opt/project/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_connect_timeout 5s; proxy_read_timeout 10s; } location / { try_files $uri $uri/ /index.html; # 配合 history 路由 } }需要注意的细节proxy_pass结尾的/和location /api/是对应的如果 location 带/api/前缀但 proxy_pass 不带尾斜杠转发路径就会变成/api/api/xxx这个错误非常隐蔽排查时会浪费大量时间。如果客户现场只提供一台机器资源有限不想单独跑 Node 服务也可以做“纯静态 内置 json”的方案把 Mock 数据打包到前端开启vite preview模式。不过这种方式只适合演示生产系统还是建议前后端分离各自独立部署。9.2 大屏上的“异形屏”与拼接屏适配经验最后提一个很容易被忽略的部署细节。很多客户的大屏并不是一台显示器而是由多块 LED 拼接屏组成的这种屏在浏览器里看到的往往并不是标准 1920x1080。经验是接到现场需求时第一件事就是用浏览器访问一下远程地址打开开发者工具查看实际视口尺寸。经常会出现物理分辨率 7680x2160、浏览器视口却是 1920x1080 的情况因为显卡做了整屏缩放。如果客户明确要求“每块屏显示独立内容”比如一块屏显示地图、另一块屏显示趋势这就不能走 transform: scale 方案了而是要用多浏览器窗口或 iframe 分屏方案每个子屏单独开发独立页面。这种需求复杂很多建议提前跟客户确认清楚避免开发完再大改。10. 常见问题与排查技巧实录10.1 图表不显示或白屏症状大概率原因快速解决地图区域空白GeoJSON 没注册成功检查 registerMap 是否在 import 后执行图表不渲染容器宽高为 0给容器显式设置宽高父级不要用 flex: 1 撑不开数字不动接口字段名不匹配打开 Network 对比大小写和嵌套层级飞线不显示lines 数据坐标格式错误确认是[lng, lat]还是[lat, lng]动画卡顿图表实例过多或数据量过大开启 dataZoom sampling卸载不可见图表10.2 大屏常见实战问题问题125 寸显示器开发到了客户 75 寸电视上字体小得可怜。原因是没有用等比缩放方案我后面统一用 scale 之后任何屏上观看效果一致。问题2数据每 5 秒刷新一次用户反馈数字看得头晕。解决方法是所有数字都加滚动动画刷新频率降到 10 秒中间区域的图表只在数据变化超过阈值时重绘避免频繁动。问题3省份下钻后返回全国时地图比例尺错乱。原因是注册新地图后图表 geo 的 zoom/center 配置没有重置。在返回逻辑里要显式setOption重置 geo 的中心点和缩放级别。问题4同时打开多个大屏页面Chrome 直接内存溢出。由于每个 Tab 都加载完整 ECharts 实例。排查后发现在路由切换时必须dispose旧图表并且把图表实例引用置空垃圾回收才能正常工作。10.3 一些独家小建议开发大屏项目最好自备一份“大屏浏览器调试技巧清单”。比如用 Chrome 设备工具栏模拟 1920x1080但注意模拟分辨率不等于真实像素比DPR部分字体渲染效果仍然有差异。配合“整屏截图检查”验收大屏效果用工具截全屏图放大到 100% 看四角和边缘是否有模糊、压字现象很多对不齐的问题只有截图后才能看出。数据接口尽量做成“批量拉取”比如一次/api/all返回全部指标、趋势、地图数据前端一次加载进 Store再分发到各图表。这样可以大幅减少 HTTP 请求数降低后端压力。大屏项目的“韧性”很重要。网络抖动了、接口挂了页面不该白屏。我给每个请求都加了超时重试 降级显示旧的缓存数据并在地图上显示“数据更新失败展示历史数据”的提示条。客户看到这个提示条一般不会怪开发反而觉得系统很严谨。11. 写在最后关于这类项目的一点个人感悟做完整套疫情监控大屏我的体会是大屏项目真正拼的不是哪个图表库用得花哨而是整体工程化的严谨程度。从接口契约、布局适配、数据刷新、性能优化到跨域部署每一环都必须提前想清楚。特别是“大数据量 高实时性 多图表联动”这种组合需求一旦前期没做好架构设计后期每加一个功能都会牵一发而动全身。如果这个项目后续还要扩展我打算把数据源从 Mock 切换到消息队列实时消费服务端通过 WebSocket 将增量数据推送到面板再把历史数据接入时序数据库做归档查询。这样一来系统就从“演示级别”跨向“生产可用级别”了。最后再分享一个实用小技巧大屏项目的验收通常需要一个“大屏模式”可以在浏览器地址栏直接模拟真实尺寸也可以按 F11 全屏。如果你负责交付记得提前在客户现场测试好操作系统缩放、浏览器缩放、屏幕分辨率的组合效果很多问题不是开发环境能复现的必须现场调一遍。这篇内容里的代码和配置我都是直接从已验证的项目里摘出来的细节上可以根据你自己的场景微调。如果你正在做一个类似的大屏可视化项目不妨把这篇文章存下来遇到具体问题时按图索骥。