能耗监测页面是智能家居 App 里唯一一个"纯数据展示"页面——没有开关、没有滑块、没有可操作的状态切换,所有数据都是只读的。从截图看,页面分四个区域:顶部三栏统计卡片、24 小时用电分布柱状图、各房间能耗横向条形图、节能建议。实现上用了两个 Builder(ECard 和 RoomEnergy),代码量是所有页面里最少的。
完整效果
在这里插入图片描述

数据依赖

import { ENERGY_DATA, EnergyRecord } from '../services/DeviceData'

只导入了 ENERGY_DATAEnergyRecord,没有导入 DEVICES、ROOMS 或任何操作函数。这说明能耗页面的数据来源只有 7 条能耗记录,和其他页面的数据没有交叉。

从 DeviceData 的定义看,ENERGY_DATA 是一个固定数组:

export const ENERGY_DATA: EnergyRecord[] = [
  { time: '0时', usage: 0.3 }, { time: '4时', usage: 0.2 },
  { time: '8时', usage: 1.5 }, { time: '12时', usage: 2.1 },
  { time: '16时', usage: 1.8 }, { time: '20时', usage: 2.8 },
  { time: '24时', usage: 0.8 },
]

在这里插入图片描述

7 条记录代表 24 小时内 7 个时间点的用电量。数据是写死的 mock,没有从真实电表获取。

三栏统计卡片

Row({ space: 12 }) {
  this.ECard('今日用电', '2.8', 'kWh', A)
  this.ECard('预计本月', '86', 'kWh', '#FF9F43')
  this.ECard('同比上月', '↓12', '%', '#00B894')
}.width('100%').padding({ left: 16, right: 16 }).margin({ bottom: 16 })

在这里插入图片描述

三个卡片横向排列,每个卡片用不同的颜色编码:

  • 今日用电:紫色(主色 #6C5CE7),中性数据
  • 预计本月:橙色(#FF9F43),预警色——月度预估如果偏高可以用暖色提醒
  • 同比上月:绿色(#00B894),正面数据——下降 12% 是好事,用绿色表示

ECard Builder

@Builder ECard(label: string, value: string, unit: string, color: string) {
  Column() {
    Text(value).fontSize(28).fontWeight(FontWeight.Bold).fontColor(color)
    Text(unit).fontSize(11).fontColor(T3).margin({ top: 2 })
    Text(label).fontSize(11).fontColor(T2).margin({ top: 4 })
  }.layoutWeight(1).padding(16).backgroundColor('#FFFFFF').borderRadius(14)
}

四层信息:数值(大号彩色)、单位(小号灰色)、标签(小号灰色)、背景(白底圆角)。layoutWeight(1) 让三个卡片平分水平空间。

注意 valuestring 类型而不是 number——因为"↓12"包含了符号,不能用纯数字表示。在需要显示符号前缀(↑、↓、±)的场景里,字符串比数字更灵活。

24 小时用电分布柱状图

Column() {
  Text('24小时用电分布').fontSize(16).fontWeight(FontWeight.Bold).fontColor(T1)
    .width('100%').margin({ bottom: 12 })
  Row() {
    ForEach(ENERGY_DATA, (e: EnergyRecord) => {
      Column() {
        Text(e.usage + 'kWh').fontSize(9).fontColor(T2).margin({ bottom: 4 })
        Column() {}.width(22).height(e.usage * 40).borderRadius(11)
          .linearGradient({ angle: 180, colors: [[A, 0], ['#A78BFA', 1]] })
        Text(e.time).fontSize(9).fontColor(T3).margin({ top: 4 })
      }.alignItems(HorizontalAlign.Center).layoutWeight(1)
    })
  }.width('100%').height(140).alignItems(VerticalAlign.Bottom)
}

在这里插入图片描述

和首页的能耗柱状图结构一样,但有两个区别:

区别一:柱子上方有数值标签

Text(e.usage + 'kWh').fontSize(9).fontColor(T2).margin({ bottom: 4 })

首页的柱状图没有显示具体数值,用户只能通过柱子高度估算用电量。能耗页面加了数值标签后,用户可以看到精确数字(如 “2.8kWh”),更适合做数据分析。

区别二:柱子高度系数更大

首页的柱子高度是 e.usage * 30,能耗页面是 e.usage * 40。因为能耗页面的柱状图区域更高(140vp vs 80vp),需要更大的高度范围来充分利用空间。

柱状图的布局原理

Row() {
  ForEach(ENERGY_DATA, (e: EnergyRecord) => {
    Column() { /* 数值标签 + 柱子 + 时间标签 */ }
      .alignItems(HorizontalAlign.Center).layoutWeight(1)
  })
}
.height(140).alignItems(VerticalAlign.Bottom)

所有柱子放在一个 Row 里,layoutWeight(1) 让每根柱子平分水平空间。alignItems(VerticalAlign.Bottom) 让所有柱子底部对齐——这是柱状图的标准布局方式,保证不同高度的柱子从同一基准线向上生长。

height(140) 固定了整个图表区域的高度。柱子的最大高度是 2.8 * 40 = 112vp,加上上下标签各约 15vp,总高度约 142vp,和 140vp 的容器高度基本匹配。如果以后能耗数据的范围变化(比如最大值超过 5 kWh),需要调整 * 40 的系数或容器高度。

各房间能耗横向条形图

Text('各房间能耗').fontSize(16).fontWeight(FontWeight.Bold).fontColor(T1)
  .width('100%').padding({ left: 16 }).margin({ bottom: 8 })

Column({ space: 10 }) {
  this.RoomEnergy('🛋️ 客厅', 1.2, 40)
  this.RoomEnergy('🛏️ 卧室', 0.8, 28)
  this.RoomEnergy('🍳 厨房', 0.5, 18)
  this.RoomEnergy('📚 书房', 0.2, 8)
  this.RoomEnergy('🛁 浴室', 0.1, 6)
}

五个房间的能耗用横向条形图展示。每个条形图有三部分:房间名称(固定 80vp 宽)、条形(弹性宽度)、数值(右对齐)。

RoomEnergy Builder

@Builder RoomEnergy(name: string, usage: number, pct: number) {
  Row() {
    Text(name).fontSize(13).fontColor(T1).width(80)
    Row() {}.width(pct * 2).height(8).borderRadius(4)
      .backgroundColor(A + '40').margin({ left: 8, right: 8 }).layoutWeight(1)
    Text(usage + ' kWh').fontSize(12).fontWeight(FontWeight.Bold).fontColor(A)
  }.width('100%')
}

三个参数:房间名称、用电量、百分比宽度。条形的宽度用 pct * 2 计算——客厅的 pct 是 40,条形宽度就是 80vp。

百分比值是手动设定的(40、28、18、8、6),不是从数据计算的。这种"手动指定比例"的做法在原型阶段是合理的——如果要从真实数据计算比例,需要先找出最大值,再用 usage / maxUsage * 100 算出每个房间的相对比例。

条形的颜色

.backgroundColor(A + '40')

A + '40' 是主色的 25% 透明度(0x40 / 0xFF = 25.1%)。半透明的紫色条形在白色卡片背景上形成一个柔和的色块,比纯色更不刺眼。

条形的高度只有 8vp,圆角 4vp(半圆),视觉上是一根细长的圆角条。这种"细条"设计适合横向条形图——如果条形太粗(比如 20vp),五行条形图会占据太多垂直空间,挤压其他区域。

房间名称的 emoji 前缀

Text('🛋️ 客厅').fontSize(13).fontColor(T1).width(80)

每个房间名称前加了 emoji 图标(🛋️ 客厅、🛏️ 卧室、🍳 厨房、📚 书房、🛁 浴室),和首页房间卡片的图标一致。emoji 让纯文字的列表有了视觉锚点,用户可以快速扫描找到目标房间。

固定 width(80) 保证所有房间名称对齐——不管名称是两个字(“卧室”)还是三个字(加上 emoji 后),右侧的条形都从同一位置开始。

节能建议

Column() {
  Text('💡 节能建议').fontSize(16).fontWeight(FontWeight.Bold).fontColor(T1)
    .width('100%').margin({ bottom: 8 })
  Text('• 空调调高1°C可节省约10%能耗').fontSize(13).fontColor(T2).lineHeight(22)
  Text('• 离家时关闭不必要的电器').fontSize(13).fontColor(T2).lineHeight(22)
  Text('• 使用智能插座定时关闭待机设备').fontSize(13).fontColor(T2).lineHeight(22)
}

三条硬编码的节能建议,用圆点符号()作为列表前缀。和美食详情页的"品尝建议"一样,这些建议是通用的、静态的,不根据实际能耗数据动态生成。

lineHeight(22) 设置了 22px 的行高,fontSize(13) × 1.7 ≈ 22,比标准行高(1.5 倍)稍宽松。建议文字通常比较长,更宽松的行高能提升可读性。

整个页面的静态特征

能耗页面是智能家居 App 里唯一一个没有 @State 的页面——所有数据都是硬编码的常量,没有用户交互,没有状态变更。这和安防页面形成了对比——安防页面虽然也不复杂,但有 @State alarm@State doorLocked 两个可切换的状态。

能耗页面的"纯静态"特征是由它的功能定位决定的——能耗监测是一个"只看不操作"的页面。用户来这里是为了了解用电情况,不是为了控制设备。设备控制在首页和房间页完成,能耗页面只负责展示分析结果。

这种"展示型页面不需要状态管理"的规律在很多 App 里都适用——统计页面、报告页面、详情展示页面通常都是纯静态的,不需要 @State

两个 Builder 的设计取舍

Builder 参数 用途 使用次数
ECard label, value, unit, color 顶部统计卡片 3 次
RoomEnergy name, usage, pct 房间能耗条形图 5 次

两个 Builder 都只在 EnergyPage 里使用,没有跨页面复用。ECard 的四参数设计和安防页面的 StatusCard 类似(都是"数值 + 标签 + 颜色"),但字段含义不同——ECard 的 value 是能耗数值,StatusCard 的 status 是状态文字。

RoomEnergy 的 pct 参数是一个设计上的简化——它直接指定了条形的相对宽度,而不是从 usage 计算。这种"手动指定比例"的做法避免了"先找最大值再算百分比"的计算逻辑,代码更简单。代价是如果 usage 数据变化,需要手动更新 pct 值。在原型阶段这是可接受的,真实 App 应该从数据自动计算。

Logo

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

更多推荐