数据洞察中心

最近 30 天 · 训练 · 恢复 · 睡眠 · 饮食 综合视图 · 截至 2026-07-08

今日健康环
Apple 风格三环 · 健身 / 饮食 / 睡眠 每日目标完成度
实时同步中
78%
健身环
102%
饮食环
--
睡眠环
周训练容量
14,610kg
▲ 8.2% 较上周
平均睡眠
6.9h
▲ 0.3h 较上周
日均蛋白
144g
▲ 6.0% 较上周
恢复 HRV
69ms
▲ 5 ms 较上周

🏋️ 健身模块

周期 · 聚焦 · 4维 · 记录
📅 10天滚动周期(3轮)· 绿=完成 灰=未完 蓝边框=当前 | HC=橙 MC=蓝 LC=绿
W1
胸
D1
背
D2
腿
D3
肩
D4
休
D5
胸
D6
背
D7
腿
D8
休
D9
胸
D10
W2
背
D1
腿
D2
肩
D3
休
D4
胸
D5
背
D6
腿
D7
肩
D8
休
D9
胸
D10
W3
腿
D1
肩
D2
休
D3
胸
D4
背
D5
腿
D6
肩
D7
休
D8
胸
D9
背
D10
② 训练聚焦卡片
肩·腹₂ · 2026-07-08 · LC 低碳日 待完成
上次训练: 2026-07-04 背·二₂ · RPE --
③ 健身环 4 维拆解
容量进步%82%
RPE 健康度88%
部位平衡75%
训练频率90%
④ 今日训练记录(动作 × 重量 × 组数,可编辑)
动作重量 (kg)组数次数完成
待开始
待开始
待开始
待开始
待开始

🍽️ 饮食模块

宏量 · 趋势 · 碳循环 · 矩阵
📌 今日 LC 日(肩·腹₂)| 目标: P139g C83g F69g | 摄入: P--g C--g F--g --kcal
--/--g
🥩 蛋白质
尚未记录
--/--g
🍚 碳水
尚未记录
--/--g
🥑 脂肪
尚未记录
----/--
🔥 热量
尚未记录
② 30天热量趋势(消耗 kcal)
MC 中碳 HC 高碳 LC 低碳 休 休息
③ 碳循环轴:D1–D10(色块背景)
D1MC
D2HC
D3HC
D4LC
D5LC
D6MC
D7HC
D8HC
D9LC
D10LC
MC(胸·三) → HC(背·二) → HC(腿) → LC(肩·腹) → LC(休) → MC(胸·三₂) → HC(背·二₂) → HC(腿₂) → LC(肩·腹₂) → LC(休)
④ IF/ELSE 饮食环权重矩阵(动态计算)
--%
蛋白质: 尚未记录
--%
碳水: 尚未记录
--%
脂肪: 尚未记录
--%
热量: 尚未记录
IF 蛋白≥2.0g/kg → score=100%   ELSE 蛋白/139g × 100%
IF 碳水∈[83,104](LC) → score=100%   ELSE min(100, 碳水/83 × 100)
IF 脂肪≤69g → score=100%   ELSE min(100, 69/脂肪 × 100)
综合=加权平均(蛋白30%, 碳水30%, 脂肪20%, 热量20%)

😴 睡眠模块

趋势 · 阶段 · 对比 · 散点
① 睡眠时长趋势(近 7 天,训练日=橙 / 休息日=紫)
训练日 休息日
② 睡眠阶段堆叠(深睡 / REM / 核心 / 清醒)
深睡 REM 核心 清醒
6.8h
🏋️ 训练日均睡眠
样本: 20 天
7.2h
🛌 休息日均睡眠
样本: 2 天 ⚠️偏少
差值: -0.4h(训练日比休息日少睡 0.4h)
④ 入睡 / 起床时间散点(每天 2 点)
入睡 起床

📊 数据洞察中心

双轴 · AI 洞察 · 细分指标

每日训练容量与睡眠时长趋势

左轴 容量 / 右轴 睡眠 · 近 30 天
训练容量 (kg) 睡眠时长 (h)
训练 → 恢复正相关强度

近期训练量逐周抬升,次日 HRV 同步 +5ms,说明身体恢复能力良好,可维持当前负荷区间。

睡眠影响训练表现建议

睡眠 <6h 的次日训练容量平均下降 12%。建议训练后 30min 拉伸助眠,稳定睡眠基线。

蛋白摄入达标营养

本周日均蛋白 144g ≈ 2.0g/kg,已达增肌目标区间,继续保持以维持瘦体重增长。

月度目标达成

训练 · 营养 · 恢复 综合

五维健康评分

力量 / 耐力 / 恢复 / 睡眠 / 饮食

训练量 与 HRV 相关

Pearson r = 0.81 · 强正相关

体成分趋势

近 7 周 · 体重 / 体脂 / 瘦体重
📦 数据联动 · 身体成分 · 基础健康 · 完整数据表
🔗 数据联动
📐 身体成分趋势
体重 (kg)
体脂 %
BMI
瘦体重 (kg)
💓 基础健康
心率 bpm
HRV ms
血氧 %
呼吸率 /min
📋 完整数据表(24 天 × 多指标)
// ===================== 真实数据驱动 ===================== const D={"generated_at": "2026-07-07 14:48", "health": [{"d": "2026-07-07", "short": "07", "steps": 0, "sleep": 0.0, "wt": null, "hr_max": null, "hrv": null, "kcal": 0, "day_type": "HC", "last_sync": "2026-07-07T14:48:40.357436"}, {"d": "2026-06-06", "short": "06-06", "steps": 22910, "dist_km": 15.1, "cyc_km": 0.0, "kcal": 500, "basal": 1570, "total": 2070, "ex": 48, "wo": 1, "hr_max": 148, "hr_mean": 120.2, "rhr": 52, "hrv": 45, "whr": 137, "spo2": 98, "rr": 12.9, "stand_h": 7, "fl": 1, "vo2": null, "sleep": 1.32, "deep": 0.0, "rem": 0.0, "core": 0.0, "awake": 0.03, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "MC", "day_number": 6, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 20, "color": "red", "status": "睡眠不足 1.3h"}, "basal_noise_filtered": 1154}, {"d": "2026-06-07", "short": "06-07", "steps": 18868, "dist_km": 13.0, "cyc_km": 0.0, "kcal": 806, "basal": 1585, "total": 2391, "ex": 55, "wo": 1, "hr_max": 147, "hr_mean": 106.4, "rhr": 52, "hrv": 32, "whr": 82, "spo2": 98, "rr": 13.4, "stand_h": 17, "fl": 0, "vo2": null, "sleep": 5.05, "deep": 0.86, "rem": 0.65, "core": 3.32, "awake": 0.23, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 7, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 72.14285714285714, "color": "red", "status": "睡眠不足 5.0h"}, "basal_noise_filtered": 1276}, {"d": "2026-06-08", "short": "06-08", "steps": 27613, "dist_km": 18.6, "cyc_km": 0.4, "kcal": 1345, "basal": 1464, "total": 2809, "ex": 174, "wo": 3, "hr_max": 165, "hr_mean": 104.0, "rhr": 52, "hrv": 40, "whr": 79, "spo2": 97, "rr": 12.7, "stand_h": 13, "fl": 1, "vo2": null, "sleep": 5.79, "deep": 1.1, "rem": 1.12, "core": 3.5, "awake": 0.07, "wt": 69.3, "bf": 18.9, "bmi": 22.8, "lm": 56.2, "day_type": "HC", "day_number": 8, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 82.71428571428572, "color": "red", "status": "睡眠不足 5.8h"}, "basal_noise_filtered": 2696}, {"d": "2026-06-09", "short": "06-09", "steps": 23488, "dist_km": 16.6, "cyc_km": 2.5, "kcal": 1118, "basal": 1456, "total": 2574, "ex": 146, "wo": 4, "hr_max": 168, "hr_mean": 108.3, "rhr": 54, "hrv": 42, "whr": 144, "spo2": 97, "rr": 12.5, "stand_h": 12, "fl": 1, "vo2": null, "sleep": 6.34, "deep": 1.1, "rem": 1.77, "core": 3.33, "awake": 0.14, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "LC", "day_number": 9, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 90.57142857142857, "color": "yellow", "status": "睡眠尚可 6.3h"}, "basal_noise_filtered": 2590}, {"d": "2026-06-10", "short": "06-10", "steps": 23184, "dist_km": 15.8, "cyc_km": 0.9, "kcal": 839, "basal": 1499, "total": 2338, "ex": 59, "wo": 1, "hr_max": 162, "hr_mean": 102.9, "rhr": 56, "hrv": 40, "whr": 87, "spo2": 98, "rr": 12.1, "stand_h": 14, "fl": 1, "vo2": null, "sleep": 7.15, "deep": 0.71, "rem": 2.17, "core": 4.11, "awake": 0.17, "wt": 69.7, "bf": 19.5, "bmi": 23, "lm": 56.1, "day_type": "LC", "day_number": 10, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.2h"}, "basal_noise_filtered": 1353}, {"d": "2026-06-11", "short": "06-11", "steps": 23330, "dist_km": 15.4, "cyc_km": 0.9, "kcal": 1222, "basal": 1480, "total": 2702, "ex": 164, "wo": 3, "hr_max": 161, "hr_mean": 104.4, "rhr": 55, "hrv": 39, "whr": 139, "spo2": 97, "rr": 12.4, "stand_h": 15, "fl": 1, "vo2": null, "sleep": 7.48, "deep": 1.42, "rem": 1.76, "core": 4.13, "awake": 0.17, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "MC", "day_number": 1, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.5h"}, "basal_noise_filtered": 2577}, {"d": "2026-06-12", "short": "06-12", "steps": 19155, "dist_km": 12.8, "cyc_km": 0.6, "kcal": 518, "basal": 1603, "total": 2121, "ex": 36, "wo": 0, "hr_max": 111, "hr_mean": 74.6, "rhr": 52, "hrv": 37, "whr": 76, "spo2": 96, "rr": 13.9, "stand_h": 14, "fl": 1, "vo2": 50.3, "sleep": 6.7, "deep": 0.75, "rem": 1.5, "core": 2.86, "awake": 1.59, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 2, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 95.71428571428572, "color": "yellow", "status": "睡眠尚可 6.7h"}, "basal_noise_filtered": 305}, {"d": "2026-06-13", "short": "06-13", "steps": 3188, "dist_km": 1.9, "cyc_km": 0.0, "kcal": 41, "basal": 1641, "total": 1682, "ex": 1, "wo": 0, "hr_max": 76, "hr_mean": 51.6, "rhr": 54, "hrv": 46, "whr": 85.0, "spo2": 97, "rr": 13.3, "stand_h": 3, "fl": 1, "vo2": null, "sleep": 8.03, "deep": 0.76, "rem": 1.75, "core": 3.75, "awake": 1.76, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 3, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 8.0h"}, "basal_noise_filtered": 14}, {"d": "2026-06-14", "short": "06-14", "steps": 18457, "dist_km": 12.6, "cyc_km": 3.7, "kcal": 960, "basal": 1538, "total": 2498, "ex": 128, "wo": 4, "hr_max": 154, "hr_mean": 97.5, "rhr": 52, "hrv": 36, "whr": 136, "spo2": 96, "rr": 13.8, "stand_h": 16, "fl": 1, "vo2": null, "sleep": 0, "deep": 1.46, "rem": 1.32, "core": 3.23, "awake": 6.02, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "LC", "day_number": 4, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 0, "color": "gray", "status": "暂无数据"}, "basal_noise_filtered": 2327}, {"d": "2026-06-15", "short": "06-15", "steps": 17731, "dist_km": 12.1, "cyc_km": 0.0, "kcal": 687, "basal": 1557, "total": 2244, "ex": 61, "wo": 1, "hr_max": 170, "hr_mean": 104.7, "rhr": 54, "hrv": 34, "whr": 145, "spo2": 99, "rr": 12.5, "stand_h": 12, "fl": 0, "vo2": null, "sleep": 7.14, "deep": 1.15, "rem": 1.8, "core": 3.6, "awake": 0.58, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "LC", "day_number": 5, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.1h"}, "basal_noise_filtered": 1139}, {"d": "2026-06-16", "short": "06-16", "steps": 19405, "dist_km": 13.1, "cyc_km": 0.0, "kcal": 1347, "basal": 1494, "total": 2841, "ex": 194, "wo": 3, "hr_max": 171, "hr_mean": 109.8, "rhr": 55, "hrv": 33, "whr": 155, "spo2": 98, "rr": 12.3, "stand_h": 15, "fl": 1, "vo2": null, "sleep": 7.63, "deep": 1.09, "rem": 1.86, "core": 4.01, "awake": 0.67, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "MC", "day_number": 6, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.6h"}, "basal_noise_filtered": 2708}, {"d": "2026-06-17", "short": "06-17", "steps": 9060, "dist_km": 6.7, "cyc_km": 2.7, "kcal": 455, "basal": 1603, "total": 2058, "ex": 42, "wo": 1, "hr_max": 172, "hr_mean": 86.9, "rhr": 57, "hrv": 37, "whr": 83, "spo2": 98, "rr": 12.9, "stand_h": 14, "fl": 0, "vo2": null, "sleep": 6.82, "deep": 1.35, "rem": 1.52, "core": 2.88, "awake": 1.08, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 7, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 97.42857142857143, "color": "yellow", "status": "睡眠尚可 6.8h"}, "basal_noise_filtered": 393}, {"d": "2026-06-18", "short": "06-18", "steps": 12648, "dist_km": 8.4, "cyc_km": 0.0, "kcal": 324, "basal": 1668, "total": 1992, "ex": 13, "wo": 0, "hr_max": 108, "hr_mean": 69.0, "rhr": 53, "hrv": 32, "whr": 93, "spo2": 98, "rr": 13.4, "stand_h": 11, "fl": 0, "vo2": null, "sleep": 0, "deep": 1.48, "rem": 1.88, "core": 3.96, "awake": 3.06, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 8, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 0, "color": "gray", "status": "暂无数据"}, "basal_noise_filtered": 115}, {"d": "2026-06-19", "short": "06-19", "steps": 2118, "dist_km": 1.5, "cyc_km": 0.0, "kcal": 266, "basal": 1742, "total": 2008, "ex": 0, "wo": 0, "hr_max": 100, "hr_mean": 65.4, "rhr": null, "hrv": 36, "whr": null, "spo2": 97, "rr": null, "stand_h": 11, "fl": 0, "vo2": null, "sleep": 0.0, "deep": 0.0, "rem": 0.0, "core": 0.0, "awake": 0.0, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "LC", "day_number": 9, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 0, "color": "gray", "status": "暂无数据"}, "basal_noise_filtered": 16}, {"d": "2026-06-21", "short": "06-21", "steps": 18894, "dist_km": 12.7, "cyc_km": 0.0, "kcal": 1112, "basal": 1466, "total": 2578, "ex": 151, "wo": 3, "hr_max": 161, "hr_mean": 107.0, "rhr": 53, "hrv": 34, "whr": 82.5, "spo2": 96, "rr": 13.5, "stand_h": 15, "fl": 1, "vo2": null, "sleep": 7.54, "deep": 1.11, "rem": 0.63, "core": 3.3, "awake": 2.49, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "MC", "day_number": 1, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.5h"}, "basal_noise_filtered": 2475}, {"d": "2026-06-22", "short": "06-22", "steps": 16739, "dist_km": 11.5, "cyc_km": 0.0, "kcal": 712, "basal": 1490, "total": 2202, "ex": 114, "wo": 2, "hr_max": 156, "hr_mean": 81.9, "rhr": 53, "hrv": 38, "whr": 74, "spo2": 97, "rr": 12.4, "stand_h": 10, "fl": 1, "vo2": null, "sleep": 7.38, "deep": 1.55, "rem": 1.68, "core": 4.0, "awake": 0.15, "wt": 69.7, "bf": 19.6, "bmi": 22.9, "lm": 56.0, "day_type": "HC", "day_number": 2, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.4h"}, "basal_noise_filtered": 1322}, {"d": "2026-06-23", "short": "06-23", "steps": 12327, "dist_km": 8.3, "cyc_km": 0.0, "kcal": 914, "basal": 1611, "total": 2525, "ex": 125, "wo": 2, "hr_max": 147, "hr_mean": 89.4, "rhr": 53, "hrv": 33, "whr": 80, "spo2": 98, "rr": 12.5, "stand_h": 18, "fl": 1, "vo2": null, "sleep": 2.87, "deep": 0.0, "rem": 0.0, "core": 0.0, "awake": 0.18, "wt": 70.2, "bf": 19.9, "bmi": 23.1, "lm": 56.2, "day_type": "HC", "day_number": 3, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 41.0, "color": "red", "status": "睡眠不足 2.9h"}, "basal_noise_filtered": 1634}, {"d": "2026-06-24", "short": "06-24", "steps": 9276, "dist_km": 6.1, "cyc_km": 0.0, "kcal": 432, "basal": 1710, "total": 2142, "ex": 6, "wo": 0, "hr_max": 138, "hr_mean": 65.0, "rhr": 54, "hrv": 32, "whr": 81, "spo2": 96, "rr": 12.9, "stand_h": 16, "fl": 0, "vo2": null, "sleep": 3.96, "deep": 0.81, "rem": 0.73, "core": 2.18, "awake": 0.24, "wt": 69.8, "bf": 19.3, "bmi": 23, "lm": 56.3, "day_type": "LC", "day_number": 4, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 56.57142857142857, "color": "red", "status": "睡眠不足 4.0h"}, "basal_noise_filtered": 152}, {"d": "2026-06-26", "short": "06-26", "steps": 8352, "dist_km": 5.7, "cyc_km": 0.0, "kcal": 319, "basal": 1681, "total": 2000, "ex": 6, "wo": 0, "hr_max": 112, "hr_mean": 68.0, "rhr": 55, "hrv": 34, "whr": 111, "spo2": 97, "rr": 12.6, "stand_h": 11, "fl": 0, "vo2": null, "sleep": 6.15, "deep": 1.2, "rem": 1.71, "core": 3.17, "awake": 0.08, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "MC", "day_number": 6, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 87.85714285714286, "color": "yellow", "status": "睡眠尚可 6.2h"}, "basal_noise_filtered": 137}, {"d": "2026-06-29", "short": "06-29", "steps": 7888, "dist_km": 5.3, "cyc_km": 0.0, "kcal": 294, "basal": 1626, "total": 1920, "ex": 16, "wo": 0, "hr_max": 149, "hr_mean": 79.8, "rhr": 60, "hrv": 36, "whr": 89, "spo2": 97, "rr": 13.0, "stand_h": 9, "fl": 9, "vo2": null, "sleep": 7.39, "deep": 1.19, "rem": 2.16, "core": 3.9, "awake": 0.14, "wt": 69.2, "bf": 19.4, "bmi": 22.8, "lm": 55.8, "day_type": "LC", "day_number": 9, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.4h"}, "basal_noise_filtered": 179}, {"d": "2026-06-30", "short": "06-30", "steps": 17954, "dist_km": 12.1, "cyc_km": 0.0, "kcal": 682, "basal": 1669, "total": 2351, "ex": 71, "wo": 1, "hr_max": 124, "hr_mean": 80.8, "rhr": 58, "hrv": 29, "whr": 84, "spo2": 97, "rr": 13.1, "stand_h": 16, "fl": 1, "vo2": null, "sleep": 4.9, "deep": 1.31, "rem": 1.2, "core": 2.37, "awake": 0.03, "wt": 69.4, "bf": 19.5, "bmi": 22.9, "lm": 55.9, "day_type": "MC", "day_number": 1, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 70.0, "color": "red", "status": "睡眠不足 4.9h"}, "basal_noise_filtered": 260}, {"d": "2026-07-01", "short": "07-01", "steps": 14253, "dist_km": 9.2, "cyc_km": 0.0, "kcal": 696, "basal": 1592, "total": 2288, "ex": 96, "wo": 2, "hr_max": 141, "hr_mean": 87.7, "rhr": 55, "hrv": 35, "whr": 85.5, "spo2": 97, "rr": 13.1, "stand_h": 13, "fl": 1, "vo2": null, "sleep": 5.8, "deep": 1.29, "rem": 1.49, "core": 2.77, "awake": 0.25, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 2, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 82.85714285714285, "color": "red", "status": "睡眠不足 5.8h"}, "basal_noise_filtered": 1291}, {"d": "2026-07-04", "short": "07-04", "steps": 14943, "dist_km": 9.7, "cyc_km": 0.0, "kcal": 419, "basal": 1676, "total": 2095, "ex": 4, "wo": 0, "hr_max": 112, "hr_mean": 76.3, "rhr": 54, "hrv": 33, "whr": 75, "spo2": 96, "rr": 14.0, "stand_h": 16, "fl": 0, "vo2": null, "sleep": 7.88, "deep": 0.54, "rem": 2.23, "core": 3.92, "awake": 1.19, "wt": 69.8, "bf": 19.7, "bmi": 23, "lm": 56.0, "day_type": "LC", "day_number": 5, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 100, "color": "green", "status": "优质睡眠 7.9h"}, "basal_noise_filtered": 211}, {"d": "2026-07-05", "short": "07-05", "steps": 6914, "dist_km": 4.6, "cyc_km": 0.0, "kcal": 681, "basal": 934, "total": 1615, "ex": 140, "wo": 2, "hr_max": 146, "hr_mean": 105.5, "rhr": null, "hrv": 34, "whr": null, "spo2": 96, "rr": null, "stand_h": 6, "fl": 0, "vo2": null, "sleep": 0.0, "deep": 0.0, "rem": 0.0, "core": 0.0, "awake": 0.0, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "MC", "day_number": 6, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": {"date": "2026-07-05", "muscle": "胸", "rpe": 7, "volume": 5330, "duration_min": 70, "source": "day6_training.md", "_prev_vol": null}, "fitness_ring": {"score": 100, "status": "完成"}, "sleep_ring": {"score": 0, "color": "gray", "status": "暂无数据"}, "basal_noise_filtered": 1699}, {"d": "2026-07-06", "short": "07-06", "steps": 0, "dist_km": 0.0, "cyc_km": 0.0, "kcal": 0, "basal": 0, "total": 0, "ex": 0, "wo": 0, "hr_max": null, "hr_mean": null, "rhr": null, "hrv": null, "whr": null, "spo2": null, "rr": null, "stand_h": 0, "fl": 0, "vo2": null, "sleep": 0.0, "deep": 0.0, "rem": 0.0, "core": 0.0, "awake": 0.0, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 7, "diet": {"protein": 142.2, "carb": 185.0, "fat": 69.9, "kcal": 1921, "date": "2026-07-06"}, "diet_ring": {"score": 76, "color": "yellow", "status": "", "breakdown": {"protein": 102, "carb": 76, "fat": 34}}, "training": {"date": "2026-07-06", "muscle": "背", "rpe": null, "volume": 7910, "duration_min": null, "source": "day7_training.md", "_prev_vol": 5330}, "fitness_ring": {"score": 100, "status": "完成"}, "sleep_ring": {"score": 0, "color": "gray", "status": "暂无数据"}, "basal_noise_filtered": 0}, {"d": "2026-07-07", "short": "07-07", "steps": 0, "dist_km": 0.0, "cyc_km": 0.0, "kcal": 0, "basal": 0, "total": 0, "ex": 0, "wo": 0, "hr_max": 65, "hr_mean": 65.5, "rhr": null, "hrv": 24, "whr": null, "spo2": null, "rr": null, "stand_h": 0, "fl": 0, "vo2": null, "sleep": 0.0, "deep": 0.0, "rem": 0.0, "core": 0.0, "awake": 0.0, "wt": null, "bf": null, "bmi": null, "lm": null, "day_type": "HC", "day_number": 8, "diet_ring": {"score": 0, "color": "gray", "status": "暂无饮食记录"}, "training": null, "fitness_ring": {"score": 0, "status": "今日训练"}, "sleep_ring": {"score": 0, "color": "gray", "status": "暂无数据"}, "basal_noise_filtered": 0}], "fitness_records": {"2026-07-05": {"date": "2026-07-05", "muscle": "胸", "rpe": 7, "volume": 5330, "duration_min": 70, "source": "day6_training.md", "_prev_vol": null}, "2026-07-06": {"date": "2026-07-06", "muscle": "背", "rpe": null, "volume": 7910, "duration_min": null, "source": "day7_training.md", "_prev_vol": 5330}}, "diet_records": [{"protein": 73.3, "carb": 95.2, "fat": 7.9, "kcal": 750, "date": "2026-07-02"}, {"protein": 67.0, "carb": 50.3, "fat": 3.1, "kcal": 484, "date": "2026-07-03"}, {"protein": 142.2, "carb": 185.0, "fat": 69.9, "kcal": 1921, "date": "2026-07-06"}], "cycle_grid": [[{"day": 1, "label": "胸·三", "type": "MC", "date": "2026-06-30", "short": "06-30", "day_number": 1, "completed": false, "is_today": false, "is_future": false}, {"day": 2, "label": "背·二", "type": "HC", "date": "2026-07-01", "short": "07-01", "day_number": 2, "completed": false, "is_today": false, "is_future": false}, {"day": 3, "label": "腿", "type": "HC", "date": "2026-07-02", "short": "07-02", "day_number": 3, "completed": false, "is_today": false, "is_future": false}, {"day": 4, "label": "肩·腹", "type": "LC", "date": "2026-07-03", "short": "07-03", "day_number": 4, "completed": false, "is_today": false, "is_future": false}, {"day": 5, "label": "休息", "type": "LC", "date": "2026-07-04", "short": "07-04", "day_number": 5, "completed": false, "is_today": false, "is_future": false}, {"day": 6, "label": "胸·三₂", "type": "MC", "date": "2026-07-05", "short": "07-05", "day_number": 6, "completed": true, "muscle": "胸", "is_today": false, "is_future": false}, {"day": 7, "label": "背·二₂", "type": "HC", "date": "2026-07-06", "short": "07-06", "day_number": 7, "completed": true, "muscle": "背", "is_today": false, "is_future": false}, {"day": 8, "label": "腿₂", "type": "HC", "date": "2026-07-07", "short": "07-07", "day_number": 8, "completed": false, "is_future": false, "is_today": false}, {"day": 9, "label": "肩·腹₂", "type": "LC", "date": "2026-07-08", "short": "07-08", "day_number": 9, "completed": null, "is_future": false, "is_today": true}, {"day": 10, "label": "休息", "type": "LC", "date": "2026-07-09", "short": "07-09", "day_number": 10, "completed": null, "is_future": true}], [{"day": 1, "label": "胸·三", "type": "MC", "date": "2026-07-10", "short": "07-10", "day_number": 1, "completed": null, "is_future": true}, {"day": 2, "label": "背·二", "type": "HC", "date": "2026-07-11", "short": "07-11", "day_number": 2, "completed": null, "is_future": true}, {"day": 3, "label": "腿", "type": "HC", "date": "2026-07-12", "short": "07-12", "day_number": 3, "completed": null, "is_future": true}, {"day": 4, "label": "肩·腹", "type": "LC", "date": "2026-07-13", "short": "07-13", "day_number": 4, "completed": null, "is_future": true}, {"day": 5, "label": "休息", "type": "LC", "date": "2026-07-14", "short": "07-14", "day_number": 5, "completed": null, "is_future": true}, {"day": 6, "label": "胸·三₂", "type": "MC", "date": "2026-07-15", "short": "07-15", "day_number": 6, "completed": null, "is_future": true}, {"day": 7, "label": "背·二₂", "type": "HC", "date": "2026-07-16", "short": "07-16", "day_number": 7, "completed": null, "is_future": true}, {"day": 8, "label": "腿₂", "type": "HC", "date": "2026-07-17", "short": "07-17", "day_number": 8, "completed": null, "is_future": true}, {"day": 9, "label": "肩·腹₂", "type": "LC", "date": "2026-07-18", "short": "07-18", "day_number": 9, "completed": null, "is_future": true}, {"day": 10, "label": "休息", "type": "LC", "date": "2026-07-19", "short": "07-19", "day_number": 10, "completed": null, "is_future": true}], [{"day": 1, "label": "胸·三", "type": "MC", "date": "2026-07-20", "short": "07-20", "day_number": 1, "completed": null, "is_future": true}, {"day": 2, "label": "背·二", "type": "HC", "date": "2026-07-21", "short": "07-21", "day_number": 2, "completed": null, "is_future": true}, {"day": 3, "label": "腿", "type": "HC", "date": "2026-07-22", "short": "07-22", "day_number": 3, "completed": null, "is_future": true}, {"day": 4, "label": "肩·腹", "type": "LC", "date": "2026-07-23", "short": "07-23", "day_number": 4, "completed": null, "is_future": true}, {"day": 5, "label": "休息", "type": "LC", "date": "2026-07-24", "short": "07-24", "day_number": 5, "completed": null, "is_future": true}, {"day": 6, "label": "胸·三₂", "type": "MC", "date": "2026-07-25", "short": "07-25", "day_number": 6, "completed": null, "is_future": true}, {"day": 7, "label": "背·二₂", "type": "HC", "date": "2026-07-26", "short": "07-26", "day_number": 7, "completed": null, "is_future": true}, {"day": 8, "label": "腿₂", "type": "HC", "date": "2026-07-27", "short": "07-27", "day_number": 8, "completed": null, "is_future": true}, {"day": 9, "label": "肩·腹₂", "type": "LC", "date": "2026-07-28", "short": "07-28", "day_number": 9, "completed": null, "is_future": true}, {"day": 10, "label": "休息", "type": "LC", "date": "2026-07-29", "short": "07-29", "day_number": 10, "completed": null, "is_future": true}]]}; // ===================== KPI 计算 ===================== function calcKPIs() { // Use last 7 entries with real data (skip zero-filled today) const recent = D.health.filter(h => h.steps > 0 || h.sleep > 0 || h.kcal > 0); const last7 = recent.slice(0, 7); const trainDays = last7.filter(h => h.training); const totalVol = trainDays.reduce((sum, h) => sum + (h.training?.volume || 0), 0); const sleepDays = last7.filter(h => h.sleep > 0); const avgSleep = sleepDays.length ? sleepDays.reduce((sum, h) => sum + h.sleep, 0) / sleepDays.length : 0; const dietDays = D.diet_records.filter(d => { return last7.some(h => h.d === d.date); }); const avgProtein = dietDays.length ? dietDays.reduce((sum, d) => sum + d.protein, 0) / dietDays.length : 0; const hrvDays = last7.filter(h => h.hrv); const avgHRV = hrvDays.length ? hrvDays.reduce((sum, h) => sum + h.hrv, 0) / hrvDays.length : 0; return { totalVol, avgSleep, avgProtein, avgHRV, last7, recent }; } const kpis = calcKPIs(); const kpiEls = document.querySelectorAll('.kpi-row .kpi .kv'); if (kpiEls[0]) kpiEls[0].innerHTML = `${kpis.totalVol.toLocaleString()}kg`; if (kpiEls[1]) kpiEls[1].innerHTML = `${kpis.avgSleep.toFixed(1)}h`; if (kpiEls[2]) kpiEls[2].innerHTML = `${Math.round(kpis.avgProtein)}g`; if (kpiEls[3]) kpiEls[3].innerHTML = `${Math.round(kpis.avgHRV)}ms`; // ===================== 环形进度 ===================== function drawRing(id, pct, color) { const c = document.getElementById(id); if (!c) return; const ctx = c.getContext('2d'); const cx = 60, cy = 60, r = 48, lw = 10; ctx.clearRect(0, 0, 120, 120); ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.strokeStyle = '#27272A'; ctx.lineWidth = lw; ctx.stroke(); const a = Math.min(100, pct); if (a > 0) { ctx.beginPath(); ctx.arc(cx, cy, r, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * a / 100); ctx.strokeStyle = color; ctx.lineWidth = lw; ctx.lineCap = 'round'; ctx.stroke(); } } // FIX: Use latest_date properly const latest_date = "2026-07-08"; const today = D.health.find(h => h.d === latest_date); const fitnessPct = today?.fitness_ring?.score || 0; const dietPct = today?.diet_ring?.score || 0; const sleepPct = today?.sleep_ring?.score || 0; drawRing('ring_fitness', fitnessPct, '#F5A623'); drawRing('ring_diet', dietPct, '#4CAF50'); drawRing('ring_sleep', sleepPct, '#4C9AFF'); const ringVals = document.querySelectorAll('.ring-val'); if (ringVals[0]) ringVals[0].textContent = fitnessPct + '%'; if (ringVals[1]) ringVals[1].textContent = dietPct + '%'; if (ringVals[2]) ringVals[2].textContent = (sleepPct > 0 ? sleepPct : '--') + '%'; // ===================== 周期网格 ===================== function renderCycleGrid() { const rows = document.querySelectorAll('.cycle-grid'); (D.cycle_grid || []).forEach((row, wi) => { if (wi >= rows.length) return; const rowEl = rows[wi]; rowEl.innerHTML = ''; row.forEach(cell => { const div = document.createElement('div'); div.className = 'cycle-cell'; if (cell.completed === true) div.classList.add('done'); else if (cell.is_today) div.classList.add('current'); else if (cell.is_future) div.classList.add('future'); div.innerHTML = `
${cell.label}
D${cell.day}
`; rowEl.appendChild(div); }); }); } renderCycleGrid(); // ===================== 饮食模块 ===================== function renderDietModule() { const todayDiet = D.diet_records.find(d => d.date === latest_date); const dietHeader = document.querySelector('.card-sub'); if (dietHeader && todayDiet) { dietHeader.textContent = `今日 ${todayDiet.kcal}kcal · P${todayDiet.protein}g C${todayDiet.carb}g F${todayDiet.fat}g`; } } renderDietModule(); // ===================== 完整数据表 ===================== function buildRealTables() { const TYPEC = { MC: '#4C9AFF', HC: '#F5A623', LC: '#4CAF50', '休': '#6E7681' }; const dietMap = {}; D.diet_records.forEach(d => { dietMap[d.date] = d; }); // 训练表 const trainThead = '日期类型步数热量(消耗)睡眠(h)体重体脂%静息心率HRV血氧'; const trainTbody = D.health.map(h => { const type = h.day_type || 'MC'; const sleepStr = h.sleep > 0 ? h.sleep.toFixed(1) : '-'; return `${h.d}${type}${h.steps}${h.kcal}${sleepStr}${h.wt || '-'}${h.bf || '-'}${h.rhr || '-'}${h.hrv || '-'}${h.spo2 || '-'}`; }).join(''); document.querySelector('#tbl_train thead').innerHTML = trainThead; document.querySelector('#tbl_train tbody').innerHTML = trainTbody; // 饮食表 const dietThead = '日期类型蛋白(g)碳水(g)脂肪(g)热量(摄入)睡眠(h)体重'; const dietTbody = D.health.map(h => { const type = h.day_type || 'MC'; const diet = dietMap[h.d]; const sleepStr = h.sleep > 0 ? h.sleep.toFixed(1) : '-'; return `${h.d}${type}${diet?.protein || '-'}${diet?.carb || '-'}${diet?.fat || '-'}${diet?.kcal || '-'}${sleepStr}${h.wt || '-'}`; }).join(''); document.querySelector('#tbl_diet thead').innerHTML = dietThead; document.querySelector('#tbl_diet tbody').innerHTML = dietTbody; } buildRealTables(); // ===================== 图表渲染 ===================== function renderCharts() { const KC = { MC: '#4C9AFF', HC: '#F5A623', LC: '#4CAF50', '休': '#6E7681' }; // Bar chart helper function barChart(canvasId, items, opts) { const c = document.getElementById(canvasId); if (!c) return; const ctx = c.getContext('2d'); const W = c.parentElement.clientWidth; c.width = W; c.height = opts.h || 200; const H = c.height, padL = opts.padL || 40, padB = 24, padT = 10; ctx.clearRect(0, 0, W, H); const maxV = opts.max; ctx.strokeStyle = '#27272A'; ctx.lineWidth = 1; for (let i = 0; i <= 4; i++) { const y = padT + (H - padT - padB) * i / 4; ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(W - 6, y); ctx.stroke(); ctx.fillStyle = '#8A97A8'; ctx.font = '10px monospace'; ctx.textAlign = 'right'; ctx.fillText(Math.round(maxV * (1 - i / 4)), padL - 4, y + 3); } const n = items.length; const slot = (W - padL - 10) / n; const bw = Math.min(18, slot * 0.6); items.forEach((it, i) => { const x = padL + slot * i + (slot - bw) / 2; const h = (it.v / maxV) * (H - padT - padB); const y = H - padB - h; ctx.fillStyle = it.color + '55'; ctx.fillRect(x, y, bw, h); ctx.strokeStyle = it.color; ctx.lineWidth = 1.2; ctx.strokeRect(x, y, bw, h); ctx.fillStyle = it.color; ctx.font = 'bold 9px monospace'; ctx.textAlign = 'center'; if (opts.showVal) ctx.fillText(it.v, x + bw / 2, y - 3); ctx.fillStyle = '#8A97A8'; ctx.font = '9px monospace'; ctx.fillText(it.label, x + bw / 2, H - padB + 12); }); } // Line chart helper function lineChart(canvasId, datasets, opts) { const c = document.getElementById(canvasId); if (!c) return; const ctx = c.getContext('2d'); const W = c.parentElement.clientWidth; c.width = W; c.height = opts.h || 200; const H = c.height, padL = opts.padL || 40, padB = 24, padT = 10; ctx.clearRect(0, 0, W, H); const maxV = opts.max; ctx.strokeStyle = '#27272A'; ctx.lineWidth = 1; for (let i = 0; i <= 4; i++) { const y = padT + (H - padT - padB) * i / 4; ctx.beginPath(); ctx.moveTo(padL, y); ctx.lineTo(W - 6, y); ctx.stroke(); ctx.fillStyle = '#8A97A8'; ctx.font = '10px monospace'; ctx.textAlign = 'right'; ctx.fillText(Math.round(maxV * (1 - i / 4)), padL - 4, y + 3); } datasets.forEach(ds => { if (!ds.data.length) return; const n = ds.data.length; const slot = (W - padL - 10) / n; ctx.strokeStyle = ds.color; ctx.lineWidth = 2; ctx.beginPath(); ds.data.forEach((v, i) => { const x = padL + slot * i + slot / 2; const y = H - padB - (v / maxV) * (H - padT - padB); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.stroke(); ds.data.forEach((v, i) => { const x = padL + slot * i + slot / 2; const y = H - padB - (v / maxV) * (H - padT - padB); ctx.fillStyle = ds.color; ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fill(); }); ctx.fillStyle = '#8A97A8'; ctx.font = '9px monospace'; ctx.textAlign = 'center'; ds.labels?.forEach((lbl, i) => { const x = padL + slot * i + slot / 2; ctx.fillText(lbl, x, H - padB + 12); }); }); } // Sparkline helper function sparkline(canvasId, data, color) { const c = document.getElementById(canvasId); if (!c) return; const ctx = c.getContext('2d'); const W = c.parentElement.clientWidth; c.width = W; c.height = 34; const H = c.height; ctx.clearRect(0, 0, W, H); if (!data.length) return; const max = Math.max(...data, 1); const step = W / (data.length - 1 || 1); ctx.strokeStyle = color; ctx.lineWidth = 1.5; ctx.beginPath(); data.forEach((v, i) => { const x = i * step; const y = H - (v / max) * (H - 4) - 2; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.stroke(); } // Donut chart helper function donutChart(canvasId, segments, size) { const c = document.getElementById(canvasId); if (!c) return; const ctx = c.getContext('2d'); c.width = size; c.height = size; const cx = size / 2, cy = size / 2, r = size / 2 - 4, lw = size / 5; ctx.clearRect(0, 0, size, size); let start = -Math.PI / 2; segments.forEach(seg => { const sweep = seg.pct * Math.PI * 2; ctx.beginPath(); ctx.arc(cx, cy, r, start, start + sweep); ctx.strokeStyle = seg.color; ctx.lineWidth = lw; ctx.lineCap = 'butt'; ctx.stroke(); start += sweep; }); } // Radar chart helper function radarChart(canvasId, values, maxVal, size) { const c = document.getElementById(canvasId); if (!c) return; const ctx = c.getContext('2d'); c.width = size; c.height = size; const cx = size / 2, cy = size / 2, r = size / 2 - 10; ctx.clearRect(0, 0, size, size); const n = values.length; for (let ring = 1; ring <= 4; ring++) { const rr = (r * ring) / 4; ctx.beginPath(); for (let i = 0; i <= n; i++) { const angle = (Math.PI * 2 * i / n) - Math.PI / 2; const x = cx + rr * Math.cos(angle); const y = cy + rr * Math.sin(angle); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); } ctx.strokeStyle = '#27272A'; ctx.lineWidth = 1; ctx.stroke(); } ctx.beginPath(); values.forEach((v, i) => { const angle = (Math.PI * 2 * i / n) - Math.PI / 2; const vr = (v / maxVal) * r; const x = cx + vr * Math.cos(angle); const y = cy + vr * Math.sin(angle); if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.closePath(); ctx.strokeStyle = '#5BCFC5'; ctx.lineWidth = 2; ctx.stroke(); ctx.fillStyle = 'rgba(91, 207, 197, 0.2)'; ctx.fill(); values.forEach((v, i) => { const angle = (Math.PI * 2 * i / n) - Math.PI / 2; const vr = (v / maxVal) * r; const x = cx + vr * Math.cos(angle); const y = cy + vr * Math.sin(angle); ctx.fillStyle = '#5BCFC5'; ctx.beginPath(); ctx.arc(x, y, 3, 0, Math.PI * 2); ctx.fill(); }); } // Body comp sparkline function bodyCompSparkline(canvasId, data, color) { const c = document.getElementById(canvasId); if (!c) return; const ctx = c.getContext('2d'); const W = c.parentElement.clientWidth; c.width = W; c.height = 70; const H = c.height; ctx.clearRect(0, 0, W, H); if (!data.length) return; const max = Math.max(...data, 1); const min = Math.min(...data, 0); const range = max - min || 1; const step = W / (data.length - 1 || 1); ctx.beginPath(); data.forEach((v, i) => { const x = i * step; const y = H - ((v - min) / range) * (H - 8) - 4; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.lineTo((data.length - 1) * step, H); ctx.lineTo(0, H); ctx.closePath(); ctx.fillStyle = color + '33'; ctx.fill(); ctx.beginPath(); data.forEach((v, i) => { const x = i * step; const y = H - ((v - min) / range) * (H - 8) - 4; if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y); }); ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.stroke(); } // ========== Render all charts ========== // 1. 30-day kcal trend const kcalData = D.health.slice(0, 30).reverse().map(h => ({ label: h.short, v: h.kcal || 0, color: KC[h.day_type] || '#4C9AFF' })); barChart('kcalChart', kcalData, { max: 3000, showVal: false, h: 200 }); // 2. Sleep trend const sleepData = D.health.filter(h => h.sleep > 0).slice(0, 7).reverse(); lineChart('sleepTrend', [{ data: sleepData.map(h => h.sleep), color: '#F5A623', labels: sleepData.map(h => h.short) }], { max: 10, h: 190 }); // 3. Sleep stages (deep sleep) const stageData = D.health.filter(h => h.sleep > 0).slice(0, 5).reverse(); lineChart('sleepStages', [{ data: stageData.map(h => h.deep || 0), color: '#4C9AFF', labels: stageData.map(h => h.short) }], { max: 10, h: 190 }); // 4. KPI sparklines sparkline('sp-cap', kpis.last7.map(h => h.training?.volume || 0), '#22D3EE'); sparkline('sp-slp', kpis.last7.map(h => h.sleep || 0), '#B388FF'); sparkline('sp-pro', kpis.last7.map(h => { const d = D.diet_records.find(r => r.date === h.d); return d?.protein || 0; }), '#5BCFC5'); sparkline('sp-hrv', kpis.last7.map(h => h.hrv || 0), '#F2C94C'); // 5. Donut chart const avgSteps = kpis.last7.reduce((s, h) => s + h.steps, 0) / (kpis.last7.length || 1); donutChart('donut', [ { pct: Math.min(1, avgSteps / 10000), color: '#5BCFC5' }, { pct: Math.min(1, kpis.avgSleep / 8), color: '#22D3EE' }, { pct: Math.min(1, kpis.avgProtein / 139), color: '#B388FF' } ], 120); // 6. Radar chart radarChart('radar', [ Math.min(100, kpis.last7.reduce((s, h) => s + (h.ex || 0), 0) / 5), Math.min(100, avgSteps / 100), Math.min(100, kpis.last7.reduce((s, h) => s + (h.hrv || 0), 0) / 2), Math.min(100, kpis.avgSleep * 14), Math.min(100, kpis.avgProtein / 1.4) ], 100, 140); // 7. Body comp const wtData = D.health.filter(h => h.wt).map(h => h.wt).reverse().slice(-7); bodyCompSparkline('bc_weight', wtData, '#5BCFC5'); const bfData = D.health.filter(h => h.bf).map(h => h.bf).reverse().slice(-7); bodyCompSparkline('bc_fat', bfData, '#22D3EE'); const bmiData = D.health.filter(h => h.bmi).map(h => h.bmi).reverse().slice(-7); bodyCompSparkline('bc_bmi', bmiData, '#B388FF'); const lmData = D.health.filter(h => h.lm).map(h => h.lm).reverse().slice(-7); bodyCompSparkline('bc_lean', lmData, '#F5A623'); // 8. Health sparklines const hrData = D.health.filter(h => h.rhr).map(h => h.rhr).reverse().slice(-14); bodyCompSparkline('sp_hr', hrData, '#F0616D'); const hrvData = D.health.filter(h => h.hrv).map(h => h.hrv).reverse().slice(-14); bodyCompSparkline('sp_hrv2', hrvData, '#F2C94C'); const spo2Data = D.health.filter(h => h.spo2).map(h => h.spo2).reverse().slice(-14); bodyCompSparkline('sp_spo2', spo2Data, '#4CAF50'); const rrData = D.health.filter(h => h.rr).map(h => h.rr).reverse().slice(-14); bodyCompSparkline('sp_rr', rrData, '#B388FF'); // 9. Footer timestamp const footerSpans = document.querySelectorAll('footer span'); if (footerSpans[1]) { footerSpans[1].textContent = `数据更新于 ${D.generated_at} · HealthSync Peak`; } } // Run after DOM ready if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', renderCharts); } else { renderCharts(); } // Tab switching function switchTab(tab) { document.querySelectorAll('.tab-btn').forEach(btn => btn.classList.remove('active')); event.target.classList.add('active'); document.getElementById('tbl_train').style.display = tab === 'train' ? '' : 'none'; document.getElementById('tbl_diet').style.display = tab === 'diet' ? '' : 'none'; }