智能家居 App 的首页是一个信息密集的控制中枢——一个页面里同时展示了设备状态总览、房间导航、快捷设备开关、能耗趋势图、智能场景控制五个区块。和其他类型的 App 不同,智能家居首页不只是数据的展示层,用户在首页就能直接操作——点击快捷控制切换设备开关,点击场景卡片激活不同的家居模式。这意味着首页的状态管理需要同时处理"读取数据"和"修改后刷新"两个方向。
完整效果
在这里插入图片描述
在这里插入图片描述

颜色体系

const A: string = '#6C5CE7'   // 主色·科技紫
const BG: string = '#F5F4FA'  // 背景灰
const CW: string = '#FFFFFF'  // 卡片白
const T1: string = '#1E1B2E'  // 标题黑
const T2: string = '#888888'  // 副文本

紫色 #6C5CE7 作为主色,在智能家居场景里代表科技感和未来感。背景色 #F5F4FA 比纯灰更偏冷调,和紫色搭配比暖灰色更协调。T1/T2 两级文本颜色负责文字层次,没有用到 T3(#BBBBBB),因为首页的辅助文字不多。

数据层:DeviceData 的完整架构

export interface Device {
  id: number; name: string; icon: string; type: string
  room: string; on: boolean; value: number; color: string
}

export interface Room {
  name: string; icon: string; deviceCount: number
  activeCount: number; color: string
}

export interface Scene {
  name: string; icon: string; desc: string
  active: boolean; color: string
}

export interface EnergyRecord { time: string; usage: number }

在这里插入图片描述

四个接口定义了智能家居的四个核心实体。Device 的 on 字段控制开关状态,value 存储数值参数(亮度百分比、温度摄氏度等),type 区分设备类型(light/thermostat/appliance/curtain)。Room 用名称而不是 ID 作为标识,因为房间数量有限且名称唯一。Scene 的 active 字段控制场景的运行状态。

操作函数

export function toggleDevice(id: number): boolean {
  for (let i: number = 0; i < DEVICES.length; i++) {
    if (DEVICES[i].id === id) { DEVICES[i].on = !DEVICES[i].on; return DEVICES[i].on }
  }
  return false
}

export function getActiveCount(): number {
  let c: number = 0
  for (let i: number = 0; i < DEVICES.length; i++) { if (DEVICES[i].on) c++ }
  return c
}

export function getDevicesByRoom(room: string): Device[] {
  const result: Device[] = []
  for (let i: number = 0; i < DEVICES.length; i++) {
    if (DEVICES[i].room === room) result.push(DEVICES[i])
  }
  return result
}

在这里插入图片描述

toggleDevice 直接修改 DEVICES 数组里对应设备的 on 属性,然后返回新的开关状态。getActiveCount 遍历数组统计 on === true 的设备数量。getDevicesByRoom 按房间名称过滤设备,返回该房间的所有设备列表。

这些函数直接操作模块级的 DEVICES 常量——因为它是 export const 的引用类型(数组),任何导入它的页面都能读写同一份数据。这种"共享可变状态"让所有页面看到的设备状态保持一致,但也意味着任何页面的修改都会影响其他页面。在原型阶段这是合理的,后续做数据持久化时需要替换成统一的服务层管理。

@State 的双重刷新机制

@State devCount: number = getActiveCount()
@State scenes: Scene[] = []

aboutToAppear(): void {
  const s: Scene[] = []
  for (let i: number = 0; i < SCENES.length; i++) { s.push(SCENES[i]) }
  this.scenes = s
}

onPageShow(): void { this.devCount = getActiveCount() }

devCount 在两个时机被更新——初始赋值时和每次 onPageShow 时。onPageShow 保证了用户从 RoomPage 返回时,如果在那里关闭了一个设备,首页的在线设备数会自动更新。

scenesaboutToAppear 里从 SCENES 常量复制到 @State。复制的目的是让组件拥有自己的数据副本——如果直接引用 SCENES,修改 active 属性会影响原始数据,可能导致其他页面看到意外的状态变化。

场景激活的排他逻辑

private activateScene(idx: number): void {
  for (let i: number = 0; i < this.scenes.length; i++) {
    this.scenes[i].active = false
  }
  this.scenes[idx].active = true
  const copy: Scene[] = []
  for (let j: number = 0; j < this.scenes.length; j++) { copy.push(this.scenes[j]) }
  this.scenes = copy
}

激活场景时先执行排他操作——把所有场景设为非激活,再把选中的场景设为激活。这保证了同一时间只有一个场景在运行。

最后的"复制 + 替换引用"是 ArkTS @State 的固定套路:直接修改数组元素的属性不会触发 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)再到浅紫(#A78BFA),135° 对角线方向。投影用紫色系的 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.color + '30' })
      .onClick(() => {
        router.pushUrl({ url: 'pages/RoomPage', params: { 'room': room.name } as Record<string,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.devCount = getActiveCount() })
  }
  // ... 其他三个设备
}
.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.color + '15' : 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 主题色边框 + 主题色"运行中"

双重视觉反馈——背景色和边框色同时变化,让用户一眼区分哪个场景在运行。每个场景有自己的主题色(离家红色、回家绿色、睡眠紫色、影院黄色、起床橙色),激活时边框和文字都变成对应颜色。

底部导航栏

@Builder NavBtn(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': '客厅' } as Record<string,Object> })
    else if (label === '安防') router.pushUrl({ url: 'pages/SecurityPage' })
    else if (label === '能耗') router.pushUrl({ url: 'pages/EnergyPage' })
    else if (label === '设置') router.pushUrl({ url: 'pages/SettingsPage' })
  })
}

五个 Tab:首页、房间、安防、能耗、设置。“房间” Tab 传了默认参数 'room': '客厅',因为 RoomPage 需要房间名称来显示对应设备列表。“首页” Tab 没有跳转逻辑——用户已在首页,点击无操作。

页面数据流

DeviceData (设备/房间/场景/能耗数据 + 操作函数)
  │
  ├→ Index (首页仪表盘)
  │    ├→ toggleDevice()  切换设备状态
  │    ├→ getActiveCount() 统计在线数
  │    ├→ activateScene()  激活场景
  │    └→ RoomPage (传房间名称,查看房间设备)
  │
  ├→ RoomPage
  │    └→ getDevicesByRoom() 按房间查询设备
  │
  ├→ SecurityPage (安防监控)
  ├→ EnergyPage (能耗详情)
  └→ SettingsPage (设置)

首页是数据流的核心——它调用 toggleDeviceactivateScene 修改数据,调用 getActiveCount 读取汇总数据,通过路由把用户引导到各个子页面。所有页面共享同一份 DEVICES 数组,修改和读取操作不会产生数据不一致的问题。

首页的信息架构

五个区块的排列顺序不是随意的,它遵循了"从宏观到微观"的信息架构:

  1. 设备状态卡片(宏观):一句话概括"11/17 在线",用户 1 秒内了解全局
  2. 房间导航(中观):按空间维度组织设备,用户可以选择进入某个房间
  3. 快捷控制(微观):直接操作最常用的 4 个设备,不需要进入房间
  4. 能耗趋势(时间维度):展示一天内的用电变化,补充时间维度的信息
  5. 智能场景(自动化):一键触发多个设备的联动操作

这种"全局状态 → 空间导航 → 快捷操作 → 趋势数据 → 自动化控制"的排列顺序,让用户可以按照自己的需求层次逐层深入——想了解全局看顶部,想控制具体设备看中间,想自动化操作看底部。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐