免费资源下载
作者:全栈开发者
发布日期:2023年11月
预计阅读:15分钟
发布日期:2023年11月
预计阅读:15分钟
一、UniApp跨平台开发的新机遇
在移动互联网时代,多端适配成为开发者的重要挑战。UniApp基于Vue.js框架,使用一套代码可以同时发布到iOS、Android、Web、以及各种小程序平台,极大地提升了开发效率。本文将带领大家从零开始,构建一个功能完整的智能家居控制APP,涵盖设备管理、场景联动、实时监控等核心功能。
项目核心功能
- 多端适配:一套代码适配iOS、Android、微信小程序、H5
- 设备管理:添加、删除、控制智能家居设备
- 场景联动:自定义设备联动场景
- 实时监控:设备状态实时更新与推送
- 用户系统:完整的注册登录和权限管理
二、开发环境搭建与项目初始化
2.1 环境要求与工具准备
# 检查Node.js版本(建议16.x以上)
node --version
# 安装HBuilderX(官方IDE)或使用VSCode+插件
# 下载地址:https://www.dcloud.io/hbuilderx.html
# 安装Vue3和TypeScript支持
npm install -g @vue/cli
npm install -g typescript
2.2 创建UniApp项目
# 使用Vue3 + TypeScript模板创建项目
vue create -p dcloudio/uni-preset-vue#vue3 smart-home-app
# 项目结构说明
smart-home-app/
├── src/
│ ├── pages/ # 页面文件
│ ├── components/ # 组件目录
│ ├── store/ # 状态管理
│ ├── api/ # 接口封装
│ ├── utils/ # 工具函数
│ └── static/ # 静态资源
├── manifest.json # 应用配置
├── pages.json # 页面配置
└── uni.scss # 全局样式
2.3 项目基础配置
// manifest.json 基础配置
{
"name": "智能家居控制",
"appid": "__UNI__XXXXXXX",
"description": "智能家居控制APP",
"versionName": "1.0.0",
"versionCode": "100",
"transformPx": false,
"app-plus": {
"usingComponents": true
},
"h5": {
"template": "template.h5.html"
},
"mp-weixin": {
"appid": "wx_xxxxxxxx",
"setting": {
"urlCheck": false
}
}
}
三、项目架构设计与状态管理
3.1 采用Pinia进行状态管理
// store/deviceStore.ts
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Device, DeviceStatus } from '@/types/device'
export const useDeviceStore = defineStore('device', () => {
// 设备列表
const deviceList = ref([])
// 当前选中的设备
const currentDevice = ref(null)
// 设备状态映射
const deviceStatus = ref<Record>({})
// 添加设备
const addDevice = (device: Device) => {
deviceList.value.push(device)
deviceStatus.value[device.id] = {
online: false,
lastUpdate: new Date(),
values: {}
}
}
// 更新设备状态
const updateDeviceStatus = (deviceId: string, status: Partial) => {
if (deviceStatus.value[deviceId]) {
deviceStatus.value[deviceId] = {
...deviceStatus.value[deviceId],
...status,
lastUpdate: new Date()
}
}
}
// 获取在线设备数量
const onlineDeviceCount = computed(() => {
return Object.values(deviceStatus.value).filter(s => s.online).length
})
return {
deviceList,
currentDevice,
deviceStatus,
addDevice,
updateDeviceStatus,
onlineDeviceCount
}
})
3.2 类型定义与接口设计
// types/device.ts
export interface Device {
id: string
name: string
type: DeviceType
icon: string
room: string
brand: string
model: string
capabilities: DeviceCapability[]
config: Record
createdAt: Date
}
export enum DeviceType {
LIGHT = 'light',
THERMOSTAT = 'thermostat',
SENSOR = 'sensor',
CAMERA = 'camera',
SWITCH = 'switch',
LOCK = 'lock'
}
export interface DeviceStatus {
online: boolean
lastUpdate: Date
values: Record
}
export interface DeviceCapability {
type: string
name: string
valueType: 'boolean' | 'number' | 'string'
min?: number
max?: number
unit?: string
}
四、核心组件开发实战
4.1 设备卡片组件
<template>
<view class="device-card" @click="handleClick">
<view class="card-header">
<view class="device-icon">
<uni-icons
:type="deviceIcon"
:color="isOnline ? '#07c160' : '#999'"
size="24"
></uni-icons>
</view>
<view class="status-indicator" :class="{ online: isOnline }"></view>
</view>
<view class="card-body">
<text class="device-name">{{ device.name }}</text>
<text class="device-room">{{ device.room }}</text>
</view>
<view class="card-footer">
<view class="device-controls" v-if="showControls">
<switch
v-if="hasSwitch"
:checked="isOn"
@change="handleSwitchChange"
/>
<slider
v-if="hasSlider"
:value="sliderValue"
min="0"
max="100"
@change="handleSliderChange"
/>
</view>
</view>
</view>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { Device, DeviceType } from '@/types/device'
const props = defineProps<{
device: Device
status?: any
}>()
const emit = defineEmits<{
(e: 'click', device: Device): void
(e: 'control', data: { deviceId: string; command: string; value: any }): void
}>()
const isOnline = computed(() => props.status?.online || false)
const isOn = computed(() => props.status?.values?.power === true)
const deviceIcon = computed(() => {
const icons = {
[DeviceType.LIGHT]: 'light-filled',
[DeviceType.THERMOSTAT]: 'thermometer',
[DeviceType.SENSOR]: 'sound',
[DeviceType.CAMERA]: 'camera-filled',
[DeviceType.SWITCH]: 'circle-filled',
[DeviceType.LOCK]: 'locked-filled'
}
return icons[props.device.type] || 'gear-filled'
})
const handleClick = () => {
emit('click', props.device)
}
const handleSwitchChange = (e: any) => {
emit('control', {
deviceId: props.device.id,
command: 'set_power',
value: e.detail.value
})
}
</script>
4.2 场景联动编辑器
// components/SceneEditor.vue
<template>
<view class="scene-editor">
<view class="editor-header">
<text class="title">创建智能场景</text>
<uni-icons type="close" @click="handleClose"></uni-icons>
</view>
<view class="editor-body">
<uni-forms ref="formRef" :model="sceneForm">
<uni-forms-item label="场景名称" name="name">
<uni-easyinput v-model="sceneForm.name" placeholder="请输入场景名称" />
</uni-forms-item>
<view class="section-title">触发条件</view>
<view class="trigger-list">
<view
class="trigger-item"
v-for="(trigger, index) in sceneForm.triggers"
:key="index"
>
<device-selector
v-model="trigger.deviceId"
@change="onDeviceSelect(index, $event)"
/>
<condition-editor
:device="selectedDevices[index]"
v-model="trigger.condition"
/>
</view>
<button @click="addTrigger">+ 添加触发条件</button>
</view>
<view class="section-title">执行动作</view>
<view class="action-list">
<view
class="action-item"
v-for="(action, index) in sceneForm.actions"
:key="index"
>
<device-selector v-model="action.deviceId" />
<action-editor
:device="getDeviceById(action.deviceId)"
v-model="action.command"
/>
</view>
<button @click="addAction">+ 添加执行动作</button>
</view>
</uni-forms>
</view>
<view class="editor-footer">
<button class="save-btn" @click="handleSave">保存场景</button>
</view>
</view>
</template>
五、网络通信与WebSocket实时推送
5.1 封装统一的API请求
// api/request.ts
import { useUserStore } from '@/store/userStore'
class Request {
private baseURL: string
private timeout: number
constructor() {
// 根据环境配置不同的baseURL
this.baseURL = process.env.NODE_ENV === 'development'
? 'http://localhost:3000/api'
: 'https://api.smarthome.com/v1'
this.timeout = 10000
}
async request<T>(options: UniApp.RequestOptions): Promise<T> {
const userStore = useUserStore()
const header: Record<string, string> = {
'Content-Type': 'application/json'
}
// 添加认证token
if (userStore.token) {
header['Authorization'] = `Bearer ${userStore.token}`
}
return new Promise((resolve, reject) => {
uni.request({
url: this.baseURL + options.url,
method: options.method || 'GET',
data: options.data,
header: { ...header, ...options.header },
timeout: this.timeout,
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data as T)
} else {
this.handleError(res, reject)
}
},
fail: (err) => {
reject(this.normalizeError(err))
}
})
})
}
// 设备相关API
device = {
list: () => this.request<Device[]>({ url: '/devices' }),
add: (data: Partial<Device>) =>
this.request<Device>({ url: '/devices', method: 'POST', data }),
control: (deviceId: string, command: string, value: any) =>
this.request({
url: `/devices/${deviceId}/control`,
method: 'POST',
data: { command, value }
})
}
}
export default new Request()
5.2 WebSocket实时通信
// utils/websocket.ts
class SmartHomeWebSocket {
private ws: UniApp.SocketTask | null = null
private reconnectTimer: number | null = null
private reconnectCount = 0
private maxReconnectCount = 5
constructor(private url: string) {}
connect(): Promise<void> {
return new Promise((resolve, reject) => {
this.ws = uni.connectSocket({
url: this.url,
success: () => {
console.log('WebSocket连接成功')
this.setupEventListeners()
resolve()
},
fail: (err) => {
console.error('WebSocket连接失败:', err)
reject(err)
}
})
})
}
private setupEventListeners() {
if (!this.ws) return
// 监听消息
this.ws.onMessage((res) => {
try {
const data = JSON.parse(res.data)
this.handleMessage(data)
} catch (error) {
console.error('消息解析失败:', error)
}
})
// 监听连接关闭
this.ws.onClose(() => {
console.log('WebSocket连接关闭')
this.scheduleReconnect()
})
// 监听错误
this.ws.onError((err) => {
console.error('WebSocket错误:', err)
})
}
private handleMessage(data: any) {
const { type, payload } = data
switch (type) {
case 'device_status_update':
// 更新设备状态
const deviceStore = useDeviceStore()
deviceStore.updateDeviceStatus(payload.deviceId, payload.status)
break
case 'scene_triggered':
// 场景触发通知
uni.showToast({
title: `场景"${payload.sceneName}"已触发`,
icon: 'none'
})
break
case 'alert':
// 设备告警
this.showAlert(payload)
break
}
}
sendCommand(deviceId: string, command: string, value: any) {
if (!this.ws) {
console.error('WebSocket未连接')
return
}
const message = {
type: 'device_control',
payload: { deviceId, command, value }
}
this.ws.send({
data: JSON.stringify(message),
success: () => {
console.log('命令发送成功')
},
fail: (err) => {
console.error('命令发送失败:', err)
}
})
}
disconnect() {
if (this.ws) {
this.ws.close()
this.ws = null
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer)
this.reconnectTimer = null
}
}
}
六、多平台适配与优化
6.1 平台条件编译
// 平台特定的代码处理
export function getPlatformConfig() {
// #ifdef APP-PLUS
return {
platform: 'app',
storagePrefix: 'app_',
pushType: 'unipush'
}
// #endif
// #ifdef MP-WEIXIN
return {
platform: 'weixin',
storagePrefix: 'wx_',
pushType: 'template'
}
// #endif
// #ifdef H5
return {
platform: 'h5',
storagePrefix: 'h5_',
pushType: 'websocket'
}
// #endif
}
// 平台特定的UI适配
export function adaptUI(style: Record) {
const result = { ...style }
// #ifdef APP-PLUS
// iOS和Android的特定样式
result.fontSize = '16px'
// #endif
// #ifdef MP-WEIXIN
// 微信小程序的特定样式
result.fontSize = '14px'
// #endif
return result
}
6.2 性能优化策略
// 图片懒加载优化
export function useLazyLoad() {
const observer = ref<IntersectionObserver | null>(null)
const initLazyLoad = () => {
// #ifdef H5
observer.value = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target as HTMLImageElement
img.src = img.dataset.src || ''
observer.value?.unobserve(img)
}
})
})
// #endif
}
const lazyLoadImage = (img: HTMLImageElement, src: string) => {
// #ifdef H5
img.dataset.src = src
observer.value?.observe(img)
// #else
img.src = src
// #endif
}
return { initLazyLoad, lazyLoadImage }
}
// 数据缓存策略
export class DataCache {
private cache = new Map<string, { data: any; timestamp: number }>()
private defaultTTL = 5 * 60 * 1000 // 5分钟
async getWithCache<T>(key: string, fetchFn: () => Promise<T>, ttl?: number): Promise<T> {
const cached = this.cache.get(key)
const now = Date.now()
if (cached && now - cached.timestamp < (ttl || this.defaultTTL)) {
return cached.data
}
const data = await fetchFn()
this.cache.set(key, { data, timestamp: now })
return data
}
clearCache(key?: string) {
if (key) {
this.cache.delete(key)
} else {
this.cache.clear()
}
}
}
七、打包发布与持续集成
7.1 多平台打包配置
// package.json 脚本配置
{
"scripts": {
"dev:h5": "uni -p h5",
"dev:mp-weixin": "uni -p mp-weixin",
"build:h5": "uni build -p h5",
"build:mp-weixin": "uni build -p mp-weixin",
"build:app": "uni build -p app",
"lint": "eslint --ext .vue,.js,.ts src",
"test": "jest"
}
}
// 环境变量配置
// .env.production
VUE_APP_API_BASE=https://api.smarthome.com/v1
VUE_APP_WS_URL=wss://ws.smarthome.com
VUE_APP_VERSION=1.0.0
// .env.development
VUE_APP_API_BASE=http://localhost:3000/api
VUE_APP_WS_URL=ws://localhost:3001
VUE_APP_VERSION=1.0.0-dev
7.2 自动化部署脚本
#!/bin/bash
# deploy.sh - 自动化部署脚本
echo "开始构建智能家居APP..."
# 检查依赖
if ! command -v node &> /dev/null; then
echo "错误: Node.js未安装"
exit 1
fi
# 安装依赖
echo "安装依赖..."
npm ci
# 代码检查
echo "运行代码检查..."
npm run lint
# 运行测试
echo "运行单元测试..."
npm run test
# 构建不同平台
platforms=("h5" "mp-weixin")
for platform in "${platforms[@]}"; do
echo "构建 $platform 平台..."
npm run build:$platform
if [ $? -eq 0 ]; then
echo "$platform 平台构建成功"
# 上传到对应平台
case $platform in
"mp-weixin")
echo "上传微信小程序..."
# 这里可以添加微信小程序上传命令
;;
"h5")
echo "部署H5版本..."
# 这里可以添加H5部署命令
;;
esac
else
echo "$platform 平台构建失败"
exit 1
fi
done
echo "所有平台构建完成!"
八、总结与进阶建议
8.1 项目总结
通过本教程,我们完成了一个完整的智能家居控制APP的开发,涵盖了:
- UniApp项目初始化与配置
- Vue3 + TypeScript + Pinia现代技术栈应用
- 设备管理、场景联动等核心功能实现
- WebSocket实时通信与状态同步
- 多平台适配与性能优化
- 自动化构建与部署流程
8.2 进阶扩展方向
- 原生插件开发:集成蓝牙、NFC等硬件功能
- AI能力集成:语音控制、图像识别
- 离线功能:实现离线场景执行和设备控制
- 数据分析:设备使用统计和能耗分析
- 多用户协作:家庭多成员权限管理
8.3 最佳实践建议
- 使用TypeScript严格模式提高代码质量
- 实现完整的错误监控和上报机制
- 定期进行性能分析和优化
- 建立组件文档和开发规范
- 采用渐进式增强策略,确保基础功能稳定
UniApp作为跨平台开发的重要工具,在不断发展的过程中,建议持续关注官方更新和社区动态,结合具体业务需求,选择最适合的技术方案。希望本教程能为你的UniApp开发之路提供有价值的参考。

