UniApp跨端智能家居控制:Vue语法直连IoT设备实战
2026/9/14 4:21:19 网站建设 项目流程

简介:这是一套基于UniApp框架开发的Vue智能居家页面设计源码,面向前端初学者与智能家居应用开发者,解决跨端智能界面快速搭建与UI组件复用问题。资源共83个文件,含15个Vue组件(如tab-bar、top-menu、gdPicker等)、20个JS文件(涵盖MQTT通信、AES/RSA加解密、HTTP请求封装等核心逻辑)、27个PNG素材及11个JSON配置(pages.json、manifest.json等),辅以SCSS全局样式与Markdown文档说明,压缩包仅4.75MB,轻量易上手。已有229人学习下载,适合用于理解UniApp多端适配机制、智能家居交互流程与模块化组件设计实践。读者可直接运行index.html或main.js启动项目,参考readme.txt部署,复用popup、time-range、temp-range等成熟UI组件,并基于smh-、rhliu-等命名空间的uni_modules快速集成设备控制与状态展示功能。

1. 这不是普通 Vue 页面——UniApp 构建的智能居家界面,天生跨端、可直连 IoT 设备、能离线渲染温控/灯光/安防状态

当你在微信小程序里滑动调节空调温度,在安卓 App 中点击开关窗帘,又在 H5 页面上实时查看门锁开合记录——这些操作背后,很可能跑着同一套代码。UniApp 不是“Vue 的小程序版”,而是以 Vue 语法为基底、通过一套源码编译出 iOS/Android/微信小程序/支付宝小程序/H5 五端的跨平台框架。本项目标题中的“基于 UniApp 的 Vue 智能居家页面设计源码”,核心价值不在“页面美观”,而在于用标准 Vue 语法声明式绑定真实家居设备状态,并通过 UniApp 提供的原生能力桥接蓝牙/WiFi/HTTP/MQTT 等协议通道。它面向的是智能家居厂商前端工程师、IoT 项目集成商、以及需要快速交付多端控制面板的嵌入式系统配套团队。新手可直接运行调试 UI 交互逻辑;有经验者会重点关注uni.getConnectedWifi()uni.connectSocket()uni.openBluetoothAdapter()等 API 在不同端的兼容性边界与降级策略——比如 iOS 微信小程序不支持蓝牙直连,但可通过“附近设备”扫码跳转原生 App 完成配网;而 Android App 则必须处理蓝牙权限动态申请与后台保活问题。这不是玩具 Demo,而是生产级设备控制面板的最小可行实现。

2. 从 Vue 单文件组件到五端可运行:UniApp 工程结构与智能居家模块拆解

2.1 为什么选 UniApp 而非纯 Vue 或 React Native?三类典型场景下的技术权衡

在智能居家场景中,设备控制端需同时满足:① 用户已在微信生态内(小程序入口最轻);② 高频操作需原生性能(如滑动调光、实时视频流 overlay);③ 企业需统一管理设备数据(H5 后台运维页)。纯 Vue CLI 项目仅能输出 Web,无法调用蓝牙或获取 WiFi 列表;React Native 虽支持原生模块,但 iOS/Android 双端需分别维护桥接代码,且小程序端完全不可行。UniApp 的核心优势在于其编译时抽象层.vue文件中写的uni.scanCode()在微信小程序中编译为wx.scanCode(),在 App 中编译为plus.barcode.scan(),在 H5 中则 fallback 为navigator.mediaDevices.getUserMedia()+ canvas 解码。这种“一次编写、多端编译”的能力,使本项目能复用同一套设备状态管理逻辑(如store/modules/device.js),仅需在api/目录下按平台提供适配器:

// api/device-adapter.js export const getDeviceStatus = () => { // 微信小程序:调用云函数查询设备影子 if (process.env.UNI_PLATFORM === 'mp-weixin') { return uniCloud.callFunction({ name: 'getDeviceShadow', data: { deviceId: 'light-001' } }) } // App 端:直连本地 MQTT Broker(如 Mosquitto) if (process.env.UNI_PLATFORM === 'app') { return new Promise((resolve) => { const client = mqtt.connect('mqtt://192.168.1.100:1883') client.on('connect', () => { client.subscribe('device/light-001/status') client.on('message', (topic, payload) => { resolve(JSON.parse(payload.toString())) }) }) }) } // H5:通过 REST API 查询设备服务 return fetch('/api/v1/devices/light-001/status').then(r => r.json()) }

提示:process.env.UNI_PLATFORM是 UniApp 编译期注入的环境变量,值为'h5''mp-weixin''app'等,不可在运行时动态修改。所有平台特有逻辑必须在此变量判断下分支,否则 H5 端可能因调用uni.connectSocket()报错中断。

2.2 智能居家页面的核心组件分层:从设备卡片到状态同步管道

本源码的页面结构严格遵循“状态驱动 UI”原则,分为三层:

  • 视图层(View)pages/index/index.vue中的<device-card>组件,接收device对象作为 prop,内部用v-if控制空调/灯光/安防等不同设备模板;
  • 状态层(Store)store/modules/device.js使用 Vuex 模块化管理设备列表、当前选中设备、连接状态,关键 action 如syncDeviceStatus每 3 秒轮询或监听 WebSocket;
  • 通信层(API)api/目录下按协议划分,mqtt.js封装 MQTT 连接与 topic 订阅,ble.js封装蓝牙设备发现与特征值读写,http.js统一处理 REST 请求拦截与 token 刷新。

下面是一个典型的设备卡片组件实现,重点展示如何响应式同步设备状态:

<!-- components/device-card.vue --> <template> <view class="device-card" @click="toggleDevice"> <text class="device-name">{{ device.name }}</text> <view class="status-indicator" :class="{ active: device.power }"></view> <!-- 温控设备显示滑块 --> <slider v-if="device.type === 'thermostat'" :value="device.temperature" @changing="onTempChange" @change="onTempConfirm" min="16" max="30" step="0.5" /> <!-- 灯光设备显示色温选择 --> <picker v-else-if="device.type === 'light'" mode="selector" :range="['暖光', '白光', '彩光']" @change="onLightModeChange" > <view class="light-mode">{{ device.mode }}</view> </picker> </view> </template> <script> export default { name: 'DeviceCard', props: { device: { type: Object, required: true, // 注意:此处 device 是响应式对象,来自 store.state.devices[0] // 修改 device.power 会触发视图更新,但不会自动同步到硬件 } }, methods: { toggleDevice() { // 触发 store action,而非直接修改 prop this.$store.dispatch('device/togglePower', this.device.id) }, onTempChange(e) { // 滑动中预览,不立即发送指令 this.$emit('tempPreview', e.detail.value) }, onTempConfirm(e) { // 松手后确认,调用 API 发送 MQTT 指令 this.$store.dispatch('device/setTemperature', { id: this.device.id, temperature: e.detail.value }) } } } </script>
2.2.1 关键参数说明:v-model在 UniApp 中的特殊处理

Vue 原生v-model在 UniApp 中对inputtextarea等基础组件有效,但对sliderpicker等平台原生组件不生效。必须显式使用@change事件并手动触发this.$emit('update:xxx')才能实现双向绑定。例如slider组件需配合:value@change实现受控模式,否则在 iOS App 中可能出现滑块位置与实际值不同步的问题。

2.2.2 设备状态同步的三种模式对比表
同步方式适用场景实现要点典型延迟
轮询 HTTPH5 后台管理页、低频设备(如门锁)setInterval(() => api.getDevice(), 5000)3–8s
WebSocketApp/小程序实时监控(如摄像头在线状态)uni.connectSocket()+uni.onSocketMessage()<500ms
MQTT 订阅App 端直连本地设备(推荐)client.subscribe('device/+/status'),通配符匹配所有设备<100ms

注意:微信小程序不支持原生 MQTT,必须通过云函数中转;而 App 端若使用uni-app内置的uni.connectSocket(),需确保服务端 WebSocket 协议与 MQTT over WS 兼容,否则需改用uni-app插件市场中的mqtt-plus插件。

3. 设备连接与状态驱动:蓝牙配网、WiFi 列表扫描与 MQTT 指令下发实战

3.1 微信小程序端实现“一键配网”:扫码 + WiFi 列表 + AP 模式切换全流程

智能设备首次联网(如新买的智能灯泡),用户需将其接入家庭 WiFi。UniApp 在小程序端通过三步完成:

  1. 扫码进入配网页:调用uni.scanCode({ onlyFromCamera: true })获取设备序列号(SN);
  2. 获取当前 WiFi 名称与密码uni.getConnectedWifi()返回 SSID,但无法直接获取密码,需引导用户手动输入;
  3. 向设备发送配网指令:设备处于 AP 模式时会广播一个热点(如SmartBulb-XXXX),小程序通过uni.connectWifi()连接该热点,再uni.request()http://192.168.4.1/configPOST WiFi 凭据。

完整代码如下:

// pages/configure/configure.vue export default { data() { return { ssid: '', password: '', deviceSn: '' } }, methods: { async startConfig() { try { // 步骤1:扫码获取设备 SN const res = await uni.scanCode() this.deviceSn = res.result // 步骤2:获取当前连接的 WiFi 名称(iOS/Android 均支持) const wifi = await uni.getConnectedWifi() this.ssid = wifi.SSID // 步骤3:提示用户输入密码(小程序无法读取) uni.showModal({ title: '请输入家庭 WiFi 密码', showCancel: false, success: () => { this.triggerConfig() } }) } catch (e) { uni.showToast({ title: '配网失败:' + e.message, icon: 'none' }) } }, async triggerConfig() { try { // 连接设备 AP 热点(需提前在 manifest.json 中配置 wifi permissions) await uni.connectWifi({ SSID: `SmartBulb-${this.deviceSn.substring(0,4)}`, password: '' // AP 模式热点通常无密码 }) // 向设备配置接口发送 WiFi 凭据 const response = await uni.request({ url: 'http://192.168.4.1/config', method: 'POST', data: { ssid: this.ssid, password: this.password, sn: this.deviceSn } }) if (response.data.code === 0) { uni.showToast({ title: '配网成功!设备将重启连接家庭网络' }) } } catch (e) { uni.showToast({ title: '配网失败:' + e.message, icon: 'none' }) } } } }
3.1.1 manifest.json 必配项说明(微信小程序)

manifest.json"mp-weixin"节点下,必须声明以下权限,否则uni.connectWifi()会静默失败:

{ "mp-weixin": { "usingComponents": true, "permission": { "scope.userLocation": { "desc": "用于获取当前位置以便搜索附近设备" }, "scope.writeContacts": { "desc": "用于保存设备配网记录" } } } }

注意:uni.getConnectedWifi()在 iOS 微信 8.0.30+ 才支持,旧版本需降级为“手动输入 WiFi 名称”。

3.2 App 端直连蓝牙设备:发现、连接、读写特征值全链路

当设备已接入家庭网络后,App 端可绕过云端,通过蓝牙直连进行低延迟控制(如调节 RGB 灯颜色)。UniApp 提供uni.openBluetoothAdapter()系列 API,但需严格遵循平台差异:

  • Android:需动态申请android.permission.BLUETOOTH_ADMINandroid.permission.ACCESS_FINE_LOCATION(蓝牙扫描需定位权限);
  • iOSinfo.plist中必须添加NSBluetoothAlwaysUsageDescription描述,且仅支持 BLE(Bluetooth Low Energy)。

下面是一个完整的蓝牙设备控制流程:

// utils/ble-controller.js export class BLEController { constructor() { this.deviceId = null this.serviceId = null this.characteristicId = null } async init() { try { await uni.openBluetoothAdapter() await uni.startBluetoothDiscovery({ services: ['0000FF00-0000-1000-8000-00805F9B34FB'] }) const devices = await uni.getConnectedBluetoothDevices() if (devices.length > 0) { this.deviceId = devices[0].deviceId await this.connectDevice() } } catch (e) { console.error('蓝牙初始化失败', e) } } async connectDevice() { await uni.createBLEConnection({ deviceId: this.deviceId }) const services = await uni.getBLEDeviceServices({ deviceId: this.deviceId }) this.serviceId = services.services.find(s => s.uuid.includes('FF00')).uuid const chars = await uni.getBLEDeviceCharacteristics({ deviceId: this.deviceId, serviceId: this.serviceId }) this.characteristicId = chars.characteristics.find(c => c.properties.write).uuid } async sendCommand(command) { // command 示例:{ type: 'color', value: [255, 128, 0] } const buffer = new ArrayBuffer(4) const view = new DataView(buffer) view.setUint8(0, command.type === 'color' ? 0x01 : 0x02) command.value.forEach((v, i) => view.setUint8(i + 1, v)) await uni.writeBLECharacteristicValue({ deviceId: this.deviceId, serviceId: this.serviceId, characteristicId: this.characteristicId, value: buffer }) } }
3.2.1 蓝牙特征值写入的字节序陷阱

上述sendCommand中,new DataView(buffer)默认使用大端序(Big Endian),但多数 BLE 设备固件期望小端序(Little Endian)解析 RGB 值。若发现颜色设置异常,需改为:

// 小端序写入 RGB view.setUint8(1, command.value[0]) // R view.setUint8(2, command.value[1]) // G view.setUint8(3, command.value[2]) // B

4. 生产环境关键配置:条件编译、多端样式隔离与 MQTT 连接保活策略

4.1 用条件编译解决“同一份 CSS 在小程序和 App 中渲染错位”问题

UniApp 支持/* #ifdef MP-WEIXIN */等条件编译区块,但CSS 中不能直接写#ifdef。正确做法是在style标签内用@import引入平台专属样式:

<!-- pages/index/index.vue --> <style> @import './index.common.css'; /* 通用样式 */ /* #ifdef MP-WEIXIN */ @import './index.mp.css'; /* 小程序特有样式 */ /* #endif */ /* #ifdef APP-PLUS */ @import './index.app.css'; /* App 特有样式 */ /* #endif */ </style>

index.mp.css中可覆盖小程序限制:

/* index.mp.css */ .device-card { /* 小程序不支持 flex: 1,改用固定高度 */ height: 120px; } .status-indicator { /* 小程序中 border-radius 大于 height 时失效,改用 background-image 模拟圆点 */ background-image: radial-gradient(circle, #4CAF50, #2E7D32); width: 20px; height: 20px; }
4.1.1 条件编译常用平台标识对照表
标识符对应平台典型用途
MP-WEIXIN微信小程序调用wx.login()wx.chooseAddress()
APP-PLUS5+ App调用plus.bluetooth.*plus.network.*
H5H5 页面使用fetchlocalStorage
MP-ALIPAY支付宝小程序调用my.scan()my.getNetworkType()

注意:/* #ifndef H5 */表示“除 H5 外所有平台”,常用于屏蔽 H5 不支持的 API 调用。

4.2 MQTT 连接保活:心跳包、断线重连与离线消息缓存

在 App 端直连 MQTT 时,网络抖动会导致连接中断。UniApp 本身不提供 MQTT 自动重连,需自行实现:

// utils/mqtt-client.js export class MQTTClient { constructor(options) { this.options = options this.client = null this.reconnectTimer = null this.messageQueue = [] // 离线期间的待发消息 } connect() { this.client = mqtt.connect(this.options.url, { username: this.options.username, password: this.options.password, keepalive: 60, // 心跳间隔(秒) reconnectPeriod: 1000 // 首次重连延迟(毫秒) }) this.client.on('connect', () => { console.log('MQTT connected') this.flushQueue() // 连接成功后发送缓存消息 this.startHeartbeat() }) this.client.on('reconnect', () => { console.log('MQTT reconnecting...') }) this.client.on('error', (err) => { console.error('MQTT error', err) this.scheduleReconnect() }) } scheduleReconnect() { if (this.reconnectTimer) clearTimeout(this.reconnectTimer) this.reconnectTimer = setTimeout(() => { this.connect() }, 5000) // 指数退避可在此处增强 } publish(topic, payload) { if (this.client && this.client.connected) { this.client.publish(topic, JSON.stringify(payload)) } else { this.messageQueue.push({ topic, payload }) } } flushQueue() { this.messageQueue.forEach(msg => this.publish(msg.topic, msg.payload)) this.messageQueue = [] } }
4.2.1 心跳包与 QoS 级别选择建议
  • keepalive: 60表示客户端每 60 秒向 Broker 发送一次心跳(PINGREQ),Broker 若 1.5 倍时间未收到则断开连接;
  • 指令类消息(如“打开灯光”)应设qos: 1,确保至少送达一次;
  • 状态上报类消息(如“温度 26.5℃”)可设qos: 0,避免重复推送造成 UI 闪烁。

5. 验证与调试技巧:用真机日志定位“小程序能连 WiFi 但 App 蓝牙找不到设备”类问题

5.1 三端日志统一收集:在main.js中注入全局错误处理器

UniApp 的console.log在不同端输出位置不同:H5 显示在浏览器控制台,小程序在开发者工具 Console,App 则需通过uni.getProvider()获取原生日志。统一方案是在main.js中重写console方法:

// main.js if (process.env.UNI_PLATFORM === 'app') { const originalLog = console.log console.log = function(...args) { // 同时输出到原生日志和 H5 控制台(便于调试) plus.nativeObj.toast({ content: args.map(String).join(' '), duration: 'short' }) originalLog.apply(console, args) } } // 全局错误捕获 window.addEventListener('error', (e) => { uni.reportAnalytics('js_error', { message: e.message, filename: e.filename, lineno: e.lineno }) })

5.2 蓝牙调试黄金组合:uni.getConnectedBluetoothDevices()+uni.getBLEDeviceServices()+uni.getBLEDeviceCharacteristics()

当 App 端“扫描不到设备”时,按此顺序执行诊断:

  1. 确认蓝牙适配器已开启

    const adapter = await uni.getBluetoothAdapterState() if (!adapter.available) throw new Error('蓝牙未开启')
  2. 检查是否已连接设备(避免重复连接)

    const connected = await uni.getConnectedBluetoothDevices() console.log('已连接设备:', connected) // 若有结果,直接走 connectDevice 流程
  3. 扫描后未发现设备?强制刷新扫描缓存

    await uni.stopBluetoothDiscovery() await uni.startBluetoothDiscovery() // 等待 3 秒后再次 getConnectedBluetoothDevices()
  4. 发现设备但无法获取服务?检查 UUID 大小写与格式

    // 错误写法(小写 uuid 无法匹配) await uni.getBLEDeviceServices({ deviceId, services: ['ff00'] }) // 正确写法(全大写 + 标准格式) await uni.getBLEDeviceServices({ deviceId, services: ['0000FF00-0000-1000-8000-00805F9B34FB'] })

提示:iOS 设备对蓝牙服务 UUID 校验极严,必须与固件广播的 UUID 完全一致(包括连字符位置),而 Android 相对宽松。调试时优先用 LightBlue(iOS)或 nRF Connect(Android)验证设备广播内容。

5.3 小程序配网失败的三个高频原因及对应检查命令

现象可能原因检查命令/方法
扫码后无反应设备 SN 格式不匹配正则console.log(res.result)查看扫码返回值是否含SN:前缀
连接 AP 热点失败设备未进入 AP 模式用手机 WiFi 列表手动搜索SmartBulb-XXXX是否存在
POST 配网请求超时设备 IP 地址错误ping 192.168.4.1确认设备是否响应 ICMP

最后一步:在uni.request()中添加timeout: 10000参数,并捕获statusCode: 0错误——这表示网络层未建立连接,大概率是设备未响应或 IP 错误。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询