
简介这是一套基于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”原则分为三层视图层Viewpages/index/index.vue中的device-card组件接收device对象作为 prop内部用v-if控制空调/灯光/安防等不同设备模板状态层Storestore/modules/device.js使用 Vuex 模块化管理设备列表、当前选中设备、连接状态关键 action 如syncDeviceStatus每 3 秒轮询或监听 WebSocket通信层APIapi/目录下按协议划分mqtt.js封装 MQTT 连接与 topic 订阅ble.js封装蓝牙设备发现与特征值读写http.js统一处理 REST 请求拦截与 token 刷新。下面是一个典型的设备卡片组件实现重点展示如何响应式同步设备状态!-- components/device-card.vue -- template view classdevice-card clicktoggleDevice text classdevice-name{{ device.name }}/text view classstatus-indicator :class{ active: device.power }/view !-- 温控设备显示滑块 -- slider v-ifdevice.type thermostat :valuedevice.temperature changingonTempChange changeonTempConfirm min16 max30 step0.5 / !-- 灯光设备显示色温选择 -- picker v-else-ifdevice.type light modeselector :range[暖光, 白光, 彩光] changeonLightModeChange view classlight-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 }) } } } /script2.2.1 关键参数说明v-model在 UniApp 中的特殊处理Vue 原生v-model在 UniApp 中对input、textarea等基础组件有效但对slider、picker等平台原生组件不生效。必须显式使用change事件并手动触发this.$emit(update:xxx)才能实现双向绑定。例如slider组件需配合:value和change实现受控模式否则在 iOS App 中可能出现滑块位置与实际值不同步的问题。2.2.2 设备状态同步的三种模式对比表同步方式适用场景实现要点典型延迟轮询 HTTPH5 后台管理页、低频设备如门锁setInterval(() api.getDevice(), 5000)3–8sWebSocketApp/小程序实时监控如摄像头在线状态uni.connectSocket()uni.onSocketMessage()500msMQTT 订阅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 在小程序端通过三步完成扫码进入配网页调用uni.scanCode({ onlyFromCamera: true })获取设备序列号SN获取当前 WiFi 名称与密码uni.getConnectedWifi()返回 SSID但无法直接获取密码需引导用户手动输入向设备发送配网指令设备处于 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_ADMIN和android.permission.ACCESS_FINE_LOCATION蓝牙扫描需定位权限iOSinfo.plist中必须添加NSBluetoothAlwaysUsageDescription描述且仅支持 BLEBluetooth 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]) // B4. 生产环境关键配置条件编译、多端样式隔离与 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 */ /styleindex.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 页面使用fetch、localStorageMP-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 发送一次心跳PINGREQBroker 若 1.5 倍时间未收到则断开连接指令类消息如“打开灯光”应设qos: 1确保至少送达一次状态上报类消息如“温度 26.5℃”可设qos: 0避免重复推送造成 UI 闪烁。5. 验证与调试技巧用真机日志定位“小程序能连 WiFi 但 App 蓝牙找不到设备”类问题5.1 三端日志统一收集在main.js中注入全局错误处理器UniApp 的console.log在不同端输出位置不同H5 显示在浏览器控制台小程序在开发者工具 ConsoleApp 则需通过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 端“扫描不到设备”时按此顺序执行诊断确认蓝牙适配器已开启const adapter await uni.getBluetoothAdapterState() if (!adapter.available) throw new Error(蓝牙未开启)检查是否已连接设备避免重复连接const connected await uni.getConnectedBluetoothDevices() console.log(已连接设备:, connected) // 若有结果直接走 connectDevice 流程扫描后未发现设备强制刷新扫描缓存await uni.stopBluetoothDiscovery() await uni.startBluetoothDiscovery() // 等待 3 秒后再次 getConnectedBluetoothDevices()发现设备但无法获取服务检查 UUID 大小写与格式// 错误写法小写 uuid 无法匹配 await uni.getBLEDeviceServices({ deviceId, services: [ff00] }) // 正确写法全大写 标准格式 await uni.getBLEDeviceServices({ deviceId, services: [0000FF00-0000-1000-8000-00805F9B34FB] })提示iOS 设备对蓝牙服务 UUID 校验极严必须与固件广播的 UUID 完全一致包括连字符位置而 Android 相对宽松。调试时优先用 LightBlueiOS或 nRF ConnectAndroid验证设备广播内容。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 错误。本文还有配套的精品资源点击获取