feat(ui): 饮食页面添加营养状况卡片与建议功能
添加今日营养状况卡片,显示热量、蛋白质、碳水、脂肪的摄入进度; 修改近7天摄入趋势图表,添加建议摄入参考线(虚线); 页面加载时调用营养建议API并展示晚餐建议。 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -5507,6 +5507,14 @@ def get_meal_page_html() -> str:
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 今日营养状况卡片 -->
|
||||
<div class="chart-container" id="nutrition-status-card">
|
||||
<h3>今日营养状况</h3>
|
||||
<div id="nutrition-status-content">
|
||||
<div class="empty">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="chart-container">
|
||||
<h3>本月饮食日历</h3>
|
||||
<div class="calendar" id="calendar">
|
||||
@@ -5877,14 +5885,24 @@ def get_meal_page_html() -> str:
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: intakeLabels,
|
||||
datasets: [{
|
||||
label: '摄入卡路里',
|
||||
data: intakeValues,
|
||||
borderColor: '#f59e0b',
|
||||
backgroundColor: 'rgba(245, 158, 11, 0.2)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
}]
|
||||
datasets: [
|
||||
{
|
||||
label: '实际摄入',
|
||||
data: intakeValues,
|
||||
borderColor: '#10B981',
|
||||
backgroundColor: 'rgba(16, 185, 129, 0.1)',
|
||||
fill: true,
|
||||
tension: 0.3,
|
||||
},
|
||||
{
|
||||
label: '建议摄入',
|
||||
data: Array(intakeLabels.length).fill(window.nutritionTarget || 1800),
|
||||
borderColor: '#F59E0B',
|
||||
borderDash: [5, 5],
|
||||
pointRadius: 0,
|
||||
fill: false,
|
||||
}
|
||||
]
|
||||
},
|
||||
options: { responsive: true, scales: { y: { beginAtZero: true } } }
|
||||
});
|
||||
@@ -6000,7 +6018,81 @@ def get_meal_page_html() -> str:
|
||||
}
|
||||
}
|
||||
|
||||
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) => target > 0 ? Math.min(Math.round(actual / target * 100), 100) : 0;
|
||||
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 },
|
||||
];
|
||||
|
||||
let html = `
|
||||
<div class="grid" style="grid-template-columns: repeat(4, 1fr); gap: 12px; margin-bottom: 16px;">
|
||||
`;
|
||||
|
||||
nutrients.forEach(n => {
|
||||
const pct = calcPercent(n.actual, n.target);
|
||||
const color = getColor(pct);
|
||||
html += `
|
||||
<div style="text-align: center; padding: 12px; background: rgba(255,255,255,0.05); border-radius: 8px;">
|
||||
<div style="font-size: 12px; color: #94A3B8;">${n.label}</div>
|
||||
<div style="font-size: 20px; font-weight: 600; color: white;">${n.actual}</div>
|
||||
<div style="font-size: 11px; color: #64748B;">/${n.target}${n.unit}</div>
|
||||
<div style="height: 4px; background: rgba(255,255,255,0.1); border-radius: 2px; margin: 8px 0;">
|
||||
<div style="height: 100%; width: ${pct}%; background: ${color}; border-radius: 2px;"></div>
|
||||
</div>
|
||||
<div style="font-size: 11px; color: ${color};">${pct}%</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
html += '</div>';
|
||||
|
||||
// Add suggestions if any
|
||||
if (data.suggestions && data.suggestions.length > 0) {
|
||||
html += `
|
||||
<div style="background: rgba(251,191,36,0.1); border: 1px solid rgba(251,191,36,0.3); border-radius: 8px; padding: 12px;">
|
||||
<div style="font-size: 13px; font-weight: 500; color: #FBBF24; margin-bottom: 8px;">晚餐建议</div>
|
||||
`;
|
||||
data.suggestions.forEach(s => {
|
||||
html += `<div style="font-size: 12px; color: #E2E8F0; margin-bottom: 4px;">${s.message}</div>`;
|
||||
if (s.options && s.options.length > 0) {
|
||||
html += '<ul style="margin: 4px 0 0 16px; padding: 0; font-size: 11px; color: #94A3B8;">';
|
||||
s.options.forEach(o => {
|
||||
html += `<li style="margin: 2px 0;">${o}</li>`;
|
||||
});
|
||||
html += '</ul>';
|
||||
}
|
||||
});
|
||||
html += '</div>';
|
||||
} else {
|
||||
html += `
|
||||
<div style="text-align: center; padding: 12px; color: #10B981; font-size: 13px;">
|
||||
今日营养摄入均衡
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
// Store nutrition target for trend chart
|
||||
window.nutritionTarget = data.daily_targets.calories;
|
||||
} catch (e) {
|
||||
console.error('加载营养建议失败:', e);
|
||||
document.getElementById('nutrition-status-content').innerHTML = '<div class="empty">加载失败</div>';
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('addMealForm').addEventListener('submit', submitMealForm);
|
||||
loadNutritionRecommendations();
|
||||
loadMealStats();
|
||||
|
||||
// 检查管理员状态
|
||||
|
||||
Reference in New Issue
Block a user