HarmonyOS智能家居——首页仪表盘架构拆解
智能家居 App 的首页是一个信息密集的控制中枢——一个页面里同时展示了设备状态总览、房间导航、快捷设备开关、能耗趋势图、智能场景控制五个区块。和其他类型的 App 不同智能家居首页不只是数据的展示层用户在首页就能直接操作——点击快捷控制切换设备开关点击场景卡片激活不同的家居模式。这意味着首页的状态管理需要同时处理读取数据和修改后刷新两个方向。完整效果颜色体系constA:string#6C5CE7// 主色·科技紫constBG:string#F5F4FA// 背景灰constCW:string#FFFFFF// 卡片白constT1:string#1E1B2E// 标题黑constT2:string#888888// 副文本紫色#6C5CE7作为主色在智能家居场景里代表科技感和未来感。背景色#F5F4FA比纯灰更偏冷调和紫色搭配比暖灰色更协调。T1/T2 两级文本颜色负责文字层次没有用到 T3#BBBBBB因为首页的辅助文字不多。数据层DeviceData 的完整架构exportinterfaceDevice{id:number;name:string;icon:string;type:stringroom:string;on:boolean;value:number;color:string}exportinterfaceRoom{name:string;icon:string;deviceCount:numberactiveCount:number;color:string}exportinterfaceScene{name:string;icon:string;desc:stringactive:boolean;color:string}exportinterfaceEnergyRecord{time:string;usage:number}四个接口定义了智能家居的四个核心实体。Device 的on字段控制开关状态value存储数值参数亮度百分比、温度摄氏度等type区分设备类型light/thermostat/appliance/curtain。Room 用名称而不是 ID 作为标识因为房间数量有限且名称唯一。Scene 的active字段控制场景的运行状态。操作函数exportfunctiontoggleDevice(id:number):boolean{for(leti:number0;iDEVICES.length;i){if(DEVICES[i].idid){DEVICES[i].on!DEVICES[i].on;returnDEVICES[i].on}}returnfalse}exportfunctiongetActiveCount():number{letc:number0for(leti:number0;iDEVICES.length;i){if(DEVICES[i].on)c}returnc}exportfunctiongetDevicesByRoom(room:string):Device[]{constresult:Device[][]for(leti:number0;iDEVICES.length;i){if(DEVICES[i].roomroom)result.push(DEVICES[i])}returnresult}toggleDevice直接修改DEVICES数组里对应设备的on属性然后返回新的开关状态。getActiveCount遍历数组统计on true的设备数量。getDevicesByRoom按房间名称过滤设备返回该房间的所有设备列表。这些函数直接操作模块级的DEVICES常量——因为它是export const的引用类型数组任何导入它的页面都能读写同一份数据。这种共享可变状态让所有页面看到的设备状态保持一致但也意味着任何页面的修改都会影响其他页面。在原型阶段这是合理的后续做数据持久化时需要替换成统一的服务层管理。State 的双重刷新机制StatedevCount:numbergetActiveCount()Statescenes:Scene[][]aboutToAppear():void{consts:Scene[][]for(leti:number0;iSCENES.length;i){s.push(SCENES[i])}this.sceness}onPageShow():void{this.devCountgetActiveCount()}devCount在两个时机被更新——初始赋值时和每次onPageShow时。onPageShow保证了用户从 RoomPage 返回时如果在那里关闭了一个设备首页的在线设备数会自动更新。scenes在aboutToAppear里从SCENES常量复制到State。复制的目的是让组件拥有自己的数据副本——如果直接引用SCENES修改active属性会影响原始数据可能导致其他页面看到意外的状态变化。场景激活的排他逻辑privateactivateScene(idx:number):void{for(leti:number0;ithis.scenes.length;i){this.scenes[i].activefalse}this.scenes[idx].activetrueconstcopy:Scene[][]for(letj:number0;jthis.scenes.length;j){copy.push(this.scenes[j])}this.scenescopy}激活场景时先执行排他操作——把所有场景设为非激活再把选中的场景设为激活。这保证了同一时间只有一个场景在运行。最后的复制 替换引用是 ArkTSState的固定套路直接修改数组元素的属性不会触发 UI 刷新必须创建新数组替换整个引用。这个模式在需要动态修改数组的场景里反复出现。设备状态卡片Stack({alignContent:Alignment.Center}){Column(){}.width(100%).height(110).borderRadius(20).linearGradient({angle:135,colors:[[A,0],[#7C6FF0,0.5],[#A78BFA,1]]})Row(){Column(){Text(设备状态).fontSize(14).fontColor(rgba(255,255,255,0.7))Text(this.devCount/DEVICES.length 在线).fontSize(22).fontWeight(FontWeight.Bold).fontColor(Color.White).margin({top:4})}.alignItems(HorizontalAlign.Start)Text().fontSize(44)}.width(100%).padding(20)}三段紫色渐变背景左侧显示在线设备数devCount/总数 在线右侧用绿色圆点表示系统正常。devCount是State变量快捷控制切换设备后会触发这里重新渲染。渐变的三段颜色从深紫#6C5CE7过渡到中紫#7C6FF0再到浅紫#A78BFA135° 对角线方向。投影用紫色系的 14% 透明度A 25和卡片背景色协调。房间横向滚动Scroll(){Row({space:10}){ForEach(ROOMS,(room:Room){Column(){Text(room.icon).fontSize(30).margin({bottom:8})Text(room.name).fontSize(13).fontWeight(FontWeight.Bold).fontColor(T1)Text(room.activeCount/room.deviceCount 设备).fontSize(10).fontColor(T2)}.width(100).padding(14).backgroundColor(CW).borderRadius(16).border({width:1.5,color:room.color30}).onClick((){router.pushUrl({url:pages/RoomPage,params:{room:room.name}asRecordstring,Object})})})}}.scrollable(ScrollDirection.Horizontal).scrollBar(BarState.Off)五个房间卡片横向排列超出屏幕时可以滚动。每个卡片 100vp 宽包含房间图标、名称、在线设备数。边框颜色是每个房间的主题色加 18% 透明度room.color 30。客厅是红色边框#FF6B6B30卧室是紫色边框#8A5DFA30厨房是橙色边框#FF9F4330。这种彩色边框让用户不用看文字就能区分不同房间。点击跳转到 RoomPage传递房间名称作为参数。RoomPage 根据名称从getDevicesByRoom()获取该房间的设备列表。快捷控制网格Grid(){GridItem(){Row(){Text().fontSize(22).margin({right:10})Text(客厅主灯).fontSize(12).fontColor(T1).layoutWeight(1)Text(DEVICES[0].on?:⚪).fontSize(16)}.width(100%).padding(12).backgroundColor(CW).borderRadius(14).onClick((){toggleDevice(1);this.devCountgetActiveCount()})}// ... 其他三个设备}.columnsTemplate(1fr 1fr).columnsGap(10).rowsGap(10)2×2 网格展示四个常用设备。右侧的绿色/灰色圆点直接从DEVICES[x].on读取状态——因为toggleDevice修改的是模块级数组这里读到的永远是最新状态。点击时执行两步操作先toggleDevice切换设备开关再getActiveCount重新统计在线数赋值给State devCount。这保证了快捷控制的圆点颜色和顶部状态卡片的数字同步更新。四个设备是硬编码的DEVICES[0]、DEVICES[1]、DEVICES[2]、DEVICES[5]对应客厅主灯、客厅空调、电视、床头灯。这是首页快捷入口的设计——把最常用的设备固定在首页用户不需要进入房间页就能快速控制。能耗柱状图Row(){ForEach(ENERGY_DATA,(e:EnergyRecord){Column(){Column(){}.width(18).height(e.usage*30).borderRadius(9).linearGradient({angle:180,colors:[[A,0],[#A78BFA,1]]})Text(e.time).fontSize(9).fontColor(T2).margin({top:4})}.alignItems(HorizontalAlign.Center).layoutWeight(1)})}.height(80).alignItems(VerticalAlign.Bottom)7 条能耗记录生成 7 个柱子。柱子高度用e.usage * 30计算——usage范围是 0.2 到 2.8 kWh乘以 30 后变成 6vp 到 84vp。alignItems(VerticalAlign.Bottom)让所有柱子底部对齐形成标准的柱状图。紫色渐变从上到下angle: 180深紫在上、浅紫在下和顶部状态卡片的渐变方向一致。每个柱子 18vp 宽圆角 9vp半圆顶部视觉上比较圆润。智能场景卡片ForEach(this.scenes,(s:Scene,idx:number){Column(){Text(s.icon).fontSize(28).margin({bottom:6})Text(s.name).fontSize(11).fontWeight(FontWeight.Bold).fontColor(T1)Text(s.active?运行中:点击启动).fontSize(9).fontColor(s.active?s.color:T2).margin({top:2})}.width(90).padding(12).backgroundColor(s.active?s.color15:CW).borderRadius(14).border({width:s.active?1.5:1,color:s.active?s.color:#F0EEF4}).onClick((){this.activateScene(idx)})})五个场景卡片有两种视觉形态未激活白色背景 1px 灰色边框 灰色点击启动激活中主题色 8% 透明度背景s.color 15 1.5px 主题色边框 主题色运行中双重视觉反馈——背景色和边框色同时变化让用户一眼区分哪个场景在运行。每个场景有自己的主题色离家红色、回家绿色、睡眠紫色、影院黄色、起床橙色激活时边框和文字都变成对应颜色。底部导航栏BuilderNavBtn(label:string,icon:string,active:boolean){Column({space:2}){Text(icon).fontSize(20)Text(label).fontSize(9).fontColor(active?A:#C0BFC6)}.onClick((){if(label房间)router.pushUrl({url:pages/RoomPage,params:{room:客厅}asRecordstring,Object})elseif(label安防)router.pushUrl({url:pages/SecurityPage})elseif(label能耗)router.pushUrl({url:pages/EnergyPage})elseif(label设置)router.pushUrl({url:pages/SettingsPage})})}五个 Tab首页、房间、安防、能耗、设置。“房间” Tab 传了默认参数room: 客厅因为 RoomPage 需要房间名称来显示对应设备列表。“首页” Tab 没有跳转逻辑——用户已在首页点击无操作。页面数据流DeviceData (设备/房间/场景/能耗数据 操作函数) │ ├→ Index (首页仪表盘) │ ├→ toggleDevice() 切换设备状态 │ ├→ getActiveCount() 统计在线数 │ ├→ activateScene() 激活场景 │ └→ RoomPage (传房间名称查看房间设备) │ ├→ RoomPage │ └→ getDevicesByRoom() 按房间查询设备 │ ├→ SecurityPage (安防监控) ├→ EnergyPage (能耗详情) └→ SettingsPage (设置)首页是数据流的核心——它调用toggleDevice和activateScene修改数据调用getActiveCount读取汇总数据通过路由把用户引导到各个子页面。所有页面共享同一份DEVICES数组修改和读取操作不会产生数据不一致的问题。首页的信息架构五个区块的排列顺序不是随意的它遵循了从宏观到微观的信息架构设备状态卡片宏观一句话概括11/17 在线用户 1 秒内了解全局房间导航中观按空间维度组织设备用户可以选择进入某个房间快捷控制微观直接操作最常用的 4 个设备不需要进入房间能耗趋势时间维度展示一天内的用电变化补充时间维度的信息智能场景自动化一键触发多个设备的联动操作这种全局状态 → 空间导航 → 快捷操作 → 趋势数据 → 自动化控制的排列顺序让用户可以按照自己的需求层次逐层深入——想了解全局看顶部想控制具体设备看中间想自动化操作看底部。