简介:一套基于Vue.js与ECharts的数据可视化大屏源码,面向前端开发者、数据可视化爱好者及有监控大屏需求的项目团队。项目以Vue为框架核心,结合vue-echarts插件完成图表渲染,配合JavaScript实现交互逻辑,CSS与HTML构建页面结构与样式,可广泛应用于运营监控、数据展示等场景。资源共28个文件,包含11个Vue组件、8个JavaScript脚本、2个JSON配置文件,以及browserslistrc、ico、css等辅助文件,压缩包约531KB,结构清晰便于拆解学习。目前已有1845人浏览学习。源码中涵盖TopView、TodayUsers、MapView等可视化模块,涉及ECharts初始化的完整写法、Vue组件化开发思路、全局样式与路由配置,适合用于理解大屏类项目从布局到数据绑定的全流程,也可作为二次开发的基础模板。
1. Vue.js + ECharts 的数据可视化大屏源码,先别急着改,先拆骨架
很多搞运维和前端的朋友下载完"基于Vue.js与ECharts的数据可视化大屏源码",第一件事就是 npm install,然后发现报错一堆,图也不显示。我一般拿到这样的源码,会先花 20 分钟把项目结构、依赖版本和启动脚本读明白。Vue.js 负责页面状态与组件生命周期,ECharts 负责把数据变成可交互图表,大屏源码真正值钱的地方往往在组件封装、数据刷新、适配缩放这三层。下面我按"跑通-封装-适配-接数据-改造成自己的"这个顺序,把一套能落地的大屏方案拆开讲清楚,适合需要做企业级数据可视化大屏或 3D 数据大屏二次开发的人。
2. 从源码结构看 Vue.js 与 ECharts 大屏的骨架:依赖、目录与跑通
大屏项目和普通后台管理系统最大的区别是:单页内图表多、定时器多、容器尺寸随时在变。如果一开始目录结构没理清,后面改接口和换图表时会非常痛苦。所以我建议把源码当成一份"前端架构样例"来看,而不是直接在上面堆代码。先弄明白 ECharts 是怎么挂进 Vue.js 的,再顺着目录找到配置和接口,最后把项目跑起来,这一步走完,大屏源码的骨架才算真正握在手里。
2.1 ECharts 在 Vue.js 中的挂载时机:全局注册、按需引入还是封装指令
ECharts 本身不依赖 Vue.js,它只负责在拿到 DOM 容器后创建实例并渲染。常见做法是在 Vue 的mounted生命周期里初始化,在beforeUnmount里调用dispose。根据大屏源码对 ECharts 的引用方式,可以分成三种:全局引入、按需引入、用自定义指令封装。
- 全局引入:
import * as echarts from 'echarts',然后挂到 Vue 原型上,写起来最方便,打包体积最大。 - 按需引入:从
echarts/core里只注册用到的图表和组件,对加载体积敏感的大屏项目更友好。 - 指令封装:写一个
v-chart指令,在inserted钩子里初始化,在unbind钩子里销毁,适合模板里大量使用<div v-chart="option">的场景。
三者的差别需要结合package.json里的依赖体积来判断。按需引入是"企业级数据可视化"项目里比较推荐的方式,因为大屏页面通常只用到柱状图、折线图、饼图和地图,没必要把 ECharts 全家桶带进来。下面这段代码是 Vue3 项目里常见的按需注册方式。
// echarts.setup.js import * as echarts from 'echarts/core'; import { BarChart, LineChart, PieChart, MapChart } from 'echarts/charts'; import { GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, GeoComponent } from 'echarts/components'; import { CanvasRenderer } from 'echarts/renderers'; echarts.use([ BarChart, LineChart, PieChart, MapChart, GridComponent, TooltipComponent, LegendComponent, DataZoomComponent, GeoComponent, CanvasRenderer ]); export default echarts;这段代码的关键是把 ECharts 的charts和components分开注册。MapChart和GeoComponent是地图相关需要的,不用地图时可以去掉。renderers里CanvasRenderer是默认渲染器,如果大屏上有大量实时刷新的折线图,可以换成SVGRenderer,但要注意动态数据量很大时 SVG 的 DOM 节点更多,性能反而不如 Canvas。这个参数直接决定了图表最终的渲染方式。
| 引用方式 | 打包体积 | 推荐场景 |
|---|---|---|
| 全局引入 | 大 | 快速原型、单屏图表种类很多 |
| 按需引入 | 小 | 企业级大屏、需要控制首屏加载 |
| 指令封装 | 中 | 模板中图表容器很多,想复用初始化逻辑 |
2.2 一份大屏源码常见的 src 目录设计
我见过的大多数 Vue.js + ECharts 大屏源码,src下都会把"请求、组件、配置、页面"分开。下面是一个经过整理后的常见目录树:
src/ api/ dashboard.js // 大屏接口请求 modules/ realtime.js // 实时数据 components/ DashboardChart/ // ECharts 基础组件 BorderBox/ // 大屏边框组件 CarouselPanel/ // 轮播面板 DigitalFlop/ // 数字翻牌器 config/ theme.js // 主题颜色 pointerTime.js // 轮询间隔 utils/ request.js // axios 实例 resize.js // 大屏适配 views/ Dashboard.vue // 大屏主页面 main.js拿到源码后不要急着删东西,先把config和api这两个目录读透。大屏的标题、轮播速度、刷新间隔、接口前缀通常都写在这两个目录里。很多"可视化大屏适配"问题,其实不是 CSS 写不出来,而是config里的基准尺寸没找到。比如有的源码会把标准屏幕宽度放在theme.js的designWidth字段里,适配逻辑全依赖这个常量。
2.3 把源码跑通:npm install 之后最常遇到的四个问题
第一步自然是安装依赖,但不同源码用的 Vue 版本不一样,Node 版本对不上时会报node-sass或Can't resolve 'fs'之类的错。我一般会先看package.json里的engines字段,或者直接按以下命令启动:
npm install npm run dev如果提示Node Sass does not support your current environment,说明 Node 版本和node-sass不匹配。常见做法是卸载node-sass,换成dart-sass,也就是在package.json里把node-sass改成sass,再重新安装。如果报错是Can't resolve 'echarts',那就是 ECharts 没有装,单独执行npm install echarts --save即可。npm 安装太慢时,可以把镜像源切到国内镜像,速度会改善很多。
| 报错信息 | 可能原因 | 处理方式 |
|---|---|---|
Node Sass does not support | Node 版本与 node-sass 不匹配 | 换成 dart-sass |
Can't resolve 'echarts' | ECharts 未安装 | npm install echarts -S |
Parsing error: Unexpected token | 缺少 Babel 配置 | 检查 .babelrc 或安装 @babel/preset-env |
ResizeObserver is not defined | 老浏览器不支持 | 安装 ResizeObserver polyfill |
项目跑起来后,先确认控制台没有红色报错,再打开 Network 面板看看数据请求是否 404。很多大屏源码默认连的是演示环境接口,没有真实后端时,需要到src/api里把 baseURL 改成自己的地址,或者临时在src/data目录下放 mock 数据。这一步不做,后面图表就会一直空白。先把静态数据跑出画面,再接接口,这是排错成本最低的顺序。
3. 封装 Vue.js + ECharts 大屏图表组件:初始化、配置合并与自适应
大屏页面通常有十几个图表,如果每个图表都在页面里直接写echarts.init,代码会非常散,而且页面销毁时容易漏掉实例。更好的做法是把 ECharts 初始化逻辑收敛到一个公共组件里,页面只负责传 option 和接收事件。这样无论源码里有 5 个图表还是 50 个图表,改动逻辑都只集中在BaseChart.vue一行。
3.1 一个能撑住大屏场景的 BaseChart 组件
我习惯把图表基础组件命名为BaseChart.vue,只做四件事:初始化、更新配置、跟随容器尺寸变化、销毁实例。用 Vue3 的<script setup>写的话,核心代码是这样的:
<template> <div ref="chartRef" class="base-chart"></div> </template> <script setup> import * as echarts from 'echarts'; import { ref, onMounted, onBeforeUnmount, watch } from 'vue'; const props = defineProps({ option: { type: Object, required: true }, theme: { type: String, default: '' }, autoresize: { type: Boolean, default: true } }); const emit = defineEmits(['click']); const chartRef = ref(null); let chartInstance = null; let resizeObserver = null; onMounted(() => { chartInstance = echarts.init(chartRef.value, props.theme); chartInstance.setOption(props.option); chartInstance.on('click', (params) => emit('click', params)); if (props.autoresize && typeof ResizeObserver !== 'undefined') { resizeObserver = new ResizeObserver(() => { chartInstance && chartInstance.resize(); }); resizeObserver.observe(chartRef.value); } else { window.addEventListener('resize', handleWindowResize); } }); function handleWindowResize() { chartInstance && chartInstance.resize(); } watch(() => props.option, (val) => { chartInstance && chartInstance.setOption(val, { notMerge: false }); }, { deep: true }); onBeforeUnmount(() => { if (resizeObserver) { resizeObserver.disconnect(); } else { window.removeEventListener('resize', handleWindowResize); } chartInstance && chartInstance.dispose(); }); </script>这段代码里的几个参数值得说明。echarts.init的第二个参数是主题名,不传就是用默认主题。setOption的第二个参数notMerge设成false,意思是在原有配置基础上做增量合并,适合大屏上某个图表的数据定时刷新时只更新series.data,而不会把xAxis的配置重置掉。如果你希望在切换页面时整个配置都重新计算,才需要设成true。
用ResizeObserver是比window.resize更稳的方案。大屏的图表容器经常是 flex 布局,窗口变化时容器尺寸可能不变,window事件会白白触发 resize;而ResizeObserver只在容器尺寸真正变化时回调,还能避免多个图表实例互相干扰。需要在老浏览器兼容时再退回window事件。事件绑定在mounted里完成,组件实例销毁时从onBeforeUnmount里移除,这就是标准的生命周期管理。
3.2 把 option 拆成"数据映射"和"组件配置"两层
大屏源码里最难看懂的部分就是 option。一个常见的问题是接口字段名变了,但配置里还写死data[i].value。我会在src/config里放一个转换函数,专门把接口数据映射成 ECharts 需要的结构:
// src/config/barOption.js export function buildBarOption(rawData) { const categories = rawData.map((item) => item.categoryName); const values = rawData.map((item) => item.num); return { tooltip: { trigger: 'axis' }, grid: { left: '3%', right: '4%', bottom: '3%', containLabel: true }, xAxis: { type: 'category', data: categories }, yAxis: { type: 'value' }, series: [ { name: '当前值', type: 'bar', data: values, itemStyle: { color: '#36cbcb' } } ] }; }这个函数的输入是原始数据,输出是一个完整的 ECharts option。页面里拿到接口数据后先调用它,再把结果传给<BaseChart :option="option">。这样做的好处是,后端换字段时只需要改这一层映射,不用去动组件。大屏里的"数据可视化"本质上就是不断做"原始数据 -> 图表配置"的转换。如果项目里用了 TypeScript,还可以给rawData定义一个接口类型,这样字段拼错时编译阶段就会报错。
3.3 大屏适配:vw/vh、rem 与 transform: scale 三种方案的取舍
"可视化大屏适配"是大屏源码里被问得最多的点。三种常见方案的区别可以看这张表:
| 方案 | 核心思路 | 优点 | 缺点 |
|---|---|---|---|
| vw/vh | 宽高全部用视口单位 | 代码直观,字体和间距跟着视口走 | 文字会变得过大或过小,设计稿还原度不够 |
| rem | 用 html 的 font-size 做基准 | 前端习惯,移动端成熟 | 大屏上需要自己计算根字号,图表内部坐标系还是 px |
| transform: scale | 按设计稿整体缩放 | 还原度最高,图表不重绘 | 适配后留白或被裁剪,交互坐标需要换算 |
我一般建议大屏采用"vw/vh 布局 + transform scale 兜底"的组合:页面骨架用100vw和100vh撑满屏幕,内部所有模块用百分比或 vw 布局,这样保证没有滚动条;如果项目要求严格按 1920x1080 设计稿还原,就用一个固定尺寸的容器,计算实际视口与设计稿的宽高比,再整体scale。下面是 vw/vh 布局的基础样式:
.dashboard { width: 100vw; height: 100vh; overflow: hidden; background: #0a1a2f; }这个例子里的width: 100vw指的是视口宽度,注意滚动条出现时vw会把滚动条宽度也算进去,所以大屏项目里通常还要给body设置overflow: hidden。图表组件里的BaseChart因为是接在容器上的,容器被 vw/vh 撑开后,ECharts 自带的resize()就能自动适配。如果页面里需要表格,element表格也要给外层容器设置固定高度,避免内部滚动条把大屏布局挤乱。
4. 在大屏源码里处理真实数据:请求、地图、轮播与内存清理
跑通静态页面之后,下一步就是接真实数据。大屏和普通后台的关键差异在于:数据要反复刷新,容器的尺寸要反复变化,图表实例要反复创建。这三件事加起来,最容易出现数据请求重复、地图数据格式不对、轮播卡顿、内存泄漏。这一章我按"请求封装 -> 地图映射 -> 轮播 -> 排查"的顺序逐个拆。
4.1 用 axios 封装一个带取消机制的数据请求模块
大屏页面里通常有多个模块,每个模块按自己的频率轮询。如果页面已经切换走了,上一个模块的请求还在返回,就会在 Vue 实例卸载后触发 setState 警告。常见做法是给每个模块建一个AbortController,在组件销毁前取消请求。
// src/utils/request.js import axios from 'axios'; const service = axios.create({ baseURL: '/api', timeout: 10000 }); service.interceptors.response.use( (response) => response.data, (error) => { if (error.code === 'ERR_CANCELED') { console.log('request canceled'); return Promise.reject(error); } return Promise.reject(error); } ); export function getRealtimeData(signal) { return service.get('/realtime', { signal }); }在组件里这样用:
const controller = new AbortController(); async function load() { try { const data = await getRealtimeData(controller.signal); // 更新图表 option } catch (e) { // 超时或取消时不做处理 } }AbortController是浏览器原生的请求取消能力,axios 在配置项里认signal字段。调到controller.abort()时,请求会被标记为ERR_CANCELED,这样就不会再有回包去更新已经销毁的图表。注意signal只能取消一次,如果模块要多次复用,需要每次重新创建AbortController。轮询的场景还要配合setInterval使用,每次轮询开始前先取消上一次请求,防止接口响应慢时多个请求堆积。
4.2 ECharts 地图的注册与数据映射:先注册 geoJSON,再匹配 name
大屏上带中国地图或省份地图时,最容易踩的坑是"地图不显示"或"数据全为 0"。原因往往是地图没有注册。ECharts 从 5.x 开始不再内置地图数据,需要自己引入 geoJSON。
import { MapChart } from 'echarts/charts'; import * as echarts from 'echarts/core'; import chinaGeoJson from '@/assets/map/china.json'; echarts.use([MapChart]); echarts.registerMap('china', chinaGeoJson); const option = { tooltip: { trigger: 'item' }, geo: { map: 'china', roam: true, itemStyle: { areaColor: '#0d2a4a', borderColor: '#4a9eff' } }, series: [ { type: 'map', map: 'china', data: [ { name: '广东省', value: 120 }, { name: '江苏省', value: 80 } ] } ] };代码里的关键是registerMap('china', geoJson)的第一个参数,要和后面map: 'china'保持一致。geoJSON 里的name必须是省份全称,比如"广东省"不能写成"广东"或"粤"。ECharts 的series.data是通过name字段去匹配地图块颜色的,匹配不上时不会报错,只是那一块不变色。
提示:地图数据文件尽量放在
src/assets/map下,而不是public里。放在src里会被打包器当成模块解析,路径错误时构建阶段就能发现;放在public里运行时请求不到会静默失败,排查起来更费时间。
如果地图显示正常但项目打包后找不到 geoJSON,检查一下文件路径。地图热力效果可以叠加一个visualMap组件,控制颜色渐变区间。需要立体效果时可以再引入echarts-gl,但这会加大打包体积,建议只在大屏的某一两个模块里用。
4.3 大屏轮播切换:用一个 currentIndex 加定时器
大屏看板上经常需要多个图表轮播展示。实现时没必要引入复杂的轮播插件,用一个currentIndex加定时器就够了。
const panels = ref([{ title: '面板1' }, { title: '面板2' }, { title: '面板3' }]); const currentIndex = ref(0); let timer = null; function startCarousel() { timer = setInterval(() => { currentIndex.value = (currentIndex.value + 1) % panels.value.length; }, 5000); } onMounted(startCarousel); onBeforeUnmount(() => clearInterval(timer));这里的setInterval间隔 5000 毫秒切换一个面板,用取模运算保证下标回到 0。模板里可以用v-show配合currentIndex控制面板显隐,或者用过渡组件实现淡入淡出效果。关键点是定时器一定要在onBeforeUnmount里清掉,否则切换路由后定时器还在跑,会继续更新已经卸载的组件。如果轮播过程中有用户鼠标悬停,还需要在mouseenter时clearInterval,mouseleave时重新启动,避免按着设计好的节奏硬切。
4.4 大屏卡顿与内存泄漏的排查点
如果大屏运行一段时间后内存越来越大,优先检查四个地方,见下表:
| 检查点 | 现象 | 验证方式 |
|---|---|---|
| ECharts 实例未 dispose | 内存持续上涨 | Memory 面板对比快照 |
| setInterval 未清除 | 多个定时器叠加 | Console 里 count 定时器数量 |
| resize 监听未移除 | 图表多次 resize | Performance 里观察 resize 事件 |
| 请求未取消 | Network 里请求堆积 | 切换路由后看请求是否 pending |
验证内存泄漏最朴素的方法是在浏览器 Performance 面板里录制一段大屏运行过程,看 JS Heap 曲线是否不断上升。如果曲线有规律的下降说明有 GC 回收,持续上升说明有引用没释放。这类问题通常不是某一个组件导致的,而是多个定时器和多个chart.resize叠加后的结果。排查时先全局搜索setInterval,再搜索addEventListener('resize',把没有对应清理动作的地方逐个清掉。
5. 把 Vue.js 与 ECharts 大屏源码改造成自己的:三个必调配置、调试技巧与性能验证
拿到源码后,我一般不会急着改页面,而是先改配置文件。大屏的标题、主题色、刷新间隔、接口前缀通常都汇总在配置里。下表是常见位置:
| 文件/位置 | 字段 | 说明 |
|---|---|---|
src/config/theme.js | primaryColor | 主色调,决定图表边框、标题装饰 |
src/api/dashboard.js | baseURL | 后端接口地址,通常需要替换 |
src/config/pointerTime.js | refreshInterval | 数据轮询间隔,单位毫秒 |
改的时候注意颜色值用同一个变量,避免每个图表写死的#36cbcb和主题不一致。刷新间隔不要低于 1 秒,否则会打爆后端接口。免费的"数据可视化大屏"源码大多会带一个mock目录,先用 mock 数据把图表调好,再切真实接口,能省下大量联调时间。
调试图表时最痛苦的是改一个 option 就要重新编译。我一般会在浏览器 Console 里直接拿当前图表实例改配置:
// 通过 Vue 组件的 DOM 元素拿实例,仅用于开发调试 const chart = document.querySelector('.base-chart').__echarts_instance__; chart.setOption({ series: [ { data: [120, 200, 150, 80, 70] } ] });__echarts_instance__是 ECharts 在 DOM 元素上挂的实例属性,属于内部约定,不适合写进业务代码。用这个命令可以快速验证新数据是否会使图表超出边界,也可以把修改后的option对象copy到剪贴板再贴回源码里。Vue Devtools 里则可以直接看props.option的深拷贝值,配合watch的调试断点能更快定位是数据变了还是配置没合并上。
大屏图表卡顿不一定出在 JS 逻辑,也可能在绘制阶段。打开 DevTools 的 Performance,录制页面滚动或数据刷新过程,然后看 Main 标签页里Render和Painting的耗时。如果发现Canvas的绘制时间很长,可以把 ECharts 的renderer切换成SVG,用同一个数据源分别跑一遍对比帧率:
const chart = echarts.init(el, null, { renderer: 'svg' });观察哪个方案的帧率更稳定。大屏上图表数量多时,我会更倾向 Canvas,因为 SVG 的 DOM 节点数量会拖慢首屏渲染。
本文还有配套的精品资源,点击获取