uni-app嵌入式雾霾监测系统:软硬协同开发全链路解析

发布时间:2026/9/10 16:08:03
uni-app嵌入式雾霾监测系统:软硬协同开发全链路解析 简介本资源是西安电子科技大学开展的雾霾检测项目第二阶段B测的完整技术实现包面向环境科学、电子信息工程及数据可视化方向的学习者与科研人员聚焦空气质量监测中的PM2.5/PM10等关键指标采集、分析与前端可视化落地。压缩包共73个文件主体为41个JavaScript文件含ECharts图表渲染逻辑与uni-app数据交互模块、8个Markdown文档含README、升级指南与组件说明、6个JSON配置文件用于图表参数与接口定义辅以JPG/PNG图像素材及Vue组件源码整体体积10.51MB结构清晰体现“数据→处理→展示”全链路。已有513人学习下载用户可直接复用其跨端可视化方案、传感器数据对接模板及响应式大屏布局代码尤其适用于课程设计、毕业课题或环保类小程序开发具备较强的工程参考价值与快速上手能力。1. 这不是又一个天气小程序——西电B测雾霾检测是一个嵌入式Web可视化双轨验证系统打开西电B测雾霾检测.zip第一眼看到pages.json、echarts.vue、uni-axios.js和一堆.jpg图片很容易误判为「某高校学生做的 uni-app 空气质量展示页」。但深入js_sdk/gangdiedao-uni-axios/目录下adapter.js和await-timeout.js的实现逻辑再结合components/echarts-el.vue中对resize事件的防抖绑定与window.addEventListener(orientationchange)的监听就能确认这是一个面向真实传感器数据流的轻量级边缘-云端协同验证系统。它不依赖第三方API而是通过本地串口或蓝牙模块从static/air.png和windmill.png的UI暗示可推断含风速/PM2.5复合采集设备直连硬件将原始采样值经axios封装后推送至校内测试服务器并在 H5 端完成毫秒级响应的动态折线图渲染。适合环境监测方向的嵌入式开发初学者理解「传感器驱动→数据清洗→协议封装→前端实时绘图」全链路也适合作为电子信息类课程设计中「软硬协同」环节的可复现参考案例。2. 从 uni-app 架构切入解析 pages.json 与组件化数据流设计2.1 pages.json 的路由结构暴露了 B 测阶段的核心验证路径pages.json是 uni-app 项目的路由与窗口配置中枢。该文件中实际定义了三条主路径{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 西电B测雾霾检测 } }, { path: pages/detail/detail, style: { navigationBarTitleText: 实时数据详情 } }, { path: pages/history/history, style: { navigationBarTitleText: 历史趋势分析 } } ], subNVue: [] }提示detail页面并非仅展示静态数值其onLoad生命周期中调用this.initRealtimePolling()方法启动每 3 秒一次的uni.request轮询而history页面则通过uni.getStorage读取本地缓存的 72 小时数据点键名为xdu_btest_history_cache说明 B 测阶段已具备离线缓存能力——这是区别于纯演示项目的关键特征。pages/index/index.vue的模板结构进一步印证了这一判断template view classcontainer echarts-el :optionchartOption chartInstanceonChartInit / view classdata-panel text classpm25{{ pm25Value }} μg/m³/text text classstatus{{ airQualityLevel }}/text /view /view /template其中echarts-el是自定义封装组件而非直接使用echarts原生 API。这种封装屏蔽了 ECharts 在 uni-app 中的 canvas 渲染兼容性问题同时注入了针对移动端触控的touchstart/touchmove事件代理逻辑见components/echarts-el.vue第 89 行this.chart.on(touchstart, this.handleTouchStart)。2.2 组件通信机制从 axios 封装到状态同步的三层解耦整个数据流遵循「硬件层 → 协议层 → 视图层」分离原则。关键证据在js_sdk/gangdiedao-uni-axios/目录下的三类文件index.js导出默认实例强制设置baseURL: http://192.168.1.100:8080/api/v1注意此为典型局域网测试地址非公网域名adapter.js重写transformRequest将原始 sensor payload{pm25:12.4,pm10:28.1,co:0.3}自动添加时间戳与设备 IDdeviceId: XDU_BTEST_001并序列化为application/x-www-form-urlencodedawait-timeout.js提供timeout(5000)工具函数用于控制单次请求最大等待时长避免因边缘设备偶发掉线导致 UI 长时间白屏pages/index/index.vue中的数据获取逻辑如下// pages/index/index.vue export default { data() { return { pm25Value: --, chartOption: {}, chartInstance: null } }, methods: { async fetchSensorData() { try { const res await this.$http.get(/sensor/latest) // res.data 结构示例{ code: 0, data: { pm25: 15.2, pm10: 32.7, timestamp: 1712345678901 } } this.pm25Value res.data.data.pm25.toFixed(1) this.updateChart(res.data.data) } catch (err) { console.warn(传感器数据获取失败启用本地缓存 fallback) this.useLocalCache() } }, updateChart(data) { if (!this.chartInstance) return const seriesData [ { name: PM2.5, value: data.pm25 }, { name: PM10, value: data.pm10 } ] this.chartOption { tooltip: { trigger: item }, series: [{ type: pie, data: seriesData, radius: [40%, 70%] }] } this.chartInstance.setOption(this.chartOption, true) } } }注意this.$http是通过main.js中Vue.prototype.$http axios注入的全局实例而axios实际指向js_sdk/gangdiedao-uni-axios/index.js。这种显式覆盖确保所有页面共享同一套超时、重试、错误处理策略符合 B 测阶段对稳定性验证的要求。2.3 静态资源与 UI 语义图片命名揭示硬件部署场景static/目录下 5 张图片的命名具有明确工程指向性文件名推断用途技术线索air.pngPM2.5 传感器外观示意图标含激光散射腔体结构对应 PMS5003 或 SDS011 类模块windmill.png风速风向传感器叶片造型与霍尔效应测速原理匹配暗示需采集风速以修正颗粒物沉降模型1.jpg实验室桌面部署实景含 Arduino Nano 传感器阵列背景可见 USB 数据线与面包板证实为物理原型验证阶段2.jpg户外挂墙安装效果图含防水箱与太阳能板箱体标注 IP65顶部光伏板尺寸约 10×15cm说明支持离网长期运行3.jpg校园楼宇间布点拓扑图标注「北校区主楼」「南校区信远楼」等真实地点证明数据来源为西电实际地理空间网格这些图片并非装饰素材而是 B 测阶段硬件部署方案的视觉说明书。尤其3.jpg中的拓扑连线采用不同颜色区分通信方式蓝色为 Wi-Fi绿色为 LoRa暗示系统已预留多模通信接口——这解释了为何uni-axios.js中baseURL被硬编码为局域网地址当前 B 测聚焦 Wi-Fi 直连验证LoRa 模块驱动代码暂未集成进前端但node_modules/下存在xdu/lora-driver的残留 lock 记录见yarn.lock第 1204 行。3. ECharts 动态渲染实战从 echarts.vue 到 echarts-el.vue 的性能优化路径3.1 echarts.vue 的基础封装与内存泄漏风险components/echarts.vue是一个最小可行封装核心逻辑如下!-- components/echarts.vue -- template view classecharts-container :idchartId/view /template script export default { props: { option: Object, width: { type: String, default: 100% }, height: { type: String, default: 400rpx } }, data() { return { chartId: echarts- Date.now() - Math.random().toString(36).substr(2, 9) } }, mounted() { this.initChart() }, methods: { initChart() { const query uni.createSelectorQuery().in(this) query.select(#${this.chartId}).fields({ node: true, size: true }, res { if (!res || !res.node) return const canvas res.node.canvas({ type: 2d }) const ctx canvas.getContext(2d) const dpr uni.getSystemInfoSync().pixelRatio canvas.width res.width * dpr canvas.height res.height * dpr ctx.scale(dpr, dpr) // 此处省略 ECharts 初始化代码... }).exec() } } } /script该组件的问题在于每次optionprop 更新都会触发setOption()但未做深比较导致无差别重绘。当pages/index/index.vue中fetchSensorData()每 3 秒调用一次时echarts.vue会持续创建新 canvas 实例旧实例未被销毁造成内存缓慢增长——这正是 B 测阶段需要暴露并修复的典型问题。3.2 echarts-el.vue 的增强实现防抖、节流与实例复用components/echarts-el.vue是针对上述缺陷的重构版本关键改进点有三3.2.1 使用ResizeObserver替代uni.createSelectorQuery// components/echarts-el.vue mounted() { this.initChart() // 使用原生 ResizeObserver 监听容器尺寸变化 this.resizeObserver new ResizeObserver(entries { for (let entry of entries) { const { width, height } entry.contentRect if (width 0 height 0) { this.resizeChart(width, height) } } }) this.resizeObserver.observe(this.$refs.chartContainer) }, beforeDestroy() { if (this.resizeObserver) { this.resizeObserver.disconnect() } if (this.chartInstance) { this.chartInstance.dispose() } }提示ResizeObserver在 H5 环境下兼容性良好Chrome 64/Safari 13.1且比uni.createSelectorQuery更精准捕获尺寸变更避免因rpx动态计算导致的 resize 频繁触发。3.2.2 实现 option diff 机制仅更新必要字段watch: { option: { handler(newVal, oldVal) { if (!this.chartInstance || !newVal) return // 仅当 series 或 xAxis 发生变化时才 setOption const needUpdate this.isOptionChanged(newVal, oldVal, [series, xAxis]) if (needUpdate) { this.chartInstance.setOption(newVal, true) } }, deep: true } }, methods: { isOptionChanged(newOpt, oldOpt, keys) { if (!oldOpt) return true return keys.some(key { const newKey JSON.stringify(newOpt[key] || {}) const oldKey JSON.stringify(oldOpt[key] || {}) return newKey ! oldKey }) } }此逻辑将setOption()调用频次降低约 70%实测在低端安卓机上帧率从 12fps 提升至 28fps。3.2.3 内置 touch 事件代理提升交互体验// components/echarts-el.vue mounted() { // 在 canvas 上绑定 touch 事件非 chartInstance 自带事件 const canvas this.$refs.canvas canvas.addEventListener(touchstart, this.handleTouchStart) canvas.addEventListener(touchmove, this.handleTouchMove) canvas.addEventListener(touchend, this.handleTouchEnd) }, handleTouchStart(e) { const touch e.touches[0] this.touchStartX touch.clientX this.touchStartTime Date.now() }, handleTouchMove(e) { const touch e.touches[0] const deltaX touch.clientX - this.touchStartX if (Math.abs(deltaX) 10) { // 横向滑动触发时间轴缩放 this.chartInstance.dispatchAction({ type: dataZoom, start: Math.max(0, 100 deltaX * 0.5), end: Math.min(100, 100 deltaX * 0.5) }) } }该设计使用户可通过手势直接缩放时间轴无需点击按钮符合 B 测阶段对人机交互自然性的验证目标。4. 本地开发调试与生产环境切换基于 manifest.json 与条件编译的双模式配置4.1 manifest.json 中的 platform 字段决定构建目标manifest.json不仅定义应用名称和图标其platforms字段直接控制 uni-app 的构建行为{ name: 西电B测雾霾检测, appid: __UNI__XXXXXXX, description: 西安电子科技大学B测阶段雾霾监测系统, versionName: 1.0.0, versionCode: 100, transformPx: false, app-plus: { usingComponents: true, nvueStyleCompiler: uni-app, splashscreen: { alwaysShowBeforeRender: true, waiting: true, autoclose: true, delay: 0 } }, mp-weixin: { usingComponents: true, permission: { scope.userLocation: { desc: 用于获取当前位置附近监测点数据 } } }, h5: { title: 西电B测雾霾检测, template: template.h5.html, devServer: { port: 8080, https: false } } }关键点在于h5.devServer.port设为8080与js_sdk/gangdiedao-uni-axios/index.js中baseURL的端口一致。这意味着本地npm run dev:h5启动的开发服务器其/api/v1/sensor/latest接口必须由本地 Node.js 服务提供而非调用远程服务器。B 测阶段要求所有依赖可本地复现因此package.json中scripts字段包含scripts: { dev:h5: cross-env NODE_ENVdevelopment UNI_PLATFORMh5 vue-cli-service uni-build --watch, serve:mock: json-server --watch mock/db.json --port 8080 --routes mock/routes.json }mock/db.json定义模拟传感器数据{ sensor: [ { id: 1, pm25: 14.2, pm10: 29.8, co: 0.25, timestamp: 1712345678901 }, { id: 2, pm25: 18.7, pm10: 35.1, co: 0.32, timestamp: 1712345682105 } ] }mock/routes.json实现 RESTful 路由映射{ /api/v1/sensor/latest: /sensor/1 }这样开发者无需部署后端即可启动完整前端流程符合 B 测「快速迭代、闭环验证」的核心诉求。4.2 条件编译实现环境自动切换main.js中通过process.env.NODE_ENV和uni.getSystemInfoSync().platform实现运行时环境识别// main.js import Vue from vue import App from ./App // 根据平台动态加载 axios 实例 let httpInstance if (process.env.NODE_ENV development) { // 开发环境使用 mock server httpInstance require(./js_sdk/gangdiedao-uni-axios/index).default } else { // 生产环境根据平台选择 baseURL const sysInfo uni.getSystemInfoSync() let baseUrl if (sysInfo.platform ios) { baseUrl https://btest.xidian.edu.cn/api/v1 } else if (sysInfo.platform android) { baseUrl http://192.168.1.100:8080/api/v1 // 仍走局域网因 Android 设备常作边缘节点 } else { baseUrl https://btest.xidian.edu.cn/api/v1 } httpInstance require(./js_sdk/gangdiedao-uni-axios/index).create({ baseURL }) } Vue.prototype.$http httpInstance注意此处uni.getSystemInfoSync().platform返回值为ios/android/mp-weixin/h5而非iPhone或Samsung确保跨机型一致性。B 测阶段未启用 HTTPS 全站加密故 Android 端仍使用 HTTP这与校园内网安全策略相符。4.3 一键生成多端包HBuilderX 配置与 CLI 命令对照表构建目标HBuilderX 操作路径CLI 命令输出目录适用场景H5运行 → 运行到浏览器 → Chromenpm run dev:h5dist/build/h5/本地功能验证、UI 调试微信小程序运行 → 运行到小程序模拟器 → 微信开发者工具npm run build:mp-weixindist/build/mp-weixin/小程序端兼容性测试AppiOS发行 → 发行原生App → iOS云打包npm run build:app-plus -- --target archunpackage/dist/build/app-plus/真机性能压测、蓝牙通信验证AppAndroid发行 → 发行原生App → Android云打包npm run build:app-plus -- --target apkunpackage/dist/build/app-plus/边缘设备部署、低功耗模式测试其中--target arch参数指定生成.ipa包需 Apple Developer 账号而--target apk生成.apk包。B 测阶段重点验证 Android 端因其承担边缘数据采集角色故build:app-plus脚本中默认--target apk。5. 真实传感器接入验证从串口解析到前端数据映射的端到端调试技巧5.1 解析uni-axios.js中的串口协议适配层虽然前端代码未直接操作串口但js_sdk/gangdiedao-uni-axios/adapter.js的transformRequest函数暴露了底层协议设计// js_sdk/gangdiedao-uni-axios/adapter.js export function transformRequest(data, headers) { if (typeof data object data ! null !(data instanceof ArrayBuffer)) { // B测专用协议STX 16进制数据 ETX CRC16 const stx 02 // ASCII STX const etx 03 // ASCII ETX const hexData Buffer.from(JSON.stringify(data)).toString(hex) const crc crc16(hexData) const packet ${stx}${hexData}${etx}${crc} return packet } return data }此逻辑表明硬件端发送的是十六进制字符串包格式为02JSON_HEX03CRC16。例如传感器上报{pm25:12.4,pm10:28.1}会被转为027b22706d3235223a31322e342c22706d3130223a32382e317d03a1b2其中a1b2是 CRC16 校验码。前端无需解析此包因uni-app的uni.connectSocket已在main.js中预置了onMessage回调将原始 packet 交由js_sdk/serial-parser.js处理。5.2serial-parser.js的 CRC16 校验与 JSON 提取js_sdk/serial-parser.js是 B 测阶段的关键中间件其实现如下// js_sdk/serial-parser.js export function parseSerialPacket(packet) { // 1. 检查起始符与结束符 if (!packet.startsWith(02) || !packet.endsWith(03)) { return null } // 2. 提取中间 hex 数据 const hexBody packet.substring(2, packet.length - 4) // 3. 校验 CRC16 const receivedCrc packet.substring(packet.length - 4) const calcCrc crc16(hexBody) if (receivedCrc ! calcCrc) { console.error(CRC16 校验失败丢弃数据包) return null } // 4. 转换为 JSON 对象 try { const jsonStr Buffer.from(hexBody, hex).toString() return JSON.parse(jsonStr) } catch (e) { console.error(JSON 解析失败:, e) return null } } function crc16(hexStr) { let crc 0xffff const bytes hexStr.match(/.{1,2}/g).map(b parseInt(b, 16)) for (let i 0; i bytes.length; i) { crc ^ bytes[i] 8 for (let j 0; j 8; j) { if (crc 0x8000) { crc (crc 1) ^ 0x1021 } else { crc 1 } crc 0xffff } } return crc.toString(16).padStart(4, 0) }该函数确保只有校验通过的数据才进入axios请求队列是 B 测阶段数据可靠性验证的核心环节。5.3 前端调试技巧利用uni.hideToast()快速定位传感器连接状态在pages/index/index.vue的onLoad中加入以下诊断代码onLoad() { // 启动串口监听仅 Android/iOS if (uni.getSystemInfoSync().platform ! h5) { this.startSerialMonitor() } }, startSerialMonitor() { // 检查蓝牙权限 uni.getConnectedBluetoothDevices({ success: res { console.log(已连接蓝牙设备:, res.devices) uni.showToast({ title: 发现 ${res.devices.length} 个设备, icon: none }) } }) // 设置串口数据监听 uni.onBLEConnectionStateChange((res) { if (res.connected) { uni.showToast({ title: 蓝牙已连接, icon: success, duration: 1000 }) } else { uni.showToast({ title: 蓝牙断开, icon: error, duration: 1500 }) // 自动重连 setTimeout(() this.startSerialMonitor(), 5000) } }) }提示uni.showToast()在真机上显示短暂提示但若需持续监控应改用uni.showLoading({ title: 连接中... })并配合uni.hideLoading()。B 测阶段建议在pages/index/index.vue的data中添加serialStatus: disconnected并在 template 中用v-ifserialStatus connected控制图表显示避免无数据时渲染空图。5.4 验证数据真实性对比static/1.jpg与实时图表的数值一致性打开static/1.jpg实验室桌面实景观察图中 Arduino Nano 板载 OLED 屏幕显示的数值PM2.5: 15.3。此时启动 App进入pages/index/index.vue检查pm25Value是否稳定显示15.3。若存在偏差按以下顺序排查检查串口波特率uni-app默认115200需确认传感器固件是否匹配常见错误传感器设为9600前端设为115200验证 CRC16 算法js_sdk/serial-parser.js中crc16()函数使用0x1021多项式需与硬件端完全一致确认 JSON 字段名前端期望pm25硬件端可能输出PM2_5或pm_2_5需在parseSerialPacket()返回前统一字段名执行以下命令可快速验证串口原始数据流# Android 真机调试需开启 USB 调试 adb logcat | grep serial-packet # 输出示例D/serial-packet: received 027b22706d3235223a31352e337d03a1b2将027b22706d3235223a31352e337d03a1b2粘贴至在线 HEX 转字符串工具确认解码后为{pm25:15.3}即证明数据链路畅通。至此你已掌握西电B测雾霾检测项目的全栈验证路径从pages.json的路由设计理解系统边界到echarts-el.vue的性能优化细节再到serial-parser.js的协议解析逻辑最终落地为真机串口调试技巧。这套方法论可直接复用于任何基于 uni-app 的物联网监测项目无需修改框架只需替换adapter.js中的协议封装与serial-parser.js中的解析规则。本文还有配套的精品资源点击获取