安防中心是智能家居 App 里功能最独立的页面——它不依赖 DeviceData 服务层,所有数据都在页面内部的 @State 里管理。从截图看,页面分三个区域:顶部两个状态卡片(门锁和警报),中间摄像头列表,底部快捷操作按钮。实现上用了两个 Builder(StatusCard 和 ActionBtn),代码量不大但有几个值得注意的设计细节。
完整效果
在这里插入图片描述

数据结构

interface Cam { name: string; icon: string; online: boolean }

@State cams: Cam[] = [
  { name: '门口摄像头', icon: '📹', online: true },
  { name: '客厅摄像头', icon: '📷', online: true },
  { name: '车库摄像头', icon: '🎥', online: false },
]
@State alarm: boolean = true
@State doorLocked: boolean = true

在这里插入图片描述

三个 @State 变量管理所有安防数据:摄像头列表、警报状态、门锁状态。和首页的设备数据不同,安防数据完全独立——没有从 DeviceData 读取任何信息,也没有暴露给其他页面。这是合理的,因为安防系统的数据结构(摄像头在线状态、布防状态)和设备系统(开关、亮度、温度)差别很大,强行放在同一个数据层里反而增加复杂度。

门锁和警报的状态卡片

Row({ space: 12 }) {
  this.StatusCard('🔒', '门锁', this.doorLocked ? '已锁定' : '未锁定',
    this.doorLocked ? '#00B894' : '#FF6B6B')
  this.StatusCard('🚨', '警报', this.alarm ? '已布防' : '已撤防',
    this.alarm ? '#00B894' : '#FF6B6B')
}.width('100%').padding({ left: 16, right: 16 })

在这里插入图片描述

两个 StatusCard 并排显示,每个卡片有四层信息:图标、标签、状态文字、状态颜色。

StatusCard Builder

@Builder StatusCard(icon: string, label: string, status: string, color: string) {
  Column() {
    Text(icon).fontSize(28).margin({ bottom: 6 })
    Text(label).fontSize(12).fontColor(T2)
    Text(status).fontSize(14).fontWeight(FontWeight.Bold).fontColor(color).margin({ top: 4 })
  }.layoutWeight(1).padding(16).backgroundColor('#FFFFFF').borderRadius(14)
}

四个参数分别控制图标、标签文字、状态文字、状态颜色。layoutWeight(1) 让两个卡片平分水平空间。

状态文字的颜色根据状态动态变化——安全状态用绿色(#00B894),不安全状态用红色(#FF6B6B)。这种颜色编码在安防场景里是通用约定:绿色=安全,红色=警告。用户不需要读文字,只看颜色就能判断当前安防状态。

门锁和警报的切换逻辑

// 门锁
this.ActionBtn('🔒', '锁门', () => { this.doorLocked = !this.doorLocked })

// 警报
this.ActionBtn('🚨', this.alarm ? '撤防' : '布防', () => { this.alarm = !this.alarm })

门锁按钮点击时直接切换 doorLocked 的布尔值。警报按钮更巧妙——按钮文字也是动态的:this.alarm ? '撤防' : '布防'。当警报处于布防状态时,按钮显示"撤防"(告诉用户点击后会做什么);当处于撤防状态时,按钮显示"布防"。

这种"按钮文字反映当前操作"的设计比固定的"开关"文字更直观——用户不需要先判断当前状态再决定要不要点,按钮本身已经告诉了他"点了之后会发生什么"。

摄像头列表

ForEach(this.cams, (c: Cam) => {
  Row() {
    Text(c.icon).fontSize(32).width(56).height(56).borderRadius(14)
      .backgroundColor('#F5F5FA').textAlign(TextAlign.Center)
    Column() {
      Text(c.name).fontSize(15).fontWeight(FontWeight.Bold).fontColor(T1)
      Text(c.online ? '🟢 在线' : '🔴 离线').fontSize(11).fontColor(T3)
    }.alignItems(HorizontalAlign.Start).margin({ left: 12 }).layoutWeight(1)
    SymbolGlyph($r('sys.symbol.chevron_right')).fontSize(14).fontColor(['#DDD'])
  }.width('100%').padding(14).backgroundColor('#FFFFFF').borderRadius(14)
    .margin({ left: 16, right: 16 })
})

在这里插入图片描述

每个摄像头卡片分三部分:左侧 56×56 的 emoji 图标容器、中间名称和在线状态、右侧右箭头。

在线状态用绿色圆点 + “在线” 或红色圆点 + “离线” 表示——和首页设备状态卡片的绿色圆点用的是同一套视觉语言。右箭头暗示"点击可查看详情",但目前没有绑定 onClick 事件,是一个 UI 占位。

Cam 接口的轻量设计

interface Cam { name: string; icon: string; online: boolean }

三个字段足够了——名称用于显示,emoji 用于图标,在线状态用于判断。没有 id 字段,因为摄像头列表不需要跳转到详情页(右箭头只是占位)。没有 room 字段,因为摄像头的位置信息已经包含在名称里(“门口”、“客厅”、“车库”)。

接口设计的原则是"只存需要的字段"——多余的字段不仅浪费内存,还会让后续维护者困惑"这个字段到底在哪里用到了"。

快捷操作按钮

Row({ space: 12 }) {
  this.ActionBtn('🔒', '锁门', () => { this.doorLocked = !this.doorLocked })
  this.ActionBtn('🚨', this.alarm ? '撤防' : '布防', () => { this.alarm = !this.alarm })
  this.ActionBtn('📞', '报警', () => { })
}.width('100%').padding({ left: 16, right: 16 })

三个操作按钮横向排列,每个按钮接收图标、文字、回调三个参数。

ActionBtn Builder

@Builder ActionBtn(icon: string, label: string, action: () => void) {
  Column() {
    Text(icon).fontSize(28).margin({ bottom: 6 })
    Text(label).fontSize(12).fontColor(T1)
  }.layoutWeight(1).padding(14).backgroundColor('#FFFFFF').borderRadius(14)
  .onClick(action)
}

和 StatusCard 的结构一样——emoji + 文字纵向排列,白底圆角卡片。区别是 StatusCard 是纯展示(没有 onClick),ActionBtn 是可交互的(绑定了 onClick)。

"报警"按钮的空操作

this.ActionBtn('📞', '报警', () => { })

报警按钮的回调是空函数——点击后什么都不会发生。这是合理的:真正的报警功能需要接入电话系统或紧急联系人,在原型阶段无法实现。先放一个占位按钮,告诉用户"这里以后可以报警",后续再接入实际功能。

整个页面的状态管理特征

安防页面的状态管理是所有页面里最简单的——没有数组操作、没有引用变更、没有跨页面同步。每个状态都是简单的布尔值切换:

this.doorLocked = !this.doorLocked  // 门锁
this.alarm = !this.alarm            // 警报

不需要"复制数组 + 替换引用"的套路,因为 @State 追踪的是变量本身的引用,布尔值的切换会被直接检测到。

这是因为安防页面的数据模型比其他页面简单得多——设备页面有 17 个设备的数组需要管理,场景页面有 5 个场景的激活状态需要排他处理,而安防页面只有两个布尔值和一个摄像头列表。数据模型的复杂度直接决定了状态管理的复杂度。

两个 Builder 的对比

维度 StatusCard ActionBtn
参数数量 4(icon, label, status, color) 3(icon, label, action)
交互 无(纯展示) 有(onClick)
状态颜色 动态传入 固定黑色
使用次数 2 次 3 次

StatusCard 的参数比 ActionBtn 多一个 color,因为状态卡片需要根据安全状态改变文字颜色。ActionBtn 不需要颜色参数——按钮文字始终是黑色,视觉焦点在图标和交互上。

两个 Builder 的视觉结构几乎一样(emoji + 文字纵向排列 + 白底圆角卡片),但职责不同。如果要强行合并成一个通用 Builder,需要加一个 interactive 参数来区分是否绑定 onClick,代码会变得更复杂。在当前规模下,分开写两个 Builder 更清晰。

页面间的数据独立性

安防页面和其他页面之间没有数据交互:

  • 不从 DeviceData 读取设备信息
  • 不向 DeviceData 写入安防状态
  • ProfilePage 的菜单里没有"安防"入口
  • 首页的设备状态卡片不包含安防设备

这种独立性是刻意的——安防系统的数据结构和设备系统完全不同,强行关联会增加不必要的耦合。在真实的智能家居 App 里,安防系统可能有自己独立的后端服务和数据存储,前端只负责展示和控制。

当前版本的安防数据全部是前端 mock,没有持久化存储。刷新页面或退出 App 后,门锁和警报状态会重置为默认值(都是 true)。后续如果要做持久化,可以用 @ohos.data.preferencesdoorLockedalarm 存到本地。

Logo

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

更多推荐