简介:一套基于React与ECharts构建的数据可视化大屏项目,面向需要快速搭建展示平台的前端开发者,解决从图表集成到布局适配的常见问题。压缩包共63个文件,核心代码集中在30个js与8个jsx中,辅以JSON配置、mock示例数据、字体图片等静态资源,整体仅11.1MB;目录涵盖router、components、services、utils、mock等模块,工程化结构清晰,开箱即可运行。已有1533人学习/下载,示例完整演示了ECharts实例创建、配置项传入、数据动态绑定与事件监听等关键流程,并对大屏渲染做了性能考虑。通过该项目可掌握将ECharts封装为React组件的思路,灵活扩展折线图、柱状图、饼图等场景,也可基于内置mock数据直接联调,快速复用至实际业务可视化大屏。
1. 数据大屏项目解读:一个开箱即用的 React + ECharts 可视化模板
接手一个数据大屏项目,甲方要求在一台 1080p 的显示设备上展示实时业务数据,包含地图、折线图、柱状图、排行榜,还要每隔几秒自动刷新——如果你也是被这样推着上路的,这套 react+echarts 数据可视化大屏展示模板就是为这个场景准备的。它不是一个只能截图交差的静态页面,而是一套结构完整、开箱即用的工程化样板:顶部是核心指标卡,中部是主图区,两侧是辅助图表,底部还配了滚动列表。无论是做企业运营驾驶舱、园区监控中心还是年度汇报大屏,拿过来改数据源就能上线,适合需要快速交付的前端工程师和数据可视化学习者。
2. 技术架构与目录结构:React 组件树和 ECharts 图表的组织方式
2.1 技术选型:React 加 ECharts 的组合逻辑
先说选型。数据大屏场景有两个硬性要求:一是数据变化时页面要高效更新,二是图表类型要多且配置灵活。React 的虚拟 DOM 机制保证了状态更新时浏览器不会整屏重绘,组件化特性又天然适合把大屏拆成指标卡、折线图、地图、排名列表这些独立模块。ECharts 这边,折线图、柱状图、散点图、地图、漏斗图几乎覆盖了大屏项目的全部图表需求,配置项虽然多,但解决方案也丰富,遇到网格、图例、坐标轴的问题,搜一下就能找到对应配置。
这套模板没有引入重型状态管理库,也没有使用 TypeScript,而是保持最轻量的 JavaScript + React + ECharts 组合。原因很简单:大屏项目通常以展示为主,交互逻辑有限,数据流基本是「接口 → 状态 → 图表」,引入 Redux 反而增加了样板代码。如果你后续要接复杂联动,可以再按需引入 zustand 这类轻量方案,模板的组件边界已经替你留好了。
2.2 目录结构:从入口文件到每个图表面板
项目目录是理解工程的第一入口。模板采用常规的 src 目录结构,按大屏业务域做了拆分。核心路径如下:
src/ main.tsx # 应用入口,挂载 BigScreen 组件 App.tsx # 路由入口,目前指向大屏页面 views/ big-screen/ index.tsx # 大屏主页面,负责整体布局 index.scss # 大屏样式,含顶部栏、左右侧栏、中间主区 components/ ScreenHeader/ # 顶部标题栏 MetricCard/ # 核心指标卡组件 ChartPanel/ # 通用图表面板,接收配置和标题 MapChart/ # 地图组件 TrendLine/ # 折线趋势图 RankList/ # 排行榜列表 PieChart/ # 饼图/环形图 hooks/ useScreenScale.ts # 大屏缩放适配逻辑 useECharts.ts # 图表初始化与销毁的通用逻辑 usePollingData.ts # 定时轮询数据逻辑 utils/ request.ts # axios 实例封装 mock/ index.ts # 各模块 mock 数据 trend.ts rank.tsmain.tsx 只做两件事:创建 React 根实例并渲染 App;App 直接指向大屏页面,没有配置 react-router。这么做不是偷懒,而是大屏项目往往就是单页展示,路由交给上层业务系统去处理,这个大屏只是其中一个组件。你需要集成到现有后台时,只需要把 views/big-screen 整个目录复制过去,BigScreen 就是一个独立组件。
2.3 数据层设计:mock 数据与接口请求解耦
模板把 mock 数据和请求逻辑分开,这是为了让你在前后端联调时不用改动组件代码。mock 目录下按模块拆文件,比如 trend.ts 导出折线图需要的日期和数值数组,rank.ts 导出榜单列表数据。组件内部只依赖一个 fetchData 函数,入参是模块名,返回值是 Promise 包裹的模拟数据。
在 utils/request.ts 里,axios 实例的 baseURL 通过环境变量控制:
// utils/request.ts import axios from 'axios'; const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 5000 }); request.interceptors.response.use( (response) => response.data, (error) => { console.error('请求失败:', error); return Promise.reject(error); } ); export default request;这里把响应拦截器的返回值直接设为 response.data,组件侧拿到的就是接口数据结构,省去一层 .data.data 的取值嵌套。你接真实后端时,只需要改环境变量指向真实服务地址,mock 文件保留作为本地开发兜底。
3. 核心模块拆解:大屏布局、图表渲染与数据刷新
3.1 自适应大屏布局:用 transform: scale 适配任意分辨率
大屏项目第一个绕不开的问题是:设计稿是 1920 × 1080,但投屏设备可能是 4K 拼接屏,也可能是 1366 × 768 的笔记本。模板使用 scale 缩放方案,思路是把整个大屏内容固定在设计稿尺寸下,然后根据实际窗口大小计算缩放比例。
// hooks/useScreenScale.ts export function useScreenScale(designWidth = 1920, designHeight = 1080) { useEffect(() => { const screen = document.getElementById('screen'); if (!screen) return; const handleResize = () => { const scaleX = window.innerWidth / designWidth; const scaleY = window.innerHeight / designHeight; // 取较小值,确保整个大屏完整可见 const scale = Math.min(scaleX, scaleY); screen.style.transform = `translate(-50%, -50%) scale(${scale})`; screen.style.left = '50%'; screen.style.top = '50%'; }; handleResize(); window.addEventListener('resize', handleResize); return () => window.removeEventListener('resize', handleResize); }, [designWidth, designHeight]); }逻辑说明:设计稿宽度和高度作为基准值,实时计算窗口与设计稿的比值,取宽高比中较小的一个作为缩放系数,避免大屏内容溢出屏幕。transform-origin 配合 translate(-50%, -50%) 让大屏始终居中显示。
参数说明:designWidth 和 designHeight 对应你设计稿的像素尺寸,如果接手的设计稿是 2560 × 1440,调用时传入这两个值即可。要注意的是,缩放后 ECharts 图表的 tooltip 弹出位置可能和鼠标实际位置有偏差,原因是 CSS 缩放会让坐标系偏移,这个问题在避坑章节会详细处理。
3.2 图表封装:把 ECharts 初始化收敛成一个 hook
大屏中图表数量多,如果每个组件都手写 echarts.init、setOption、resize、dispose,代码会重复十几遍。模板定义了 useECharts 这个 hook,把生命周期管理统一收口:
// hooks/useECharts.ts import * as echarts from 'echarts'; import { useEffect, useRef } from 'react'; export function useECharts(option: { [key: string]: any }) { const chartRef = useRef<HTMLDivElement>(null); const chartInstance = useRef<echarts.ECharts | null>(null); useEffect(() => { if (!chartRef.current) return; chartInstance.current = echarts.init(chartRef.current); chartInstance.current.setOption(option); const handleResize = () => { chartInstance.current?.resize(); }; window.addEventListener('resize', handleResize); return () => { window.removeEventListener('resize', handleResize); chartInstance.current?.dispose(); chartInstance.current = null; }; }, [option]); return chartRef; }逻辑说明:hook 接收一个 ECharts option 对象,在组件挂载时初始化图表,组件卸载时自动销毁实例,同时清理 resize 监听。当你传入新的 option 时,会重新 setOption。
参数说明:option 是 ECharts 的核心配置对象,包含 series(数据系列)、xAxis(横轴)、yAxis(纵轴)、legend(图例)等配置。这里的优化点在于,模板要求子组件把对 option 的修改收敛在上层,比如父组件把趋势图的数据计算好,再以新对象形式传入,避免在子组件里直接修改嵌套对象导致 ECharts 无法感知变化。
3.3 数据刷新:定时轮询与防泄漏清理
大屏数据是动态的,每隔几秒需要拉取最新数据。最常见的做法是 setInterval,但要特别注意清理时机,否则页面切换后定时器依然在跑,轻则白占内存,重则触发 React 警告。
// hooks/usePollingData.ts import { useEffect, useState } from 'react'; export function usePollingData(fetcher: () => Promise<any>, interval = 5000) { const [data, setData] = useState<any>(null); const [error, setError] = useState(null); useEffect(() => { let active = true; const load = async () => { try { const res = await fetcher(); if (active) setData(res); } catch (err) { if (active) setError(err); } }; load(); const timer = setInterval(load, interval); return () => { active = false; clearInterval(timer); }; }, [fetcher, interval]); return { data, error }; }逻辑说明:usePollingData 接收一个返回 Promise 的数据请求函数和轮询间隔,首次立即执行一次,之后按间隔重复调用。active 标志位防止组件卸载后异步请求回写状态,clearInterval 确保定时器随组件卸载一同释放。
参数说明:fetcher 参数的稳定性很重要。如果你在这个 hook 的调用处写成箭头函数里调用 request,那么每次渲染都会生成新函数引用,useEffect 会反复重建定时器,导致轮询频率异常。模板里的做法是在组件外侧定义 fetcher,或者用 useCallback 包裹。
4. 环境搭建与运行:从依赖安装到生产构建
4.1 环境要求与依赖清单
在跑起来之前,先确认环境。模板基于 Vite 构建,需要 Node.js 16 以上版本,包管理器用 npm 或 pnpm 均可。依赖方面,核心就三个:react、react-dom、echarts。axios 用于接口请求,sass 用于样式编译,dayjs 用于日期格式化。安装命令如下:
npm install echarts axios dayjs sass如果你用的是 pnpm,换成 pnpm add 即可。依赖装完之后,node_modules 体积可能偏大,这是正常的,ECharts 的包体本来就不小。后续如果考虑性能优化,可以做按需引入,二次开发章节会讲到。
4.2 开发模式启动:一条命令跑起来
依赖安装完成后,启动开发服务器:
npm run devVite 默认占用 5173 端口,启动日志会打印本地访问地址。浏览器打开后如果看到顶部标题栏、左侧环形图、中间地图、右侧柱状图、底部滚动列表,说明环境没问题。
这里有几个可能出现的拦路问题。第一个是端口被占用,Vite 会自动切换一个可用端口,但你的防火墙规则可能拦截了非预期端口,此时可以手动指定端口:npm run dev -- --port 8080。第二个是远程访问场景,比如你在一台服务器上启动开发服务器,想从本机预览,需要加上--host参数允许外部访问:npm run dev -- --host 0.0.0.0。
开发模式下修改代码会自动热更新,但 ECharts 的 option 变化偶尔不会触发即时刷新,如果改了图表配置却看不到变化,手动刷新一下页面就好,这是 ECharts 实例的渲染机制导致的,不是代码问题。
4.3 生产构建与部署:静态资源放哪、路由怎么配
开发调试完毕要部署上线。构建命令是:
npm run build产物会输出到 dist 目录,包含 index.html、assets 下的 JS 和 CSS 文件。这个大屏默认使用 Hash 路由或直接单页渲染,所以部署时路由配置比较宽松。
常见部署方式是扔到 Nginx 静态目录:
server { listen 80; server_name your-domain.com; root /usr/share/nginx/html/big-screen; index index.html; # 单页应用兜底路由 location / { try_files $uri $uri/ /index.html; } # 反向代理后端接口 location /api/ { proxy_pass http://your-backend-service; proxy_set_header Host $host; } }注意生产环境的接口地址。前端构建后,请求地址写死在包体里,所以模板用环境变量区分开发与生产环境:开发环境 VITE_API_BASE_URL 指向 mock 服务,生产环境指向真实后端域名。如果你没有配置环境变量文件,构建产物里 request.ts 的 baseURL 会默认走 /api 前缀,此时必须在 Nginx 里配置对应的反向代理,否则所有数据请求都会打到静态服务器上,返回 404。
5. 避坑指南:大屏项目中五个高频问题
5.1 图表渲染空白:容器高度为 0
现象:页面加载完,图表区域一片空白,控制台没报错,ECharts 实例也初始化了,但啥也看不见。
原因:ECharts 初始化时,容器元素必须有一个可计算的高度。如果图表组件的父容器没有显式设置高度,默认高度为 0,ECharts 渲染结果自然不会显示。模板中的 ChartPanel 组件要求外部传入一个 style 对象,其中必须包含 height。
解决:给图表容器设置明确的宽度和高度,推荐在组件内用style={{ width: '100%', height: '100%' }},同时确保父元素也有确定高度。如果父元素由 flex 布局撑开,要给父元素设置display: flex; flex-direction: column; flex: 1;这类组合。排查时打开浏览器开发者工具,选中容器节点看计算样式,高度不再为 0 问题就解决了。
5.2 缩放适配后 tooltip 位置偏移
现象:大屏通过 transform: scale 缩放后,鼠标悬停到图表上,tooltip 提示框显示的位置和鼠标指针距离明显拉开,偏左上或偏右下。
原因:CSS transform 会改变元素在视觉上的位置,但 echarts 内部监听的是鼠标事件在文档坐标系中的值,二者没有同步。这是所有用 scale 缩放方案做自适应大屏的通用病。
解决:模板在 useScreenScale 中把缩放比例计算出来后,可以把它传给 ECharts,在图表初始化时设置echarts.init(dom, theme, { width, height })并不会改变事件坐标逻辑,真正有效的做法是在 tooltip 的 position 回调里根据 scale 反向计算:
tooltip: { position: (point, params, dom, rect, size) => { const scale = window.__screenScale__ || 1; return { left: point[0] / scale, top: point[1] / scale }; } }逻辑说明:point 是鼠标在屏幕上的坐标,除以缩放比例后映射回设计稿坐标系,tooltip 就能显示在正确位置。window.screenScale是 useScreenScale 挂载到 window 上的全局缩放值,如果你对这个全局变量有洁癖,也可以用 context 传递。
5.3 定时轮询叠加翻车
现象:切换页面再回来,网络请求频率翻倍;从详情页返回大屏,请求数呈倍数增长。
原因:usePollingData 的依赖项 fetcher 是内联箭头函数,每次组件渲染都会产生新引用,useEffect 认为依赖变化了,重新建立定时器而没有清掉旧的。多次重渲染导致多个 setInterval 叠加。
解决:把 fetcher 用 useCallback 包裹,或者在组件外定义。更稳妥的做法是检查 usePollingData 内部 effect 返回值,确保清理函数确实在每次 effect 重新执行前触发。我的习惯是在依赖数组里用 interval 单依赖,fetcher 通过 ref 持有:
const fetcherRef = useRef(fetcher); fetcherRef.current = fetcher; useEffect(() => { const load = () => fetcherRef.current(); load(); const timer = setInterval(load, interval); return () => clearInterval(timer); }, [interval]);5.4 动态数据更新时图表闪烁
现象:每 5 秒刷新一次数据,刷新瞬间图表整体闪一下白屏,视觉上看起来是「闪没再出现」。
原因:setOption 默认的 notMerge 行为与 updateMode 配合不当。当你给 setOption 传入全新对象且没有指定第二个参数为 false 时,ECharts 内部会对已有组件做合并,这本身不会闪烁;真正闪烁是因为部分配置项在两次 option 之间缺失,ECharts 把它理解成移除组件,触发了一次重建。
解决:在更新数据时保留原有配置结构,只替换 data 字段,同时显式指定 setOption 的合并行为:
chartInstance.setOption( { series: [{ data: newData }] }, { notMerge: false, lazyUpdate: true } );逻辑说明:notMerge 为 false 表示增量更新,只在原有系列上替换数据,不重建整个图表;lazyUpdate 表示在框架渲染的下一帧再应用更新,避免了同步渲染造成的视觉闪烁。
5.5 开发正常、上线后字体和图片丢失
现象:本地 npm run dev 一切正常,npm run build 部署到测试环境后,大屏上的背景图片、icon 图标、自定义字体全变成空白,控制台一堆 404。
原因:静态资源路径写的是相对路径,而生产环境部署到子目录下。构建时 Vite 会根据 base 配置生成资源 URL,默认是/assets/xxx.png,如果你的大屏挂在 Nginx 的/big-screen子路径下,图片请求就访问不到了。
解决:在 vite.config.js 里显式设置 base:
export default { base: process.env.NODE_ENV === 'production' ? '/big-screen/' : '/' };然后重新构建。这样生成的资源路径会带上 /big-screen/ 前缀,配合 Nginx 静态目录根路径即可。血泪经验:每次部署前先去 dist 目录看一眼 index.html,确认 script 标签的 src 路径前缀是否和实际部署路径一致,能省掉一次排查时间。
6. 二次开发:从模板到完整业务系统的三个改造方向
大屏模板的核心价值在于能把设计稿快速变成可交互页面,但真正落地业务时,你基本需要做三个层面的改造:接真实接口、按需打包、定制主题。下面分别给出具体操作建议。
6.1 接入真实接口:替换 mock 数据
mock 数据与业务解耦的设计在这里体现价值。你要做的第一步是把 usePollingData 的 fetcher 换成真实请求:
const fetchTrendData = useCallback(() => { return request({ url: '/api/trend', method: 'get', params: { period: 'last7days' } }); }, []); const { data } = usePollingData(fetchTrendData, 5000);字段映射问题会在这时候暴露。mock 数据和后端返回字段大概率不一致,比如 mock 用 value,后端用 count。建议在 fetcher 里做一次统一转换,让组件侧只依赖领域字段,避免把转换逻辑散落在渲染代码里。
6.2 按需引入 ECharts:从 1MB 降到 200KB
ECharts 全量包体积偏大,初次加载大屏时首屏渲染会慢。改造方式是从echarts/core引入,只注册需要的图表和组件:
import * as echarts from 'echarts/core'; import { LineChart, BarChart, PieChart } from 'echarts/charts'; import { GridComponent, TooltipComponent, LegendComponent } from 'echarts/components'; import { CanvasRenderer } from 'echarts/renderers'; echarts.use([LineChart, BarChart, PieChart, GridComponent, TooltipComponent, LegendComponent, CanvasRenderer]);这样三步操作后,构建产物体积有明显下降。注意地图场景需要额外注册 MapChart,并且加载 GeoJSON 数据。
6.3 主题与布局定制:让大屏有了自己的视觉身份
模板的样式集中在 index.scss,变量定义在文件头部:
:root { --primary-color: #00d4ff; --bg-color: #0a1a2f; --border-color: rgba(0, 212, 255, 0.3); }改主题最快的方式是替换这几个 CSS 变量。如果你想做浅色风格或者企业 VI 色系,只需调整变量值,不需要动组件结构。更深度的定制是把 useECharts 扩展出主题参数,传入echarts.init(dom, theme)。
改造完成后,建议用一台真实分辨率设备走一遍全流程:启动 → 缩放 → 轮询 → 构建 → 部署。我从那以后每次接手大屏项目都强制走一遍「确认设计稿分辨率 → 封装图表组件 → 写 mock 数据 → 接真实接口 → 构建部署」的链路,多花半天时间,但上线后踩坑的概率低了很多。希望这篇拆解能帮到你,少走我已经替你走过的弯路。
本文还有配套的精品资源,点击获取