UniApp跨平台开发实战:从零构建智能家居控制APP全流程指南

2026-02-25 0 411
免费资源下载

一、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的开发,涵盖了:

  1. UniApp项目初始化与配置
  2. Vue3 + TypeScript + Pinia现代技术栈应用
  3. 设备管理、场景联动等核心功能实现
  4. WebSocket实时通信与状态同步
  5. 多平台适配与性能优化
  6. 自动化构建与部署流程

8.2 进阶扩展方向

  • 原生插件开发:集成蓝牙、NFC等硬件功能
  • AI能力集成:语音控制、图像识别
  • 离线功能:实现离线场景执行和设备控制
  • 数据分析:设备使用统计和能耗分析
  • 多用户协作:家庭多成员权限管理

8.3 最佳实践建议

  • 使用TypeScript严格模式提高代码质量
  • 实现完整的错误监控和上报机制
  • 定期进行性能分析和优化
  • 建立组件文档和开发规范
  • 采用渐进式增强策略,确保基础功能稳定

UniApp作为跨平台开发的重要工具,在不断发展的过程中,建议持续关注官方更新和社区动态,结合具体业务需求,选择最适合的技术方案。希望本教程能为你的UniApp开发之路提供有价值的参考。

UniApp跨平台开发实战:从零构建智能家居控制APP全流程指南
收藏 (0) 打赏

感谢您的支持,我会继续努力的!

打开微信/支付宝扫一扫,即可进行扫码打赏哦,分享从这里开始,精彩与您同在
点赞 (0)

淘吗网 uniapp UniApp跨平台开发实战:从零构建智能家居控制APP全流程指南 https://www.taomawang.com/web/uniapp/1629.html

常见问题

相关文章

猜你喜欢
发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务