ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Vue3+Echarts从零搭建智慧农业监控大屏:业务拆解与图表实现

Vue3+Echarts从零搭建智慧农业监控大屏:业务拆解与图表实现 简介这是一套面向Vue3与ECharts数据可视化开发者的智慧农业监控大屏实例资源适合有基础前端知识、希望快速搭建可视化看板的工程师与学习者。资源基于vue-echarts完成监控大屏搭建包含高德地图集成、报表展示、菜单布局整理、全屏切换与退出等功能并提供HBuilderX开发环境下的完整项目案例。压缩包共包含12463个文件大小约44.75MB以js、ts、vue源码为主辅以map、json、scss/css样式文件及少量md文档和mp4视频结构清晰便于按模块查阅。已有4442人学习下载是一份兼具参考价值与实用性的智慧农业可视化模板。读者可获得可运行源码、开发过程视频、相关文档与素材直接在此基础上扩展业务图表或替换数据源缩短监控大屏类项目从零到一的上手时间。 作为监控大屏系列的第四篇这次我把场景切到了智慧农业。刚接到这个模板需求的时候我心里其实有点想当然无非是 Vue 搭架子、Echarts 画图表把温湿度、光照、产量这些数据怼上去就行。等真正动手才意识到“画出一张图”和“做出一块能用的监控大屏”之间隔着一整条沟。这篇就把我用 VueEcharts 从零做智慧农业监控大屏模板实例的过程完整拆开讲先梳理业务再定工程骨架然后逐一说核心图表的实现最后是我实测中踩过的坑和几个值得做的扩展方向。适合正在做监控大屏的前端朋友、刚接手数据可视化项目的新手以及农业信息化方向想快速出原型的人参考。1. 先拆业务这块屏不是展示厅是“作战室”1.1 先回答三个问题给谁看、盯什么、看完干什么很多大屏项目失败不是图表画得不好而是根本没想清楚业务逻辑。智慧农业大屏的目标用户基本是三类人农场管理员、生产主管、农技人员。他们站在屏幕前不是来欣赏动画的而是要快速回答几个问题现在环境是否正常哪个地块有问题趋势是在变好还是变坏要不要派人处理所以我做这个模板时第一件事不是开 IDE而是先列了一张“决策路径”清单如果大棚温度持续超过 35℃就要准备通风或遮阳如果某块地的土壤湿度低于阈值就要启动灌溉如果某个区域产量占比异常降低就要回溯该区域的施肥或光照记录。这张清单直接决定了大屏上放什么、怎么布局。1.2 布局怎么定核心指标占顶趋势和空间分布占主体我用的布局思路是“顶部 KPI 左右侧分析图 中央地块总览 底部辅助信息”这是监控大屏比较通用的框架也适合智慧农业场景顶部一行放全局 KPI今日平均温度、实时空气湿度、灌溉总面积、当前告警数左侧放环境趋势温湿度 24 小时折线、二氧化碳浓度变化中央放地块分布总览显示各分区的种植状态和健康度右侧放环境综合指标雷达图、产量构成环形图、设备在线率底部跑一条最近告警列表按时间倒序滚动。这样排布的逻辑是顶部给结论中央给位置左右给原因和构成底部给事件流。用户扫一眼就能形成“哪里有问题、为什么有问题、严重到什么程度”的完整判断。1.3 接口字段怎么约定一开始就把 mock 数据定好大屏项目最忌讳前端等后端接口出来才动工。我在模板里直接定义好了一套 mock 数据格式前后端按这个字段联调省去后面大量返工{ code: 0, data: { kpi: { avgTemperature: 26.3, avgHumidity: 68.5, irrigationArea: 1260, alarmCount: 3 }, envTrend: [ { time: 00:00, temperature: 22.1, humidity: 72.4 }, { time: 01:00, temperature: 21.6, humidity: 73.1 } ], plotList: [ { id: A01, name: 番茄一区, crop: 番茄, status: normal, health: 92 }, { id: A02, name: 番茄二区, crop: 番茄, status: warning, health: 61 } ], yieldRatio: [ { name: 番茄, value: 420 }, { name: 黄瓜, value: 300 }, { name: 叶菜, value: 240 } ], deviceStatus: { online: 24, offline: 3 }, alarmList: [ { time: 14:32, level: 高, content: A02 地块土壤湿度过低 } ] } }接口结构统一了后面每个图表组件只看自己需要的那段数据逻辑非常清爽。2. 搭工程骨架Vue3 Vite Echarts 的目录与按需加载2.1 为什么这次选 Vue3 Vite早期版本我用过 Vue CLI 做这类项目现在做新模板一律推荐 Vite。原因很实际Vite 冷启动速度快开发时改代码热更新几乎秒级大屏项目动辄几十个图表组件开发体验差距非常明显。Vue3 的组合式 API 也适合按业务功能组织图表逻辑一个面板一个setup代码内聚性比 Vue2 的 options API 好很多。初始化项目npm create vitelatest smart-agri-screen -- --template vue cd smart-agri-screen npm install npm install echartsVite 4 创建 Vue 项目后默认就是main.js入口 App.vue没有多余的东西直接在这上面叠大屏结构。2.2 Echarts 一定要按需引入别图省事全量导入很多人图省事在main.js里写import * as echarts from echarts整个 echarts 包体积几十 KB 起步压缩后也相当可观。大屏项目里图表种类其实有限按需引入能把打包体积降得很明显。我这个模板只用了几类图表按需注册如下import * as echarts from echarts/core import { LineChart, PieChart, RadarChart, BarChart } from echarts/charts import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, DataZoomComponent } from echarts/components import { CanvasRenderer } from echarts/renderers echarts.use([ LineChart, PieChart, RadarChart, BarChart, TitleComponent, TooltipComponent, LegendComponent, GridComponent, DataZoomComponent ]) export default echarts这样在任意组件里import echarts from /utils/echarts就能用打包体积比全量导入少一大截。2.3 目录结构每个面板一个组件别把所有图表堆在一个文件里我见过把十几个 option 写在同一个.vue文件里的项目改起来头皮发麻。模板里我按面板拆组件每个组件只负责一个图表实例互不干扰src/ ├── api/ │ └── screen.js # 接口请求封装 ├── mock/ │ └── screenData.js # mock 数据 ├── utils/ │ └── echarts.js # echarts 按需注册 │ └── screenAdapter.js # 大屏缩放适配 ├── components/ │ ├── KpiHeader/ # 顶部KPI组件 │ ├── EnvTrend/ # 温湿度趋势折线 │ ├── PlotMap/ # 地块分布图 │ ├── EnvRadar/ # 环境综合指标雷达 │ ├── YieldPie/ # 产量占比环形图 │ └── AlarmList/ # 告警列表 └── App.vue # 大屏整体布局每个组件内部统一走“初始化实例 - 监听数据变化 - setOption - 监听 resize”这套流程后面章节按这个流程展开。2.4 大屏适配推荐 transform: scale 方案而不是改 rem大屏最常见的是在 1920x1080 设计稿下开发但实际显示器可能是 1366 宽、2560 宽甚至竖屏拼接。我推荐用transform: scale做整体缩放思路是按设计稿固定尺寸开发运行时计算浏览器窗口和设计稿的缩放比然后对整个根容器做缩放。// utils/screenAdapter.js export function fitScreen(designWidth 1920, designHeight 1080) { const scaleX window.innerWidth / designWidth const scaleY window.innerHeight / designHeight // 等比缩放用 Math.min铺满用分别缩放这里推荐等比缩放宽高居中 const scale Math.min(scaleX, scaleY) document.documentElement.style.fontSize 16px const screenDom document.getElementById(screen) if (screenDom) { screenDom.style.transform scale(${scale}) screenDom.style.transformOrigin left top // 若不铺满外层容器留白居中 } }这种方案的好处是Echarts 内部使用的像素坐标直接跟着整个容器缩放不用在图表里单独考虑适配问题。要铺满大屏时也可以用scaleX和scaleY分开缩放代价是图形会被拉变形我一般不用。3. 核心图表到底怎么写趋势、构成、综合指标和地块分布3.1 温湿度趋势折线普通折线不够要带阈值警戒线环境趋势图没有“正常/异常参考线”就是一张废图。以番茄大棚为例温度超过 35℃ 就要预警。我在折线图里用markLine把阈值直接画出来一眼能看到数据是否越界// EnvTrend.vue const option { tooltip: { trigger: axis }, legend: { data: [温度, 湿度] }, grid: { left: 50, right: 20, top: 40, bottom: 30 }, xAxis: { type: category, data: timeList }, yAxis: [ { type: value, name: 温度(℃) }, { type: value, name: 湿度(%), max: 100 } ], series: [ { name: 温度, type: line, smooth: true, data: tempList, markLine: { silent: true, symbol: none, label: { formatter: 高温阈值 35℃ }, data: [{ yAxis: 35, lineStyle: { color: #ff4d4f, type: dashed } }] } }, { name: 湿度, type: line, yAxisIndex: 1, smooth: true, data: humidityList } ] }实际使用中我会把阈值也做成配置项这样不同作物不同季节可以在接口里直接下发不用改前端代码。3.2 产量占比环形图把总量放在中间信息量直接翻倍普通饼图占地方中间还空着大屏上浪费。环形图配合title居中放总量用同样面积承载更多信息const option { title: { text: 960\n总产量(吨), left: center, top: 38%, textStyle: { fontSize: 16, color: #fff, lineHeight: 24 } }, tooltip: { trigger: item }, legend: { bottom: 10 }, series: [ { type: pie, radius: [45%, 70%], label: { formatter: {b}: {c}吨 ({d}%) }, data: yieldData } ] }注意radius用数组[45%, 70%]表示内径和外径中间留出的区域放总数。如果要在小于这个环的位置放图例记得把图例bottom调低一点别压到圆环。3.3 环境综合指标雷达图让“整体环境好不好”一目了然雷达图特别适合展示多维度的综合状态。我取温度、湿度、光照、二氧化碳、土壤湿度、土壤 pH 六个维度然后把“当前值”和“目标值”两条圈叠在一起管理人员一眼就能看出哪一项偏离了目标const option { radar: { indicator: [ { name: 温度, max: 40 }, { name: 湿度, max: 100 }, { name: 光照, max: 10000 }, { name: 二氧化碳, max: 2000 }, { name: 土壤湿度, max: 100 }, { name: 土壤pH, max: 14 } ], radius: 65% }, series: [ { type: radar, data: [ { name: 目标值, value: [26, 70, 8000, 1200, 60, 6.5] }, { name: 当前值, value: [31, 55, 9200, 1450, 45, 5.8] } ] } ] }实际用的时候不同作物对环境指标的要求差很多雷达图的indicator和max也应该由配置或接口动态控制不然换作物品种就得改代码。这个我踩过坑后面避坑章节会展开说。3.4 地块分布用 registerMap 注册自定义地块不用硬磕中国地图智慧农业大屏上最需要的往往是“农场自己的地块分布”不是全国地图。Echarts 的map系列要求 geoJSON 数据你可以让后端导出地块轮廓的 GeoJSON前端用registerMap注册后正常渲染标注各分区状态。如果暂时没有 GeoJSON也可以用散点图或者柱状图方案把每个地块当作独立分类展示效果也很直观。4. 数据刷新、轮询与联动大屏不能是一张静态图4.1 用一个统一的数据池管理全屏数据如果每个图表组件各自去请求接口会出现一个很尴尬的局面同一份数据被请求了几十次页面刷新时各种短时白屏。我的做法是维护一个全局数据池只在 App 层面请求数据然后分发到各个子组件。用 Vue3 的reactive做这个数据池非常简单// store/screen.js import { reactive } from vue export const screenStore reactive({ loading: false, data: { kpi: {}, envTrend: [], plotList: [], yieldRatio: [], deviceStatus: {}, alarmList: [] }, setData(newData) { Object.assign(this.data, newData) } })子组件里用watch(() screenStore.data.envTrend, callback)监听自己关心的数据段变化然后更新图表。这样做的好处是所有组件共享同一个数据源不会出现“顶部 KPI 显示 35℃、趋势图最后一个点是 33.8℃”这种对不上的情况。4.2 定时轮询的正确写法直接关系内存泄漏大屏数据要么用 WebSocket 推送要么轮询。用轮询做模板最通用因为不需要后端配合升级协议。但轮询的写法决定性能很多人的写法是// 错误示范放在哪个组件里也不知道卸载了还在跑 setInterval(() { fetchData() }, 5000)正确写法是在 App.vue 的setup里创建定时器通过数据池分发数据并且在onBeforeUnmount里清理定时器import { onMounted, onBeforeUnmount } from vue import { screenStore } from /store/screen let timer null async function fetchScreenData() { const res await getScreenData() screenStore.setData(res.data) } onMounted(() { fetchScreenData() timer setInterval(fetchScreenData, 5000) }) onBeforeUnmount(() { if (timer) clearInterval(timer) })这里有个细节轮询间隙不要强制让 setOption 立即重绘否则用户鼠标正悬停在某个点位看 tooltip突然被刷新打断会很恼火。可以在 setOption 时判断一下如果当前图表正在被 hover就延迟更新。4.3 点击联动从一个图表驱动整屏更新大屏如果不能交互价值直接减半。我做了这样的联动点击产量环形图的某个作物扇形左侧趋势图切换成该作物的详细环境数据地块地图高亮对应分区底部告警列表只显示该作物的告警。实现方式是利用 Echarts 的click事件// YieldPie.vue chart.on(click, (params) { screenStore.currentCrop params.name // 修改数据池标记其他组件 watch 到这个标记后更新自己的图表 })这种“事件发布 - 数据池变更 - 组件各自响应”的模式比组件之间直接调用方法要干净得多。后续要再增加联动方只要在数据池里加一个状态字段就行不用改联动源组件。5. 避坑清单照着这几条排查能省一整天的调试时间5.1 setOption 后图表不更新先查 notMerge 和数据引用最常遇到的问题是接口数据变了但图表还停留在旧数据。排查顺序确认setOption调用到了别用console.log骗自己确认传给 series 的数组是新对象因为 Echarts 默认开启 merge 模式新老数据引用一样时可能不重绘如果 series 的类型、数量发生了变化比如从 1 条折线变成 2 条折线需要在setOption(option, true)里把第二个参数传true强制 notMerge 全量替换。大屏上我一般统一用chart.setOption(option, true)简单粗暴避免很多莫名奇妙的问题。代价是地图上的动画会重置但监控场景可以接受。5.2 白屏十有八九是容器高度为 0Echarts 初始化时要求容器元素已经渲染完成并且有明确的高度。很多大屏白屏问题就是因为父容器用了 flex 或百分比高度但某个祖先元素高度没有撑开导致 canvas 高度是 0。我的判据是初始化图表前打印el.clientHeight如果是 0先处理布局。另外要避免在v-if刚切换为 true 后立刻初始化图表DOM 还没渲染完这时要用nextTick包裹初始化逻辑或者干脆用v-show保持 DOM 存在。5.3 resize 事件要统一管理别和缩放方案打架大屏项目通常有多个图表实例如果每个图表都监听一遍window.resize很难统一清理。我建议只在一个地方监听resize然后通过事件渠道广播给所有图表// App.vue window.addEventListener(resize, handleResize) function handleResize() { fitScreen() screenStore.resizeKey // 子组件 watch 到这个 key 变化后各自 resize }我踩过的坑是用了transform: scale缩放方案以后又给每个图表单独监听window.resize结果窗口拉一下图表缩放了两三遍动画颤抖得很明显。统一管理后问题消失。5.4 组件卸载后 setOption 继续执行导致的报错大屏内部如果有点击切页、tab 切图等操作组件被卸载时定时器还在跑或者异步请求还没回来代码里又调用了chart.setOption控制台就会报一堆“Cant get DOM element”之类的错。解决方式是在组件卸载钩子里统一dispose图表实例并清理定时器onBeforeUnmount(() { if (chart) { chart.dispose() chart null } })5.5 农业场景的“硬编码阈值”坑刚开始我把雷达图的温度上限写成 40、土壤 pH 上限写成 14结果换了叶菜大棚的数据页面直接显示变形。后来我把所有阈值、指标项全部并入接口配置前端只负责渲染。农业大屏尤其要这样不同作物、不同季节环境标准完全不同前端硬编码等于埋雷。6. 后续扩展方向从“能跑”到“能用”再到“好看”6.1 轮询升级为 WebSocket/MQTT数据延迟能降到秒级定时轮询做得再好5 秒才刷一次有些强实时场景不够。智慧农业现在的做法是接物联网平台比如用 MQTT over WebSocket 订阅传感器遥测数据。前端拿到推送后直接更新数据池图表完全不需要改动因为数据池在设计上已经和请求方式解耦了。模板里把 fetch 函数和 WebSocket 封装成同一个数据入口后面切换协议成本很小。6.2 视频监控画面接入大屏上留一路实时视频很有必要智慧农业大屏通常会配套摄像头把田间、大棚实时画面接入。视频流很多是 m3u8 格式浏览器原生不支持播放我一般用 hls.js 解决。实现也很简单在页面某个角落放一个video动态加载 m3u8 地址秒开播放。需要注意控制路数一路一路播别同时拉十几路很容易把带宽打满。6.3 想更炫可以上 echarts-gl但要先确认硬件撑得住如果想让地块分布、产量构成更有立体感可以用 echarts-gl 里的geo3d、map3d、scatter3d做 3D 效果。但我的经验是3D 图表在低端电脑上很卡而且信息表达不一定比 2D 清晰。我的建议是核心分析类图表保持 2D只有展示性的“总览地图”部分可以做 3D 点缀。另外 echarts-gl 和 echarts 的版本必须匹配装完以后要立刻验证旧图表有没有异常这个坑我到现在遇到都会多留个心眼。做这个智慧农业模板实例我最大的感受是Vue 和 Echarts 只是工具真正让一块屏好用起来的是对业务的理解和对数据的管理。最后再分享一个实际操作中的建议尽早把 mock 数据结构定死前端先把整个流程跑通等真实接口出来直接替换数据源整个开发节奏会舒服很多。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进