健康足迹:鸿蒙健康数据种子与仪表盘效果


实例:健康数据(Health)|技术:14 天记录、数值梯度、趋势可视化
一、种子数据的设计目标
实例 14 的种子数据要撑起「仪表盘 + 趋势图 + 周汇总」,设计目标:
- 14 天记录:近两周连续记录——柱状图 7 根柱有数据、周汇总有值;
- 数值合理:体重 72~74kg 微波动、步数 5000~12000、睡眠 6~8.5h、心率 62~72、饮水 1000~2000ml、运动 10~60min——真实健康数据范围;
- 趋势可见:体重「先高后低」(74→72kg 下降趋势)、睡眠「有起伏」——趋势箭头与柱状图有故事;
- 今天有记录:最近一天(today)有数据——今日概览卡显示大数字。
二、14 天健康记录种子数据
| 日期(距今天) | 体重kg | 步数 | 睡眠h | 心率 | 饮水ml | 运动min | 备注 |
|---|---|---|---|---|---|---|---|
| 13 天前 | 74.2 | 6000 | 6.5 | 68 | 1200 | 20 | 加班 |
| 12 天前 | 74.0 | 8200 | 7.0 | 67 | 1500 | 30 | |
| 11 天前 | 73.8 | 4500 | 6.0 | 70 | 1000 | 15 | 熬夜 |
| 10 天前 | 73.9 | 9800 | 7.5 | 65 | 1800 | 40 | |
| 9 天前 | 73.6 | 7600 | 8.0 | 64 | 1600 | 35 | 跑了 5km |
| 8 天前 | 73.7 | 5200 | 6.5 | 69 | 1100 | 20 | |
| 7 天前 | 73.4 | 9000 | 7.0 | 66 | 1700 | 30 | |
| 6 天前 | 73.5 | 6800 | 6.8 | 67 | 1400 | 25 | |
| 5 天前 | 73.2 | 11000 | 7.8 | 63 | 1900 | 45 | 晨跑 |
| 4 天前 | 73.0 | 8800 | 7.2 | 65 | 1600 | 30 | |
| 3 天前 | 73.1 | 5800 | 6.5 | 68 | 1200 | 20 | |
| 2 天前 | 72.8 | 10200 | 7.6 | 63 | 1800 | 40 | |
| 1 天前 | 72.5 | 9200 | 7.4 | 64 | 1700 | 35 | 游泳 |
| 今天 | 72.3 | 8300 | 7.5 | 65 | 1500 | 30 |
设计要点:
- 14 天连续:
record_date从 daysAgo(13) 到 today 逐日填充——柱状图近 7 天(daysAgo(6)~today)恰好 7 根柱; - 体重下降趋势:74.2 → 72.3(-1.9kg)——本周均值(72.6)vs 上周均值(73.7)差 -1.1kg → 趋势箭头「↓ 较上周 -1.1kg」绿色(减重健康);
- 步数起伏:4500(熬夜)~ 11000(晨跑)——有故事的数据(备注解释异常值);
- 睡眠起伏:6.0(熬夜)~ 8.0(跑了 5km 睡得香)——柱状图高低错落;
- 今天有记录:72.3kg/8300 步/7.5h——今日概览卡数字饱满。
三、代码实现
const today = HealthDao.today();
for (let i = 13; i >= 0; i--) {
const date = HealthDao.daysAgo(i);
const seed = seedByIndex(i); // 按偏移取对应数值(见第二节表格)
const values: relationalStore.ValuesBucket = {
record_date: date, weight: seed.weight, steps: seed.steps,
sleep_hours: seed.sleep, heart_rate: seed.heart,
water: seed.water, exercise_minutes: seed.exercise,
remark: seed.remark,
};
await store.insert(HealthDao.TABLE, values);
}
逐日插入:daysAgo(13) 到 daysAgo(0)(今天)循环——日期字符串由工具函数生成,数值按表格映射。幂等保护:查 health COUNT,非空即返回。
与前面实例种子的差异:健康种子是「按日期生成的连续序列」而非「固定数组」——因为 record_date 依赖运行时日期(today/daysAgo),不能写死。动态日期种子是时序类应用的特点。
四、注入后的仪表盘效果预测
| 区块 | 预期效果 |
|---|---|
| 今日概览 | 体重 72.3kg · 步数 8300 · 睡眠 7.5h · 心率 65bpm |
| 体重趋势 | 「↓ 较上周 -1.1kg」绿色(减重中) |
| 睡眠柱状图 | 7 根柱高低错落(6.0~8.0h),今天高亮深蓝 |
| 周汇总 | 日均步数 ~8457 · 日均饮水 ~1585ml · 日均运动 ~32min |
| 记录列表 | 7 行(近 7 天),今天在最上(ASC 倒序看最新?按日期降序显示) |
演示脚本:指着今日概览说「今天体重 72.3、步数 8300、睡眠 7.5 小时」→ 指体重趋势「本周比上周轻了 1.1 公斤,绿色箭头」→ 指柱状图「昨天睡得最好 8 小时,前天熬夜只有 6 小时」→ 指周汇总「日均八千四百步、一千五百毫升水、半小时运动」→ 滚动看 7 天明细 → 点一行编辑(把睡眠改成 8h)→ 柱状图与汇总刷新。
五、验证方法
- hilog 日志:搜
HealthDao,看到「已填充 14 天健康记录」; - 页面显示:今日概览 72.3/8300/7.5/65、趋势「↓ -1.1kg」、柱状图 7 柱、周汇总有值;
- 数据库直查:
SELECT COUNT(*) FROM health; -- 14 SELECT AVG(weight) FROM health WHERE record_date >= '2024-06-19'; -- ≈72.6(本周) SELECT AVG(weight) FROM health WHERE record_date >= '2024-06-12' AND record_date <= '2024-06-18'; -- ≈73.7(上周) SELECT AVG(steps) FROM health; -- ≈8400 SELECT MAX(sleep_hours) FROM health; -- 8.0 - 交互验证:新增(今天已有 → 提示编辑)、编辑、删除、刷新。
六、FAQ
Q1:为什么体重是「缓慢下降」而不是波动?
A:体重趋势是页面的叙事核心——「↓ -1.1kg 绿色」需要两周均值差。缓慢下降(74.2→72.3)让趋势箭头有说服力。真实健康数据也是「微趋势 + 日波动」(73.7→73.5→73.8),种子数据略去噪音突出趋势。种子数据要「讲一个趋势故事」。
Q2:为什么今天必须有记录?
A:今日概览卡显示 todayRecord——今天没记录则四格全显示「-」,仪表盘失去「今日」的即时感。today 有数据是健康 Demo 的必然要求(用户打开 App 先看今天)。
Q3:步数为什么从 4500 到 11000 波动这么大?
A:真实步数就是「工作日 vs 运动日」的波动——4500(熬夜/久坐)、11000(晨跑)。备注(跑了 5km / 熬夜)解释异常值,让数据「讲得通」。数值波动的背后要有备注支撑。
Q4:14 天的数据量够吗?
A:近 7 天柱状图 + 周汇总 + 上周对比——14 天恰好覆盖「本周 7 天 + 上周 7 天」两个统计窗口。再少(7 天)上周对比没数据,再多(30 天)Demo 冗余。种子数量 = 统计窗口的覆盖需求。
Q5:插入的日期顺序为什么从旧到新?
A:for (let i = 13; i >= 0; i--)——先插 13 天前再插今天。虽然插入顺序不影响查询结果(排序在查询时做),但从旧到新让 id 与日期同序(id 小的日期旧),便于调试。种子插入顺序从旧到新是时序数据的习惯。
Q6:体重 72.3 这种小数怎么保证精度?
A:weight 是 REAL 类型,72.3 原样存储。显示时 toFixed(1) 保持一位小数。REAL 存小数 + 展示层格式化——数据层不截断,展示层控制精度。
七、文章小结
本篇文章展示了实例 14 的种子数据:14 天连续健康记录(体重下降趋势 74.2→72.3 + 步数/睡眠起伏 + 今天有数据)。核心是「趋势故事的种子设计」——体重缓慢下降让趋势箭头(↓ -1.1kg 绿)有说服力,睡眠起伏让柱状图高低错落,备注解释异常值。14 天恰好覆盖本周 + 上周两个统计窗口,仪表盘一开屏就数字饱满。
下一篇(14-5)是实例 14 的收官文章,展示 HealthPage 全量代码与运行效果。
八、附录:种子数据的代码级补全与效果复盘
本附录把实例 14 种子数据「从表格到代码」的完整链路补齐:14 天种子表的字段级数值、initSeedData 全量代码、注入后的仪表盘效果预测与验证 SQL——方便直接对照落地版代码逐行核对。
8.1 14 天种子数据表补全(含设计意图列)
| 日期(距今天) | 体重kg | 步数 | 睡眠h | 心率 | 饮水ml | 运动min | 备注 | 设计意图 |
|---|---|---|---|---|---|---|---|---|
| 13 天前 | 74.2 | 6000 | 6.5 | 68 | 1200 | 20 | 加班 | 体重起点(14 天最高值) |
| 12 天前 | 74.0 | 8200 | 7.0 | 67 | 1500 | 30 | 平稳回落 | |
| 11 天前 | 73.8 | 4500 | 6.0 | 70 | 1000 | 15 | 熬夜 | 步数/睡眠双谷值 |
| 10 天前 | 73.9 | 9800 | 7.5 | 65 | 1800 | 40 | 运动日反弹 | |
| 9 天前 | 73.6 | 7600 | 8.0 | 64 | 1600 | 35 | 跑了 5km | 睡眠峰值(运动助眠) |
| 8 天前 | 73.7 | 5200 | 6.5 | 69 | 1100 | 20 | 小低谷 | |
| 7 天前 | 73.4 | 9000 | 7.0 | 66 | 1700 | 30 | 上周收尾 | |
| 6 天前 | 73.5 | 6800 | 6.8 | 67 | 1400 | 25 | 本周第一日 | |
| 5 天前 | 73.2 | 11000 | 7.8 | 63 | 1900 | 45 | 晨跑 | 步数峰值(11000) |
| 4 天前 | 73.0 | 8800 | 7.2 | 65 | 1600 | 30 | ||
| 3 天前 | 73.1 | 5800 | 6.5 | 68 | 1200 | 20 | ||
| 2 天前 | 72.8 | 10200 | 7.6 | 63 | 1800 | 40 | ||
| 1 天前 | 72.5 | 9200 | 7.4 | 64 | 1700 | 35 | 游泳 | |
| 今天 | 72.3 | 8300 | 7.5 | 65 | 1500 | 30 | 今日概览数据源 |
补全说明:原表格只给了数值,这里补上「设计意图」列——每一行都能讲出「为什么是这个数」。种子数据不是随机数,而是**「趋势 + 事件」双线叙事**:趋势线(体重 74.2→72.3)保证统计结果稳定,事件线(熬夜/晨跑/游泳)让柱状图起伏有解释。
8.2 种子设计复盘
三件事是这套种子的核心:
- 体重下降趋势:14 天从 74.2kg 缓降到 72.3kg(-1.9kg),日均约 -0.14kg。上周均值(73.7)vs 本周均值(72.6)差 -1.1kg——趋势箭头「↓ 较上周 -1.1kg」绿色成立。若体重随机波动(72 与 74 交替),两周均值差趋近 0,箭头就消失了——趋势必须「缓慢且单向」;
- 步数起伏:4500(熬夜久坐)到 11000(晨跑)——跨度 6500 步,柱状图高低分明。且异常值都有备注解释(熬夜/跑了 5km/游泳),用户点开明细「讲得通」——数值波动的背后要有备注支撑;
- 今天有数据:today 行(72.3/8300/7.5/65)是今日概览卡的数据源——没有它四格全「-」,仪表盘失去开屏即用的即时感。
复盘结论:种子数据的价值 = 让页面每个区块「首屏即有故事」。数值要服务于页面结构——统计窗口 7 天 → 数据至少 14 天;趋势箭头 → 两周均值差;今日卡 → today 必须有记录。
8.3 initSeedData 全量代码
static async initSeedData(context: common.Context): Promise<void> {
const store = await HealthDao.getStore(context);
// 幂等保护:已有记录直接返回,避免重复填充冲掉用户数据
const countResult = await store.querySql(
`SELECT COUNT(*) AS c FROM ${HealthDao.TABLE}`);
if (countResult.goToNextRow() &&
countResult.getLong(countResult.getColumnIndex('c')) > 0) {
hilog.info(0x0000, 'HealthDao', '健康数据已存在,跳过种子');
return;
}
// 14 天种子:从旧到新插入,id 与日期同序,便于调试
for (let i = 13; i >= 0; i--) {
const date = HealthDao.daysAgo(i);
const seed = seedByIndex(i); // 按偏移取 8.1 表格对应数值
const values: relationalStore.ValuesBucket = {
record_date: date,
weight: seed.weight,
steps: seed.steps,
sleep_hours: seed.sleep,
heart_rate: seed.heart,
water: seed.water,
exercise_minutes: seed.exercise,
remark: seed.remark,
};
await store.insert(HealthDao.TABLE, values);
}
hilog.info(0x0000, 'HealthDao', '已填充 14 天健康记录');
}
seedByIndex(i) 返回 { weight, steps, sleep, heart, water, exercise, remark } 结构——i 从 13(13 天前)到 0(今天)逐行映射 8.1 表格。动态日期种子的关键:日期由 daysAgo(i) 运行时生成,数值由固定表驱动——日期动态、数值可控,两者解耦。
8.4 注入后的仪表盘效果预测
| 区块 | 预期效果 | 数据来源 |
|---|---|---|
| 今日概览 | 体重 72.3kg · 步数 8300 · 睡眠 7.5h · 心率 65bpm | today 行(daysAgo(0)) |
| 体重趋势 | 「↓ 较上周 -1.1kg」绿色箭头(减重健康) | 本周均值 72.6 vs 上周均值 73.7 |
| 睡眠柱状图 | 7 根柱 6.0~8.0h 高低错落,今天高亮深蓝 | daysAgo(6)~today 范围查询 |
| 周汇总 | 日均步数 ~8457 · 日均饮水 ~1585ml · 日均运动 ~32min | 本周 7 天 AVG |
| 记录列表 | 7 行明细,今天在最上(日期降序) | 近 7 天查询 |
演示话术:今日概览「今天 72.3 公斤、八千三百步、睡了七小时半」→ 趋势「这周比上周轻了 1.1 公斤」→ 柱状图「前天熬夜只睡 6 小时,前天跑了 5 公里那天睡了 8 小时」→ 周汇总「日均八千四百步、一千五百毫升水」。
8.5 验证 SQL(直查数据库核对)
-- 总数:应为 14
SELECT COUNT(*) FROM health;
-- 本周体重均值:≈72.6(减重趋势的「今」)
SELECT AVG(weight) FROM health
WHERE record_date >= (SELECT date('now', '-6 day'));
-- 上周体重均值:≈73.7(趋势对比的「昨」)
SELECT AVG(weight) FROM health
WHERE record_date >= (SELECT date('now', '-13 day'))
AND record_date < (SELECT date('now', '-6 day'));
-- 步数峰值:11000(晨跑日)
SELECT MAX(steps) FROM health;
-- 睡眠峰值:8.0(跑了 5km 日)
SELECT MAX(sleep_hours) FROM health;
-- 今日记录:应返回 1 行(72.3/8300/7.5/65)
SELECT * FROM health WHERE record_date = (SELECT date('now'));
与正文第五节的区别:这里用 date('now', '-6 day') 动态日期,不写死 ‘2024-06-19’——演示环境日期会变,验证 SQL 与种子一样要「动态日期」。
8.6 FAQ
Q1:为什么在 seedByIndex 里写死 14 组数,而不是用公式生成?
A:公式生成的数值「太均匀」——真实健康数据恰恰是不均匀的(熬夜日步数骤降、运动日睡眠好)。写死 14 组数让异常值(4500 步、6.0h)可控、备注可对应,页面「讲得出故事」。种子数据优先「可控」而非「随机」。
Q2:initSeedData 为什么每次启动都查 COUNT?
A:幂等保护。健康页 onPageShow 会调用 initSeedData,若用户已新增/删除过记录,COUNT>0 直接跳过——不会把用户数据冲掉。种子只在空库执行一次,之后由用户数据接管。
Q3:今天(today)的记录是怎么保证存在的?
A:循环 i 从 13 到 0,daysAgo(0) 就是当天——种子天然包含 today 行。这是「动态日期种子」相比「固定数组种子」的优势:不管哪天打开 App,今天都有数据,今日概览卡永不落空。
Q4:周汇总的日均值为什么有小数点(8457)?
A:AVG(steps) 返回 REAL(8457.14…),展示层 Math.round 取整。数据层保留精度、展示层格式化——与体重 toFixed(1) 同一原则,数据层不截断,展示层控制精度。
Q5:如果演示当天恰好跨周(比如周三),趋势对比还准吗?
A:准。「本周」按 daysAgo(6)~today 滚动取 7 天,「上周」取前 7 天——两个窗口永远相邻且各 7 天,与日历周无关。滚动窗口让趋势对比在任何演示日期都成立。
更多推荐




所有评论(0)