微信小程序:农户手机上的「农场管家」
微信小程序:农户手机上的「农场管家」
平台搭好了,但农户不可能打开电脑看数据。他们需要手机上扫一下就能看到大棚温湿度、点一下就能远程开水泵。这篇用 UniApp(Vue 3)开发一个小程序:实时数据、远程控制、告警推送、农事记录。
为什么不学微信原生开发?
微信原生开发用 WXML + WXSS + JS,语法独一份,不能用 Vue/React 生态。UniApp 的好处:
- 一套代码编译到微信小程序 + H5 + App
- 用 Vue 3 组合式 API,前端人不需额外学习成本
- 插件生态(uView UI 等)够用
- 微信扫码即用,农户零安装
安装 UniApp CLI:
npminstall-g@dcloudio/uvm uvm use3.0.0 npx degit dcloudio/uni-preset-vue#vite farm-miniappcdfarm-miniapp&&npminstall页面架构——四个 Tab 搞定
TabBar ├── 🏠 首页 → 大棚/地块卡片列表 ├── 📊 监控 → 选中地块的实时数据 + 曲线 ├── 🎮 控制 → 灌溉/通风/补光 远程开关 └── 👤 我的 → 设置/农事记录/关于首页:大棚卡片
<!-- pages/home/index.vue --> <script setup> import { ref, onMounted, onUnmounted } from 'vue'; import { connectMQTT } from '@/utils/mqtt'; const devices = ref([]); let mqttClient = null; onMounted(async () => { // 先从服务端拉设备列表 const res = await uni.request({ url: `${BASE_URL}/api/devices` }); devices.value = res.data; // 再连 MQTT 收实时数据更新卡片 mqttClient = connectMQTT(); mqttClient.on('message', (topic, payload) => { const data = JSON.parse(payload); const device = devices.value.find(d => d.deviceId === data.dev); if (device) { device.airTemp = data.data.air_temp; device.airHumidity = data.data.air_humidity; device.soilMoisture = data.data.soil_moisture; device.lastUpdate = new Date(data.ts * 1000); } }); mqttClient.subscribe('farm/+/sensor/+/data'); }); onUnmounted(() => mqttClient?.disconnect()); const navigateToDetail = (device) => { uni.navigateTo({ url: `/pages/monitor/index?deviceId=${device.deviceId}` }); }; </script> <template> <view class="home"> <view class="header"> <text class="title">我的农场</text> <text class="weather">晴 26℃</text> </view> <scroll-view class="device-list" scroll-y> <view v-for="device in devices" :key="device.deviceId" class="device-card" :class="{ offline: !device.online }" @click="navigateToDetail(device)"> <view class="card-header"> <text class="card-title">{{ device.name }}</text> <view class="status-dot" :class="device.online ? 'online' : 'offline'" /> </view> <view class="card-body"> <view class="data-item"> <text class="data-value">{{ device.airTemp ?? '--' }}℃</text> <text class="data-label">温度</text> </view> <view class="data-item"> <text class="data-value">{{ device.airHumidity ?? '--' }}%</text> <text class="data-label">湿度</text> </view> <view class="data-item"> <text class="data-value">{{ device.soilMoisture ?? '--' }}%</text> <text class="data-label">土壤</text> </view> </view> <text class="card-time">{{ device.lastUpdate ?? '暂无数据' }}</text> </view> <view v-if="devices.length === 0" class="empty"> <text>还没有添加设备,联系管理员</text> </view> </scroll-view> </view> </template>监控页:实时曲线
用 ECharts 的微信小程序版(echarts-for-weixin)画实时曲线:
<!-- pages/monitor/index.vue --> <script setup> import { ref, onMounted, onUnmounted } from 'vue'; const chartData = ref({ temp: [], humidity: [], soil: [] }); let ws = null; onMounted(() => { // WebSocket 订阅该设备的历史 + 实时数据 ws = uni.connectSocket({ url: `wss://your-server/ws/device/${deviceId}`, }); ws.onMessage((res) => { const point = JSON.parse(res.data); chartData.value.temp.push([point.ts * 1000, point.air_temp]); chartData.value.humidity.push([point.ts * 1000, point.air_humidity]); chartData.value.soil.push([point.ts * 1000, point.soil_moisture]); // 只保留最近 500 个点,防止内存溢出 if (chartData.value.temp.length > 500) { chartData.value.temp.shift(); } }); }); onUnmounted(() => ws?.close()); </script> <template> <view class="monitor"> <view class="current-values"> <text class="big-temp">26.5℃</text> <text class="sub-value">湿度 68% | 土壤 35%</text> </view> <!-- echarts 组件,用 echarts-for-weixin --> <ec-canvas ref="chart" canvas-id="monitor-chart" :ec="chartOption" /> <view class="time-tabs"> <text class="tab active">1h</text> <text class="tab">6h</text> <text class="tab">24h</text> <text class="tab">7d</text> </view> </view> </template>控制页:一键灌溉
远程控制的核心是「小程序 → MQTT → ESP32 → 继电器 → 电磁阀」。
<!-- pages/control/index.vue --> <script setup> import { ref } from 'vue'; const BASE_URL = 'https://your-server'; const valves = ref([ { id: 1, name: '大棚A 灌溉阀', status: false, deviceId: 'pump_01' }, { id: 2, name: '大棚A 通风扇', status: false, deviceId: 'fan_01' }, { id: 3, name: '大棚A 补光灯', status: false, deviceId: 'light_01' }, ]); const toggleValve = async (valve) => { // 1. 调后端 API,后端 publish MQTT 指令 const res = await uni.request({ url: `${BASE_URL}/api/devices/${valve.deviceId}/cmd`, method: 'POST', data: { cmd: valve.status ? 'off' : 'on', seq: Date.now() } }); if (res.data.success) { valve.status = !valve.status; uni.showToast({ title: `${valve.name} ${valve.status ? '已开启' : '已关闭'}` }); } }; // 定时灌溉 const startTimedIrrigation = () => { uni.request({ url: `${BASE_URL}/api/devices/pump_01/cmd`, method: 'POST', data: { cmd: 'irrigate', seq: Date.now(), params: { duration: 600, valve: 1 } // 10 分钟 } }); }; </script> <template> <view class="control"> <view class="section"> <text class="section-title">执行器</text> <view v-for="valve in valves" :key="valve.id" class="control-item"> <text>{{ valve.name }}</text> <switch :checked="valve.status" @change="toggleValve(valve)" color="#07c160" /> </view> </view> <view class="section"> <text class="section-title">快捷操作</text> <button class="quick-btn" @click="startTimedIrrigation"> 💧 定时灌溉 10 分钟 </button> </view> </view> </template>告警推送:微信服务通知
普通消息模板需要用户主动打开小程序才能看到。要让农户及时收到告警,必须用微信服务通知(订阅消息)。
- 在微信公众平台 → 订阅消息 → 申请模板(如「设备告警通知」)
- 用户在小程序里点击授权按钮
- 后端检测到告警时,调微信 API 推送
// 后端发订阅消息publicvoidsendAlarmNotification(Stringopenid,AlarmRecordalarm){WxMaSubscribeMessagemsg=newWxMaSubscribeMessage();msg.setToUser(openid);msg.setTemplateId("模板ID");msg.setPage("pages/monitor/index?deviceId="+alarm.getDeviceId());msg.addData(newData("thing1",alarm.getRuleName()));// 告警类型msg.addData(newData("thing2",alarm.getDeviceName()));// 设备名称msg.addData(newData("amount3",String.valueOf(alarm.getValue())));// 当前值msg.addData(newData("thing4",alarm.getLevelText()));// 告警级别wxMaService.getSubscribeService().sendSubscribeMsg(msg);}农事记录
让农户在手机上记下每次浇水、施肥、打药的时间——这个功能做极简就行,一个表单 + 一个列表。
<script setup> const records = ref([]); const form = reactive({ type: '', note: '', date: new Date() }); const addRecord = async () => { await uni.request({ url: `${BASE_URL}/api/farm-records`, method: 'POST', data: { ...form, deviceId: currentDevice.deviceId } }); records.value.unshift({ ...form }); form.note = ''; }; </script> <template> <view class="record-form"> <picker mode="selector" :range="['浇水','施肥','打药','除草','采收']" @change="(e) => form.type = ['water','fertilize','pesticide','weed','harvest'][e.detail.value]"> <text>{{ form.type || '选择类型' }}</text> </picker> <textarea v-model="form.note" placeholder="备注..." /> <button @click="addRecord">记一笔</button> </view> </template>小程序提醒:首次打开需要微信授权登录(uni.login→ 后端换 openid → 存用户绑定)。UI 尽量大字、大按钮——农户里不少是 50 岁以上的大叔大妈,别搞 12px 的极小字。
下一篇:《自动灌溉系统:AI 什么时候浇水,比老农还准?》——从简单的定时/阈值控制,到结合天气预报、作物生长周期的智能算法。
