HarmonyOS智能家居——能耗监测页面与数据可视化
能耗监测页面是智能家居 App 里唯一一个"纯数据展示"页面——没有开关、没有滑块、没有可操作的状态切换,所有数据都是只读的。从截图看,页面分四个区域:顶部三栏统计卡片、24 小时用电分布柱状图、各房间能耗横向条形图、节能建议。实现上用了两个 Builder(ECard 和 RoomEnergy),代码量是所有页面里最少的。
完整效果
数据依赖
import { ENERGY_DATA, EnergyRecord } from '../services/DeviceData'
只导入了 ENERGY_DATA 和 EnergyRecord,没有导入 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) 让三个卡片平分水平空间。
注意 value 是 string 类型而不是 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 应该从数据自动计算。
更多推荐



所有评论(0)