ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

基于Vue的疫情数据可视化大屏实战:从零搭建可维护工程方案

基于Vue的疫情数据可视化大屏实战:从零搭建可维护工程方案 简介面向Vue学习者与前端开发者的疫情数据可视化大屏项目源码适合需要快速搭建数据看板或学习大屏交互方案的中级开发者。项目基于Vue生态构建包含数据模拟、API请求封装、路由配置及多个可视化组件模块如省级排名柱状图、数据地图、治愈与死亡趋势、当前确诊对比等能够完整演示从数据获取到图表渲染的大屏实现流程。压缩包共34个文件以vue组件、js逻辑、json数据三类为主辅以HTML入口、图标及配置文件整体仅724KB结构紧凑便于本地运行与二次改造。目前已有475人学习下载。通过该源码可掌握大屏项目目录组织方式、常见可视化图表接入思路以及组件化拆分技巧对独立完成数据可视化类作品或课程设计有直接参考价值。1. 基于Vue的疫情数据可视化大屏到底解决什么问题打开这个标题的人大概率是两类一类接了一个要做疫情数据可视化大屏的活儿想找一套能改能跑的源码另一类想把 Vue 路由、组件化、ECharts 配置和大屏适配串成一个完整项目来学。两类人真正要的一致不是一张好看的截图而是一套从数据到像素都讲得清的工程方案。这个标题的本质就是用 Vue 全家桶搭一块 16:9 全屏展示的数据大屏把累计确诊、新增趋势、地区分布这类指标变成可维护的图表而不是静态页面。它管的是组件和路由图表交给 ECharts数据用 mock 先顶住后端就绪后随时可以切真实接口。适合前端初中级开发者做作品集也适合需要快速交付演示屏的团队。下面按技术选型、组件实现、适配性能和排错路径一步步展开每个环节都给可直接抄的参数和代码。2. 从零初始化一套大屏源码工程技术选型与数据链路打开任何一套开发中的数据大屏源码第一件事都不是看图表怎么配的而是确认这套技术栈能在你机器上跑起来、数据从哪来、路由怎么走。很多教程默认你已经解决了 vue 安装及环境配置结果新手全卡在第一步的依赖报错上。这一章先把选型理由讲透再给你一套能直接用的目录结构和数据链路设计后面写图表才有地方放。2.1 选型逻辑为什么是 Vue为什么路由要用 hash 模式大屏本质上是一个展示型单页应用对服务端渲染没有刚需所以 Vue 3 Vite 是目前最舒服的组合。Vite 的冷启动速度比 vue-cli 快一个量级而大屏开发里改图表配置是最高频的操作改完一秒内热更新和等三秒刷新对开发体验的影响是决定性的。如果你接手的是团队里已有的 Vue 2 项目也别急着折腾重构ECharts 5 对 Vue 2 和 Vue 3 都兼容真正要关注的是路由模式和数据响应式这两个边界。路由建议直接用 hash 模式这是一个很容易被忽略的坑。数据大屏经常要部署到 nginx 的子路径或者打包成一个静态目录直接扔给客户跑用 history 模式在这种场景下必须让后端配合做 rewrite否则一刷新就是 404。hash 模式虽然 URL 里多个#但在这个场景下换来的稳定性非常值。Vue Router 4 里创建路由时默认就是 hash你只需要显式写上createWebHashHistory团队里的新人也一眼能看懂意图。2.2 项目初始化一条命令跑出可运行的最小工程一套源码能不能跑起来往往取决于初始化步骤是否干净。我习惯用 Vite 官方脚手架创建工程不需要额外配置一堆 loader装完依赖直接npm run dev就能看到页面。具体命令如下npm create vitelatest epidemic-screen -- --template vue cd epidemic-screen npm install vue-router4 echarts npm run dev逻辑说明npm create vite是当前创建 Vue 3 工程最干净的方式--template vue会生成基于组合式 API 的模板省去手动搭 Vite 配置的步骤vue-router4是 Vue 3 对应的路由版本echarts不锁版本号时默认安装最新的 5.x兼容性没问题。跑完npm run dev后Vite 默认开 5173 端口浏览器能打开就说明 vue 安装及环境配置已经过了。工程初始化完成后还需要做两件事。第一件是把开发端口固定下来大屏项目经常要同时联调后端接口端口漂移会导致跨域配置反复修改在vite.config.js里加一行server: { port: 8088 }就能固定。第二件是设置路径别名把指向src这样后面所有组件里引用目录都写/components/xxx避免一长串../../相对路径。// vite.config.js import { defineConfig } from vite import vue from vitejs/plugin-vue import { fileURLToPath, URL } from node:url export default defineConfig({ plugins: [vue()], resolve: { alias: { : fileURLToPath(new URL(./src, import.meta.url)) } }, server: { port: 8088, host: true } })说明host: true允许局域网内其他设备通过 IP 访问大屏演示时经常要用另一台电脑或平板投屏这个配置能省掉代理工具的麻烦。alias里的路径是基于node:url计算的Windows 和 macOS 通用。2.3 目录结构按功能拆组件而不是按页面拆看过很多大屏源码最容易翻车的结构是把所有图表堆在一个Screen.vue里数据请求、图表初始化、定时器全混在一起。这种写法短期跑得通但只要有一天要换数据源或者新增一个图表就会牵连出一堆副作用。我一般按功能把代码拆成下面这样src/ ├── api/ │ └── epidemic.js # 唯一的数据出口组件只认这个目录 ├── assets/ │ ├── geo/ # 中国地图 / 省份 GeoJSON │ └── style/ # 全局主题变量、大屏背景 ├── components/ │ ├── charts/ # 图表组件一个图表一个文件 │ ├── layout/ # 大屏框架组件Header、SidePanel │ └── common/ # 数字滚动、边框、标题等复用组件 ├── mock/ │ └── data.js # 没有后端时先用静态数据顶住 ├── router/ │ └── index.js # 路由配置主屏 下钻详情页 ├── views/ │ ├── Screen.vue # 大屏主页面 │ └── Detail.vue # 图表下钻详情页 └── App.vue逻辑说明components/charts是这套源码里最核心的目录每个图表独立成一个单文件组件自己管自己的 ECharts 实例mock目录专门放静态数据等后端接口就绪后只改api/epidemic.js这一个文件所有图表组件完全不动。很多项目失败在数据层和组件层强耦合这个结构的价值就是让数据源可替换。路由上建议保留一个Detail.vue因为大屏交互经常要“点击省份跳详情”预留坑位比临时加路由要省事得多。2.4 数据链路mock、模拟接口和真实 API 怎么和平共处数据可视化大屏的常见死法是后端还没好前端拿一个写死的 JSON 开始开发等接口好了再回头改所有调用的地方。正确做法是设计一个统一的数据出口组件层永远只认 Promise 返回值不关心数据是从 mock 来的还是 axios 来的。下面是我常用的写法// api/epidemic.js —— 唯一的数据出口 export async function getOverview() { // 后端就绪后把下面这行换成 // return axios.get(/api/epidemic/overview).then(res res.data) return import(../mock/data.js).then(m m.overview) } export async function getProvinceTrend(id) { return import(../mock/data.js).then(m m.provinceTrend[id]) }逻辑说明把每个接口都写成一个返回 Promise 的函数组件调用时用await等待结果。mock 阶段用动态import加载静态数据换真实接口时只需要改函数内部的实现对外契约不变。参数id是省份编码overview是全局概要指标这两个字段名在 mock 和真实接口里保持一致后续切换零成本。这么做还有一个好处并行开发时前端不需要等后端自己就能把大屏全部功能跑通。3. 核心图表实现把疫情指标拆成可维护的 ECharts 组件这一章进入源码的主体部分。企业级数据可视化的习惯是每个图表独立成一个组件自己初始化、自己销毁、自己订阅数据而不是把十几张图全堆在页面里。下面以三个最典型的疫情大屏组件为例趋势折线图、地区分布地图、核心指标卡片每个都给出可直接运行的代码和关键参数说明。3.1 趋势折线图时间序列的配置要点与数据更新策略趋势图是疫情大屏里最常被盯着看的一块现有确诊、新增治愈、新增死亡都适合用平滑折线表达。组件内部维护 ECharts 实例通过setOption更新数据重点在于销毁时释放实例否则切路由后 Canvas 会残留。template div refchartRef classtrend-chart/div /template script setup import { ref, onMounted, onBeforeUnmount } from vue import * as echarts from echarts import { getTrend } from /api/epidemic const chartRef ref(null) let chart null onMounted(async () { chart echarts.init(chartRef.value) const data await getTrend(confirm) chart.setOption({ tooltip: { trigger: axis }, grid: { left: 50, right: 20, top: 40, bottom: 30 }, xAxis: { type: category, boundaryGap: false, data: data.dates }, yAxis: { type: value, name: 人数, scale: true }, series: [{ name: 现有确诊, type: line, smooth: true, showSymbol: false, lineStyle: { width: 2 }, areaStyle: { opacity: 0.25 }, data: data.values }] }) }) onBeforeUnmount(() { if (chart) { chart.dispose() chart null } }) /script style scoped .trend-chart { width: 100%; height: 100%; min-height: 260px; } /style逻辑说明onMounted里先echarts.init再setOption顺序不能反否则实例不存在会报错await getTrend(confirm)拿到的数据对象里包含dates和values两个数组分别对应 x 轴和 y 轴。smooth: true让折线变平滑适合展示连续日期的疫情趋势showSymbol: false是数据点太多时防止圆点密密麻麻的关键参数。boundaryGap: false让折线从第一个点开始贴合坐标轴左边界。scale: true这个参数经常被人忽略它的作用是当数据最小值离 0 很远时y 轴自动压缩让波动更明显但如果想强调“从零开始”的对比就不该开这个参数。另外onBeforeUnmount里调用dispose是必须的Vue 组件销毁后 DOM 也会被移除ECharts 实例不手动释放会造成内存泄漏这也是很多大屏越用越卡的主要原因。3.2 地图组件动态注册 GeoJSON 与视觉映射配置疫情大屏几乎必有一块中国地图按省份着色表示累计确诊量。地图组件最容易踩的坑是地图数据没有注册ECharts 5 之后的版本不再内置地图必须先用registerMap注册 GeoJSON。script setup import * as echarts from echarts import chinaGeoJson from /assets/geo/china.json echarts.registerMap(china, chinaGeoJson) export function buildMapOption(mapData) { const maxValue Math.max(...mapData.map(item item.value)) return { tooltip: { formatter: ({ name, value }) ${name}br/累计确诊${value ?? 0} 例 }, visualMap: { min: 0, max: maxValue, calculable: true, left: 20, bottom: 20, text: [高, 低], inRange: { color: [#e8f4ff, #7ec8ff, #1890ff, #003a70] } }, series: [{ type: map, map: china, roam: true, label: { show: false }, itemStyle: { borderColor: #ffffff, borderWidth: 0.5 }, emphasis: { label: { show: true, color: #333 }, itemStyle: { areaColor: #ffd666 } }, data: mapData }] } } /script逻辑说明registerMap是整个地图组件的前提chinaGeoJson通过import静态引入打包时会自动处理路径避免运行时 404。mapData的数据格式要求是[{ name: 湖北, value: 67800 }, ...]name必须和 GeoJSON 里的省名完全一致比如“湖北”不能写成“湖北省”。获取地图背景数据不要太依赖线上 CDN常见的做法是发布前把 GeoJSON 下载到本地assets/geo/目录和源码一起走版本管理。visualMap有四个关键参数min和max决定颜色映射范围max这里取数据最大值保证颜色分布有区分度calculable: true显示可拖动的滑块方便人手工筛选区间inRange.color是四段渐变色。roam: true允许缩放和拖拽在大屏演示时观众经常会要求看一眼某个省份的细节这个参数一定要开。3.3 指标卡片把环比计算封进组件避免每个页面重写大屏顶部通常是一排数字卡片累计确诊、累计治愈、累计死亡、现有确诊每张卡片还要显示“较昨日 xxx”或者“较昨日 -xxx”。这个逻辑从数据上就是简单的减法但如果不封装每个页面都会复制粘贴一份加减法代码。把环比计算收进卡片组件里父级只负责传当前值和昨日值。template div classstat-card div classlabel{{ label }}/div div classvalue{{ value.toLocaleString() }}/div div classdiff :classdiffClass 较昨日 {{ diffText }} /div /div /template script setup import { computed } from vue const props defineProps({ label: { type: String, required: true }, value: { type: Number, required: true }, prev: { type: Number, required: true } }) const diffText computed(() { const diff props.value - props.prev return diff 0 ? ${diff.toLocaleString()} : diff.toLocaleString() }) const diffClass computed(() { return props.value props.prev ? diff-up : diff-down }) /script逻辑说明卡片本身不请求数据而是通过props接收label、value、prev三个参数。value.toLocaleString()是让数字每隔三位加一个逗号大屏上显示67,800比67800更专业。diffText这个计算属性处理正负号的格式化123和-45能直接用。diffClass控制上涨和下降时的颜色样式上涨为红色、下降为绿色这是国内大屏约定俗成的配色习惯除非客户明确要求别用否则不要反着来。这套设计的好处是主页面里写StatCard label累计确诊 :valueoverview.confirmed :prevoverview.prevConfirmed /就够了后续换数据源、加统计口径都只改父组件传参卡片内部永远不动。4. 大屏适配与性能优化三组参数和两个高频坑数据大屏的设计稿几乎都是 1920 × 1080但实际投放的屏幕可能是 1366 的笔记本、2560 的显示器、甚至 4K 的会议屏。很多新手在这一步开始疯狂写媒体查询结果越写越乱。工作里最可靠的大屏适配方案还真不是 rem而是 transform 缩放。4.1 为什么大屏不适合 rem推荐 transform: scale 方案rem 方案在普通网页里很好用但在可视化大屏上有个致命问题ECharts 绘制在 Canvas 里文字和线条的尺寸在渲染时已经按像素定死了rem 只对 DOM 元素生效。你通过媒体查询改了根字体大小页面的标题和表格变小了但图表的坐标轴文字、图例、折线粗细完全无感结果是整块屏的视觉比例是乱的。transform: scale 方案思路完全不同页面内部永远按 1920 × 1080 布局最外层容器固定这个宽高然后用 JS 计算当前视口和设计稿的缩放比例整体缩放。这样做保证图表内部和外部 DOM 的缩放比例完全一致视觉上是一个无损的等比缩小或放大。// utils/useScreenScale.js export function useScreenScale(baseWidth 1920, baseHeight 1080) { const applyScale () { const screen document.getElementById(screen-root) if (!screen) return const scaleX window.innerWidth / baseWidth const scaleY window.innerHeight / baseHeight const scale Math.min(scaleX, scaleY) screen.style.transform scale(${scale}) screen.style.transformOrigin left top } window.addEventListener(resize, applyScale) applyScale() return { applyScale } }逻辑说明Math.min(scaleX, scaleY)取两个方向中较小的值这样才能保证页面等比缩放不会出现横向或纵向拉伸变形。transformOrigin: left top是关键它让缩放后的页面左上角对准视口左上角否则默认的缩放中心在元素中线页面会跑到屏幕外面去。调用useScreenScale()后大屏外层容器会被加载这个 hook 在Screen.vue的onMounted里调用一次并返回applyScale这样当全屏状态变化时可以手动再执行一次来修正比例。4.2 三个必调参数devicePixelRatio、notMerge、resize 时机第一组必调参数是 ECharts 的devicePixelRatio。大屏经常跑在 2K、4K 屏上浏览器默认把 Canvas 的分辨率按设备像素比放大但 ECharts 默认devicePixelRatio: 1导致高分屏上图表文字边缘发虚。解决办法是在echarts.init时显式传入chart echarts.init(chartRef.value, null, { devicePixelRatio: window.devicePixelRatio || 1 })第二个必调参数是setOption的notMerge。大屏数据定时刷新时如果新旧数据里的系列数量不一致比如早上还是 4 条曲线下午接口改成 3 条默认的 merge 模式会保留旧系列导致图表上出现幽灵数据。刷新数据时改成chart.setOption(option, { notMerge: true })第三个是图表 resize 的时机。window resize 事件里要同时处理外层缩放和每个图表实例的尺寸修正如果只缩放外层、不调chart.resize()图表内部会保持之前的像素宽高出现内容挤压。常见做法是在 window resize 监听里遍历所有已注册的图表实例统一调用resize()window.addEventListener(resize, () { applyScale() chartRefs.value.forEach(item item.chart item.chart.resize()) })4.3 性能优化按需引入 ECharts 模块和清理副作用全套引入import * as echarts from echarts开发时方便但打包体积会多出几百 KB。大屏项目一般只用 line、bar、map、pie 这几种基础图表完全可以用按需引入把体积压下来import * as echarts from echarts/core import { LineChart, BarChart, PieChart, MapChart } from echarts/charts import { TooltipComponent, GridComponent, VisualMapComponent, LegendComponent } from echarts/components import { CanvasRenderer } from echarts/renderers echarts.use([ LineChart, BarChart, PieChart, MapChart, TooltipComponent, GridComponent, VisualMapComponent, LegendComponent, CanvasRenderer ])逻辑说明echarts/core是核心包只包含基础架构echarts/charts里按需要引入图表类型echarts/components里引入组件比如 tooltip、grid、visualMap最后一个CanvasRenderer是渲染器。按这种方式改完打包产物明显变小对加载速度有直接帮助。这里有个需要注意的地方如果用了echarts/core按需引入registerMap依然有效但要确保MapChart和GeoComponent都已注册否则地图还是白屏。性能上第二件常被忽略的事是副作用清理。大屏里的定时刷新、轮播、resize 监听都是需要在onBeforeUnmount里释放的资源。一个靠谱的写法是把所有定时器句柄收集到一个数组销毁时统一clearInterval监听 window 的事件要用removeEventListener移除并且传入的 handler 要保持引用不能每次onMounted都新建匿名函数。5. 疫情大屏开发避坑五个高发问题与排查记录可视化大屏看着炫翻车的时候也是真的玄学。下面是做过多次这类项目后最常遇到的五个问题每条都按现象、原因、解决三段记录希望帮你把血泪经验直接变成排查手册。5.1 现象一地图区域完全空白控制台报 Map not found这应该是疫情大屏里最高频的问题。现象是页面其他图表正常唯独中间的地图区域一片空白浏览器控制台提示Map china not found.或者类似的错误。原因是 ECharts 5 之后不再内置任何地图数据你必须先执行echarts.registerMap(china, geoJson)并且这个动作要在setOption之前完成。很多人把地图数据用fetch去线上拉取网络一慢或者地址失效地图就没了。解决方式是直接把 GeoJSON 下载到src/assets/geo/下用import chinaGeoJson from /assets/geo/china.json静态引入打包后文件跟随工程走不依赖外网。注意registerMap只需要调用一次放在模块顶层或者main.js里都行。5.2 现象二路由切换后再回来图表出现重叠或残留大屏主页面和详情页来回切换时图表区域会出现两个 Canvas 叠在一起或者图表直接花屏。根本原因是组件销毁时没有释放 ECharts 实例旧的 Canvas 跟着 DOM 被移除了但实例的监听和渲染状态没清干净再回来时新实例和残留状态冲突。解决方法是把上一章的onBeforeUnmount清理逻辑当成铁律真正执行dispose()并把局部变量置空。如果使用了keep-alive缓存页面那么不能在onUnmounted销毁图表要改用onDeactivated和onActivated配合在onActivated里重新resize()。5.3 现象三窗口放大后大屏两边留白图表被拉伸变形大屏部署好后把浏览器窗口拖大页面内容没有铺满而是左右两大块空白或者横向变宽、纵向不变图表的圆被拉成了椭圆。原因通常是把外层缩放了但图表容器自身还在响应式布局里跟着变宽。解决方式是明确两层职责外层固定 1920 × 1080专门承担 transform: scale内层所有图表组件一律width: 100%; height: 100%但外层缩放时内层容器的实际布局像素不参与变化这样图表只会整体缩放、不会重新布局。5.4 现象四数据更新了setOption 调了但图表纹丝不动接口返回新数据后控制台打印数据是新的但图表不刷新。最常见的原因有两个一个是setOption没有传notMerge: true旧数据和新数据结构有差异时 ECharts 默认增量更新导致看起来像没刷新另一个是数据经过了 Vue 的响应式代理ECharts 直接操作被代理的对象时内部判断数据没有变化默认不重绘。解决方式是先JSON.parse(JSON.stringify(data))或者用toRaw还原原始数据然后chart.setOption(option, { notMerge: true })强制全量更新。5.5 现象五本地运行正常打包部署后地图和字体 404本地开发好好的执行npm run build后放到服务器上地图没了、自定义字体也变成默认字体。原因几乎都是资源引用方式不对GeoJSON 如果用fetch(/assets/geo/china.json)这种方式写打包后路径匹配不上部署子目录字体文件如果用 CSS 里相对路径引用但没被 Vite 处理也会 404。根治方式是所有静态资源统一走 import 引入或者放在public目录下。GeoJSON 用import保证被打包进产物同时确认部署时把base配置成实际子路径比如base: /data-screen/。字体文件尽量用系统字体或者把字体文件放进assets并显式font-face引入。6. 让大屏活起来轮播、主题切换和定时刷新图表能正常显示只是第一步落到实际演示场景大屏要能自己动页面每隔几秒自动轮播一屏数据深色主题可以持久化数据定时刷新时不能白屏或闪跳。这一章把最后这点进阶能力补齐。自动轮播可以用一个通用 hook 实现它接收当前页面的索引和总页数在setInterval里递增取模实现循环。注意大屏轮播的间隔不要太短8 到 10 秒是大多数客户能接受的节奏太短会让人来不及看清图表内容。轮播时最好配合图表的高亮动画比如让折线图重新播放动画视觉上更有“活”的感觉。主题切换和数据刷新也要一起考虑。主题色存进localStorage刷新页面后保持主题一致ECharts 的主题切换则通过echarts.registerTheme注册两套主题切换时重新init当前实例。定时刷新接口时优先保留旧数据展示等新数据拿到后再一次性setOption更新而不是先把图表清空再等接口返回。这样即使接口慢了几秒大屏上也不是空白用户感知会好很多。我早期做大屏时最常犯的错就是忘记清理定时器和 resize 监听结果演示到一半浏览器变卡图表动画也一顿一顿的。后来养成的习惯是每个图表组件里所有副作用都在同一个钩子里释放全局只保留一个刷新入口宁可代码看起来啰嗦一点也要保证长时间运行不倒。做可视化大屏炫酷是面子稳定才是里子希望这套思路能帮你在自己的项目里少走几段弯路。本文还有配套的精品资源点击获取
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表