feat(ui): 运动页面添加 BMI 健康分析卡片

在运动页面统计卡片区域下方添加 BMI 分析功能:
- 显示当前体重、BMI 值和健康状态
- 可视化 BMI 范围指示条 (偏瘦/正常/偏重/肥胖)
- 目标达成预估 (目标体重、目标BMI、距离目标、预计天数)
- 计算依据展示 (TDEE、摄入、运动卡路里)
- 数据不足时引导用户完善设置

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 16:27:05 +08:00
parent 211c2c2c4e
commit 36bbd909bf

View File

@@ -4758,6 +4758,17 @@ def get_exercise_page_html() -> str:
</div>
</div>
<!-- BMI 健康分析卡片 -->
<div class="chart-container" id="bmi-analysis-card" style="background: linear-gradient(135deg, rgba(16,185,129,0.1) 0%, rgba(59,130,246,0.1) 100%); border: 1px solid rgba(16,185,129,0.2);">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;">
<h3 style="margin: 0;">BMI 健康分析</h3>
<a href="/settings" style="color: #60A5FA; font-size: 14px; text-decoration: none; cursor: pointer;">设置目标 →</a>
</div>
<div id="bmi-content">
<div class="empty">加载中...</div>
</div>
</div>
<div class="chart-container">
<h3>近 30 天运动时长趋势</h3>
<canvas id="duration-chart" height="120"></canvas>
@@ -4941,6 +4952,87 @@ def get_exercise_page_html() -> str:
}
}
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 = `
<div style="text-align: center; padding: 20px; color: #94A3B8;">
<p style="margin-bottom: 12px;">数据不足</p>
<p style="font-size: 14px;">请完善身高、体重信息以启用 BMI 分析</p>
<a href="/settings" style="display: inline-block; margin-top: 12px; padding: 8px 16px; background: #3B82F6; color: white; border-radius: 6px; text-decoration: none; cursor: pointer;">去设置</a>
</div>
`;
return;
}
const statusColor = data.bmi_status?.color || '#94A3B8';
const bmiPercent = Math.min(Math.max((data.current_bmi - 15) / 20 * 100, 0), 100);
container.innerHTML = `
<div class="grid" style="grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 16px;">
<div style="text-align: center; padding: 12px; background: rgba(255,255,255,0.05); border-radius: 8px;">
<div style="font-size: 12px; color: #94A3B8;">当前体重</div>
<div style="font-size: 24px; font-weight: 600; color: white;">${data.current_weight || '--'}<span style="font-size: 14px; color: #94A3B8;"> kg</span></div>
</div>
<div style="text-align: center; padding: 12px; background: rgba(255,255,255,0.05); border-radius: 8px;">
<div style="font-size: 12px; color: #94A3B8;">当前 BMI</div>
<div style="font-size: 24px; font-weight: 600; color: white;">${data.current_bmi || '--'}</div>
</div>
<div style="text-align: center; padding: 12px; background: rgba(255,255,255,0.05); border-radius: 8px;">
<div style="font-size: 12px; color: #94A3B8;">BMI 状态</div>
<div style="font-size: 18px; font-weight: 600; color: ${statusColor};">${data.bmi_status?.status || '--'}</div>
<div style="font-size: 11px; color: #64748B;">(${data.bmi_status?.range || ''})</div>
</div>
</div>
<div style="margin-bottom: 20px;">
<div style="display: flex; height: 8px; border-radius: 4px; overflow: hidden; margin-bottom: 4px;">
<div style="flex: 18.5; background: #60A5FA;"></div>
<div style="flex: 5.5; background: #34D399;"></div>
<div style="flex: 4; background: #FBBF24;"></div>
<div style="flex: 7; background: #F87171;"></div>
</div>
<div style="position: relative; height: 12px;">
<div style="position: absolute; left: ${bmiPercent}%; transform: translateX(-50%); color: white; font-size: 10px;">▲</div>
</div>
<div style="display: flex; font-size: 10px; color: #64748B;">
<div style="flex: 18.5; text-align: center;">偏瘦 &lt;18.5</div>
<div style="flex: 5.5; text-align: center;">正常</div>
<div style="flex: 4; text-align: center;">偏重</div>
<div style="flex: 7; text-align: center;">肥胖 &gt;28</div>
</div>
</div>
<div style="background: rgba(255,255,255,0.05); border-radius: 8px; padding: 16px;">
<div style="font-size: 14px; font-weight: 500; color: #E2E8F0; margin-bottom: 12px;">目标达成预估</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 8px; font-size: 13px; color: #CBD5E1;">
<div>目标体重: <span style="color: white; font-weight: 500;">${data.target_weight} kg</span></div>
<div>目标 BMI: <span style="color: #34D399; font-weight: 500;">${data.target_bmi}</span></div>
<div>距离目标: <span style="color: ${data.weight_diff > 0 ? '#FBBF24' : '#34D399'}; font-weight: 500;">${data.weight_diff > 0 ? '-' : '+'}${Math.abs(data.weight_diff || 0)} kg</span></div>
<div>预估达成: <span style="color: white; font-weight: 500;">${data.estimated_days ? '' + data.estimated_days + '' : '数据不足'}</span></div>
</div>
${data.calculation_basis ? `
<div style="margin-top: 12px; padding-top: 12px; border-top: 1px solid rgba(255,255,255,0.1); font-size: 11px; color: #64748B;">
计算依据: 近30天平均每日净消耗 ${data.calculation_basis.daily_deficit}
(TDEE ${data.calculation_basis.tdee} - 摄入 ${data.calculation_basis.avg_intake} + 运动 ${data.calculation_basis.avg_exercise})
</div>
` : ''}
</div>
<div style="margin-top: 12px; font-size: 12px; color: #64748B; text-align: center;">
健康减重建议每周 0.5-1 kg
</div>
`;
} catch (e) {
console.error('加载 BMI 分析失败:', e);
document.getElementById('bmi-content').innerHTML = '<div class="empty">加载失败</div>';
}
}
async function loadExerciseList() {
try {
const res = await fetch('/api/exercises?days=30');
@@ -5027,6 +5119,7 @@ def get_exercise_page_html() -> str:
loadExerciseStats();
loadExerciseList();
loadTodaySteps();
loadBmiAnalysis();
async function loadTodaySteps() {
try {