第 11 篇:小程序进阶——ECharts 图表 + 历史查询 + 告警推送
上篇做了一个能看能控的基础版。这篇加上三个进阶功能:温度曲线(ECharts)、历史数据查询(HTTP API + InfluxDB)、阈值告警推送。
1. 集成 ECharts 温度曲线
微信小程序用 echarts-for-weixin 组件。这是 ECharts 官方适配的小程序版。
安装:
# 项目根目录
npm install ec-canvas
# 然后:开发者工具 → 工具 → 构建 npm
页面中引入:
pages/history/history.json
{
"usingComponents": {
"ec-canvas": "../../ec-canvas/ec-canvas"
},
"navigationBarTitleText": "温度历史"
}
pages/history/history.wxml
<view class="container">
<view class="chart-box">
<ec-canvas id="tempChart" canvas-id="tempChart" ec="{{ec}}"></ec-canvas>
</view>
<view class="time-tabs">
<button size="mini" bindtap="loadHistory" data-range="1h">近1小时</button>
<button size="mini" bindtap="loadHistory" data-range="6h">近6小时</button>
<button size="mini" bindtap="loadHistory" data-range="24h">近24小时</button>
</view>
</view>
pages/history/history.js
import * as echarts from '../../ec-canvas/echarts';
Page({
data: {
ec: {
// 懒加载:canvas 渲染完成后才初始化 ECharts
lazyLoad: true,
},
tempData: [],
humiData: [],
},
onLoad() {
// 获取 ec-canvas 组件实例
this.ecComponent = this.selectComponent('#tempChart');
this.initChart();
this.loadHistory('1h');
},
initChart() {
this.ecComponent.init((canvas, width, height, dpr) => {
const chart = echarts.init(canvas, null, {
width: width,
height: height,
devicePixelRatio: dpr,
});
chart.setOption(this.getChartOption());
this.chart = chart;
return chart;
});
},
getChartOption() {
return {
color: ['#ff6b6b', '#48dbfb'],
legend: { data: ['温度', '湿度'], bottom: 0 },
grid: { top: 20, bottom: 40, left: 50, right: 20 },
xAxis: { type: 'time', axisLabel: { fontSize: 10 } },
yAxis: [
{ type: 'value', name: '°C', min: 0, max: 50 },
{ type: 'value', name: '%', min: 0, max: 100 },
],
tooltip: { trigger: 'axis' },
series: [
{
name: '温度', type: 'line', smooth: true,
data: [], yAxisIndex: 0,
},
{
name: '湿度', type: 'line', smooth: true,
data: [], yAxisIndex: 1,
},
],
};
},
async loadHistory(range) {
wx.showLoading({ title: '加载中...' });
try {
const res = await wx.request({
url: `https://api.your-server.com/history?range=${range}`,
method: 'GET',
});
const tempData = res.data.temp.map(d => [new Date(d.time), d.value]);
const humiData = res.data.humi.map(d => [new Date(d.time), d.value]);
this.chart.setOption({
series: [
{ data: tempData },
{ data: humiData },
],
});
} catch (err) {
wx.showToast({ title: '加载失败', icon: 'error' });
} finally {
wx.hideLoading();
}
},
});
2. 后端 API——从 InfluxDB 查历史数据
小程序不能直接连数据库,需要一个中间 API 服务。最简单的方案:用 Node.js/Express 写一个轻量 API,跑在服务器上。
// api-server.js (Node.js + Express)
const express = require('express');
const { InfluxDB } = require('@influxdata/influxdb-client');
const app = express();
const influx = new InfluxDB({
url: 'http://localhost:8086',
token: 'my-super-secret-token',
});
app.get('/history', async (req, res) => {
const range = req.query.range || '1h'; // 1h / 6h / 24h
const queryApi = influx.getQueryApi('iot-org');
const query = `
from(bucket: "sensor-data")
|> range(start: -${range})
|> filter(fn: (r) => r._measurement == "mqtt_consumer")
|> filter(fn: (r) => r._field == "temp" or r._field == "humi")
|> aggregateWindow(every: ${range === '1h' ? '30s' : '5m'}, fn: mean)
`;
const temp = [], humi = [];
for await (const {values, row} of queryApi.iterateRows(query)) {
const point = { time: row[2], value: row[5] };
if (row[4] === 'temp') temp.push(point);
else humi.push(point);
}
res.json({ temp, humi });
});
app.listen(3000, () => console.log('API Server on :3000'));
3. 阈值告警推送
方案 A:小程序内告警
// 在 MQTT message 回调中
this.client.on('message', (topic, payload) => {
const data = JSON.parse(payload.toString());
if (data.temp > 40) {
wx.showModal({
title: '⚠️ 高温警告',
content: `当前温度 ${data.temp}°C,超过阈值 40°C!`,
showCancel: false,
});
// 播放提示音(小程序支持)
wx.vibrateLong();
}
this.setData({
temperature: data.temp.toFixed(1),
humidity: data.humi.toFixed(1),
});
});
方案 B:微信服务通知(需要订阅消息)
流程:
1. 用户点击"订阅告警"按钮 → 调用 wx.requestSubscribeMessage
2. 用户同意 → 拿到一次推送权限
3. 后端检测到温度超标 → 调用微信 API 推送模板消息
// 小程序端:订阅
wx.requestSubscribeMessage({
tmplIds: ['模板ID_高温告警'],
success(res) {
if (res['模板ID_高温告警'] === 'accept') {
// 保存用户 openid → 后端用于推送
}
},
});
4. 增加刷新与重连机制
IoT 小程序最大的痛点是"不知道数据为什么不更新了"。增强健壮性:
Page({
data: { connected: false, lastUpdate: '' },
onLoad() {
this.connectMQTT();
// 每 30 秒检查一次 MQTT 心跳
this.heartbeatTimer = setInterval(() => {
if (!this.data.connected) {
console.log('重连中...');
this.connectMQTT();
}
}, 30000);
},
// 记录最后更新时间
updateLastTime() {
const now = new Date();
this.setData({
lastUpdate: `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}`,
});
},
// 手动刷新
refreshData() {
this.client.publish('device/refresh/cmd', '1');
},
onUnload() {
clearInterval(this.heartbeatTimer);
if (this.client) this.client.end();
},
});
5. 调试技巧
| 问题 | 排查方法 |
|---|---|
| MQTT 连不上 | 检查 wss:// 域名是否已配置到白名单 |
| 数据不更新 | 打开开发者工具 → Console → 看有没有 message 日志 |
| ECharts 白屏 | 检查 ec-canvas 组件路径是否正确 |
| API 请求失败 | 检查服务器域名白名单(request 合法域名) |
| 真机不显示 | 预览时勾选"不校验合法域名"仅开发有效,真机必须配置 |
下一篇:完整项目——智能 WiFi 插座,从硬件到小程序全链路