# 健康模块增强实现计划 > **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. **Goal:** 为运动、饮食、睡眠三个模块添加健康分析功能,包括 BMI 分析、营养建议区间、睡眠预警提示。 **Architecture:** - 新增 3 个 API 端点提供分析数据 - 修改 3 个页面 HTML 模板添加新 UI 组件 - 复用现有 `UserConfig.tdee` 和 `User.bmi` 计算逻辑 **Tech Stack:** FastAPI, Chart.js, 原生 HTML/CSS/JS --- ## 任务概览 | 任务 | 模块 | 预估步骤 | |-----|------|---------| | Task 1 | API: BMI 分析端点 | 5 步 | | Task 2 | API: 营养建议端点 | 5 步 | | Task 3 | API: 睡眠评估端点 | 5 步 | | Task 4 | UI: 运动页面 BMI 卡片 | 5 步 | | Task 5 | UI: 饮食页面营养建议 | 5 步 | | Task 6 | UI: 睡眠页面预警卡片 | 5 步 | --- ## Task 1: API - BMI 分析端点 **Files:** - Modify: `src/vitals/web/app.py:1163` (在 `/api/exercises/stats` 后添加) - Test: `tests/test_web.py` ### Step 1: 写失败测试 在 `tests/test_web.py` 末尾添加: ```python def test_bmi_analysis_endpoint(client): """测试 BMI 分析 API""" response = client.get("/api/bmi/analysis") assert response.status_code == 200 data = response.json() assert "current_weight" in data assert "current_bmi" in data assert "bmi_status" in data assert "target_weight" in data assert "estimated_days" in data ``` ### Step 2: 运行测试确认失败 ```bash cd /Users/rocky/Projects/vitals && python -m pytest tests/test_web.py::test_bmi_analysis_endpoint -v ``` 预期: FAIL - 404 Not Found ### Step 3: 实现 API 端点 在 `src/vitals/web/app.py` 第 1163 行后添加: ```python @app.get("/api/bmi/analysis") async def get_bmi_analysis(): """获取 BMI 分析数据""" active_user = db.get_active_user() if not active_user: raise HTTPException(status_code=400, detail="没有激活的用户") config = db.get_config(active_user.id) # 获取最新体重记录 today = date.today() weight_records = db.get_weight_records( start_date=today - timedelta(days=30), end_date=today, user_id=active_user.id ) current_weight = weight_records[0].weight_kg if weight_records else config.weight # 计算 BMI height_m = (config.height or 170) / 100 current_bmi = round(current_weight / (height_m * height_m), 1) if current_weight else None # BMI 状态 def get_bmi_status(bmi): if bmi is None: return None if bmi < 18.5: return {"status": "偏瘦", "color": "#60A5FA", "range": "<18.5"} elif bmi < 24: return {"status": "正常", "color": "#34D399", "range": "18.5-24"} elif bmi < 28: return {"status": "偏重", "color": "#FBBF24", "range": "24-28"} else: return {"status": "肥胖", "color": "#F87171", "range": ">28"} bmi_info = get_bmi_status(current_bmi) # 目标体重(从用户配置或默认 BMI 22 计算) target_weight = getattr(config, 'target_weight', None) if not target_weight: # 默认目标 BMI 22 target_weight = round(22 * height_m * height_m, 1) target_bmi = round(target_weight / (height_m * height_m), 1) weight_diff = round(current_weight - target_weight, 1) if current_weight else None # 计算预估达成天数 estimated_days = None daily_deficit = None calculation_basis = None if current_weight and target_weight and config.tdee: # 获取近30天饮食数据 meals = db.get_meals( start_date=today - timedelta(days=30), end_date=today, user_id=active_user.id ) # 获取近30天运动数据 exercises = db.get_exercises( start_date=today - timedelta(days=30), end_date=today, user_id=active_user.id ) # 计算平均每日摄入 total_intake = sum(m.calories for m in meals) days_with_data = len(set(m.date for m in meals)) or 1 avg_daily_intake = total_intake / days_with_data # 计算平均每日运动消耗 total_exercise = sum(e.calories for e in exercises) avg_daily_exercise = total_exercise / 30 # 每日净消耗 = TDEE - 摄入 + 运动 daily_deficit = round(config.tdee - avg_daily_intake + avg_daily_exercise) if daily_deficit > 0 and weight_diff > 0: # 7700 卡 ≈ 1 kg estimated_days = round(weight_diff * 7700 / daily_deficit) calculation_basis = { "tdee": config.tdee, "avg_intake": round(avg_daily_intake), "avg_exercise": round(avg_daily_exercise), "daily_deficit": daily_deficit } return { "current_weight": current_weight, "current_bmi": current_bmi, "bmi_status": bmi_info, "height": config.height, "target_weight": target_weight, "target_bmi": target_bmi, "weight_diff": weight_diff, "estimated_days": estimated_days, "calculation_basis": calculation_basis, "has_sufficient_data": bool(calculation_basis) } ``` ### Step 4: 运行测试确认通过 ```bash cd /Users/rocky/Projects/vitals && python -m pytest tests/test_web.py::test_bmi_analysis_endpoint -v ``` 预期: PASS ### Step 5: 提交 ```bash git add src/vitals/web/app.py tests/test_web.py git commit -m "feat(api): 添加 BMI 分析端点 /api/bmi/analysis" ``` --- ## Task 2: API - 营养建议端点 **Files:** - Modify: `src/vitals/web/app.py` (在 BMI API 后添加) - Test: `tests/test_web.py` ### Step 1: 写失败测试 ```python def test_nutrition_recommendations_endpoint(client): """测试营养建议 API""" response = client.get("/api/nutrition/recommendations") assert response.status_code == 200 data = response.json() assert "daily_targets" in data assert "today_intake" in data assert "gaps" in data assert "suggestions" in data ``` ### Step 2: 运行测试确认失败 ```bash cd /Users/rocky/Projects/vitals && python -m pytest tests/test_web.py::test_nutrition_recommendations_endpoint -v ``` 预期: FAIL - 404 Not Found ### Step 3: 实现 API 端点 ```python @app.get("/api/nutrition/recommendations") async def get_nutrition_recommendations(): """获取营养建议数据""" active_user = db.get_active_user() if not active_user: raise HTTPException(status_code=400, detail="没有激活的用户") config = db.get_config(active_user.id) tdee = config.tdee or 1800 # 根据目标调整热量目标 goal_multipliers = { "lose": 0.8, # 减重: 80% TDEE "maintain": 1.0, # 维持: 100% TDEE "gain": 1.15 # 增重: 115% TDEE } calorie_target = int(tdee * goal_multipliers.get(config.goal, 1.0)) # 营养素目标 (基于 TDEE) # 蛋白质 25%, 碳水 50%, 脂肪 25% daily_targets = { "calories": calorie_target, "protein": round(calorie_target * 0.25 / 4), # 每克蛋白质 4 卡 "carbs": round(calorie_target * 0.50 / 4), # 每克碳水 4 卡 "fat": round(calorie_target * 0.25 / 9), # 每克脂肪 9 卡 } # 建议区间 recommended_ranges = { "protein": {"min": round(calorie_target * 0.20 / 4), "max": round(calorie_target * 0.30 / 4)}, "carbs": {"min": round(calorie_target * 0.45 / 4), "max": round(calorie_target * 0.55 / 4)}, "fat": {"min": round(calorie_target * 0.20 / 9), "max": round(calorie_target * 0.30 / 9)}, } # 今日摄入 today = date.today() today_meals = db.get_meals(start_date=today, end_date=today, user_id=active_user.id) today_intake = { "calories": sum(m.calories for m in today_meals), "protein": sum(m.protein or 0 for m in today_meals), "carbs": sum(m.carbs or 0 for m in today_meals), "fat": sum(m.fat or 0 for m in today_meals), } # 计算差距 gaps = { "calories": daily_targets["calories"] - today_intake["calories"], "protein": daily_targets["protein"] - today_intake["protein"], "carbs": daily_targets["carbs"] - today_intake["carbs"], "fat": daily_targets["fat"] - today_intake["fat"], } # 餐次分布 meal_type_calories = {} for m in today_meals: meal_type_calories[m.meal_type] = meal_type_calories.get(m.meal_type, 0) + m.calories meal_distribution = { "早餐": {"actual": meal_type_calories.get("早餐", 0), "recommended_pct": "25-30%"}, "午餐": {"actual": meal_type_calories.get("午餐", 0), "recommended_pct": "35-40%"}, "晚餐": {"actual": meal_type_calories.get("晚餐", 0), "recommended_pct": "25-30%"}, "加餐": {"actual": meal_type_calories.get("加餐", 0), "recommended_pct": "0-10%"}, } # 生成建议 suggestions = [] if gaps["protein"] > 20: suggestions.append({ "type": "protein", "message": f"蛋白质缺口较大 ({gaps['protein']}g),建议增加:", "options": [ "鸡胸肉 150g (+46g 蛋白质, 165 卡)", "鸡蛋 3 个 (+18g 蛋白质, 210 卡)", "豆腐 200g (+16g 蛋白质, 160 卡)", ] }) if gaps["calories"] > 300: suggestions.append({ "type": "calories", "message": f"热量摄入不足 ({gaps['calories']} 卡),注意补充能量", "options": [] }) # 近 7 天趋势 week_start = today - timedelta(days=6) week_meals = db.get_meals(start_date=week_start, end_date=today, user_id=active_user.id) weekly_trend = [] for i in range(7): day = week_start + timedelta(days=i) day_meals = [m for m in week_meals if m.date == day] weekly_trend.append({ "date": day.isoformat(), "calories": sum(m.calories for m in day_meals), "protein": sum(m.protein or 0 for m in day_meals), "carbs": sum(m.carbs or 0 for m in day_meals), "fat": sum(m.fat or 0 for m in day_meals), }) return { "tdee": tdee, "goal": config.goal, "daily_targets": daily_targets, "recommended_ranges": recommended_ranges, "today_intake": today_intake, "gaps": gaps, "meal_distribution": meal_distribution, "suggestions": suggestions, "weekly_trend": weekly_trend, } ``` ### Step 4: 运行测试确认通过 ```bash cd /Users/rocky/Projects/vitals && python -m pytest tests/test_web.py::test_nutrition_recommendations_endpoint -v ``` 预期: PASS ### Step 5: 提交 ```bash git add src/vitals/web/app.py tests/test_web.py git commit -m "feat(api): 添加营养建议端点 /api/nutrition/recommendations" ``` --- ## Task 3: API - 睡眠评估端点 **Files:** - Modify: `src/vitals/web/app.py` (在营养 API 后添加) - Test: `tests/test_web.py` ### Step 1: 写失败测试 ```python def test_sleep_assessment_endpoint(client): """测试睡眠评估 API""" response = client.get("/api/sleep/assessment") assert response.status_code == 200 data = response.json() assert "avg_duration" in data assert "status" in data assert "health_impacts" in data assert "ideal_days_count" in data ``` ### Step 2: 运行测试确认失败 ```bash cd /Users/rocky/Projects/vitals && python -m pytest tests/test_web.py::test_sleep_assessment_endpoint -v ``` 预期: FAIL - 404 Not Found ### Step 3: 实现 API 端点 ```python @app.get("/api/sleep/assessment") async def get_sleep_assessment(): """获取睡眠评估数据""" active_user = db.get_active_user() if not active_user: raise HTTPException(status_code=400, detail="没有激活的用户") today = date.today() week_start = today - timedelta(days=6) month_start = today - timedelta(days=29) # 获取近 7 天和近 30 天睡眠数据 week_records = db.get_sleep_records(start_date=week_start, end_date=today, user_id=active_user.id) month_records = db.get_sleep_records(start_date=month_start, end_date=today, user_id=active_user.id) # 计算平均睡眠时长 week_durations = [r.duration for r in week_records] month_durations = [r.duration for r in month_records] avg_week = sum(week_durations) / len(week_durations) if week_durations else 0 avg_month = sum(month_durations) / len(month_durations) if month_durations else 0 # 判断睡眠状态 def get_sleep_status(avg_hours): if avg_hours < 5: return { "level": "severe", "label": "严重不足", "color": "#EF4444", "icon": "alert-circle" } elif avg_hours < 6: return { "level": "insufficient", "label": "睡眠不足", "color": "#F59E0B", "icon": "alert-triangle" } elif avg_hours <= 9: return { "level": "ideal", "label": "睡眠充足", "color": "#10B981", "icon": "check-circle" } else: return { "level": "excessive", "label": "睡眠过多", "color": "#3B82F6", "icon": "info" } status = get_sleep_status(avg_week) # 统计理想天数 ideal_days = sum(1 for d in week_durations if 7 <= d <= 9) insufficient_days = sum(1 for d in week_durations if d < 7) month_ideal_days = sum(1 for d in month_durations if 7 <= d <= 9) month_insufficient_days = sum(1 for d in month_durations if d < 7) # 健康影响信息 warning_impacts = { "cognitive": { "title": "认知能力", "effects": ["注意力下降 40%", "记忆力减退", "决策能力受损"] }, "physical": { "title": "身体健康", "effects": ["免疫力下降", "肥胖风险增加 33%", "心血管疾病风险上升"] }, "emotional": { "title": "情绪状态", "effects": ["焦虑抑郁风险增加", "情绪波动大", "压力耐受力降低"] }, "exercise": { "title": "运动表现", "effects": ["反应速度下降", "肌肉恢复减慢", "受伤风险增加"] } } benefit_impacts = { "cognitive": { "title": "认知提升", "effects": ["记忆巩固增强", "专注力提高", "创造力活跃"] }, "physical": { "title": "身体修复", "effects": ["免疫系统强化", "肌肉组织修复", "激素分泌平衡"] }, "emotional": { "title": "情绪稳定", "effects": ["情绪调节能力增强", "压力抵抗力提升", "心态积极"] }, "exercise": { "title": "运动增益", "effects": ["运动表现提升 15%", "恢复速度加快", "耐力增强"] } } health_impacts = warning_impacts if status["level"] in ["severe", "insufficient"] else benefit_impacts # 建议 if status["level"] == "severe": suggestion = "建议立即调整作息,每天提前 1 小时入睡" elif status["level"] == "insufficient": suggestion = "尝试提前 30 分钟入睡,逐步调整作息" elif status["level"] == "ideal": suggestion = "继续保持,规律作息是健康的基石!" else: suggestion = "睡眠时间过长也可能影响健康,建议控制在 7-9 小时" # 距理想的差距 ideal_target = 7.5 gap_hours = round(ideal_target - avg_week, 1) if avg_week < 7 else 0 return { "avg_duration": round(avg_week, 1), "avg_duration_month": round(avg_month, 1), "status": status, "gap_hours": gap_hours, "ideal_days_count": ideal_days, "insufficient_days_count": insufficient_days, "total_days": len(week_durations), "month_stats": { "ideal_days": month_ideal_days, "insufficient_days": month_insufficient_days, "total_days": len(month_durations) }, "health_impacts": health_impacts, "suggestion": suggestion, "thresholds": { "severe": 5, "insufficient": 6, "ideal_min": 7, "ideal_max": 9 } } ``` ### Step 4: 运行测试确认通过 ```bash cd /Users/rocky/Projects/vitals && python -m pytest tests/test_web.py::test_sleep_assessment_endpoint -v ``` 预期: PASS ### Step 5: 提交 ```bash git add src/vitals/web/app.py tests/test_web.py git commit -m "feat(api): 添加睡眠评估端点 /api/sleep/assessment" ``` --- ## Task 4: UI - 运动页面 BMI 分析卡片 **Files:** - Modify: `src/vitals/web/app.py:4104` (`get_exercise_page_html` 函数) ### Step 1: 定位插入位置 在 `get_exercise_page_html()` 函数中,找到统计卡片 grid 结束位置(约第 4404 行 `` 之后),插入 BMI 分析卡片。 ### Step 2: 添加 BMI 分析卡片 HTML 在第 4404 行后插入: ```html

BMI 健康分析

设置目标 →
加载中...
``` ### Step 3: 添加 JavaScript 加载逻辑 在 `loadExerciseStats()` 函数后添加 `loadBmiAnalysis()` 函数: ```javascript async function loadBmiAnalysis() { try { const res = await fetch('/api/bmi/analysis'); const data = await res.json(); const container = document.getElementById('bmi-content'); if (!data.current_bmi) { container.innerHTML = `

⚠️ 数据不足

请完善身高、体重信息以启用 BMI 分析

去设置
`; return; } const statusColor = data.bmi_status?.color || '#94A3B8'; const bmiPercent = Math.min(Math.max((data.current_bmi - 15) / 20 * 100, 0), 100); container.innerHTML = `
当前体重
${data.current_weight || '--'} kg
当前 BMI
${data.current_bmi || '--'}
BMI 状态
● ${data.bmi_status?.status || '--'}
(${data.bmi_status?.range || ''})
偏瘦 <18.5
正常
偏重
肥胖 >28
🎯 目标达成预估
目标体重: ${data.target_weight} kg
目标 BMI: ${data.target_bmi} (正常)
距离目标: ${data.weight_diff > 0 ? '-' : '+'}${Math.abs(data.weight_diff)} kg
预估达成: ${data.estimated_days ? '约 ' + data.estimated_days + ' 天' : '数据不足'}
${data.calculation_basis ? `
计算依据: 近30天平均每日净消耗 ${data.calculation_basis.daily_deficit} 卡
(TDEE ${data.calculation_basis.tdee} - 摄入 ${data.calculation_basis.avg_intake} + 运动 ${data.calculation_basis.avg_exercise})
` : ''}
💡 健康减重建议每周 0.5-1 kg
`; } catch (e) { console.error('加载 BMI 分析失败:', e); } } ``` ### Step 4: 在页面加载时调用 在 `DOMContentLoaded` 事件中添加调用: ```javascript document.addEventListener('DOMContentLoaded', () => { loadExerciseStats(); loadExerciseRecords(); loadTodaySteps(); loadBmiAnalysis(); // 添加这行 }); ``` ### Step 5: 提交 ```bash git add src/vitals/web/app.py git commit -m "feat(ui): 运动页面添加 BMI 健康分析卡片" ``` --- ## Task 5: UI - 饮食页面营养建议 **Files:** - Modify: `src/vitals/web/app.py:4745` (`get_meal_page_html` 函数) ### Step 1: 添加今日营养状况卡片 在饮食页面统计卡片后(约第 5060 行)添加: ```html

🍽 今日营养状况

加载中...
``` ### Step 2: 修改近 7 天趋势图 更新趋势图以包含参考线,在 Chart.js 配置中添加 annotation 插件支持。 ### Step 3: 添加 JavaScript 加载逻辑 ```javascript async function loadNutritionRecommendations() { try { const res = await fetch('/api/nutrition/recommendations'); const data = await res.json(); const container = document.getElementById('nutrition-status-content'); const calcPercent = (actual, target) => Math.min(Math.round(actual / target * 100), 100); const getColor = (percent) => percent >= 80 ? '#10B981' : percent >= 50 ? '#F59E0B' : '#EF4444'; const nutrients = [ { key: 'calories', label: '热量', unit: '卡', target: data.daily_targets.calories, actual: data.today_intake.calories }, { key: 'protein', label: '蛋白质', unit: 'g', target: data.daily_targets.protein, actual: data.today_intake.protein }, { key: 'carbs', label: '碳水', unit: 'g', target: data.daily_targets.carbs, actual: data.today_intake.carbs }, { key: 'fat', label: '脂肪', unit: 'g', target: data.daily_targets.fat, actual: data.today_intake.fat }, ]; container.innerHTML = `
${nutrients.map(n => { const pct = calcPercent(n.actual, n.target); const color = getColor(pct); return `
${n.label}
${n.actual}
/${n.target}${n.unit}
${pct}%
`; }).join('')}
${data.suggestions.length > 0 ? `
💡 晚餐建议
${data.suggestions.map(s => `
${s.message}
${s.options.length > 0 ? ` ` : ''} `).join('')}
` : `
✅ 今日营养摄入均衡
`} `; // 更新趋势图参考线 updateIntakeChartWithReference(data); } catch (e) { console.error('加载营养建议失败:', e); } } function updateIntakeChartWithReference(data) { // 在 loadMealStats 中的 intake-chart 添加参考线 const target = data.daily_targets.calories; window.nutritionTarget = target; } ``` ### Step 4: 修改趋势图添加参考线 在原有的 `loadMealStats()` 函数中修改 intake-chart 的配置: ```javascript // 近 7 天趋势图 - 添加参考线 const intakeCtx = document.getElementById('intake-chart').getContext('2d'); const targetLine = window.nutritionTarget || 1800; new Chart(intakeCtx, { type: 'line', data: { labels: last7Days.map(d => d.slice(5)), datasets: [ { label: '实际摄入', data: dailyCalories, borderColor: '#10B981', backgroundColor: 'rgba(16, 185, 129, 0.1)', fill: true, tension: 0.3, }, { label: '建议摄入', data: Array(7).fill(targetLine), borderColor: '#F59E0B', borderDash: [5, 5], pointRadius: 0, fill: false, } ] }, options: { responsive: true, scales: { y: { beginAtZero: true } }, plugins: { annotation: { annotations: { targetZone: { type: 'box', yMin: targetLine * 0.9, yMax: targetLine * 1.1, backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 0 } } } } } }); ``` ### Step 5: 提交 ```bash git add src/vitals/web/app.py git commit -m "feat(ui): 饮食页面添加营养建议区间与配餐建议" ``` --- ## Task 6: UI - 睡眠页面预警卡片 **Files:** - Modify: `src/vitals/web/app.py:5602` (`get_sleep_page_html` 函数) ### Step 1: 添加睡眠评估卡片 在统计卡片后(约第 5835 行)添加: ```html

😴 睡眠健康评估

加载中...
``` ### Step 2: 添加 JavaScript 加载逻辑 ```javascript async function loadSleepAssessment() { try { const res = await fetch('/api/sleep/assessment'); const data = await res.json(); // 渲染评估卡片 const assessmentContainer = document.getElementById('sleep-assessment-content'); const statusColor = data.status.color; const avgPercent = Math.min(Math.max((data.avg_duration - 4) / 6 * 100, 0), 100); assessmentContainer.innerHTML = `
近 7 天平均睡眠
${data.avg_duration} 小时
偏少 <5h
不足 5-6h
理想 7-9h
过多 >9h
● ${data.status.label} ${data.gap_hours > 0 ? `距理想还差 ${data.gap_hours} 小时/天` : ''}
本周: 理想 ${data.ideal_days_count}/${data.total_days} 天
`; // 渲染影响卡片 const impactCard = document.getElementById('sleep-impact-card'); const impactContainer = document.getElementById('sleep-impact-content'); const isWarning = data.status.level === 'severe' || data.status.level === 'insufficient'; const bgColor = isWarning ? 'rgba(127, 29, 29, 0.2)' : 'rgba(6, 95, 70, 0.2)'; const borderColor = isWarning ? 'rgba(239, 68, 68, 0.3)' : 'rgba(16, 185, 129, 0.3)'; const titleColor = isWarning ? '#F87171' : '#34D399'; const title = isWarning ? '⚠️ 睡眠不足警告' : '✅ 睡眠状态良好'; const intro = isWarning ? `您近 7 天平均睡眠仅 ${data.avg_duration} 小时,低于健康标准 7 小时。
长期睡眠不足会对身体造成以下影响:` : `您近 7 天平均睡眠 ${data.avg_duration} 小时,处于健康范围内!
充足睡眠为您带来以下益处:`; impactCard.style.display = 'block'; impactCard.style.background = bgColor; impactCard.style.border = `1px solid ${borderColor}`; const impacts = data.health_impacts; impactContainer.innerHTML = `

${title}

${intro}

${Object.values(impacts).map(impact => `
${impact.title}
`).join('')}
💡 ${data.suggestion}
`; } catch (e) { console.error('加载睡眠评估失败:', e); } } ``` ### Step 3: 修改睡眠趋势图添加理想区间 在原有的 `loadSleepData()` 函数中修改 duration-chart 配置: ```javascript // 添加理想睡眠区间背景 new Chart(durationCtx, { type: 'line', data: { labels: labels, datasets: [ { label: '睡眠时长(小时)', data: durationData, borderColor: '#6d28d9', backgroundColor: 'rgba(109, 40, 217, 0.2)', fill: true, tension: 0.3, pointBackgroundColor: durationData.map(d => d >= 7 && d <= 9 ? '#10B981' : d < 6 ? '#EF4444' : '#F59E0B' ), } ] }, options: { responsive: true, scales: { y: { beginAtZero: true, max: 12 } }, plugins: { annotation: { annotations: { idealZone: { type: 'box', yMin: 7, yMax: 9, backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 0, label: { content: '理想区间', display: true, position: 'start' } } } } } } }); ``` ### Step 4: 在页面加载时调用 ```javascript document.addEventListener('DOMContentLoaded', () => { loadSleepData(); loadSleepAssessment(); // 添加这行 }); ``` ### Step 5: 提交 ```bash git add src/vitals/web/app.py git commit -m "feat(ui): 睡眠页面添加健康评估与预警卡片" ``` --- ## 最终验收 ### 功能测试 1. **运动页面**: 访问 `/exercise`,确认 BMI 分析卡片正确显示 2. **饮食页面**: 访问 `/meal`,确认营养状况卡片和趋势参考线 3. **睡眠页面**: 访问 `/sleep`,确认睡眠评估和警告/益处卡片 ### 运行全部测试 ```bash cd /Users/rocky/Projects/vitals && python -m pytest tests/ -v ``` ### 最终提交 ```bash git add . git commit -m "feat: 完成健康模块增强 - BMI分析/营养建议/睡眠预警" ```