技术前言

在这里插入图片描述

HarmonyOS ArkUI声明式UI框架作为华为鸿蒙生态的核心开发技术,正在深刻改变移动端应用的开发方式。与传统的命令式UI编程不同,ArkUI采用了一种状态驱动视图的声明式范式——开发者通过描述界面的目标状态而非操作步骤来构建UI。框架内部的状态追踪系统会自动监听状态变量的变化,并精确地更新与之关联的UI组件,实现了"数据变即视图变"的响应式编程模型。在智能家居平台这种设备数量多、状态变化频繁、场景交互复杂的物联网应用中,ArkUI的声明式架构展现出了显著的技术优势。

在这里插入图片描述
智能家居是一个典型的多设备、多场景、多状态的物联网应用领域。一个现代家庭中可能拥有智能灯、空调、扫地机、智能门锁、窗帘、净化器、音箱、温湿度计等多种设备,这些设备分布在客厅、主卧、厨房、书房、卫生间、阳台等不同房间。用户需要在一个统一的界面中管理所有设备的状态、查看能耗数据、设置自动化场景、监控安防事件。这种"多维度信息聚合"的需求对UI框架的组件化能力和状态管理能力提出了极高要求——每一个设备、每一个场景、每一条安防记录都是独立的数据实体,它们的状态变化需要实时反映在界面上。

在这里插入图片描述
ArkUI的@Entry/@Component装饰器体系提供了组件化的基础能力。@Entry标识页面入口组件,@Component标识自定义组件。在智享家智能家居平台中,Page803作为@Entry入口组件承载了整个应用的核心逻辑。@Observed装饰器为数据模型类提供了可观察能力——DeviceItem、SceneItem、EnergyItem、SecurityItem、RoomItem五个数据模型类都被@Observed装饰,当它们的实例作为@State变量的值时,属性变化能够被框架感知并触发UI更新。例如,当用户将设备从"在线"切换为"离线"时,设备状态文字的颜色会通过deviceStatusColor辅助函数即时更新——绿色变为红色,无需手动调用任何刷新方法。

在这里插入图片描述
@State状态变量是ArkUI响应式系统的核心。在智享家平台中,currentTab控制六个Tab页面的切换,breath驱动呼吸动画效果,deviceList/sceneList/energyList/securityList/roomList五个状态变量分别承载设备列表、场景列表、能耗记录、安防事件和房间信息。这些状态变量建立了一个细粒度的"状态-视图"绑定网络——当任何一个列表中的数据发生变化时,只有引用了该数据的UI组件会被重新渲染,其他组件保持不变。这种精确的更新机制在设备数量众多的智能家居场景中尤为重要,避免了全局重绘的性能浪费。

在这里插入图片描述
@Builder装饰器提供了UI片段的封装和复用能力。智享家平台的六个Tab页面被封装为六个@Builder方法:tabHome(首页概览)、tabDevice(设备网格)、tabScene(场景横滑)、tabEnergy(能耗分析)、tabSecurity(安防时间轴)、tabMine(房屋统计)。每个@Builder方法内部通过Column、Row、Stack、Flex等容器组件的组合,构建出布局结构完全不同的页面。tabHome使用了数据大卡+快捷宫格的组合布局,tabDevice使用了Flex换行的四列网格布局,tabScene使用了横向滑动的Stack叠层大卡,tabEnergy使用了Canvas折线图+统计列表的组合布局,tabSecurity使用了竖向时间轴的圆点竖线布局,tabMine使用了渐变大卡+房间清单的列表布局。这种"每个Tab布局完全不同"的设计让用户在不同功能模块间切换时获得丰富的视觉体验。

在这里插入图片描述
生命周期管理在物联网应用中尤为关键。aboutToAppear中创建的定时器驱动了呼吸动画——breath状态变量每秒在true和false之间切换,所有引用了breath的UI组件都会产生闪烁效果。这种定时器驱动的动画在智能家居场景中有特殊含义——安防布防图标的闪烁提醒用户安防系统正在运行,添加设备入口的闪烁引导用户进行设备配网。aboutToDisappear中清理定时器防止了组件销毁后的内存泄漏。

在这里插入图片描述
Canvas绘图在智享家平台中服务于能耗数据可视化。drawLine方法绘制了24小时用电趋势折线图,通过渐变填充区域、折线、数据点三个层次的绘制,将抽象的电量数据转化为直观的视觉曲线。Canvas绑定lineCtx绘图上下文,通过onReady回调在组件初始化后触发首次绘制。Canvas 2D API的beginPath、moveTo、lineTo、arc、stroke、fill等指令构成了完整的绘图工具集,让开发者能够精确控制每一个像素的渲染效果。

颜色系统在智能家居的深色主题中扮演着双重角色——既要营造科技感,又要实现设备状态的快速辨识。科技蓝(#3B82F6)和暖光橙(#F97316)构成了平台的双主色调——蓝色代表科技与冷色调设备(如空调、温湿度),橙色代表温暖与热能设备(如照明、加热)。深色背景(#061824)和卡片色(#0E2A3A)营造了科技氛围,功能色系包括绿色(在线/正常)、红色(离线/警报)、黄色(休眠/警告)、紫色(特殊状态)等。每种颜色都有明确的语义映射,用户无需阅读文字就能通过颜色判断设备状态。

数据模型层的@Observed类设计体现了物联网设备管理的领域模型。DeviceItem封装了设备名称、房间、状态、图标、在线布尔值;SceneItem封装了场景名称、描述、图标、设备数量;EnergyItem封装了日期、用电量、费用;SecurityItem封装了时间、事件、类型、状态;RoomItem封装了房间名称、设备数量、图标。五个类各自对应智能家居的一个核心实体,构造函数和预置数据数组既用于演示也体现了数据规范。

辅助函数层提供了设备状态和安防事件的视觉映射。deviceStatusColor函数将设备状态映射为颜色——在线绿色、离线红色、休眠黄色。securityColor函数将安防事件类型映射为颜色——警报红色、解锁绿色、监控蓝色。这些函数将抽象的状态字符串转化为直观的颜色信号,是业务逻辑与视觉呈现之间的桥梁。

弹窗系统通过Stack叠层和modalOverlay遮罩实现。panelAdd提供了设备类型选择和配网方式选择,panelDel提供了设备删除的二次确认。两个弹窗都接受onClose回调函数,通过点击遮罩或按钮触发关闭。弹窗内的布局结构根据功能需求进行了差异化设计——panelAdd使用四列设备类型宫格+三选配网方式按钮,panelDel使用设备信息展示+双按钮确认。

底部Tab栏采用单排6个Tab的布局。每个Tab由emoji图标和文字标签组成,选中态通过图标放大、文字变色加粗实现视觉反馈。六个Tab分别对应首页、设备、场景、能耗、安防、我的六个功能模块。tabOn色设为橙色(COLORS.orange),与科技蓝形成冷暖对比,让选中Tab在视觉上更加突出。

在整体架构上,智享家平台采用了"头部固定+中间滚动+底部导航"的三段式布局。头部展示了位置信息、环境指标大数字(温度/湿度/PM2.5)、搜索栏、快捷入口和安防状态条。中间的Scroll区域根据currentTab条件渲染对应Tab页面,并统一附加月度能耗柱状图。底部Tab栏提供全局导航。这种布局在信息密度和操作效率之间取得了良好平衡。

Canvas绘图与呼吸动画的联动是本平台的一个技术细节。breath状态变量不仅驱动UI组件的opacity变化,在drawLine方法中也可能参与视觉参数的计算。定时器每秒切换breath,所有引用breath的组件产生呼吸效果——安防图标的闪烁、添加设备入口的脉动、场景大卡图标的明暗交替,这些微动画让界面产生"活"的感觉,在物联网场景中暗示着设备持续在线运行的状态。

颜色系统深度解析

颜色系统是智能家居应用的视觉基石。在智享家平台中,颜色不仅承担美学功能,更承担着设备状态标识、安防事件分类、能耗等级提示等重要的信息传递职责。

ColorPalette接口定义

// 颜色调色板接口:定义智能家居平台所有颜色字段
interface ColorPalette {
  bg: string;        // 全局背景色
  card: string;      // 卡片背景色
  chip: string;     // 标签/芯片背景色
  title: string;     // 主标题文字色
  sub: string;       // 副标题文字色
  text3: string;    // 三级辅助文字色
  blue: string;      // 科技蓝主色
  blueD: string;    // 深蓝色调
  blueL: string;    // 浅蓝色背景
  orange: string;   // 暖光橙主色
  orangeD: string;  // 深橙色调
  orangeL: string;  // 浅橙色背景
  green: string;    // 在线/正常绿色
  greenD: string;   // 深绿色调
  greenL: string;  // 浅绿色背景
  red: string;      // 离线/警报红色
  redL: string;    // 浅红色背景
  yellow: string;   // 休眠/警告黄色
  yellowL: string; // 浅黄色背景
  purple: string;  // 紫色辅助
  purpleL: string; // 浅紫色背景
  cyan: string;     // 青色辅助
  cyanL: string;    // 浅青色背景
  line: string;     // 分割线颜色
  tabOn: string;    // Tab选中色
  mask: string;     // 遮罩层颜色
}

ColorPalette接口定义了26个颜色字段,比健康监测平台多了两个辅助色(cyan和yellow系列)。这反映了智能家居场景对颜色多样性的更高需求——设备类型更多、状态更复杂、场景更丰富,需要更多的颜色来区分不同的语义。接口通过类型约束确保所有字段都被定义,避免了运行时颜色缺失错误。

接口设计遵循语义化命名原则。bg/card/chip三级背景色形成从深到浅的层次结构。title/sub/text3三级文字色形成从亮到暗的信息权重梯度。blue/orange双主色调形成冷暖对比——蓝色代表科技与冷设备(空调、温湿度计),橙色代表温暖与热设备(照明、加热器)。green/red/yellow三色构成设备状态的标准色系。purple/cyan作为辅助色用于特殊场景标识。

COLORS常量实例化

// 颜色常量实例化:科技蓝+暖光橙双主色深色主题
const COLORS: ColorPalette = {
  bg: '#061824',        // 深海蓝黑:全局背景,营造科技氛围
  card: '#0E2A3A',      // 深青灰:卡片背景,浮出全局背景
  chip: '#15384E',      // 中青灰:标签/进度条背景
  title: '#E8F6FF',     // 亮蓝白:主标题,高对比度
  sub: '#7AB0C8',       // 中青蓝:副标题,辅助信息
  text3: '#4A7896',     // 暗青灰:三级文字,弱视觉权重
  blue: '#3B82F6',     // 科技蓝:冷设备/监控事件标识
  blueD: '#1E3A5F',    // 深蓝:渐变起点
  blueL: '#0A1A3A',    // 深蓝底:蓝色相关背景
  orange: '#F97316',  // 暖光橙:热设备/Tab选中/能耗
  orangeD: '#EA580C',  // 深橙:渐变起点
  orangeL: '#2A1A10',  // 深橙底:橙色相关背景
  green: '#4ADE80',   // 在线绿:设备在线/正常状态
  greenD: '#22C55E',  // 深绿:渐变起点
  greenL: '#0A3D1F',  // 深绿底:布防状态背景
  red: '#EF4444',     // 离线红:设备离线/警报事件
  redL: '#2A1010',    // 深红底:警报状态背景
  yellow: '#FBBF24',  // 休眠黄:设备休眠/警告状态
  yellowL: '#2A2410', // 深黄底:黄色相关背景
  purple: '#A78BFA',  // 紫色:辅助标识
  purpleL: '#1A1530', // 深紫底:紫色相关背景
  cyan: '#22D3EE',    // 青色:辅助标识
  cyanL: '#0A2A3A',   // 深青底:青色相关背景
  line: '#15384E',     // 分割线:与chip同色
  tabOn: '#F97316',   // Tab选中:暖光橙
  mask: 'rgba(0,0,0,0.55)'  // 弹窗遮罩:55%透明黑
};

COLORS常量将接口字段映射为具体颜色值。背景色#061824是一个极深的蓝黑色,营造了科技感十足的暗色环境。卡片色#0E2A3A比背景亮一个层级,通过明度差异将卡片"浮"出背景。chip色#15384E用于标签、进度条槽等次要容器。

科技蓝#3B82F6和暖光橙#F97316构成了双主色调。蓝色#3B82F6是一种饱和度适中的蓝色,既有科技感又不会过于冰冷——用于在线设备的图标背景边框、监控类型安防事件的标识、房屋渐变大卡的起点色。橙色#F97316是一种温暖的橙红色,既有家的温馨感又有能源的暗示——用于Tab选中态、能耗数据、温度数值、添加设备入口的强调色。tabOn设为orange而非blue,是因为在深色背景上橙色比蓝色更具视觉冲击力,Tab选中态需要更醒目的反馈。

设备状态三色——绿色#4ADE80(在线/正常)、红色#EF4444(离线/警报)、黄色#FBBF24(休眠/警告)——构成了物联网设备状态的标准色系。绿色代表设备运行正常且可控,红色代表设备断联或安全威胁,黄色代表设备处于低功耗或需要注意的状态。这三种颜色在设备状态指示灯、安防事件标签、布防状态卡中被反复使用,形成了统一的视觉语言。

每个功能色都配有对应的浅色背景版本。例如greenL(#0A3D1F)用于布防状态卡的背景——绿色文字+深绿背景的组合让"布防中"状态既醒目又不刺眼。redL(#2A1010)用于警报事件标签的背景——红色文字+深红背景标识危险事件。这种"浅色背景+功能色文字"的组合在深色主题中避免了功能色大面积使用带来的视觉疲劳。

常量定义与数据架构

常量定义层为UI渲染提供了静态数据骨架,包括Tab导航、快捷入口、Canvas图表数据和月度统计数据。

Tab导航与快捷入口

// Tab元数据接口
interface TabMeta {
  icon: string;   // Tab图标
  label: string;  // Tab文字标签
}

// 6个Tab导航项
const TAB_LIST: TabMeta[] = [
  { icon: '🏠', label: '首页' },
  { icon: '🔌', label: '设备' },
  { icon: '🎬', label: '场景' },
  { icon: '📊', label: '能耗' },
  { icon: '🔒', label: '安防' },
  { icon: '👤', label: '我的' }
];

// 快捷入口标签
const QUICK_TAGS: string[] = ['客厅', '主卧', '厨房', '书房', '安防', '能耗'];

TabMeta接口定义了Tab项结构。六个Tab对应智能家居的六个核心功能:首页提供全屋概览,设备管理所有智能硬件,场景展示自动化模式,能耗分析用电趋势,安防监控安全事件,我的管理房屋信息。QUICK_TAGS定义了六个快捷入口——前四个是房间快捷导航,后两个是安防和能耗的直达入口。

Canvas图表数据

// 24小时能耗数据(度)
const ENERGY_HR_DATA: number[] = [0.8, 0.6, 0.5, 0.4, 0.3, 0.4, 0.8, 1.5, 2.2, 2.8, 3.2, 3.5, 3.8, 3.2, 2.8, 2.5, 2.8, 3.5, 4.2, 4.8, 4.5, 3.8, 2.5, 1.2];
const ENERGY_HR_MAX: number = 5;
const ENERGY_HR_LABELS: string[] = ['00', '06', '12', '18', '24'];

// 月度能耗数据
const MONTH_IDX: number[] = [0, 1, 2, 3, 4, 5];
const MONTH_NAME: string[] = ['03', '04', '05', '06', '07', '08'];
const ENERGY_MONTH_VAL: number[] = [280, 320, 295, 340, 310, 365];
const ENERGY_MONTH_MAX: number = 400;

ENERGY_HR_DATA数组包含24个小时的用电量数据(单位:度)。数据展示了典型的家庭用电模式——凌晨0-6点用电最低(0.3-0.8度),符合睡眠时段;早晨7-9点用电上升(0.8-2.2度)对应起床洗漱;上午9-12点持续上升(2.2-3.8度)对应居家活动;下午12-17点有所回落(3.8-2.5度);傍晚18-21点达到峰值(3.5-4.8度)对应晚餐和照明高峰;夜间22-24点逐渐下降(2.5-1.2度)。ENERGY_HR_MAX设为5度作为Y轴满刻度。

月度能耗数据ENERGY_MONTH_VAL记录了近6个月的用电量。第6个月(index 5)的365度是最高值,在柱状图中用橙色高亮显示。ENERGY_MONTH_MAX设为400度作为柱状图满刻度。

辅助函数解析

智享家平台定义了两个辅助函数,分别处理设备状态和安防事件的颜色映射。

设备状态颜色映射

/**
 * 设备状态颜色映射
 * 根据设备状态返回对应的语义颜色
 * @param status - 设备状态字符串
 * @returns 对应状态的颜色值
 */
function deviceStatusColor(status: string): string {
  if (status === '在线') {
    // 在线状态:使用绿色标识,表示设备正常连接
    return COLORS.green;
  }
  if (status === '离线') {
    // 离线状态:使用红色标识,表示设备断联
    return COLORS.red;
  }
  if (status === '休眠') {
    // 休眠状态:使用黄色标识,表示设备低功耗待机
    return COLORS.yellow;
  }
  // 默认:使用三级文字色
  return COLORS.text3;
}

deviceStatusColor函数将三种设备状态映射为三种语义颜色。在线绿色表示设备正常工作且可通过App控制;离线红色表示设备与网关断开连接,无法远程操作;休眠黄色表示设备处于低功耗待机状态,可能需要唤醒才能操作。这三种状态覆盖了物联网设备最常见的运行模式。函数在设备网格的状态指示灯、设备列表的状态文字、删除弹窗的状态标签中被反复调用。

安防事件颜色映射

/**
 * 安防事件类型颜色映射
 * 根据事件类型返回对应的语义颜色
 * @param type - 事件类型字符串
 * @returns 对应类型的颜色值
 */
function securityColor(type: string): string {
  if (type === '警报') {
    // 警报类型:使用红色标识,表示安全威胁事件
    return COLORS.red;
  }
  if (type === '解锁') {
    // 解锁类型:使用绿色标识,表示正常操作事件
    return COLORS.green;
  }
  if (type === '监控') {
    // 监控类型:使用蓝色标识,表示系统自动记录
    return COLORS.blue;
  }
  // 默认:使用三级文字色
  return COLORS.text3;
}

securityColor函数将三种安防事件类型映射为颜色。警报红色表示发生了安全威胁事件(如陌生人逗留、烟雾报警),需要用户关注或处理;解锁绿色表示正常的门锁操作(指纹解锁、密码解锁);监控蓝色表示系统自动记录的布防/撤防事件。这三种颜色在安防时间轴的圆点、事件类型标签中被使用,让用户能够通过颜色快速区分事件的紧急程度。

设备状态

在线

离线

休眠

其他

安防事件

警报

解锁

监控

其他

输入数据

数据类型

status 判断

绿色 - 正常连接

红色 - 设备断联

黄色 - 低功耗待机

灰色 - 未知状态

type 判断

红色 - 安全威胁

绿色 - 正常操作

蓝色 - 系统记录

灰色 - 未知类型

上图展示了两个辅助函数的决策路径。设备状态和安防事件虽然属于不同的业务领域,但都遵循"红黄绿"三色体系——红色代表危险/异常、绿色代表正常/安全、黄色代表注意/中间态。蓝色作为安防特有的颜色,用于区分系统自动事件与用户操作事件。这种统一的颜色语义让用户在任何场景下都能通过颜色快速理解信息含义。

数据模型层

智享家平台定义了五个@Observed数据模型类,分别对应设备、场景、能耗、安防和房间五个核心实体。

DeviceItem - 设备项

/**
 * 智能设备数据模型
 * 记录设备的基本信息和在线状态
 */
@Observed
export class DeviceItem {
  name: string;    // 设备名称
  room: string;    // 所在房间
  status: string;  // 设备状态
  icon: string;    // 设备图标
  online: boolean;  // 是否在线

  /**
   * 构造函数
   * @param name - 设备名称
   * @param room - 所在房间
   * @param status - 设备状态
   * @param icon - 设备图标Emoji
   * @param online - 是否在线
   */
  constructor(name: string, room: string, status: string, icon: string, online: boolean) {
    this.name = name;
    this.room = room;
    this.status = status;
    this.icon = icon;
    this.online = online;
  }
}

// 预置设备数据:8台设备分布于6个房间
const DEVICE_LIST: DeviceItem[] = [
  new DeviceItem('智能灯', '客厅', '在线', '💡', true),
  new DeviceItem('空调', '主卧', '在线', '❄️', true),
  new DeviceItem('扫地机', '客厅', '离线', '🤖', false),
  new DeviceItem('智能门锁', '入户', '在线', '🔐', true),
  new DeviceItem('智能窗帘', '客厅', '在线', '🪟', true),
  new DeviceItem('净化器', '书房', '在线', '🌬️', true),
  new DeviceItem('智能音箱', '客厅', '在线', '🔊', true),
  new DeviceItem('温湿度计', '阳台', '离线', '🌡️', false)
];

DeviceItem类封装了智能设备的五个核心属性。name和room属性定义了设备的基本身份信息——“智能灯"在"客厅”。status和online是两个关联属性——status是字符串类型的语义描述(“在线”/“离线”/“休眠”),online是布尔值用于UI的快速判断(如设备图标背景色的选择)。icon使用Emoji字符,无需维护图标资源文件。

预置数据包含了8台设备,覆盖了照明(智能灯)、空调(空调)、清洁(扫地机)、安防(智能门锁)、遮阳(智能窗帘)、空气净化(净化器)、音频(智能音箱)、环境监测(温湿度计)等常见智能家居品类。6台在线、2台离线——扫地机和温湿度计离线,这为设备在线率统计(6/8=75%)和安防状态条(“2台设备离线”)提供了数据基础。

SceneItem - 场景项

/**
 * 智能场景数据模型
 * 记录自动化场景的名称、描述和关联设备数
 */
@Observed
export class SceneItem {
  name: string;   // 场景名称
  desc: string;    // 场景描述
  icon: string;    // 场景图标
  count: number;   // 关联设备数量

  /**
   * 构造函数
   * @param name - 场景名称
   * @param desc - 场景描述
   * @param icon - 场景图标
   * @param count - 关联设备数量
   */
  constructor(name: string, desc: string, icon: string, count: number) {
    this.name = name;
    this.desc = desc;
    this.icon = icon;
    this.count = count;
  }
}

// 预置场景数据:6个智能场景
const SCENE_LIST: SceneItem[] = [
  new SceneItem('回家模式', '灯光+空调+窗帘自动开启', '🏡', 5),
  new SceneItem('离家模式', '全屋关闭+安防布防', '🚪', 12),
  new SceneItem('观影模式', '灯光变暗+窗帘关闭+电视开启', '🎬', 6),
  new SceneItem('睡眠模式', '卧室灯关+空调睡眠+安防布防', '😴', 4),
  new SceneItem('起床模式', '窗帘渐开+音乐播放+咖啡机启动', '☀️', 3),
  new SceneItem('晚餐模式', '餐厅灯亮+背景音乐+排气扇开', '🍽️', 3)
];

SceneItem类封装了智能场景的四个属性。预置的六个场景覆盖了一天中的典型生活时段——回家(傍晚)、离家(白天出门)、观影(晚间娱乐)、睡眠(夜间)、起床(早晨)、晚餐(傍晚用餐)。每个场景关联的设备数量从3到12不等,离家模式最多(12台设备全关闭+布防),反映了离家场景的安全重要性。

EnergyItem - 能耗项

/**
 * 能耗记录数据模型
 * 记录每日的用电量和费用
 */
@Observed
export class EnergyItem {
  date: string;   // 日期
  usage: string;  // 用电量(度)
  cost: string;   // 费用(元)

  /**
   * 构造函数
   * @param date - 日期
   * @param usage - 用电量
   * @param cost - 费用
   */
  constructor(date: string, usage: string, cost: string) {
    this.date = date;
    this.usage = usage;
    this.cost = cost;
  }
}

// 预置能耗数据:近6天用电记录
const ENERGY_LIST: EnergyItem[] = [
  new EnergyItem('08-24', '12.5', '6.25'),
  new EnergyItem('08-23', '15.2', '7.60'),
  new EnergyItem('08-22', '10.8', '5.40'),
  new EnergyItem('08-21', '14.3', '7.15'),
  new EnergyItem('08-20', '11.6', '5.80'),
  new EnergyItem('08-19', '13.1', '6.55')
];

EnergyItem类封装了能耗记录的三个属性。usage和cost的关系基于0.5元/度的电价计算——12.5度x0.5=6.25元。预置数据中08-23用电最高(15.2度),08-22最低(10.8度),日均约12.9度,符合三口之家的正常用电水平。

SecurityItem - 安防事件项

/**
 * 安防事件数据模型
 * 记录安防事件的时间、内容、类型和状态
 */
@Observed
export class SecurityItem {
  time: string;    // 事件时间
  event: string;   // 事件内容
  type: string;    // 事件类型
  status: string;  // 处理状态

  /**
   * 构造函数
   * @param time - 事件时间
   * @param event - 事件内容描述
   * @param type - 事件类型(警报/解锁/监控)
   * @param status - 处理状态
   */
  constructor(time: string, event: string, type: string, status: string) {
    this.time = time;
    this.event = event;
    this.type = type;
    this.status = status;
  }
}

// 预置安防数据:6条安防事件记录
const SECURITY_LIST: SecurityItem[] = [
  new SecurityItem('14:32', '门锁指纹解锁', '解锁', '正常'),
  new SecurityItem('12:15', '门前陌生人逗留', '警报', '处理中'),
  new SecurityItem('09:00', '厨房烟雾报警', '警报', '已解除'),
  new SecurityItem('昨日 22:10', '阳台门窗异常', '警报', '已解除'),
  new SecurityItem('昨日 18:45', '门锁密码解锁', '解锁', '正常'),
  new SecurityItem('昨日 06:30', '布防模式已启动', '监控', '正常')
];

SecurityItem类封装了安防事件的四个属性。预置的六条记录展示了三种事件类型——警报(陌生人逗留、烟雾报警、门窗异常)、解锁(指纹解锁、密码解锁)、监控(布防启动)。其中12:15的"门前陌生人逗留"标记为"处理中",在时间轴中会用橙色标识,提醒用户关注未处理的事件。

RoomItem - 房间项

/**
 * 房间数据模型
 * 记录房间名称、设备数量和图标
 */
@Observed
export class RoomItem {
  name: string;       // 房间名称
  deviceCount: number; // 设备数量
  icon: string;       // 房间图标

  /**
   * 构造函数
   * @param name - 房间名称
   * @param deviceCount - 设备数量
   * @param icon - 房间图标
   */
  constructor(name: string, deviceCount: number, icon: string) {
    this.name = name;
    this.deviceCount = deviceCount;
    this.icon = icon;
  }
}

// 预置房间数据:6个房间
const ROOM_LIST: RoomItem[] = [
  new RoomItem('客厅', 6, '🛋️'),
  new RoomItem('主卧', 4, '🛏️'),
  new RoomItem('厨房', 3, '🍳'),
  new RoomItem('书房', 2, '📚'),
  new RoomItem('卫生间', 2, '🚿'),
  new RoomItem('阳台', 3, '🌿')
];

RoomItem类封装了房间的三个属性。六个房间的设备数量从2到6不等——客厅最多(6台,包括智能灯、扫地机、窗帘、音箱等),书房和卫生间最少(各2台)。总设备数20台(6+4+3+2+2+3)与DEVICE_LIST中的8台不完全对应,因为ROOM_LIST代表的是"我的"页面中的房屋全貌数据,可能包含未在当前列表中展示的设备。

组件主体

组件主体承载了应用的核心运行时逻辑,包括状态管理、生命周期和抗锯齿特性。

装饰器与状态变量

/**
 * 智享家智能家居平台主页面
 * 6个Tab:首页/设备/场景/能耗/安防/我的
 * Canvas图表:24小时能耗折线图
 */
@Entry
@Component
struct Page803 {
  // 当前选中的Tab索引
  @State currentTab: number = 0;
  // 弹窗状态控制
  @State addModal: boolean = false;
  @State delModal: boolean = false;
  @State delIdx: number = -1;
  // 呼吸动画状态
  @State breath: boolean = false;
  // Canvas抗锯齿开关
  @State antialiasOn: boolean = true;
  // 定时器ID
  timer: number = -1;

  // Canvas绘图上下文:能耗折线图
  private lineCtx: CanvasRenderingContext2D = new CanvasRenderingContext2D(new RenderingContextSettings(true));

  // 数据列表状态变量
  @State deviceList: DeviceItem[] = DEVICE_LIST;
  @State sceneList: SceneItem[] = SCENE_LIST;
  @State energyList: EnergyItem[] = ENERGY_LIST;
  @State securityList: SecurityItem[] = SECURITY_LIST;
  @State roomList: RoomItem[] = ROOM_LIST;

@Entry和@Component装饰器将Page803标记为页面入口自定义组件。@State状态变量分为三类:交互控制类(currentTab/addModal/delModal/delIdx)、动画效果类(breath)、技术特性类(antialiasOn)。

与健康监测平台不同,智享家只有一个Canvas绘图上下文lineCtx——因为只有能耗Tab使用了Canvas折线图。lineCtx使用private修饰符而非@State,因为绘图上下文不需要触发UI刷新,Canvas刷新通过手动调用drawLine方法触发。RenderingContextSettings(true)在创建时开启了抗锯齿。

五个数据列表状态变量deviceList/sceneList/energyList/securityList/roomList分别引用预置数据数组。这些变量被@State装饰后,当数组内容变化时(如添加/删除设备),引用了这些变量的ForEach循环会自动更新渲染。

生命周期函数

  /**
   * 组件即将出现时调用
   * 创建呼吸动画定时器
   */
  aboutToAppear() {
    this.timer = setInterval(() => {
      // 切换呼吸状态
      this.breath = !this.breath;
    }, 1000);
  }

  /**
   * 组件即将消失时调用
   * 清理定时器防止内存泄漏
   */
  aboutToDisappear() {
    clearInterval(this.timer);
  }

aboutToAppear创建了每秒触发的定时器,切换breath状态变量。与健康监测平台不同,智享家的定时器回调中没有Canvas重绘逻辑——因为唯一的Canvas图表(能耗折线图)不需要呼吸动画联动。breath状态在UI中驱动了多处opacity变化:安防布防图标的闪烁、添加设备入口的脉动、场景大卡图标的明暗交替、房屋统计图标的呼吸效果。

aboutToDisappear通过clearInterval清理定时器,这是ArkUI资源管理的标准实践——未清理的定时器会在组件销毁后继续运行,造成内存泄漏和潜在的空指针异常。

build方法与整体布局

build方法声明了组件的UI结构,采用Stack叠层+Column三段式的经典布局。

整体布局架构

  /**
   * 构建主页面
   * Stack叠层:内容层 + 弹窗层
   * Column三段:头部 + 滚动区 + Tab栏
   */
  build() {
    Stack() {
      Column() {
        // 智能家居头部
        this.headerHome()
        // 分割线
        Divider().strokeWidth(1).color(COLORS.line)
        // 中间可滚动内容区
        Scroll() {
          Column() {
            // 根据currentTab条件渲染对应Tab页面
            if (this.currentTab === 0) {
              this.tabHome()
            } else if (this.currentTab === 1) {
              this.tabDevice()
            } else if (this.currentTab === 2) {
              this.tabScene()
            } else if (this.currentTab === 3) {
              this.tabEnergy()
            } else if (this.currentTab === 4) {
              this.tabSecurity()
            } else if (this.currentTab === 5) {
              this.tabMine()
            }
            // 月度能耗柱状图(所有Tab通用)
            this.chartCard()
          }
          .padding({ left: 14, right: 14, top: 12, bottom: 12 })
        }
        .layoutWeight(1)
        .scrollBar(BarState.Off)

        // 底部Tab导航栏
        this.tabBar()
      }
      .width('100%')
      .height('100%')

      // 弹窗系统
      if (this.addModal) {
        this.panelAdd(() => {
          this.addModal = false;
        })
      }
      if (this.delModal) {
        this.panelDel(() => {
          this.delModal = false;
        })
      }
    }
    .width('100%')
    .height('100%')
    .backgroundColor(COLORS.bg)
  }

build方法的结构与健康监测平台高度一致——Stack根容器管理内容层和弹窗层的叠层关系。内容层的Column将屏幕垂直分为三段:headerHome(头部) + Scroll(滚动区) + tabBar(底部导航)。Scroll使用layoutWeight(1)占据剩余空间,scrollBar设为Off隐藏滚动条。

条件渲染通过if-else if链实现六个Tab页面的切换。chartCard作为通用组件在所有Tab下都显示。两个弹窗(panelAdd和panelDel)通过各自的状态变量独立控制,使用箭头函数传递onClose回调。

0

1

2

3

4

5

true

true

build 方法

Stack 叠层容器

Column 主内容层

addModal?

delModal?

headerHome 头部

Divider 分割线

Scroll 滚动区

tabBar 底部导航

currentTab?

tabHome 首页

tabDevice 设备

tabScene 场景

tabEnergy 能耗

tabSecurity 安防

tabMine 我的

chartCard 月度图表

panelAdd 添加设备弹窗

panelDel 删除确认弹窗

智能家居头部构建器

头部是用户进入应用后首先看到的区域,需要在紧凑空间内展示环境数据、搜索入口和安防状态。

headerHome 方法

  /**
   * 智能家居头部
   * 包含:位置信息、环境指标大数字、搜索栏、快捷入口、安防状态条
   */
  @Builder
  headerHome() {
    Column({ space: 10 }) {
      // 第一行:位置信息与用户入口
      Row() {
        Column({ space: 2 }) {
          Text('📍 成都·天府新区').fontSize(12).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
          Text('智享家 · 全屋智能').fontSize(9).fontColor(COLORS.text3)
        }
        Column().layoutWeight(1)
        Row({ space: 8 }) {
          Text('➕').fontSize(16)
            .onClick(() => { this.addModal = true; })
          Text('🔔').fontSize(16)
          Text('👤').fontSize(18)
        }
      }
      .width('100%')
      .padding({ left: 16, right: 16, top: 12, bottom: 8 })

      // 第二行:环境指标三列大数字
      Row({ space: 0 }) {
        Column({ space: 2 }) {
          Text('24.5').fontSize(28).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
          Text('温度 ℃').fontSize(9).fontColor(COLORS.text3)
        }
        .layoutWeight(1)
        Column({ space: 2 }) {
          Text('56').fontSize(28).fontColor(COLORS.blue).fontWeight(FontWeight.Bold)
          Text('湿度 %').fontSize(9).fontColor(COLORS.text3)
        }
        .layoutWeight(1)
        Column({ space: 2 }) {
          Text('35').fontSize(28).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
          Text('PM2.5').fontSize(9).fontColor(COLORS.text3)
        }
        .layoutWeight(1)
      }
      .width('90%')

      // 第三行:搜索栏
      Row({ space: 8 }) {
        Text('🔍').fontSize(14)
        Text('搜索设备·场景·能耗…').fontSize(11).fontColor(COLORS.text3)
        Column().layoutWeight(1)
        Text('搜索').fontSize(11).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
      }
      .width('90%')
      .height(38)
      .backgroundColor(COLORS.card)
      .borderRadius(19)
      .padding({ left: 14, right: 14 })
      .alignItems(VerticalAlign.Center)

      // 第四行:快捷入口宫格
      Row({ space: 0 }) {
        ForEach(QUICK_TAGS, (tag: string, idx: number) => {
          Column({ space: 4 }) {
            Text(idx === 0 ? '🛋️' : idx === 1 ? '🛏️' : idx === 2 ? '🍳' : idx === 3 ? '📚' : idx === 4 ? '🔒' : '📊').fontSize(20)
            Text(tag).fontSize(9).fontColor(COLORS.sub)
          }
          .layoutWeight(1)
        })
      }
      .width('90%')
      .padding({ top: 6, bottom: 6 })

      // 第五行:安防状态条(呼吸动画)
      Row({ space: 8 }) {
        Text('🛡️').fontSize(14)
          .opacity(this.breath ? 1 : 0.5)
        Text('安防已布防 · 2台设备离线').fontSize(10).fontColor(COLORS.orangeD)
        Column().layoutWeight(1)
        Text('查看 ›').fontSize(9).fontColor(COLORS.orange)
      }
      .width('90%')
      .height(32)
      .backgroundColor(COLORS.orangeL)
      .borderRadius(8)
      .padding({ left: 10, right: 10 })
      .alignItems(VerticalAlign.Center)
    }
    .width('100%')
    .backgroundColor(COLORS.card)
  }

headerHome以Column为容器垂直排列五行信息。第一行是位置信息和用户入口——左侧显示定位"成都·天府新区"和副标题"智享家·全屋智能",右侧三个入口中➕按钮绑定了onClick事件打开panelAdd弹窗。第二行是三个环境指标大数字——温度24.5(橙色,代表热能)、湿度56(蓝色,代表水汽)、PM2.5值35(绿色,代表空气优良)。三个数字使用28号粗体字体,颜色选择遵循"温度用暖色、湿度用冷色、空气质量用健康色"的语义映射。

第三行搜索栏使用card背景+19圆角的胶囊造型。第四行快捷入口通过ForEach遍历QUICK_TAGS生成六个宫格,图标通过三元运算符根据idx选择。第五行安防状态条使用orangeL背景+orangeD文字的组合,🛡️图标通过this.breath控制opacity在1和0.5间切换,产生安防系统运行中的呼吸闪烁效果。状态文字"2台设备离线"直接引用了DEVICE_LIST中online为false的设备数量。

Tab 0 首页页面构建器

首页Tab提供全屋设备概览,包含环境大卡、快捷设备宫格和添加设备入口。

tabHome 方法

  /**
   * Tab 0:首页
   * 环境概览大卡 + 快捷设备宫格 + 添加设备入口
   */
  @Builder
  tabHome() {
    Column({ space: 14 }) {
      // 标题行
      Row() {
        Text('🏠 全屋概览').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('08-24').fontSize(10).fontColor(COLORS.text3)
      }

      // 环境概览大卡
      Column({ space: 12 }) {
        Row({ space: 0 }) {
          Column({ space: 4 }) {
            Text('24.5').fontSize(24).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
            Text('温度 ℃').fontSize(9).fontColor(COLORS.text3)
          }
          .layoutWeight(1)
          Column({ space: 4 }) {
            Text('56%').fontSize(24).fontColor(COLORS.blue).fontWeight(FontWeight.Bold)
            Text('湿度').fontSize(9).fontColor(COLORS.text3)
          }
          .layoutWeight(1)
          Column({ space: 4 }) {
            Text('35').fontSize(24).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
            Text('PM2.5 优').fontSize(9).fontColor(COLORS.text3)
          }
          .layoutWeight(1)
        }

        // 设备在线状态条
        Column({ space: 4 }) {
          Row() {
            Text('设备在线率').fontSize(10).fontColor(COLORS.sub)
            Column().layoutWeight(1)
            Text('6/8').fontSize(10).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
          }
          Row() {
            Column()
              .width('75%')
              .height(6)
              .backgroundColor(COLORS.orange)
              .borderRadius(3)
            Column().layoutWeight(1)
          }
          .width('100%')
          .height(6)
          .backgroundColor(COLORS.chip)
          .borderRadius(3)
        }
      }
      .width('100%')
      .padding(16)
      .backgroundColor(COLORS.card)
      .borderRadius(16)

环境概览大卡复用了头部的三个环境指标,但字号从28降为24,因为内容区需要同时展示更多信息。设备在线率进度条使用75%宽度对应6/8的在线率(6/8=0.75),填充色用orange而非green——因为75%的在线率不是理想状态(有2台离线),橙色提示用户关注离线设备。

      // 快捷设备宫格
      Column({ space: 10 }) {
        Row() {
          Text('⚡ 快捷设备').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('管理 ›').fontSize(9).fontColor(COLORS.orange)
        }

        Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
          ForEach(this.deviceList, (item: DeviceItem, idx: number) => {
            Column({ space: 6 }) {
              Stack() {
                // 图标背景
                Column()
                  .width(48)
                  .height(48)
                  .backgroundColor(item.online ? COLORS.blueL : COLORS.chip)
                  .borderRadius(12)
                // 设备图标
                Text(item.icon).fontSize(24)
                // 在线状态指示灯
                Column()
                  .width(10)
                  .height(10)
                  .borderRadius(5)
                  .backgroundColor(item.online ? COLORS.green : COLORS.red)
                  .border({ width: 2, color: COLORS.card })
                  .position({ x: 36, y: 36 })
              }
              .width(48)
              .height(48)
              .alignContent(Alignment.Center)

              Text(item.name).fontSize(10).fontColor(item.online ? COLORS.title : COLORS.text3)
                .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
              Text(item.room).fontSize(8).fontColor(COLORS.text3)
            }
            .width('23%')
            .padding({ top: 10, bottom: 10 })
            .alignItems(HorizontalAlign.Center)
            .margin({ bottom: 8 })
            .onClick(() => {
              this.currentTab = 1;
            })
          })
        }
      }
      .width('100%')
      .padding(14)
      .backgroundColor(COLORS.chip)
      .borderRadius(12)

快捷设备宫格使用Flex+FlexWrap.Wrap实现自动换行布局。每个设备项宽度23%,4个一行。Stack叠层包含三层:图标背景Column(48x48圆角12)、设备图标Emoji、在线状态指示灯(10x10圆形,通过position定位在右下角)。指示灯颜色通过item.online判断——在线绿色、离线红色,border使用card色形成"嵌入"效果。设备名称的颜色也根据online状态变化——在线用title亮色、离线用text3暗色。点击设备项会切换到设备Tab(currentTab=1)。

      // 添加设备入口
      Row({ space: 10 }) {
        Text('➕').fontSize(16)
          .opacity(this.breath ? 1 : 0.5)
        Text('添加新设备').fontSize(11).fontColor(COLORS.sub)
        Column().layoutWeight(1)
        Text('配网 ›').fontSize(9).fontColor(COLORS.orange)
      }
      .width('100%')
      .height(44)
      .backgroundColor(COLORS.card)
      .borderRadius(12)
      .padding({ left: 14, right: 14 })
      .alignItems(VerticalAlign.Center)
      .onClick(() => {
        this.addModal = true;
      })
    }
  }

添加设备入口使用Row布局,➕图标通过breath状态产生呼吸效果引导用户点击。点击触发addModal=true打开设备添加弹窗。这个入口在首页和设备Tab中都有出现,提供了便捷的设备添加路径。

Tab 1 设备页面构建器

设备Tab以四列宫格的形式展示所有设备的详细信息,每台设备都有图标背景、在线指示灯和状态文字。

tabDevice 方法

  /**
   * Tab 1:设备
   * 4列宫格图标网格 + 添加设备按钮
   */
  @Builder
  tabDevice() {
    Column({ space: 12 }) {
      // 标题行
      Row() {
        Text('🔌 全部设备').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('8台 · 6在线').fontSize(10).fontColor(COLORS.text3)
      }

      Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
        ForEach(this.deviceList, (item: DeviceItem, idx: number) => {
          Column({ space: 8 }) {
            Stack() {
              // 图标背景(带边框)
              Column()
                .width(56)
                .height(56)
                .backgroundColor(item.online ? COLORS.blueL : COLORS.chip)
                .borderRadius(14)
                .border({ width: 1, color: item.online ? COLORS.blue : COLORS.line })
              // 设备图标
              Text(item.icon).fontSize(28)
              // 在线指示灯
              Column()
                .width(12)
                .height(12)
                .borderRadius(6)
                .backgroundColor(item.online ? COLORS.green : COLORS.red)
                .border({ width: 2, color: COLORS.card })
                .position({ x: 42, y: 42 })
            }
            .width(56)
            .height(56)
            .alignContent(Alignment.Center)

            // 设备信息
            Column({ space: 2 }) {
              Text(item.name).fontSize(10).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
                .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
              Text(item.room).fontSize(8).fontColor(COLORS.text3)
              Text(item.status).fontSize(8)
                .fontColor(deviceStatusColor(item.status))
            }
          }
          .width('23%')
          .padding({ top: 12, bottom: 12 })
          .backgroundColor(COLORS.card)
          .borderRadius(12)
          .alignItems(HorizontalAlign.Center)
          .margin({ bottom: 8 })
          .onClick(() => {
            this.delIdx = idx;
            this.delModal = true;
          })
        })
      }

      // 添加设备按钮
      Row({ space: 10 }) {
        Text('➕').fontSize(16)
          .opacity(this.breath ? 1 : 0.5)
        Text('添加新设备').fontSize(11).fontColor(COLORS.sub)
        Column().layoutWeight(1)
        Text('配网 ›').fontSize(9).fontColor(COLORS.orange)
      }
      .width('100%')
      .height(44)
      .backgroundColor(COLORS.card)
      .borderRadius(12)
      .padding({ left: 14, right: 14 })
      .alignItems(VerticalAlign.Center)
      .onClick(() => {
        this.addModal = true;
      })
    }
  }

tabDevice与首页的快捷设备宫格结构类似但信息更丰富。每个设备项的图标背景从48x48增大到56x56,圆角从12增到14,添加了1px边框——在线设备用blue边框,离线设备用line色边框。在线指示灯从10x10增大到12x12,位置从(36,36)调整到(42,42)。设备信息从两行(名称+房间)增加到三行(名称+房间+状态),状态颜色通过deviceStatusColor函数映射。点击设备项打开panelDel删除确认弹窗,而非切换Tab——这体现了设备Tab的"管理"属性,允许删除操作。

Tab 2 场景页面构建器

场景Tab使用横向滑动的大卡Stack叠层展示六个智能场景,每个场景卡使用渐变背景。

tabScene 方法

  /**
   * Tab 2:场景
   * 横滑大卡(Stack叠层+渐变背景) + 场景列表
   */
  @Builder
  tabScene() {
    Column({ space: 12 }) {
      // 标题行
      Row() {
        Text('🎬 智能场景').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('6 个场景').fontSize(10).fontColor(COLORS.text3)
      }

      // 横滑场景大卡
      Scroll() {
        Row({ space: 12 }) {
          ForEach(this.sceneList, (item: SceneItem, idx: number) => {
            Stack() {
              // 渐变背景层
              Column()
                .width('100%')
                .height('100%')
                .borderRadius(16)
                .linearGradient({
                  angle: 135,
                  colors: [[idx % 2 === 0 ? COLORS.orangeD : COLORS.blueD, 0], [idx % 2 === 0 ? COLORS.orangeL : COLORS.blueL, 1]]
                })

              // 内容层
              Column({ space: 10 }) {
                Text(item.icon).fontSize(40)
                  .opacity(this.breath ? 1 : 0.5)
                Text(item.name).fontSize(14).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
                Text(item.desc).fontSize(9).fontColor('rgba(255,255,255,0.8)')
                  .maxLines(2).textOverflow({ overflow: TextOverflow.Ellipsis })
                Row({ space: 6 }) {
                  Text('🔗 ' + item.count.toString() + ' 台设备').fontSize(9).fontColor('rgba(255,255,255,0.9)')
                  Column().layoutWeight(1)
                  Text('执行 ›').fontSize(9).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
                }
                .width('100%')
              }
              .width('100%')
              .height('100%')
              .padding(16)
              .justifyContent(FlexAlign.Center)
              .alignItems(HorizontalAlign.Center)
            }
            .width(160)
            .height(200)
            .borderRadius(16)
            .alignContent(Alignment.Center)
          })
        }
        .padding({ left: 4, right: 4 })
      }
      .scrollable(ScrollDirection.Horizontal)
      .scrollBar(BarState.Off)
      .width('100%')

场景大卡使用Scroll+Row实现横向滑动。每个卡片宽160高200,使用Stack叠层包含两层——渐变背景层和内容层。渐变通过linearGradient实现,135度角,偶数idx使用orangeD到orangeL的橙色渐变,奇数idx使用blueD到blueL的蓝色渐变。这种交替色系让卡片在横滑时产生色彩节奏感。

内容层的icon使用40号字体并通过breath产生呼吸效果。名称14号粗体白色,描述使用半透明白色rgba(255,255,255,0.8)确保在渐变背景上的可读性。底部显示关联设备数量和执行按钮。

      // 场景列表
      Column({ space: 8 }) {
        Text('📋 场景列表').fontSize(11).fontColor(COLORS.sub).fontWeight(FontWeight.Bold)
        ForEach(this.sceneList, (item: SceneItem, idx: number) => {
          Row({ space: 10 }) {
            Text(item.icon).fontSize(16)
            Column({ space: 2 }) {
              Text(item.name).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
              Text(item.desc).fontSize(9).fontColor(COLORS.text3)
                .maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
            }
            Column().layoutWeight(1)
            Text(item.count.toString() + '台').fontSize(11).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
          }
          .width('100%')
          .padding({ top: 6, bottom: 6 })
          .alignItems(VerticalAlign.Center)
        })
      }
      .width('100%')
      .padding(14)
      .backgroundColor(COLORS.card)
      .borderRadius(12)
    }
  }

场景列表使用ForEach遍历sceneList,以简洁的行列表形式展示场景信息。每行包含图标、名称、描述(单行省略)和关联设备数。设备数量用orange色加粗显示,与大卡的橙色渐变形成呼应。

Tab 3 能耗页面构建器

能耗Tab通过Canvas折线图展示24小时用电趋势,配合关键数据卡和用电明细列表。

tabEnergy 方法

  /**
   * Tab 3:能耗
   * Canvas 折线图 + 关键数据 + 用电统计列表
   */
  @Builder
  tabEnergy() {
    Column({ space: 12 }) {
      // 标题行
      Row() {
        Text('📊 能耗分析').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('今日 12.5 度').fontSize(10).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
      }

      // Canvas 折线图
      Column({ space: 8 }) {
        Row() {
          Text('⚡ 24小时用电趋势').fontSize(11).fontColor(COLORS.sub).fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('峰值 4.8度').fontSize(9).fontColor(COLORS.orange)
        }
        Canvas(this.lineCtx)
          .width('100%')
          .height(220)
          .onReady(() => {
            this.drawLine();
          })

        // 能耗图例
        Row({ space: 12 }) {
          Row({ space: 4 }) {
            Column().width(8).height(8).backgroundColor(COLORS.orange).borderRadius(2)
            Text('用电量').fontSize(8).fontColor(COLORS.text3)
          }
          Row({ space: 4 }) {
            Column().width(8).height(8).backgroundColor(COLORS.blue).borderRadius(2)
            Text('谷段').fontSize(8).fontColor(COLORS.text3)
          }
          Row({ space: 4 }) {
            Column().width(8).height(8).backgroundColor(COLORS.green).borderRadius(2)
            Text('平段').fontSize(8).fontColor(COLORS.text3)
          }
        }
      }
      .width('100%')
      .padding(14)
      .backgroundColor(COLORS.card)
      .borderRadius(12)

折线图卡片是能耗Tab的核心。Canvas绑定lineCtx,高度220。onReady回调调用drawLine方法首次绘制。图例展示了三种颜色——橙色(用电量/峰段)、蓝色(谷段)、绿色(平段),对应电费分时计价的三个时段。峰段(18-21点)电价最高,谷段(0-6点)电价最低,平段为其余时段。

      // 关键数据
      Column({ space: 8 }) {
        Row() {
          Text('📈 关键数据').fontSize(11).fontColor(COLORS.sub).fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
        }
        Row({ space: 0 }) {
          Column({ space: 2 }) {
            Text('12.5').fontSize(18).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
            Text('今日 度').fontSize(8).fontColor(COLORS.text3)
          }
          .layoutWeight(1)
          Column({ space: 2 }) {
            Text('77.5').fontSize(18).fontColor(COLORS.blue).fontWeight(FontWeight.Bold)
            Text('本周 度').fontSize(8).fontColor(COLORS.text3)
          }
          .layoutWeight(1)
          Column({ space: 2 }) {
            Text('¥38.75').fontSize(18).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
            Text('今日费用').fontSize(8).fontColor(COLORS.text3)
          }
          .layoutWeight(1)
        }
        .width('100%')
      }
      .width('100%')
      .padding(14)
      .backgroundColor(COLORS.card)
      .borderRadius(12)

      // 用电统计列表
      Column({ space: 6 }) {
        Text('📋 近期用电明细').fontSize(11).fontColor(COLORS.sub).fontWeight(FontWeight.Bold)
        ForEach(this.energyList, (item: EnergyItem, idx: number) => {
          Row({ space: 10 }) {
            Text(item.date).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            Column().layoutWeight(1)
            Text(item.usage + ' 度').fontSize(11).fontColor(COLORS.sub)
            Text('¥' + item.cost).fontSize(11).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
          }
          .width('100%')
          .padding({ top: 6, bottom: 6 })
          .alignItems(VerticalAlign.Center)
        })
      }
      .width('100%')
      .padding(14)
      .backgroundColor(COLORS.card)
      .borderRadius(12)
    }
  }

关键数据卡展示三个核心指标——今日12.5度(橙色)、本周77.5度(蓝色)、今日费用¥38.75(绿色)。费用使用绿色因为"花费"在心理学上更倾向于中性/正面色。用电明细列表通过ForEach遍历energyList,每行显示日期、用电量和费用,费用用orange色突出。

Tab 4 安防页面构建器

安防Tab使用竖向时间轴展示安防事件记录,每条记录包含时间、圆点指示器和事件详情。

tabSecurity 方法

  /**
   * Tab 4:安防
   * 布防状态卡 + 竖向时间轴
   */
  @Builder
  tabSecurity() {
    Column({ space: 12 }) {
      // 标题行
      Row() {
        Text('🔒 安防监控').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('已布防').fontSize(10).fontColor(COLORS.green).fontWeight(FontWeight.Bold)
      }

      // 布防状态卡
      Row({ space: 12 }) {
        Text('🛡️').fontSize(28)
          .opacity(this.breath ? 1 : 0.5)
        Column({ space: 4 }) {
          Text('家中安全').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
          Text('布防中 · 6个传感器在线').fontSize(9).fontColor(COLORS.sub)
        }
        Column().layoutWeight(1)
        Text('撤防 ›').fontSize(10).fontColor(COLORS.orange)
      }
      .width('100%')
      .padding(16)
      .backgroundColor(COLORS.greenL)
      .borderRadius(12)
      .alignItems(VerticalAlign.Center)

      // 安防时间轴
      Column({ space: 0 }) {
        Text('🕐 安防事件记录').fontSize(11).fontColor(COLORS.sub).fontWeight(FontWeight.Bold)
        ForEach(this.securityList, (item: SecurityItem, idx: number) => {
          Row({ space: 12 }) {
            // 左侧时间
            Column({ space: 2 }) {
              Text(item.time).fontSize(10).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            }
            .width(70)
            .height('100%')
            .justifyContent(FlexAlign.Center)

            // 中间圆点+竖线
            Column() {
              // 上竖线
              Column()
                .width(2)
                .layoutWeight(1)
                .backgroundColor(idx === 0 ? COLORS.card : COLORS.line)
              // 圆点
              Column()
                .width(12)
                .height(12)
                .borderRadius(6)
                .backgroundColor(securityColor(item.type))
                .border({ width: 2, color: COLORS.card })
              // 下竖线
              Column()
                .width(2)
                .layoutWeight(1)
                .backgroundColor(idx === this.securityList.length - 1 ? COLORS.card : COLORS.line)
            }
            .height('100%')
            .alignItems(HorizontalAlign.Center)

            // 右侧事件详情
            Column({ space: 4 }) {
              Row() {
                Text(item.event).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
                Column().layoutWeight(1)
                Text(item.type).fontSize(9)
                  .fontColor(securityColor(item.type))
                  .backgroundColor(item.type === '警报' ? COLORS.redL : item.type === '解锁' ? COLORS.greenL : COLORS.blueL)
                  .borderRadius(4)
                  .padding({ left: 6, right: 6, top: 2, bottom: 2 })
              }
              Text(item.status).fontSize(9)
                .fontColor(item.status === '处理中' ? COLORS.orange : COLORS.text3)
            }
            .layoutWeight(1)
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .padding({ left: 10 })
          }
          .width('100%')
          .height(72)
          .alignItems(VerticalAlign.Top)
        })
      }
      .width('100%')
      .padding(14)
      .backgroundColor(COLORS.card)
      .borderRadius(12)
    }
  }

布防状态卡使用greenL背景——深绿色背景配合🛡️图标的呼吸闪烁效果,传达"安防系统正在运行"的安心感。撤防按钮用orange色提示操作入口。

安防时间轴是本Tab的核心创新——通过三列Row布局实现时间轴效果。左列(宽度70)显示时间,中列是圆点+上下竖线,右列是事件详情。中列的竖线设计很巧妙:第一条记录的上竖线用card色(不可见),最后一条的下竖线用card色(不可见),中间的竖线用line色(可见)。圆点颜色通过securityColor函数映射——警报红色、解锁绿色、监控蓝色。圆点的border使用card色,让圆点看起来像"嵌入"在竖线中。

事件详情中的类型标签使用securityColor着色+对应浅色背景(警报redL、解锁greenL、监控blueL)。处理状态文字中,"处理中"用orange色突出显示,提醒用户关注未处理完毕的事件。

警报

解锁

监控

安防时间轴

左列:时间 70px

中列:圆点+竖线

右列:事件详情

上竖线 2px

圆点 12px 圆形

下竖线 2px

idx === 0?

card色 不可见

line色 可见

事件类型

红色

绿色

蓝色

idx === last?

card色 不可见

line色 可见

事件名称 title色

类型标签 securityColor

处理状态 处理中橙色

Tab 5 我的页面构建器

"我的"Tab展示房屋渐变大卡和房间设备统计清单。

tabMine 方法

  /**
   * Tab 5:我的
   * 房屋渐变大卡 + 房间设备统计清单
   */
  @Builder
  tabMine() {
    Column({ space: 14 }) {
      // 房屋渐变大卡
      Column({ space: 10 }) {
        Row() {
          Text('🏠').fontSize(32)
            .opacity(this.breath ? 1 : 0.5)
          Column().layoutWeight(1)
          Column({ space: 2 }) {
            Text('我的家').fontSize(10).fontColor('rgba(255,255,255,0.8)')
            Row({ space: 4 }) {
              Text('智享家·天府里').fontSize(16).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
            }
          }
        }
        Row() {
          Text('48 台设备 · 42 台在线').fontSize(9).fontColor('rgba(255,255,255,0.8)')
          Column().layoutWeight(1)
          Text('87%').fontSize(9).fontColor('rgba(255,255,255,0.9)')
        }
        Row() {
          Column()
            .width('87%')
            .height(4)
            .backgroundColor(COLORS.orange)
            .borderRadius(2)
          Column().layoutWeight(1)
        }
        .width('100%')
        .height(4)
        .backgroundColor('rgba(255,255,255,0.2)')
        .borderRadius(2)
      }
      .width('100%')
      .padding(20)
      .borderRadius(16)
      .linearGradient({
        angle: 135,
        colors: [[COLORS.blueD, 0], [COLORS.blueL, 1]]
      })

房屋大卡使用blueD到blueL的蓝色渐变背景——与场景大卡中的蓝色渐变保持一致,但在"我的"页面中使用蓝色渐变而非橙色,因为这里展示的是房屋整体信息而非特定场景。大卡内部文字使用半透明白色,在深蓝渐变上保持可读性。设备在线率87%(42/48)的进度条用orange色填充,与蓝色渐变形成冷暖对比。

      // 房间设备统计清单
      Column({ space: 10 }) {
        Row() {
          Text('🏠 房间设备统计').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
          Column().layoutWeight(1)
          Text('6 个房间').fontSize(9).fontColor(COLORS.orange)
        }

        ForEach(this.roomList, (item: RoomItem, idx: number) => {
          Row({ space: 12 }) {
            Stack() {
              Column()
                .width(36)
                .height(36)
                .backgroundColor(idx % 2 === 0 ? COLORS.blueL : COLORS.orangeL)
                .borderRadius(10)
              Text(item.icon).fontSize(18)
            }
            .width(36)
            .height(36)
            .alignContent(Alignment.Center)

            Column({ space: 2 }) {
              Text(item.name).fontSize(11).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
              Text(item.deviceCount.toString() + ' 台设备').fontSize(9).fontColor(COLORS.text3)
            }
            Column().layoutWeight(1)
            Text('›').fontSize(14).fontColor(COLORS.text3)
          }
          .width('100%')
          .padding({ top: 6, bottom: 6 })
          .alignItems(VerticalAlign.Center)
        })
      }
      .width('100%')
      .padding(14)
      .backgroundColor(COLORS.chip)
      .borderRadius(12)
    }
  }

房间清单通过ForEach遍历roomList,每行使用Stack叠层包含一个36x36的图标背景和emoji图标。背景色通过idx%2交替使用blueL和orangeL——这种蓝橙交替与场景大卡的渐变色系呼应,形成了视觉一致性。每行右侧有›箭头提示可点击进入房间详情。

月度能耗图表卡片

chartCard是所有Tab底部通用的月度数据可视化组件,使用ArkUI原生组件实现柱状图。

chartCard 方法

  /**
   * 月度能耗柱状图卡片
   * 使用ArkUI原生组件实现柱状图
   */
  @Builder
  chartCard() {
    Column({ space: 12 }) {
      // 标题行
      Row() {
        Text('📊 近 6 月用电').fontSize(13).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Column().layoutWeight(1)
        Text('均 318 度').fontSize(11).fontColor(COLORS.orange).fontWeight(FontWeight.Bold)
      }

      // 柱状图区域
      Row({ space: 0 }) {
        ForEach(MONTH_IDX, (mi: number, idx: number) => {
          Column({ space: 4 }) {
            // 柱体
            Column()
              .width(16)
              .height(30 + ENERGY_MONTH_VAL[idx] / ENERGY_MONTH_MAX * 100)
              .backgroundColor(idx === 5 ? COLORS.orange : COLORS.orangeL)
              .borderRadius(4)

            // 数值
            Text(ENERGY_MONTH_VAL[idx].toString()).fontSize(7)
              .fontColor(idx === 5 ? COLORS.orange : COLORS.text3)

            // 月份
            Text(MONTH_NAME[idx]).fontSize(8).fontColor(COLORS.text3)
          }
          .layoutWeight(1)
          .justifyContent(FlexAlign.End)
          .alignItems(HorizontalAlign.Center)
        })
      }
      .width('100%')
      .height(110)
      .padding({ top: 8, bottom: 8 })
      .backgroundColor(COLORS.chip)
      .borderRadius(10)
      .alignItems(VerticalAlign.Bottom)
    }
    .width('100%')
    .padding(14)
    .backgroundColor(COLORS.card)
    .borderRadius(12)
    .margin({ top: 12 })
  }

柱状图的柱体高度通过30 + ENERGY_MONTH_VAL[idx] / ENERGY_MONTH_MAX * 100计算——基础高度30确保最低值也有可见柱体,乘以100的比例系数将度数差异映射到像素差异。第6个月(idx 5)的365度是最高值,用orange主色高亮,其他月份用orangeL深色。标题中的"均318度"是六个月平均值(280+320+295+340+310+365)/6=318.3,四舍五入为318。

底部Tab栏

tabBar提供全局导航,通过currentTab控制选中态。

tabBar 方法

  /**
   * 底部 Tab 导航栏
   * 6个Tab单排,选中态用暖光橙高亮
   */
  @Builder
  tabBar() {
    Row({ space: 0 }) {
      ForEach(TAB_LIST, (tab: TabMeta, idx: number) => {
        Column({ space: 2 }) {
          // 图标:选中时放大
          Text(tab.icon).fontSize(this.currentTab === idx ? 21 : 19)
            .opacity(this.currentTab === idx ? 1 : 0.6)
          // 标签:选中时变色加粗
          Text(tab.label).fontSize(9)
            .fontColor(this.currentTab === idx ? COLORS.tabOn : COLORS.text3)
            .fontWeight(this.currentTab === idx ? FontWeight.Bold : FontWeight.Normal)
        }
        .layoutWeight(1)
        .justifyContent(FlexAlign.Center)
        .alignItems(HorizontalAlign.Center)
        .onClick(() => {
          this.currentTab = idx;
        })
      })
    }
    .width('100%')
    .height(58)
    .backgroundColor(COLORS.card)
    .padding({ top: 6, bottom: 6 })
  }

tabBar使用ForEach遍历TAB_LIST生成6个Tab项。选中态的三重视觉差异化:图标字号21vs19、不透明度1vs0.6、标签颜色orangevs text3、字重Boldvs Normal。tabOn色设为orange(#F97316)——在深色背景上,橙色的暖色调比蓝色更具视觉冲击力,让选中Tab更加醒目。点击设置currentTab触发条件渲染切换页面。

弹窗系统

弹窗系统通过Stack叠层和条件渲染实现,包含遮罩层和两个功能弹窗。

modalOverlay 通用遮罩

  /**
   * 通用遮罩层
   * 半透明黑色背景,点击关闭弹窗
   * @param onClose - 关闭回调函数
   */
  @Builder
  modalOverlay(onClose: () => void) {
    Stack() {
      Column()
        .width('100%')
        .height('100%')
        .backgroundColor(COLORS.mask)
    }
    .width('100%')
    .height('100%')
    .onClick(() => {
      onClose();
    })
  }

modalOverlay是弹窗共享的遮罩层,使用55%透明黑色覆盖全屏,点击触发onClose关闭弹窗。

panelAdd 添加设备弹窗

  /**
   * 添加设备弹窗
   * 设备类型选择 + 配网方式选择 + 操作按钮
   * @param onClose - 关闭回调函数
   */
  @Builder
  panelAdd(onClose: () => void) {
    Stack() {
      this.modalOverlay(onClose)

      Column({ space: 14 }) {
        Text('➕ 添加设备').fontSize(16).fontColor(COLORS.title).fontWeight(FontWeight.Bold)
        Text('选择设备类型进行配网').fontSize(11).fontColor(COLORS.sub).textAlign(TextAlign.Center)

        // 设备类型四宫格
        Row({ space: 10 }) {
          Column({ space: 4 }) {
            Text('💡').fontSize(24)
            Text('照明').fontSize(9).fontColor(COLORS.sub)
          }
          .layoutWeight(1)
          .padding({ top: 10, bottom: 10 })
          .backgroundColor(COLORS.blueL)
          .borderRadius(10)
          .alignItems(HorizontalAlign.Center)

          Column({ space: 4 }) {
            Text('❄️').fontSize(24)
            Text('空调').fontSize(9).fontColor(COLORS.sub)
          }
          .layoutWeight(1)
          .padding({ top: 10, bottom: 10 })
          .backgroundColor(COLORS.blueL)
          .borderRadius(10)
          .alignItems(HorizontalAlign.Center)

          Column({ space: 4 }) {
            Text('🔐').fontSize(24)
            Text('门锁').fontSize(9).fontColor(COLORS.sub)
          }
          .layoutWeight(1)
          .padding({ top: 10, bottom: 10 })
          .backgroundColor(COLORS.blueL)
          .borderRadius(10)
          .alignItems(HorizontalAlign.Center)

          Column({ space: 4 }) {
            Text('🤖').fontSize(24)
            Text('扫地机').fontSize(9).fontColor(COLORS.sub)
          }
          .layoutWeight(1)
          .padding({ top: 10, bottom: 10 })
          .backgroundColor(COLORS.blueL)
          .borderRadius(10)
          .alignItems(HorizontalAlign.Center)
        }
        .width('100%')

        // 配网方式选择
        Column({ space: 6 }) {
          Text('配网方式').fontSize(11).fontColor(COLORS.sub)
          Row({ space: 8 }) {
            Text('WiFi').fontSize(10).fontColor(COLORS.bg)
              .backgroundColor(COLORS.orange)
              .borderRadius(8)
              .padding({ left: 10, right: 10, top: 8, bottom: 8 })
            Text('蓝牙').fontSize(10).fontColor(COLORS.text3)
              .backgroundColor(COLORS.chip)
              .borderRadius(8)
              .padding({ left: 10, right: 10, top: 8, bottom: 8 })
            Text('Zigbee').fontSize(10).fontColor(COLORS.text3)
              .backgroundColor(COLORS.chip)
              .borderRadius(8)
              .padding({ left: 10, right: 10, top: 8, bottom: 8 })
          }
        }

        // 按钮行
        Row({ space: 10 }) {
          Text('取消').fontSize(12).fontColor(COLORS.sub)
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(COLORS.chip)
            .borderRadius(10)
            .onClick(() => { onClose(); })

          Text('开始配网').fontSize(12).fontColor(COLORS.bg)
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(COLORS.orange)
            .borderRadius(10)
            .onClick(() => { onClose(); })
        }
        .width('100%')
      }
      .width('85%')
      .padding(20)
      .backgroundColor(COLORS.card)
      .borderRadius(16)
    }
    .width('100%')
    .height('100%')
    .alignContent(Alignment.Center)
  }

panelAdd弹窗提供了完整的设备添加流程。设备类型四宫格使用blueL背景——与设备Tab中的图标背景色系一致,保持了视觉连贯性。配网方式三选按钮中WiFi被选中(orange背景),蓝牙和Zigbee未选中(chip背景)。底部按钮行使用取消(chip)+开始配网(orange)的双色设计,"开始配网"用橙色强调主操作。

panelDel 删除确认弹窗

  /**
   * 删除设备确认弹窗
   * 展示设备信息 + 二次确认
   * @param onClose - 关闭回调函数
   */
  @Builder
  panelDel(onClose: () => void) {
    Stack() {
      this.modalOverlay(onClose)

      Column({ space: 14 }) {
        Text('⚠️ 删除设备').fontSize(16).fontColor(COLORS.red).fontWeight(FontWeight.Bold)

        // 设备信息展示(条件渲染)
        if (this.delIdx >= 0 && this.delIdx < this.deviceList.length) {
          Column({ space: 8 }) {
            Row() {
              Text(this.deviceList[this.delIdx].icon).fontSize(20)
              Column({ space: 2 }) {
                Text(this.deviceList[this.delIdx].name).fontSize(12)
                  .fontColor(COLORS.title).fontWeight(FontWeight.Bold)
                Text(this.deviceList[this.delIdx].room).fontSize(10).fontColor(COLORS.text3)
              }
              Column().layoutWeight(1)
              Text(this.deviceList[this.delIdx].status).fontSize(10)
                .fontColor(deviceStatusColor(this.deviceList[this.delIdx].status))
            }
            .width('100%')
          }
          .width('100%')
          .padding(12)
          .backgroundColor(COLORS.chip)
          .borderRadius(10)
        }

        Text('确定要删除这台设备吗?删除后需重新配网。').fontSize(11).fontColor(COLORS.sub).textAlign(TextAlign.Center)

        // 按钮行
        Row({ space: 10 }) {
          Text('再想想').fontSize(12).fontColor(COLORS.sub)
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(COLORS.chip)
            .borderRadius(10)
            .onClick(() => { onClose(); })

          Text('确认删除').fontSize(12).fontColor(COLORS.card)
            .layoutWeight(1)
            .textAlign(TextAlign.Center)
            .padding({ top: 10, bottom: 10 })
            .backgroundColor(COLORS.red)
            .borderRadius(10)
            .onClick(() => {
              this.delIdx = -1;
              onClose();
            })
        }
        .width('100%')
      }
      .width('80%')
      .padding(20)
      .backgroundColor(COLORS.card)
      .borderRadius(16)
    }
    .width('100%')
    .height('100%')
    .alignContent(Alignment.Center)
  }

panelDel弹窗用于设备删除的二次确认。标题使用红色(COLORS.red)和⚠️emoji传达危险操作信号。设备信息通过条件渲染保护——只有delIdx在有效范围内时才显示。信息展示了设备图标、名称、房间和状态(状态颜色通过deviceStatusColor映射)。底部按钮"再想想"(chip)+ “确认删除”(red)的配色设计让用户明确感知到删除操作的不可逆性。

弹窗系统

modalOverlay 遮罩层

panelAdd 添加设备

panelDel 删除确认

半透明黑色背景

点击关闭

设备类型四宫格

配网方式三选

取消/开始配网按钮

宽度85%

设备信息展示

删除提示文字

再想想/确认删除按钮

宽度80%

Canvas绘制方法

智享家平台只有一个Canvas绘制方法——drawLine,用于绘制24小时能耗折线图。

drawLine 能耗折线图绘制

  /**
   * 绘制24小时能耗折线图
   * 包含:背景网格 + X轴标签 + 渐变填充 + 折线 + 数据点
   */
  drawLine() {
    const ctx = this.lineCtx;
    // 同步抗锯齿状态
    ctx.antialias = this.antialiasOn;
    const data = ENERGY_HR_DATA;
    const w = 300;
    const h = 180;
    const pad = 30;
    const max = ENERGY_HR_MAX;
    const stepX = (w - pad * 2) / (data.length - 1);

    // 清空画布
    ctx.clearRect(0, 0, w + 20, h + 20);

    // 绘制背景网格(4条水平线)
    ctx.strokeStyle = COLORS.line;
    ctx.lineWidth = 1;
    for (let i = 0; i <= 3; i++) {
      const y = pad + (h - pad * 2) * i / 3;
      ctx.beginPath();
      ctx.moveTo(pad, y);
      ctx.lineTo(w - pad, y);
      ctx.stroke();
    }

    // 绘制X轴标签
    ctx.font = '8px sans-serif';
    ctx.fillStyle = COLORS.text3;
    ctx.textAlign = 'center';
    for (let i = 0; i < ENERGY_HR_LABELS.length; i++) {
      const x = pad + i * (w - pad * 2) / (ENERGY_HR_LABELS.length - 1);
      ctx.fillText(ENERGY_HR_LABELS[i], x, h - 8);
    }

    // 绘制渐变填充区域
    const grad = ctx.createLinearGradient(0, pad, 0, h - pad);
    grad.addColorStop(0, COLORS.orange);
    grad.addColorStop(1, 'rgba(249,115,22,0.05)');

    ctx.beginPath();
    ctx.moveTo(pad, h - pad);
    for (let i = 0; i < data.length; i++) {
      const x = pad + i * stepX;
      const y = h - pad - (data[i] / max) * (h - pad * 2);
      ctx.lineTo(x, y);
    }
    ctx.lineTo(w - pad, h - pad);
    ctx.closePath();
    ctx.fillStyle = grad;
    ctx.fill();

    // 绘制折线
    ctx.beginPath();
    for (let i = 0; i < data.length; i++) {
      const x = pad + i * stepX;
      const y = h - pad - (data[i] / max) * (h - pad * 2);
      if (i === 0) {
        ctx.moveTo(x, y);
      } else {
        ctx.lineTo(x, y);
      }
    }
    ctx.strokeStyle = COLORS.orange;
    ctx.lineWidth = 2;
    ctx.stroke();

    // 绘制数据点
    for (let i = 0; i < data.length; i++) {
      const x = pad + i * stepX;
      const y = h - pad - (data[i] / max) * (h - pad * 2);
      ctx.beginPath();
      ctx.arc(x, y, 3, 0, Math.PI * 2);
      ctx.fillStyle = COLORS.card;
      ctx.fill();
      ctx.strokeStyle = COLORS.orange;
      ctx.lineWidth = 1.5;
      ctx.stroke();
    }
  }

drawLine方法的结构与心率折线图类似,但颜色从teal改为orange——呼应智能家居的暖光橙主题色。坐标映射公式y = h - pad - (data[i] / max) * (h - pad * 2)将用电量(0-5度)映射到Canvas的Y坐标。由于Canvas的Y轴向下递增,用h减去映射值反转坐标系,让高用电量显示在上方。

绘制分为五步:4条水平网格线提供参考刻度;X轴标签(00/06/12/18/24)显示时间点;渐变填充区域从orange到几乎透明的orange,为折线下方区域添加视觉重量;折线用orange色2px线宽绘制;每个数据点绘制为card色填充+orange色描边的"空心圆"。

渐变颜色的选择基于平台主题色——橙色代表能源/热能,在能耗图表中使用橙色折线让数据与主题色形成呼应。渐变填充区域的颜色从#F97316(opaque)到rgba(249,115,22,0.05)(几乎透明),让填充区域上深下浅,产生"用电量堆积"的视觉效果。

drawLine 能耗折线图

同步antialias状态

定义画布参数

clearRect清空画布

绘制背景网格 - 4条水平线

绘制X轴标签 - 00/06/12/18/24

绘制渐变填充区域

绘制折线 - orange色2px

绘制数据点 - 空心圆

架构总结

智享家智能家居平台采用了与康脉健康监测平台类似的分层架构,但在数据模型、可视化类型和交互模式上针对物联网场景进行了专门设计。

渲染错误: Mermaid 渲染失败: Parse error on line 28: ...bgraph 组件主体 EN[@Entry @Component ----------------------^ Expecting 'SEMI', 'NEWLINE', 'SPACE', 'EOF', 'subgraph', 'end', 'acc_title', 'acc_descr', 'acc_descr_multiline_value', 'AMP', 'COLON', 'STYLE', 'LINKSTYLE', 'CLASSDEF', 'CLASS', 'CLICK', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', 'direction_tb', 'direction_bt', 'direction_rl', 'direction_lr', 'direction_td', got 'LINK_ID'

功能模块对比表

模块名称核心功能数据模型Canvas图表布局特点交互方式
首页Tab全屋设备概览DeviceItem环境大卡+快捷宫格+添加入口点击设备跳转设备Tab
设备Tab设备网格管理DeviceItemFlex四列宫格+添加按钮点击设备打开删除弹窗
场景Tab智能场景展示SceneItem横滑大卡+列表横滑浏览+执行入口
能耗Tab用电趋势分析EnergyItem折线图Canvas+关键数据+明细列表查看详情入口
安防Tab安防事件记录SecurityItem布防状态卡+竖向时间轴撤防入口
我的Tab房屋设备统计RoomItem渐变大卡+房间清单点击房间进入详情
月度图表近6月用电趋势ENERGY_MONTH_VAL无(原生组件)柱状图纯展示
头部面板环境数据概览QUICK_TAGS三段式五层布局搜索+快捷入口+添加
Tab导航全局页面切换TAB_LIST单排6Tab点击切换
添加弹窗设备配网流程85%宽度四宫格+三选取消+开始配网
删除弹窗设备删除确认DeviceItem80%宽度信息+双按钮再想想+确认删除

技术亮点对比表

技术亮点实现方式应用场景工程价值创新程度
@Observed五模型类装饰器+@State绑定设备/场景/能耗/安防/房间数据变化自动刷新UI
Canvas能耗折线图CanvasRenderingContext2D24小时用电趋势渐变填充+折线+数据点
呼吸动画驱动setInterval+breath状态安防图标+添加入口+场景图标声明式状态驱动UI动画
运行时抗锯齿ctx.antialias属性能耗折线图视觉模式动态切换极高
Stack叠层设备图标背景+图标+指示灯设备宫格图标三层叠层实现状态指示
Flex换行四宫格FlexWrap.Wrap+SpaceBetween设备网格+场景布局自动适配不同数量
渐变场景大卡linearGradient+135度场景横滑卡片蓝橙交替色系节奏
竖向时间轴三列Row+圆点竖线安防事件记录首尾隐藏竖线的巧妙设计
语义化双主色blue+orange双色调全局颜色体系统一冷暖对比+设备状态映射
条件渲染弹窗if状态变量+onClose回调添加设备+删除确认回调模式提高复用性
设备在线指示灯position定位+border嵌入设备宫格+设备网格视觉化的设备状态标识
渐变房屋大卡linearGradient+135度我的页房屋统计视觉焦点+品牌强化

深度总结

智享家智能家居平台是一个以HarmonyOS ArkUI声明式框架为基础,面向全屋智能物联网领域的综合性移动应用。通过对该平台源代码的深度解析,我们可以从多个技术维度总结其设计理念、工程价值和创新亮点。

从架构层面看,智享家平台采用了"颜色系统-常量定义-辅助函数-数据模型-组件主体-构建器群-弹窗系统-Canvas绘制"八层分层架构,与康脉健康监测平台保持了一致的架构风格。这种一致性不是简单的复制,而是体现了ArkUI应用开发的标准模式——分层架构让代码结构清晰可读,每一层都有明确的职责边界,颜色系统提供视觉统一性,常量定义提供数据骨架,辅助函数提供映射逻辑,数据模型提供可观察数据,组件主体管理状态生命周期,构建器群封装UI片段,弹窗系统处理模态交互,Canvas绘制实现数据可视化。在团队协作开发中,不同的开发者可以负责不同的层级,这种分层模式极大地提高了开发效率和代码可维护性。

从数据模型层面看,五个@Observed类(DeviceItem/SceneItem/EnergyItem/SecurityItem/RoomItem)完整覆盖了智能家居领域的核心实体。DeviceItem封装了设备的基本信息和在线状态——name/room/status/icon/online五个属性定义了一个设备的完整身份。SceneItem封装了自动化场景——name/desc/icon/count四个属性让用户了解场景的名称、描述和关联设备数。EnergyItem封装了能耗记录——date/usage/cost三个属性展示了用电量和费用的关系。SecurityItem封装了安防事件——time/event/type/status四个属性记录了安全事件的完整信息。RoomItem封装了房间信息——name/deviceCount/icon三个属性提供了房间维度的设备统计。这五个类的组合构成了智能家居数据模型的完整图谱,从设备到场景到能耗到安防到空间,覆盖了用户管理智能家居的所有维度。

从状态管理层面看,@State/@Observed/@Builder三个装饰器构成了响应式状态管理的铁三角。currentTab控制Tab页面切换,breath驱动呼吸动画,addModal/delModal控制弹窗显隐,deviceList/sceneList/energyList/securityList/roomList承载数据列表。当用户在设备Tab中点击某台设备时,delIdx被设置为对应索引,delModal变为true,panelDel弹窗被条件渲染出来——这一系列状态变化都是声明式的,开发者只需要修改状态变量,框架自动处理UI更新。这种"状态驱动视图"的编程模型让复杂的状态管理变得简单直观。

从可视化层面看,智享家平台的Canvas折线图展示了ArkUI Canvas 2D API在能耗数据可视化中的应用。drawLine方法通过五步绘制——背景网格、X轴标签、渐变填充、折线、数据点——将24小时的用电量数据转化为直观的趋势曲线。渐变填充区域从orange到几乎透明的orange,为折线下方添加了视觉重量,让用户能够直观感受用电量的"堆积"效果。折线本身使用orange色2px线宽绘制,与平台的暖光橙主题色呼应。每个数据点绘制为card色填充+orange色描边的空心圆,既标记了精确数据位置又不喧宾夺主。这种多层次的绘制策略让折线图既有美观性又有信息密度。

从交互设计层面看,平台在多个细节处体现了对用户体验的深入考量。设备图标的Stack三层叠层(背景+图标+指示灯)让用户一眼就能判断设备在线状态——绿色指示灯代表在线可控,红色指示灯代表离线需关注。安防时间轴的首尾竖线隐藏设计让时间轴有明确的起止点——第一条记录上方无竖线表示这是最新事件,最后一条记录下方无竖线表示这是最早事件。场景大卡的蓝橙交替渐变让横滑体验产生色彩节奏感——用户在滑动时看到蓝色和橙色交替出现,避免了单一色调的视觉疲劳。添加设备入口的呼吸闪烁效果引导新用户进行设备配网——这是一种不依赖文字的视觉引导。

从颜色工程层面看,科技蓝+暖光橙的双主色调设计是智享家平台最显著的视觉特征。蓝色#3B82F6代表科技与冷设备(空调、温湿度计、监控事件),橙色#F97316代表温暖与热设备(照明、加热、能耗数据)。这种冷暖对比不仅具有美学价值,更承载着信息分类的功能——用户通过颜色就能判断设备类型或事件类别。tabOn设为orange而非blue,是因为在深色背景上橙色比蓝色更醒目,Tab选中态需要更强的视觉反馈。设备状态三色(绿/红/黄)和安防事件三色(红/绿/蓝)构成了两套独立的颜色语义体系,但都遵循"红=危险、绿=安全"的通用原则。

从性能优化层面看,平台采用了多项性能策略。Canvas上下文私有化(lineCtx使用private而非@State)避免了不必要的状态追踪开销。定时器中不包含Canvas重绘逻辑(因为能耗折线图不需要呼吸动画联动)避免了不可见图表的重绘浪费。scrollBar设为Off不仅提升视觉简洁性也减少渲染开销。ForEach配合keyGenerator函数(如(item: DeviceItem) => item.name)优化了列表渲染的diff效率。这些优化策略虽然单个效果微小,但在设备数量众多的物联网场景中累积效果显著。

从物联网场景层面看,智享家平台深入结合了智能家居的实际需求。设备管理涵盖了照明、空调、清洁、安防、遮阳、空气净化、音频、环境监测等8种常见设备类型,覆盖了现代家庭的主要智能硬件品类。场景模式设计了回家、离家、观影、睡眠、起床、晚餐6个典型场景,每个场景关联3-12台设备,体现了自动化场景的核心价值——一键控制多设备协同。能耗分析通过24小时折线图和6天明细列表,让用户了解用电高峰和费用构成。安防监控通过时间轴展示6条事件记录,涵盖了解锁、警报、监控三种事件类型,让用户回溯家庭安全状况。房间统计从空间维度展示了6个房间的设备分布,帮助用户了解全屋设备的空间布局。

从代码质量层面看,平台的源代码体现了良好的工程实践。函数和类都有JSDoc注释说明参数含义。常量命名使用UPPER_SNAKE_CASE。类名使用PascalCase。辅助函数名使用camelCase。颜色字段使用语义化命名(blue/orange/green而非#3B82F6/#F97316/#4ADE80)。预置数据数组既用于演示也体现了数据结构的规范。这些命名规范和注释习惯让代码具有高可读性和可维护性,新开发者能够快速理解代码意图。

从扩展性层面看,平台的架构设计为未来扩展预留了充足空间。新增设备类型只需要在DEVICE_LIST中添加DeviceItem实例。新增场景只需要在SCENE_LIST中添加SceneItem实例。新增安防事件只需要在SECURITY_LIST中添加SecurityItem实例。新增Tab只需要在TAB_LIST中添加TabMeta项并实现对应的@Builder方法。新增Canvas图表类型只需要创建CanvasRenderingContext2D实例并实现draw方法。这种"数据驱动+组件化"的架构让功能扩展变得简单而安全,不需要修改已有代码即可添加新功能。

综上所述,智享家智能家居平台不仅是一个功能完整的物联网管理应用,更是一个展示HarmonyOS ArkUI在多设备、多场景、多状态交互场景下技术能力的优秀案例。它涵盖了状态管理、组件化开发、Canvas绘图、生命周期管理、颜色工程、数据可视化、交互设计、性能优化等多个技术维度,为HarmonyOS生态中的智能家居类应用开发提供了有价值的参考。其五模型数据架构、双主色颜色系统、Stack叠层设备图标、竖向时间轴安防记录、渐变场景大卡等技术亮点,代表了HarmonyOS在物联网应用开发中的最佳实践水平。随着智能家居市场的持续发展和鸿蒙生态的不断壮大,这类应用的架构模式和设计理念将为更多开发者提供借鉴和启发。

附录:DevEco Studio 创建新项目与查看 SDK 版本

本章节演示如何使用 DevEco Studio 创建一个 HarmonyOS 新项目,并查看当前 IDE 已安装的 SDK 版本,适合作为其他技术博文的补充操作指南。


一、创建新项目

1.1 进入欢迎界面

启动 DevEco Studio 后,首先看到的是欢迎界面。左侧导航栏默认选中 “项目”,右侧提供三个主要入口:

  • 新建项目:从头创建新项目
  • 打开项目:打开本地已有项目
  • 克隆仓库:从 Git 等版本控制拉取代码

点击 “新建项目” 按钮,进入项目创建向导。

在这里插入图片描述

1.2 选择项目模板

在弹出的"新建项目"对话框中,左侧分类标签提供了两种项目类型:

类型说明
应用(Application)开发标准的 HarmonyOS 应用,具备完整的 Ability 生命周期
元服务(Atomic Service)开发轻量级的原子化服务,无需安装即可使用

选择 “应用” 标签后,右侧展示多种模板。对于大多数场景,推荐选择 “Empty Ability” —— 这是一个最基础的入门模板,仅包含 Hello World 功能,适合从零开始构建应用。

在这里插入图片描述

1.3 配置项目信息

点击 “下一步” 后,进入项目配置界面,需要填写以下核心参数:

配置项示例值说明
项目名称(Project name)rollboat应用的项目名称,建议使用英文命名
包名(Bundle name)com.rollboat.myapplication应用唯一标识,采用反向域名格式
保存路径(Save location)D:\CodeFactory\rollboat项目本地存储路径,避免使用中文和空格
兼容 SDK(Compatible SDK)6.1.1(24)目标 HarmonyOS API 版本,点击"查看参考"可了解各版本差异
模块名称(Module name)entry主模块名称,默认 entry 为应用入口模块
设备类型(Device types)☑ Phone勾选目标设备:Phone / Tablet / 2in1 / Car / Wearable / TV

右侧预览区会实时展示当前模板的默认效果 —— 一个居中显示的 “Hello World” 文本。

在这里插入图片描述

1.4 完成创建

确认配置无误后,点击右下角 “完成” 按钮,IDE 将自动执行以下操作:

  1. 生成项目骨架(Stage 模型目录结构)
  2. 执行 ohpm install 安装依赖
  3. 运行 Hvigor 构建初始化(Build Init

构建日志中显示 “退出代码为 0” 表示项目初始化成功。

在这里插入图片描述

1.5 项目结构概览

创建完成后,左侧项目面板展示的是标准的 Stage 模型 目录结构:

rollboat/
├── .hvigor/                   # Hvigor 构建工具缓存
├── .idea/                     # IDE 配置文件
├── AppScope/                  # 应用级全局配置
│   └── app.json5
├── entry/                     # 主模块(入口模块)
│   ├── src/main/ets/
│   │   ├── entryability/      # Ability 生命周期管理
│   │   │   └── EntryAbility.ets
│   │   └── pages/             # UI 页面
│   │       └── Index.ets      # 首页(默认 Hello World)
│   ├── src/main/resources/    # 资源文件
│   ├── module.json5           # 模块配置
│   └── build-profile.json5    # 构建配置
├── oh_modules/                # OHPM 依赖包
├── build-profile.json5        # 工程构建配置
├── hvigorfile.ts              # Hvigor 构建脚本
└── oh-package.json5           # 包管理配置

核心文件 Index.ets 的默认代码如下,采用 ArkTS 声明式 UI 语法:

@Entry
@Component
struct Index {
  @State message: string = 'Hello World';

  build() {
    RelativeContainer() {
      Text(this.message)
        .id('HelloWorld')
        .fontSize($r('app.float.page_text_font_size'))
        .fontWeight(FontWeight.Bold)
        .alignRules({
          center: { anchor: '__container__', align: VerticalAlign.Center },
          middle: { anchor: '__container__', align: HorizontalAlign.Center }
        })
        .onClick(() => {
          this.message = 'Welcome';
        })
    }
    .height('100%')
    .width('100%')
  }
}
关键语法作用
@Entry标记为页面入口,可用于路由跳转
@Component声明为自定义组件
@State状态变量,数据变更时自动触发 UI 刷新
RelativeContainer相对布局容器,替代传统线性布局
.onClick()点击事件,此处点击后文本变为 “Welcome”

打开右侧 Previewer(预览器),选择 Phone 设备,即可实时预览 Hello World 效果,无需连接真机或启动模拟器。

在这里插入图片描述


二、查看 SDK 版本

2.1 查看 HarmonyOS SDK

DevEco Studio 安装时已内置 HarmonyOS SDK,无需单独下载。通过以下路径查看:

文件 → 设置 → HarmonyOS SDK(或快捷键 Ctrl + Alt + S 搜索 “HarmonyOS SDK”)

在设置面板中,可以看到当前已安装的 SDK 版本信息:

名称阶段状态
HarmonyOS 6.1.1Release✅ 已安装

界面顶部提示:“HarmonyOS SDK 已经包含在 IDE,无需单独安装”,省去了手动配置 SDK 的繁琐步骤。

在这里插入图片描述

2.2 查看 ArkUI-X SDK(跨平台扩展)

如果项目需要将 ArkUI 框架扩展到多个 OS 平台(Android / iOS / OpenHarmony),还需要配置 ArkUI-X SDK。路径如下:

文件 → 设置 → 语言和框架 → ArkUI-X

在这里可以查看已安装和可选的 ArkUI-X SDK 版本:

版本SDK 版本号阶段状态
API Version 246.1.1.100Release✅ 已安装
API Version 236.1.0.28Beta1未安装
API Version 226.0.2.112Release未安装

安装路径示例:D:\DevTools\ArkUI-X\sdk

说明:ArkUI-X 允许开发者使用一套 ArkTS 主代码,同时构建多平台应用。如果仅开发 HarmonyOS 原生应用,无需额外安装 ArkUI-X SDK。

在这里插入图片描述


三、小结

步骤操作关键点
创建项目欢迎页 → 新建项目 → 选择 Empty Ability 模板 → 配置项目信息 → 完成使用 Stage 模型 + ArkTS 语言
查看 SDK设置 → HarmonyOS SDKSDK 已内置,无需手动安装
跨平台扩展设置 → ArkUI-X根据需要安装对应 API 版本

至此,DevEco Studio 的项目创建与 SDK 环境确认全部完成,可以开始 HarmonyOS 应用的功能开发。


本文基于 DevEco Studio 6.1.1 Release 版本编写,不同版本界面可能存在细微差异。

更多推荐