十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Flask+ECharts打造实时数据大屏:接口设计、前端配置与部署实践

Flask+ECharts打造实时数据大屏:接口设计、前端配置与部署实践 简介基于Echarts与Python Flask构建的数据可视化动态实时大屏范例面向需要开发企业宣传大屏或数据看板的前后端开发者也适合作为可视化课程设计的完整参考。项目演示了如何利用Flask编写后端接口、处理业务数据并通过JSON格式与前端Echarts图表联动实现定时更新和实时交互完整覆盖前后端分离开发中的关键环节。资源包共233个文件包含131个JavaScript脚本、15个HTML页面、15个CSS样式、8个JSON配置及4个Python程序同时提供操作手册和演示视频压缩包总大小43.84MB目录组织清晰利于直接部署与二次开发。按手册步骤即可完成从环境搭建、Flask配置、Echarts接入到上线展示的全过程既能学习大屏布局与样式定制也能掌握动态数据刷新的实现思路。已有3025人学习是快速上手数据可视化大屏项目的优质参考资料。1. 大屏项目的难点不在图表而在数据通道做企业宣传大屏新手往往把注意力放在 ECharts 图表配置上拆过几个真实项目之后会发现静态图表只占工作量的一小半真正决定成败的是后端怎么出数据、前端多久拉一次、时间轴怎么对齐。这份基于 ECharts 5.x Python Flask 的资源包完整跑通了一条链路后端负责组织业务数据并暴露 JSON 接口前端用 jQuery/Ajax 做定时拉取再把折线图、饼图、柱状图、地图模块组装在一块深色大屏上。它适合刚接触数据可视化、需要一个能抄的工程骨架的开发者也适合想把已有报表改成实时展示、但不想引入 Vue/React 重前端框架的团队。下面按拆这类项目的顺序来先定数据接口再装配图表最后处理刷新和部署细节。2. Flask 数据通道先把后端接口设计清楚2.1 为什么大屏场景适合用 Flask大屏的实时性要求其实不高一般 3 到 10 秒刷新一次就是主流节奏。服务端只需要做一件事把数据整理成前端能直接用的 JSON。Flask 的轻量在这一场景是明显优势单文件能启动、自带开发服务器、不强制 ORM 和模板适合快速搭一个数据源。和 Node Express 相比省去 npm 依赖的维护成本和 Spring Boot 相比省去 JVM 启动时间和工程骨架的复杂度。模板包里出现的 bootstrap.min.css、all.min.css、dialog.css 这些文件也暗示原项目把前端静态资源放在static/目录里由 Flask 直接托管这是不依赖前端构建链路的务实方案拿过来解压就能跑。环境上只要装了 Python 3.9 以上pip install flask就能启动VSCode 里装好 Python 插件直接 F5 调试 app.py不需要额外配虚拟环境之外的东西。2.2 接口设计拆开还是聚合大屏页面上通常有六到八个模块顶部指标卡、销售趋势折线、品类占比饼图、地区分布地图、排行榜滚动列表。接口有两种做法一种是全部聚合到一个/api/overview另一种是按模块拆开。聚合接口写起来省事但轮询时会被最低频的模块拖累比如地图 30 秒刷一次指标卡 3 秒刷一次聚合在中间就只能按 30 秒的节奏来指标卡就失去了实时性。实际项目中建议按模块拆接口各模块按自己的频率独立拉取。接口路径返回内容建议轮询间隔/api/sales/trend近 5 分钟销售趋势60 个采样点5s/api/sales/category品类销售占比10s/api/geo/distribution分省销售额地图数据30s/api/rank/order门店排行榜 Top2010s每个接口统一返回三层结构code表示业务状态data放图表数据updateAt放后端生成数据的时间戳。联调排错时前端只需判断code 0再比较updateAt和本地时间的差值就能判断是通道慢还是数据本身旧。2.3 用后台线程维护内存数据源真实项目里数据库往往还没就绪或者外部业务接口不稳定大屏联调不能干等。常见做法是先做一个内存数据源模块用后台线程定时更新共享数据Flask 路由只负责读取快照这样后续接数据库时只需替换数据源内部实现路由层完全不用动。# data_source.py import copy import random import threading import time _shared {sales: [], total: 0, updated_at: 0} _lock threading.Lock() def _worker(): while True: ts int(time.time() * 1000) value round(random.uniform(80, 150), 2) with _lock: _shared[sales].append({ts: ts, value: value}) # 只保留最近 60 个点避免内存无限增长 _shared[sales] _shared[sales][-60:] total sum(p[value] for p in _shared[sales]) _shared[total] round(total, 2) _shared[updated_at] ts time.sleep(2) def start(): # daemonTrue 保证主进程退出时线程跟随退出 threading.Thread(target_worker, daemonTrue).start() def snapshot(): # 深拷贝一份防止外层调用方改到共享数据 with _lock: return copy.deepcopy(_shared)_worker每两秒生成一个新采样点并推进时间戳_shared[sales]用[-60:]截断天然维护了一个滑动窗口。加锁的原因在于 Flask 的多线程开发服务器下路由读取和后台线程写入可能同时发生虽然 CPython 的 GIL 让 list 追加操作是原子的但读取一个正在被整体替换的字典时深拷贝能避免拿到半写状态。快照接口返回的是副本前端无论怎么处理都不会污染内部。# app.py import time from flask import Flask, jsonify import data_source app Flask(__name__) def now_ts(): # 统一转成毫秒时间戳前端拿到后可直接 new Date(ts) return int(time.time() * 1000) app.route(/api/sales/trend) def sales_trend(): snap data_source.snapshot() return jsonify({ code: 0, data: snap[sales], total: snap[total], updateAt: snap[updated_at], }) app.route(/api/health) def health(): # 联调阶段用这个接口验证服务和网络延迟 return jsonify({code: 0, serverTime: now_ts()}) if __name__ __main__: data_source.start() # use_reloaderFalsedebug 模式的 reloader 会再拉起一个进程 # 导致 data_source 的线程被创建两次数据出现双写 app.run(host0.0.0.0, port8000, debugTrue, use_reloaderFalse, threadedTrue)路由里不直接操作random而是统一读快照这是为了让数据更新频率由后台线程控制而不是由请求频率决定否则大屏一旦停几秒再恢复数据会出现空档。host0.0.0.0是把服务绑定到全部网卡局域网内其他机器才能通过 IP 访问大屏页面这是企业宣传大屏的常态主机放在展厅平板和电视通过 IP 打开。3. Echarts 大屏前端布局、配置、地图装配3.1 静态资源组织方式资源包里没有package.json和 node_modules说明前端是纯静态文件 CDN 或本地 lib 的路线。ECharts 通常把echarts.min.js放到static/lib/echarts/目录下CSS 文件则按职责拆base.css做样式重置bootstrap.min.css提供基础栅格和按钮main.css写大屏专属布局dialog.css和input.css属于弹窗和表单模块首屏用不到可以不加载。HTML 里统一用/static/绝对路径引用不要写../static这种相对路径否则在 Flask 的 URL 规则下很容易 404。link relstylesheet href/static/css/bootstrap.min.css link relstylesheet href/static/css/main.css script src/static/lib/echarts/echarts.min.js/scriptECharts 5.x 默认按需构建比全量包小很多但分发工程里直接用全量echarts.min.js更省事它内置了折线、柱状、饼图、地图散点等常用图表和组件省去手动配置echarts/core的导入路径。如果后续要上 3D 效果再单独引入echarts-gl它会以插件形式注册到同一个 echarts 实例上。3.2 大屏布局用 CSS GridBootstrap 的 12 栅格更适合普通后台页面大屏模块的跨列和高度往往不规则强行套栅格会产生大量嵌套row和col代码冗余且响应式表现一般。用 CSS Grid 能直接描述模块边界。/* main.css */ .screen { display: grid; grid-template-columns: repeat(12, 1fr); grid-template-rows: 64px 1fr 1.2fr 80px; gap: 12px; height: 100vh; background: #0a1428; } .screen .topbar { grid-column: 1 / -1; } .screen .chart-trend { grid-column: span 5; grid-row: span 2; } .screen .chart-pie { grid-column: span 3; grid-row: span 2; } .screen .map-box { grid-column: span 4; grid-row: span 3; }grid-template-columns: repeat(12, 1fr)把宽度切成 12 等份span控制每个图表块占几列几行gap是模块间距。这样的布局代码一眼就能看出哪块大哪块小后续调整只需要改 span 数值。每个图表容器必须有一个确定的高度ECharts 初始化时拿不到高度会渲染成空白所以 Grid 的grid-template-rows要显式写不能用auto。3.3 图表实例化管理图表初始化逻辑收敛成一个函数页面里所有模块共用一个 resize 监听避免每个图表都注册一遍。// dashboard.js function mountChart(domId, option) { const dom document.getElementById(domId); const chart echarts.init(dom); chart.setOption(option); window.addEventListener(resize, function () { chart.resize(); // 大屏从 1080P 切换到 4K 时依赖这个事件自适应 }); return chart; }echarts.init的第二个参数可以传主题深色大屏通常配合暗色主题使用。resize事件在浏览器窗口变化时触发展厅的大屏主机如果分辨率切了所有图表会自动重新计算尺寸不需要手动刷新页面。折线图的配置里最容易被忽略的是 xAxis 的类型选择和时间格式化。const trendChart mountChart(trendBox, { grid: { top: 30, right: 20, bottom: 30, left: 50 }, tooltip: { trigger: axis }, xAxis: { type: time, // 数据点直接传毫秒时间戳 axisLabel: { formatter: function (ts) { const d new Date(ts); const p (n) String(n).padStart(2, 0); return p(d.getHours()) : p(d.getMinutes()) : p(d.getSeconds()); } } }, yAxis: { type: value, splitLine: { lineStyle: { color: #1e2a3a } } }, series: [ { name: 销售额, type: line, smooth: true, showSymbol: false, lineStyle: { width: 2, color: #37d6ff }, areaStyle: { color: { type: linear, x: 0, y: 0, x2: 0, y2: 1, colorStops: [ { offset: 0, color: rgba(55, 214, 255, 0.35) }, { offset: 1, color: rgba(55, 214, 255, 0) } ] } }, data: [] } ] });xAxis.type: time是关键它允许 data 里的每项直接传毫秒时间戳ECharts 内部会自动计算刻度范围。formatter里手写补零而不是依赖第三方日期库是因为模板没引入 moment/dayjs手写九行代码就能解决。showSymbol: false在 60 个数据点下能让渲染更流畅areaStyle用线性渐变形成光晕效果这是企业大屏常见的视觉处理方式。grid的四个值分别是 left、top、right、bottom单位是像素大屏上留白不对称反而有设计感但别让坐标轴文字被裁掉。3.4 中国地图模块registerMap 与 markPointECharts 4.x 时代地图数据是内置的5.x 起官方不再内置中国地图 GeoJSON需要自己准备一份china.json。常见做法是把文件放在static/map/目录下页面加载后先 fetch 再注册。// geo.js fetch(/static/map/china.json) .then((res) res.json()) .then((geo) { echarts.registerMap(china, geo); const mapChart mountChart(mapBox, { visualMap: { min: 0, max: 1000, left: 10, bottom: 10, text: [高, 低], inRange: { color: [#0d2f4e, #3fa7ff] } }, series: [ { type: map, map: china, roam: false, label: { show: false }, data: [ { name: 广东, value: 860 }, { name: 江苏, value: 720 }, { name: 浙江, value: 690 } ], markPoint: { data: [ { name: 广州, coord: [113.26, 23.13] } ] } } ] }); });registerMap只需要调用一次注册名china会作为后续 series 里map字段的引用。visualMap负责把数值映射到inRange的颜色渐变。markPoint里的coord是经纬度数组用于在地图上标重点城市比如总部、分公司位置热词里常见的“echarts map 里的 markpoint”指的就是这个配置。roam: false关闭缩放拖拽因为大屏是展示场景用户不会去操作地图。4. 实时刷新与联调轮询、滚动更新、时间对齐4.1 大屏场景轮询优于 WebSocket很多人在“实时”两个字面前第一反应是 WebSocket但企业宣传大屏的刷新节奏是秒级不是毫秒级HTTP 轮询在实现成本和稳定性上反而更合适。维度HTTP 轮询WebSocket连接开销每次请求独立无长连接一次握手常驻连接实现成本fetch setTimeout 即用后端需要 flask-sock 或 Socket.IO刷新频率3 秒以上完全够用适合亚秒级推送大屏适用性模板默认就是这种设计多个大屏共用服务端推送时考虑人眼对大屏数字的感知节奏远低于监控系统3 秒刷新在视觉上已经是“一直在跳”的效果。轮询还有一个隐藏好处服务端无状态前端刷新页面后重新拉一次数据就能恢复而 WebSocket 断线重连和消息补偿的复杂度在这个场景里是过度设计。4.2 防请求重叠的轮询控制器日常开发里常见的写法是setInterval配合 Ajax这里建议换成setTimeout递归核心原因是避免请求没返回时下一次轮询已经发出导致响应乱序和请求堆积。// poll.js function startPolling(url, interval, render) { let stopped false; async function tick() { if (stopped) return; try { const resp await fetch(url); const body await resp.json(); if (body.code 0) { render(body.data, body.updateAt); } } catch (err) { console.warn(poll failed:, url, err); } finally { // 无论请求成功失败都等 interval 后发起下一次 setTimeout(tick, interval); } } tick(); return function stop() { stopped true; }; }stopped标志位让控制器可被终止页面卸载时调用返回的stop()能避免组件销毁后还有定时回调在跑这是大屏长时间无人值守时内存泄漏的主要来源。fetch默认不带超时如果后端卡死这个轮询会一直挂在 pending 状态finally里的setTimeout依然会执行但更稳妥的做法是在 fetch 外层包一个 AbortController 设 10 秒超时。失败时不弹窗只打 warn因为大屏无人点击确认框一旦弹窗会一直挂在那里。4.3 折线图滚动更新数据到达前端后要做的是把新点追加进 series同时把最旧的点挤出去形成向右流动的效果。// chart-update.js function appendPoints(chart, newPoints, maxPoints) { const max maxPoints || 60; const series chart.getOption().series[0]; const merged series.data.concat(newPoints); const next merged.slice(-max); chart.setOption({ series: [{ data: next }] }); }chart.getOption().series[0].data取到的是当前图表内存中的数据concat 后从尾部截断maxPoints个点视觉上就是滚动窗口。这里故意只更新data字段是因为 ECharts 的setOption默认是 merge 语义坐标轴、tooltip、颜色这些配置都会保留不会重建整个图表动画也不会中断。只有当你改了 xAxis 的 type 或 series 的数量这种结构性配置时才需要传入{ notMerge: true }做一次全量替换代价是图表会瞬间闪烁并丢失动画状态。ECharts 还提供一个appendData接口专门用于流式数据但它要求数据是按时间严格递增写入的且只支持部分图表类型。大屏场景用setOption全量更新 60 个点足够几毫秒就能完成没必要为了“流式”两个字引入额外限制。4.4 用 updateAt 验证通道健康度数据看起来“没在动”时先判断是后端没更新还是前端没拉取。打开浏览器控制台执行一段 fetch就能量化通道延迟。fetch(/api/health) .then((r) r.json()) .then((d) { console.log(serverTime:, d.serverTime); console.log(本地时间差(ms):, Date.now() - d.serverTime); });serverTime是后端生成响应时的时间戳Date.now() - d.serverTime得到的是端到端时间差。正常局域网内这个值应该在几十毫秒以内。如果差值超过轮询间隔说明后端处理慢或网络有拥塞如果差值接近 0 但图表不动问题就在前端的 render 逻辑里。另外要统一时间格式后端一律返回毫秒时间戳不要返回“2024-01-01 10:00:00”这种 ISO 字符串避免前端new Date()解析时的时区偏差。这一点在跨时区部署时特别重要之前在展厅机器上遇到过系统时区被改成别的地区导致图表时间轴整体偏移 8 小时的案例改用纯时间戳后问题消失。5. 部署与排错大屏从能跑到跑得稳5.1 绑定地址与端口冲突开发环境app.run默认绑 127.0.0.1只能本机访问。展厅大屏需要修改为host0.0.0.0让同一局域网里的平板和电视都能打开页面。端口被占用时先看是谁占了再换端口# macOS / Linux lsof -i:8000 # Windows netstat -ano | findstr :8000确定占用后直接换端口启动不必强行 kill因为大屏主机上往往还跑着其他服务。Flask 的开发服务器并发能力有限threadedTrue能处理简单的并发请求但多台大屏同时访问时开发服务器会串行处理长响应这时候就该换 waitress。5.2 静态资源 404 与 favicon 刷屏静态资源 404 多半是路径问题。Flask 默认把static/目录映射到/static/前缀HTML 里应该写/static/css/main.css而不是./css/main.css。如果用了 Jinja2 模板统一用{{ url_for(static, filenamecss/main.css) }}生成路径它能自动处理 Flask 应用的根路径配置。另一个常见问题是 favicon浏览器访问任何页面都会默认请求/favicon.ico开发日志里会刷出一行 404不影响功能但干扰排查。可以在 app.py 里加一个轻量路由app.route(/favicon.ico) def favicon(): return , 2045.3 生产运行waitress 替代开发服务器Flask 自带的开发服务器是 Werkzeug启动时会打一行警告明确指出不适合生产环境。企业大屏虽然访问量不大但要在展厅连续跑几天进程稳定性很重要。waitress 是纯 Python 的 WSGI 服务器Windows 和 macOS 都能跑不需要额外装 C 扩展。pip install waitress waitress-serve --host0.0.0.0 --port8000 --threads8 app:appapp:app的左边是文件名右边是 Flask 实例名。--threads8对应开发环境里的threadedTrue大屏场景 8 个线程足够。waitress 的日志比 Werkzeug 干净默认只输出错误适合无人值守。如果入口文件改了名比如叫server.py命令相应改成server:app。5.4 数据变慢时的排查顺序实时大屏最常见的故障表现是“数字在跳但图表不动”或“整体卡顿”。不要一上来就怀疑 ECharts 性能先按顺序排查。第一步看updateAt的差值如果后端生成数据的时间比当前时间慢了几秒说明数据源模块的线程卡住了检查是不是数据库慢查询阻塞了更新第二步看轮询请求在 Network 面板里的耗时如果持续超过 1 秒优先压缩接口返回的 JSONFlask 可以挂flask-compress或直接在进程外用 Nginx 做 gzip地图 GeoJSON 文件很大压缩后能从几 MB 降到几百 KB第三步才是图表渲染数据量在几百个点以内时ECharts 的 Canvas 渲染压力可以忽略。大屏项目排错到最后本质上就是一条数据链路的健康度检查数据源、接口、轮询、渲染每一环都能被监控到问题就藏不住。用/api/health做探针配合Date.now() - serverTime的差值观察就能把实时通道的延迟量化出来比靠肉眼盯屏幕可靠得多。# 启动后连续拉两次 health观察 serverTime 的增量是否接近轮询间隔 watch -n 1 curl -s http://127.0.0.1:8000/api/health本文还有配套的精品资源点击获取
返回列表