简介:这是一套面向Vue3与ECharts数据可视化开发者的智慧农业监控大屏实例资源,适合有基础前端知识、希望快速搭建可视化看板的工程师与学习者。资源基于vue-echarts完成监控大屏搭建,包含高德地图集成、报表展示、菜单布局整理、全屏切换与退出等功能,并提供HBuilderX开发环境下的完整项目案例。压缩包共包含12463个文件,大小约44.75MB,以js、ts、vue源码为主,辅以map、json、scss/css样式文件及少量md文档和mp4视频,结构清晰,便于按模块查阅。已有4442人学习下载,是一份兼具参考价值与实用性的智慧农业可视化模板。读者可获得可运行源码、开发过程视频、相关文档与素材,直接在此基础上扩展业务图表或替换数据源,缩短监控大屏类项目从零到一的上手时间。 作为监控大屏系列的第四篇,这次我把场景切到了智慧农业。刚接到这个模板需求的时候,我心里其实有点想当然:无非是 Vue 搭架子、Echarts 画图表,把温湿度、光照、产量这些数据怼上去就行。等真正动手才意识到,“画出一张图”和“做出一块能用的监控大屏”之间隔着一整条沟。这篇就把我用 Vue+Echarts 从零做智慧农业监控大屏模板实例的过程完整拆开讲:先梳理业务,再定工程骨架,然后逐一说核心图表的实现,最后是我实测中踩过的坑和几个值得做的扩展方向。适合正在做监控大屏的前端朋友、刚接手数据可视化项目的新手,以及农业信息化方向想快速出原型的人参考。
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 vite@latest 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 白屏:十有八九是容器高度为 0
Echarts 初始化时要求容器元素已经渲染完成,并且有明确的高度。很多大屏白屏问题,就是因为父容器用了 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,控制台就会报一堆“Can't 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 数据结构定死,前端先把整个流程跑通,等真实接口出来直接替换数据源,整个开发节奏会舒服很多。
本文还有配套的精品资源,点击获取