feat: 体重管理v2页面优化,新增饮食运动详情页,代码精简重构

This commit is contained in:
liuyunqin
2026-08-03 14:49:49 +08:00
parent de20b8693c
commit 3f64691732
6 changed files with 1073 additions and 278 deletions
@@ -95,7 +95,10 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.advice-food-item:last-child { margin-bottom: 0; }
.advice-food-item__emoji { font-size: 22px; width: 28px; text-align: center; flex-shrink: 0; line-height: 1.2; }
.advice-food-item__info { flex: 1; min-width: 0; }
.advice-food-item__name { font-size: 13px; font-weight: 600; color: #333; }
.advice-food-item__name { font-size: 13px; font-weight: 600; color: #333; display: flex; align-items: center; gap: 6px; }
.advice-food-item__tag { font-size: 9px; font-weight: 600; padding: 1px 6px; border-radius: 6px; white-space: nowrap; line-height: 1.5; }
.advice-food-item__tag--canteen { color: #d46b08; background: #fff7e6; }
.advice-food-item__tag--ai { color: #0891b2; background: #ecfeff; }
.advice-food-item__meta { font-size: 11px; color: #bbb; margin-top: 2px; }
.advice-food-item__reason { font-size: 11px; color: #888; margin-top: 4px; line-height: 1.5; display: flex; align-items: flex-start; gap: 3px; }
.advice-food-item__reason svg { flex-shrink: 0; margin-top: 1px; }
@@ -621,21 +624,21 @@ var dayData = {
intake: 1050, summary: '今日热量摄入合理,营养素搭配均衡。早餐提供了充足蛋白质,午餐以碳水为主,晚餐清淡适中。',
meals: {
breakfast: { kcal: 280, time: '07:30', items: [
{ e: '🥚', n: '水煮鸡蛋', m: '×1 · 约50g', k: 80, reason: '优质蛋白质来源,富含卵磷脂,有助于大脑功能' },
{ e: '🥛', n: '纯牛奶', m: '×1 · 250ml', k: 120, reason: '补充钙质和蛋白质,250ml满足每日钙需求约30%' },
{ e: '🍞', n: '全麦面包', m: '×1', k: 80, reason: '全谷物碳水,富含膳食纤维,升糖指数低' }
{ e: '🥚', n: '水煮鸡蛋', m: '×1 · 约50g', k: 80, tag: 'canteen', reason: '优质蛋白质来源,富含卵磷脂,有助于大脑功能' },
{ e: '🥛', n: '纯牛奶', m: '×1 · 250ml', k: 120, tag: 'ai', reason: '补充钙质和蛋白质,250ml满足每日钙需求约30%' },
{ e: '🍞', n: '全麦面包', m: '×1', k: 80, tag: 'canteen', reason: '全谷物碳水,富含膳食纤维,升糖指数低' }
]},
lunch: { kcal: 450, time: '12:00', items: [
{ e: '🍚', n: '白米饭', m: '×1 · 约200g', k: 232, reason: '主食碳水来源,提供午后能量' },
{ e: '🐔', n: '红烧鸡块', m: '×1 · 约150g', k: 140, reason: '鸡肉为低脂高蛋白,适合减脂期食用' },
{ e: '🥬', n: '炒青菜', m: '×1', k: 78, reason: '绿叶蔬菜补充维生素和膳食纤维' }
{ e: '🍚', n: '白米饭', m: '×1 · 约200g', k: 232, tag: 'canteen', reason: '主食碳水来源,提供午后能量' },
{ e: '🐔', n: '红烧鸡块', m: '×1 · 约150g', k: 140, tag: 'canteen', reason: '鸡肉为低脂高蛋白,适合减脂期食用' },
{ e: '🥬', n: '炒青菜', m: '×1', k: 78, tag: 'canteen', reason: '绿叶蔬菜补充维生素和膳食纤维' }
]},
dinner: { kcal: 260, time: '18:00', items: [
{ e: '🥗', n: '蔬菜沙拉', m: '×1 · 约200g', k: 160, reason: '低热量高纤维,含多种维生素' },
{ e: '🍠', n: '蒸红薯', m: '×1', k: 100, reason: '复合碳水,富含β-胡萝卜素和膳食纤维' }
{ e: '🥗', n: '蔬菜沙拉', m: '×1 · 约200g', k: 160, tag: 'canteen', reason: '低热量高纤维,含多种维生素' },
{ e: '🍠', n: '蒸红薯', m: '×1', k: 100, tag: 'canteen', reason: '复合碳水,富含β-胡萝卜素和膳食纤维' }
]},
snack: { kcal: 60, time: '15:30', items: [
{ e: '🍌', n: '香蕉', m: '×1', k: 60, reason: '富含钾元素,有助于运动后恢复,天然甜味满足口欲' }
{ e: '🍌', n: '香蕉', m: '×1', k: 60, tag: 'ai', reason: '富含钾元素,有助于运动后恢复,天然甜味满足口欲' }
]}
},
macros: { carb: 140, carbT: 260, protein: 52, proteinT: 82, fat: 28, fatT: 60 },
@@ -648,20 +651,20 @@ var dayData = {
intake: 1120, summary: '今日摄入略高,但蛋白质充足。午餐牛肉面碳水含量偏高,建议增加蔬菜比例。',
meals: {
breakfast: { kcal: 350, time: '08:00', items: [
{ e: '🥛', n: '豆浆', m: '×1 · 300ml', k: 90, reason: '植物蛋白来源,含大豆异黄酮,有益心血管' },
{ e: '🥮', n: '肉包', m: '×2', k: 180, reason: '提供碳水和蛋白质,但注意控制量' },
{ e: '🥚', n: '茶叶蛋', m: '×1', k: 80, reason: '便携蛋白来源,适合快节奏早餐' }
{ e: '🥛', n: '豆浆', m: '×1 · 300ml', k: 90, tag: 'canteen', reason: '植物蛋白来源,含大豆异黄酮,有益心血管' },
{ e: '🥮', n: '肉包', m: '×2', k: 180, tag: 'canteen', reason: '提供碳水和蛋白质,但注意控制量' },
{ e: '🥚', n: '茶叶蛋', m: '×1', k: 80, tag: 'canteen', reason: '便携蛋白来源,适合快节奏早餐' }
]},
lunch: { kcal: 480, time: '12:20', items: [
{ e: '🍜', n: '牛肉面', m: '×1 · 约400g', k: 380, reason: '牛肉提供优质蛋白和铁,但面条碳水偏高' },
{ e: '🥒', n: '凉拌黄瓜', m: '×1', k: 100, reason: '爽口低卡蔬菜,平衡主食热量' }
{ e: '🍜', n: '牛肉面', m: '×1 · 约400g', k: 380, tag: 'canteen', reason: '牛肉提供优质蛋白和铁,但面条碳水偏高' },
{ e: '🥒', n: '凉拌黄瓜', m: '×1', k: 100, tag: 'canteen', reason: '爽口低卡蔬菜,平衡主食热量' }
]},
dinner: { kcal: 210, time: '18:30', items: [
{ e: '🐟', n: '清蒸鱼', m: '×1 · 约150g', k: 130, reason: '鱼肉富含Omega-3脂肪酸,清蒸法低脂健康' },
{ e: '🥦', n: '西兰花', m: '×1', k: 80, reason: '十字花科蔬菜,富含维生素C和抗氧化物质' }
{ e: '🐟', n: '清蒸鱼', m: '×1 · 约150g', k: 130, tag: 'canteen', reason: '鱼肉富含Omega-3脂肪酸,清蒸法低脂健康' },
{ e: '🥦', n: '西兰花', m: '×1', k: 80, tag: 'canteen', reason: '十字花科蔬菜,富含维生素C和抗氧化物质' }
]},
snack: { kcal: 80, time: '10:30', items: [
{ e: '🥜', n: '坚果', m: '×1把 · 约20g', k: 80, reason: '含健康不饱和脂肪酸,适量有益心血管' }
{ e: '🥜', n: '坚果', m: '×1把 · 约20g', k: 80, tag: 'ai', reason: '含健康不饱和脂肪酸,适量有益心血管' }
]}
},
macros: { carb: 165, carbT: 260, protein: 44, proteinT: 82, fat: 34, fatT: 60 },
@@ -674,22 +677,22 @@ var dayData = {
intake: 980, summary: '今日热量控制优秀,蛋白质摄入充足,碳水偏低。饮食搭配非常健康,值得保持!',
meals: {
breakfast: { kcal: 220, time: '07:15', items: [
{ e: '🥚', n: '水煮蛋', m: '×2', k: 160, reason: '两颗蛋提供约12g蛋白质,蛋黄含胆碱有益大脑' },
{ e: '☕', n: '黑咖啡', m: '×1', k: 5, reason: '几乎零卡路里,咖啡因可提神并轻微促进代谢' },
{ e: '🍞', n: '全麦吐司', m: '×1', k: 55, reason: '全谷物提供缓释碳水,配合蛋白质延长饱腹感' }
{ e: '🥚', n: '水煮蛋', m: '×2', k: 160, tag: 'canteen', reason: '两颗蛋提供约12g蛋白质,蛋黄含胆碱有益大脑' },
{ e: '☕', n: '黑咖啡', m: '×1', k: 5, tag: 'ai', reason: '几乎零卡路里,咖啡因可提神并轻微促进代谢' },
{ e: '🍞', n: '全麦吐司', m: '×1', k: 55, tag: 'canteen', reason: '全谷物提供缓释碳水,配合蛋白质延长饱腹感' }
]},
lunch: { kcal: 420, time: '11:50', items: [
{ e: '🍚', n: '杂粮饭', m: '×1 · 约180g', k: 200, reason: '杂粮升糖指数低于白米,膳食纤维更丰富' },
{ e: '🐔', n: '鸡胸肉', m: '×1 · 约120g', k: 120, reason: '极低脂高蛋白,减脂增肌首选蛋白质来源' },
{ e: '🥬', n: '蒜蓉菠菜', m: '×1', k: 100, reason: '菠菜富含铁和叶酸,蒜蓉增添风味且有益心血管' }
{ e: '🍚', n: '杂粮饭', m: '×1 · 约180g', k: 200, tag: 'canteen', reason: '杂粮升糖指数低于白米,膳食纤维更丰富' },
{ e: '🐔', n: '鸡胸肉', m: '×1 · 约120g', k: 120, tag: 'canteen', reason: '极低脂高蛋白,减脂增肌首选蛋白质来源' },
{ e: '🥬', n: '蒜蓉菠菜', m: '×1', k: 100, tag: 'canteen', reason: '菠菜富含铁和叶酸,蒜蓉增添风味且有益心血管' }
]},
dinner: { kcal: 280, time: '17:40', items: [
{ e: '🥗', n: '凯撒沙拉', m: '×1 · 约250g', k: 220, reason: '多种蔬菜搭配,含膳食纤维和维生素' },
{ e: '🍎', n: '苹果', m: '×1', k: 60, reason: '苹果果胶有助于肠道健康,天然甜味代替甜点' }
{ e: '🥗', n: '凯撒沙拉', m: '×1 · 约250g', k: 220, tag: 'canteen', reason: '多种蔬菜搭配,含膳食纤维和维生素' },
{ e: '🍎', n: '苹果', m: '×1', k: 60, tag: 'ai', reason: '苹果果胶有助于肠道健康,天然甜味代替甜点' }
]},
snack: { kcal: 60, time: '15:00', items: [
{ e: '🥒', n: '黄瓜', m: '×1根', k: 15, reason: '极低热量,富含水分和膳食纤维' },
{ e: '🍅', n: '小番茄', m: '×5颗', k: 45, reason: '番茄红素抗氧化,天然维C补充' }
{ e: '🥒', n: '黄瓜', m: '×1根', k: 15, tag: 'canteen', reason: '极低热量,富含水分和膳食纤维' },
{ e: '🍅', n: '小番茄', m: '×5颗', k: 45, tag: 'ai', reason: '番茄红素抗氧化,天然维C补充' }
]}
},
macros: { carb: 120, carbT: 260, protein: 58, proteinT: 82, fat: 22, fatT: 60 },
@@ -702,21 +705,21 @@ var dayData = {
intake: 1086, summary: '今日三餐搭配合理,蛋白质和碳水分配均衡。加餐选择苹果是不错的低热量选择,有助于控制总摄入。',
meals: {
breakfast: { kcal: 320, time: '07:20', items: [
{ e: '🥚', n: '水煮鸡蛋', m: '×1 · 约50g', k: 80, reason: '优质完全蛋白质,含所有必需氨基酸,饱腹感强' },
{ e: '🥛', n: '纯牛奶', m: '×1 · 250ml', k: 120, reason: '补充钙质(约300mg)和维生素D,促进骨骼健康' },
{ e: '🍞', n: '全麦面包', m: '×2 · 约80g', k: 120, reason: '全谷物提供缓释能量,富含B族维生素和膳食纤维' }
{ e: '🥚', n: '水煮鸡蛋', m: '×1 · 约50g', k: 80, tag: 'canteen', reason: '优质完全蛋白质,含所有必需氨基酸,饱腹感强' },
{ e: '🥛', n: '纯牛奶', m: '×1 · 250ml', k: 120, tag: 'ai', reason: '补充钙质(约300mg)和维生素D,促进骨骼健康' },
{ e: '🍞', n: '全麦面包', m: '×2 · 约80g', k: 120, tag: 'canteen', reason: '全谷物提供缓释能量,富含B族维生素和膳食纤维' }
]},
lunch: { kcal: 480, time: '12:10', items: [
{ e: '🍚', n: '白米饭', m: '×1 · 约200g', k: 232, reason: '提供充足碳水能量,满足午后工作和代谢需求' },
{ e: '🐔', n: '宫保鸡丁', m: '×1 · 约150g', k: 148, reason: '鸡肉低脂高蛋白,搭配花生提供健康不饱和脂肪' },
{ e: '🥬', n: '清炒时蔬', m: '×1 · 约200g', k: 100, reason: '足量蔬菜补充膳食纤维,促进肠道蠕动' }
{ e: '🍚', n: '白米饭', m: '×1 · 约200g', k: 232, tag: 'canteen', reason: '提供充足碳水能量,满足午后工作和代谢需求' },
{ e: '🐔', n: '宫保鸡丁', m: '×1 · 约150g', k: 148, tag: 'canteen', reason: '鸡肉低脂高蛋白,搭配花生提供健康不饱和脂肪' },
{ e: '🥬', n: '清炒时蔬', m: '×1 · 约200g', k: 100, tag: 'canteen', reason: '足量蔬菜补充膳食纤维,促进肠道蠕动' }
]},
dinner: { kcal: 220, time: '18:15', items: [
{ e: '🥗', n: '鸡胸肉沙拉', m: '×1 · 约250g', k: 180, reason: '晚餐以蔬菜为主,鸡胸肉提供蛋白质但不过量' },
{ e: '🍠', n: '蒸红薯', m: '×1 · 约100g', k: 40, reason: '低GI碳水,蒸制方式无额外油脂,含丰富β-胡萝卜素' }
{ e: '🥗', n: '鸡胸肉沙拉', m: '×1 · 约250g', k: 180, tag: 'canteen', reason: '晚餐以蔬菜为主,鸡胸肉提供蛋白质但不过量' },
{ e: '🍠', n: '蒸红薯', m: '×1 · 约100g', k: 40, tag: 'canteen', reason: '低GI碳水,蒸制方式无额外油脂,含丰富β-胡萝卜素' }
]},
snack: { kcal: 66, time: '15:30', items: [
{ e: '🍎', n: '苹果', m: '×1 · 约200g', k: 66, reason: '天然果糖满足甜味需求,果胶增加饱腹感,热量可控' }
{ e: '🍎', n: '苹果', m: '×1 · 约200g', k: 66, tag: 'ai', reason: '天然果糖满足甜味需求,果胶增加饱腹感,热量可控' }
]}
},
macros: { carb: 155, carbT: 260, protein: 47, proteinT: 82, fat: 31, fatT: 60 },
@@ -790,23 +793,23 @@ var DEFAULT_RECIPE_DATA = {
summary: '暂无当日饮食记录,以下为推荐的均衡饮食示例,可参考搭配安排今日三餐。',
meals: {
breakfast: { kcal: 320, items: [
{ e: '🥚', n: '水煮鸡蛋', m: '×1 · 约50g', k: 80, reason: '优质完全蛋白质,含所有必需氨基酸,饱腹感强' },
{ e: '🥛', n: '纯牛奶', m: '×1 · 250ml', k: 120, reason: '补充钙质(约300mg)和维生素D,促进骨骼健康' },
{ e: '🍞', n: '全麦面包', m: '×1 · 约40g', k: 120, reason: '全谷物提供缓释能量,富含B族维生素和膳食纤维' }
{ e: '🥚', n: '水煮鸡蛋', m: '×1 · 约50g', k: 80, tag: 'canteen', reason: '优质完全蛋白质,含所有必需氨基酸,饱腹感强' },
{ e: '🥛', n: '纯牛奶', m: '×1 · 250ml', k: 120, tag: 'ai', reason: '补充钙质(约300mg)和维生素D,促进骨骼健康' },
{ e: '🍞', n: '全麦面包', m: '×1 · 约40g', k: 120, tag: 'canteen', reason: '全谷物提供缓释能量,富含B族维生素和膳食纤维' }
]},
lunch: { kcal: 480, items: [
{ e: '🍚', n: '杂粮饭', m: '×1 · 约150g', k: 180, reason: '杂粮升糖指数低,膳食纤维丰富,有助于血糖稳定' },
{ e: '🐔', n: '鸡胸肉', m: '×1 · 约120g', k: 150, reason: '极低脂高蛋白,是减脂增肌的首选蛋白来源' },
{ e: '🥬', n: '清炒时蔬', m: '×1 · 约200g', k: 100, reason: '足量蔬菜补充膳食纤维,促进肠道蠕动' },
{ e: '🍅', n: '小番茄', m: '×5颗', k: 50, reason: '番茄红素抗氧化,天然维C补充' }
{ e: '🍚', n: '杂粮饭', m: '×1 · 约150g', k: 180, tag: 'canteen', reason: '杂粮升糖指数低,膳食纤维丰富,有助于血糖稳定' },
{ e: '🐔', n: '鸡胸肉', m: '×1 · 约120g', k: 150, tag: 'canteen', reason: '极低脂高蛋白,是减脂增肌的首选蛋白来源' },
{ e: '🥬', n: '清炒时蔬', m: '×1 · 约200g', k: 100, tag: 'canteen', reason: '足量蔬菜补充膳食纤维,促进肠道蠕动' },
{ e: '🍅', n: '小番茄', m: '×5颗', k: 50, tag: 'ai', reason: '番茄红素抗氧化,天然维C补充' }
]},
dinner: { kcal: 280, items: [
{ e: '🐟', n: '清蒸鱼', m: '×1 · 约150g', k: 150, reason: '鱼肉富含Omega-3脂肪酸,清蒸法低脂健康' },
{ e: '🥦', n: '西兰花', m: '×1 · 约150g', k: 80, reason: '十字花科蔬菜,富含维生素C和抗氧化物质' },
{ e: '🍠', n: '蒸红薯', m: '×1 · 约100g', k: 50, reason: '低GI碳水,蒸制方式无额外油脂,富含β-胡萝卜素' }
{ e: '🐟', n: '清蒸鱼', m: '×1 · 约150g', k: 150, tag: 'canteen', reason: '鱼肉富含Omega-3脂肪酸,清蒸法低脂健康' },
{ e: '🥦', n: '西兰花', m: '×1 · 约150g', k: 80, tag: 'canteen', reason: '十字花科蔬菜,富含维生素C和抗氧化物质' },
{ e: '🍠', n: '蒸红薯', m: '×1 · 约100g', k: 50, tag: 'canteen', reason: '低GI碳水,蒸制方式无额外油脂,富含β-胡萝卜素' }
]},
snack: { kcal: 60, items: [
{ e: '🍎', n: '苹果', m: '×1 · 约200g', k: 60, reason: '天然果糖满足甜味需求,果胶增加饱腹感,热量可控' }
{ e: '🍎', n: '苹果', m: '×1 · 约200g', k: 60, tag: 'ai', reason: '天然果糖满足甜味需求,果胶增加饱腹感,热量可控' }
]}
}
};
@@ -827,10 +830,16 @@ function renderRecipeTab(data) {
kcalEl.textContent = md.kcal;
var h = '';
md.items.forEach(function(item) {
var tagHtml = '';
if (item.tag === 'canteen') {
tagHtml = '<span class="advice-food-item__tag advice-food-item__tag--canteen">食堂提供</span>';
} else if (item.tag === 'ai') {
tagHtml = '<span class="advice-food-item__tag advice-food-item__tag--ai">AI推荐</span>';
}
h += '<div class="advice-food-item">' +
'<div class="advice-food-item__emoji">' + item.e + '</div>' +
'<div class="advice-food-item__info">' +
'<div class="advice-food-item__name">' + item.n + '</div>' +
'<div class="advice-food-item__name">' + item.n + tagHtml + '</div>' +
'<div class="advice-food-item__meta">' + item.m + '</div>' +
'<div class="advice-food-item__reason"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="#36cfc9" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4M12 8h.01"/></svg>' + item.reason + '</div>' +
'</div>' +
@@ -85,7 +85,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.recipe-btn { position: absolute; top: 44px; right: 8px; padding: 5px 12px; border-radius: 14px; border: none; background: linear-gradient(160deg, #36cfc9, #2db8b0); color: #fff; font-size: 11px; font-weight: 600; cursor: pointer; z-index: 5; box-shadow: 0 2px 6px rgba(54,207,201,0.3); }
/* ===== 营养素三列 ===== */
.macros-row { display: flex; gap: 10px; padding: 0 4px 8px; }
.macros-row { display: flex; gap: 10px; padding: 0 4px 8px; margin-top: -20px; }
.macros-row.hidden { display: none; }
.macro-col { flex: 1; padding: 8px 10px; background: #f7f8fa; border-radius: 10px; text-align: center; }
.macro-col__name { font-size: 11px; color: #999; margin-bottom: 4px; }
@@ -114,6 +114,8 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.meal-card__kcal--in span { color: #fa8c16; }
.meal-card__kcal--out { color: #52c41a; }
.meal-card__kcal--out span { color: #52c41a; }
.meal-card__kcal .meal-card__kcal-target,
.meal-card__kcal .meal-card__kcal-unit { font-size: 12px; font-weight: 400; color: #bbb; }
.meal-card__expand { width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #bbb; transition: transform .25s; }
.meal-card__expand.open { transform: rotate(180deg); }
@@ -131,6 +133,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.meal-item__src--wechat { color: #597ef7; background: #f0f5ff; }
.meal-item__meta { font-size: 11px; color: #bbb; margin-top: 1px; }
.meal-item__kcal { font-size: 13px; font-weight: 700; color: #fa8c16; white-space: nowrap; flex-shrink: 0; }
.meal-item__kcal-unit { font-size: 11px; font-weight: 400; color: #bbb; }
.meal-item--exercise .meal-item__kcal { color: #52c41a; }
.meal-empty { text-align: center; padding: 16px 0; color: #ccc; font-size: 13px; }
@@ -241,18 +244,10 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<circle cx="90" cy="135" r="5" fill="#bbb" stroke="#fff" stroke-width="2.5"/>
<circle cx="250" cy="135" r="5" fill="#52c41a" stroke="#fff" stroke-width="2.5"/>
<!-- 左侧端点下方:饮食摄入 -->
<text x="90" y="150" font-size="12" fill="#999" text-anchor="middle">饮食摄入</text>
<text x="90" y="165" font-size="18" fill="#fa8c16" text-anchor="middle" font-weight="800"><tspan id="intakeText">1086</tspan><tspan font-size="10" font-weight="400" fill="#bbb">/1950 千卡</tspan></text>
<!-- 右侧端点下方:运动消耗 -->
<text x="250" y="150" font-size="12" fill="#999" text-anchor="middle">运动消耗</text>
<text x="250" y="165" font-size="18" fill="#52c41a" text-anchor="middle" font-weight="800"><tspan id="consumeText">820</tspan><tspan font-size="10" font-weight="400" fill="#bbb">/1000 千卡</tspan></text>
<!-- 中心 -->
<text x="170" y="88" font-size="12" fill="#999" text-anchor="middle">吃动热量差</text>
<text id="gapCenterVal" x="170" y="120" font-size="28" fill="#fa8c16" text-anchor="middle" font-weight="800">-364</text>
<text x="170" y="137" font-size="11" fill="#bbb" text-anchor="middle">/ -500 千卡</text>
<text x="170" y="88" font-size="12" fill="#999" text-anchor="middle">总摄入</text>
<text id="gapCenterVal" x="170" y="120" font-size="28" fill="#fa8c16" text-anchor="middle" font-weight="800">1086</text>
<text x="170" y="137" font-size="11" fill="#bbb" text-anchor="middle">/ 1950 千卡</text>
</svg>
</div>
@@ -284,7 +279,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="meal-card__name">早餐</span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalBreakfast">320</span> kcal</span>
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalBreakfast">320</span><span class="meal-card__kcal-target" id="kcalBreakfastTarget">/500</span><span class="meal-card__kcal-unit"> 千卡</span></span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
@@ -326,7 +321,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="meal-card__name">午餐</span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalLunch">480</span> kcal</span>
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalLunch">480</span><span class="meal-card__kcal-target" id="kcalLunchTarget">/650</span><span class="meal-card__kcal-unit"> 千卡</span></span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
@@ -368,7 +363,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="meal-card__name">晚餐</span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalDinner">220</span> kcal</span>
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalDinner">220</span><span class="meal-card__kcal-target" id="kcalDinnerTarget">/550</span><span class="meal-card__kcal-unit"> 千卡</span></span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
@@ -402,7 +397,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="meal-card__name">加餐</span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalSnack">66</span> kcal</span>
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalSnack">66</span><span class="meal-card__kcal-target" id="kcalSnackTarget">/250</span><span class="meal-card__kcal-unit"> 千卡</span></span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
@@ -420,40 +415,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<!-- ====== 运动卡片 ====== -->
<div class="meal-card" id="cardExercise">
<div class="meal-card__header" onclick="toggleMeal('exercise', this)">
<div class="meal-card__left">
<div class="meal-card__icon meal-card__icon--exercise">🏃</div>
<span class="meal-card__name">运动</span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="meal-card__kcal meal-card__kcal--out"><span id="kcalExercise">-320</span> kcal</span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
</div>
<div class="meal-items" id="groupExercise">
<div class="meal-item meal-item--exercise">
<div class="meal-item__emoji">🏃</div>
<div class="meal-item__info">
<div class="meal-item__name">户外跑步</div>
<div class="meal-item__meta">5.2km · 32分钟</div>
</div>
<div class="meal-item__kcal">-260 kcal</div>
</div>
<div class="meal-item meal-item--exercise">
<div class="meal-item__emoji">🚶</div>
<div class="meal-item__info">
<div class="meal-item__name">步行</div>
<div class="meal-item__meta">6800步 · 今日累计</div>
</div>
<div class="meal-item__kcal">-60 kcal</div>
</div>
</div>
</div>
<!-- 饮食未记录缺省卡片 -->
<div class="meal-card" id="cardEmptyDiet" style="display:none;">
<div style="text-align:center;padding:24px 0;color:#ccc;">
@@ -483,10 +444,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
加餐
</button>
<button class="toolbar__btn toolbar__btn--exercise" onclick="addMeal('exercise')">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
运动
</button>
</div>
</div>
@@ -776,9 +733,8 @@ function selectDay(el) {
data = { intake: 0, consume: 0, gap: 0 };
}
var gapAbs = Math.abs(data.gap);
var maxGap = 500;
var progress = data.gap === 0 ? 0 : Math.min(gapAbs / maxGap, 1);
var targetIntake = 1950;
var progress = data.intake === 0 ? 0 : Math.min(data.intake / targetIntake, 1);
var pt = arcPoint(progress);
var arcD = 'M 90 135 A 80 80 0 0 1 ' + pt.x + ' ' + pt.y;
@@ -786,21 +742,13 @@ function selectDay(el) {
var gapArc = document.getElementById('gapArc');
gapArc.setAttribute('d', arcD);
document.getElementById('intakeText').textContent = data.intake === 0 ? '--' : data.intake;
document.getElementById('consumeText').textContent = data.consume === 0 ? '--' : data.consume;
var gapCenter = document.getElementById('gapCenterVal');
gapCenter.textContent = data.gap === 0 ? '--' : ('-' + Math.abs(data.gap));
gapCenter.textContent = data.intake === 0 ? '--' : data.intake;
var colorIntake = data.intake === 0 ? '#bbb' : '#fa8c16';
var colorConsume = data.consume === 0 ? '#bbb' : '#52c41a';
var colorGap = data.gap === 0 ? '#bbb' : '#fa8c16';
document.getElementById('intakeText').setAttribute('fill', colorIntake);
document.getElementById('consumeText').setAttribute('fill', colorConsume);
gapCenter.setAttribute('fill', colorGap);
gapCenter.setAttribute('fill', data.intake === 0 ? '#bbb' : '#fa8c16');
var rightDot = document.querySelector('.gauge-wrap svg circle[cy="135"][cx="250"]');
rightDot.setAttribute('fill', data.gap === 0 ? '#bbb' : '#52c41a');
rightDot.setAttribute('fill', data.intake === 0 ? '#bbb' : '#52c41a');
// 更新餐食卡片
renderMeals(dateStr);
@@ -819,6 +767,9 @@ function renderMeals(dateStr) {
if (md && md.items && md.items.length > 0) hasAnyDiet = true;
});
// ===== 各餐推荐千卡 =====
var mealTargets = { breakfast: 500, lunch: 650, dinner: 550, snack: 250 };
if (hasAnyDiet) {
// 有饮食记录:逐卡片显示/隐藏
document.getElementById('cardEmptyDiet').style.display = 'none';
@@ -827,6 +778,7 @@ function renderMeals(dateStr) {
var card = document.getElementById('card' + cap);
var container = document.getElementById('group' + cap);
var kcalEl = document.getElementById('kcal' + cap);
var kcalTargetEl = document.getElementById('kcal' + cap + 'Target');
var md = meals ? meals[type] : null;
if (md && md.items && md.items.length > 0) {
@@ -839,11 +791,12 @@ function renderMeals(dateStr) {
html += '<div class="meal-item">' +
'<div class="meal-item__emoji">' + item.e + '</div>' +
'<div class="meal-item__info"><div class="meal-item__name">' + item.n + '<span class="meal-item__src meal-item__src--' + src + '">' + srcLabel + '</span></div><div class="meal-item__meta">' + item.m + '</div></div>' +
'<div class="meal-item__kcal">' + item.k + ' kcal</div></div>';
'<div class="meal-item__kcal">' + item.k + '<span class="meal-item__kcal-unit"> 千卡</span></div></div>';
});
container.innerHTML = html;
container.style.display = '';
kcalEl.textContent = md.kcal;
kcalTargetEl.textContent = '/' + mealTargets[type];
kcalEl.parentElement.style.display = '';
} else {
card.style.display = 'none';
@@ -857,32 +810,6 @@ function renderMeals(dateStr) {
document.getElementById('cardEmptyDiet').style.display = '';
}
// 运动卡片独立处理
var exCard = document.getElementById('cardExercise');
var exContainer = document.getElementById('groupExercise');
var exKcalEl = document.getElementById('kcalExercise');
var exMd = meals ? meals['exercise'] : null;
if (exMd && exMd.items && exMd.items.length > 0) {
exCard.style.display = '';
var html = '';
exMd.items.forEach(function(item) {
var srcTag = '';
if (item.src) {
srcTag = '<span class="meal-item__src meal-item__src--' + item.src + '">' + ({wearable:'穿戴',equip:'器械',wechat:'微信',manual:'自记'}[item.src] || item.src) + '</span>';
}
html += '<div class="meal-item meal-item--exercise">' +
'<div class="meal-item__emoji">' + item.e + '</div>' +
'<div class="meal-item__info"><div class="meal-item__name">' + item.n + srcTag + '</div><div class="meal-item__meta">' + item.m + '</div></div>' +
'<div class="meal-item__kcal">' + item.k + ' kcal</div></div>';
});
exContainer.innerHTML = html;
exContainer.style.display = '';
exKcalEl.textContent = exMd.kcal;
exKcalEl.parentElement.style.display = '';
} else {
exCard.style.display = 'none';
}
// 更新营养素三列
var macrosRow = document.getElementById('macrosRow');
var macros = data && data.macros;
@@ -0,0 +1,790 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>吃动平衡 - 员工端 | 健康CQ原型 v2</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #f0f2f5; display: flex; justify-content: center; align-items: flex-start; min-height: 100vh; padding: 20px; font-family: -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; }
.phone-shell { width: 390px; height: 844px; background: #f5f7fa; border-radius: 44px; box-shadow: 0 20px 60px rgba(0,0,0,0.25), inset 0 0 0 2px #e0e0e0; overflow: hidden; display: flex; flex-direction: column; }
.status-bar { height: 44px; display: flex; align-items: center; justify-content: space-between; padding: 0 24px; flex-shrink: 0; background: #fff; }
.status-bar .time { font-size: 15px; font-weight: 700; color: #333; }
.status-bar .icons { display: flex; gap: 6px; align-items: center; }
.nav-bar { height: 44px; background: #fff; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; border-bottom: 1px solid #f0f0f0; }
.nav-back { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #1a1a1a; }
.nav-title { flex: 1; text-align: center; font-size: 17px; font-weight: 600; color: #1a1a1a; }
.nav-placeholder { width: 28px; height: 28px; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* ===== 周日历 ===== */
.calendar-card { background: #fff; border-radius: 16px; margin: 12px 12px 0; padding: 14px 12px 10px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); position: relative; }
.calendar-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.calendar-card__arrow { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #999; border-radius: 50%; transition: background .2s; user-select: none; }
.calendar-card__arrow:hover { background: #f0f0f0; color: #333; }
.calendar-card__range { font-size: 13px; font-weight: 600; color: #333; display: inline-flex; align-items: center; gap: 4px; cursor: pointer; user-select: none; }
.calendar-card__range svg { flex-shrink: 0; }
.calendar-card__date-arrow { transition: transform .2s; }
.calendar-card__date-arrow.open { transform: rotate(180deg); }
/* ===== 月历弹窗 ===== */
.month-picker { position: absolute; top: 46px; left: 12px; right: 12px; z-index: 20; background: #fff; border-radius: 14px; padding: 12px 14px; box-shadow: 0 8px 30px rgba(0,0,0,0.15); display: none; }
.month-picker.show { display: block; }
.month-picker__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.month-picker__title { font-size: 14px; font-weight: 700; color: #1a1a1a; }
.month-picker__nav { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #999; border-radius: 50%; font-size: 13px; }
.month-picker__nav:hover { background: #f0f0f0; }
.month-picker__weekdays { display: grid; grid-template-columns: repeat(7, 1fr); text-align: center; font-size: 11px; color: #bbb; margin-bottom: 6px; }
.month-picker__grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; text-align: center; }
.month-picker__day { padding: 6px 0; font-size: 13px; color: #333; border-radius: 8px; cursor: pointer; font-weight: 500; position: relative; }
.month-picker__day:hover { background: #f5f5f5; }
.month-picker__day--other { color: #ddd; }
.month-picker__day--today { background: #e8fbfa; color: #36cfc9; font-weight: 700; }
.month-picker__day--selected { background: #36cfc9; color: #fff; font-weight: 700; }
/* 月历日期数字 + 进度圆环 */
.month-picker__date-wrap { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 26px; height: 26px; }
.month-picker__ring-svg { position: absolute; inset: 0; width: 26px; height: 26px; }
.cal-week { display: flex; gap: 4px; }
.cal-day { flex: 1; text-align: center; padding: 8px 0 8px; border-radius: 12px; cursor: pointer; background: #f7f8fa; transition: background .2s; }
.cal-day.active { background: #e8fbfa; }
.cal-day.today .cal-day__num { color: #1a1a1a; font-weight: 800; }
.cal-day.active .cal-day__num { color: #36cfc9; font-weight: 800; }
.cal-day.active .cal-day__wd { color: #36cfc9; font-weight: 600; }
.cal-day__wd { font-size: 10px; color: #999; margin-bottom: 4px; }
/* 日期数字 + 进度圆环 */
.cal-day__date-wrap { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; margin: 0 auto; }
.cal-day__ring-svg { position: absolute; inset: 0; width: 30px; height: 30px; z-index: 1; }
.cal-day__num { font-size: 15px; font-weight: 700; color: #333; position: relative; z-index: 2; }
/* ===== 月历进度图例 ===== */
.month-picker__legend { display: flex; align-items: center; justify-content: center; flex-wrap: wrap; gap: 14px; margin-top: 8px; padding-top: 8px; font-size: 11px; color: #666; }
.month-picker__legend-item { display: inline-flex; align-items: center; gap: 4px; }
.month-picker__legend-ring { width: 12px; height: 12px; border-radius: 50%; border: 2.5px solid #36cfc9; background: transparent; }
/* ===== 热量缺口标尺卡片 ===== */
.gap-card { background: #fff; border-radius: 16px; margin: 12px 12px 0; padding: 16px 16px 14px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.gap-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; }
.gap-card__title { font-size: 16px; font-weight: 800; color: #1a1a1a; }
.gap-card__target { font-size: 12px; font-weight: 600; color: #36cfc9; }
/* ===== 标尺 SVG ===== */
.gauge-wrap { width: 100%; margin-top: -30px; position: relative; }
.gauge-wrap svg { width: 100%; height: auto; display: block; }
.recipe-btn { position: absolute; top: 44px; padding: 5px 12px; border-radius: 14px; border: none; color: #fff; font-size: 11px; font-weight: 600; cursor: pointer; z-index: 5; }
.recipe-btn--diet { left: 8px; background: linear-gradient(160deg, #36cfc9, #2db8b0); box-shadow: 0 2px 6px rgba(54,207,201,0.3); }
.recipe-btn--exercise { right: 8px; background: linear-gradient(160deg, #ffb066, #ff8a3d); box-shadow: 0 2px 6px rgba(255,138,61,0.3); }
/* ===== 餐食卡片通用 ===== */
.meal-card { background: #fff; border-radius: 16px; margin: 12px 12px 0; padding: 14px 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.meal-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; cursor: pointer; }
.meal-card__left { display: flex; align-items: center; gap: 8px; }
.meal-card__icon { width: 28px; height: 28px; border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 14px; }
.meal-card__icon--breakfast { background: #fff3e0; color: #fa8c16; }
.meal-card__icon--lunch { background: #fff1f0; color: #ee6666; }
.meal-card__icon--dinner { background: #f0f0ff; color: #5470c6; }
.meal-card__icon--snack { background: #fffbe6; color: #fac858; }
.meal-card__icon--exercise { background: #f0fff0; color: #52c41a; }
.meal-card__name { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.meal-card__kcal { font-size: 13px; font-weight: 600; }
.meal-card__kcal span { font-size: 17px; font-weight: 800; }
.meal-card__kcal--in { color: #fa8c16; }
.meal-card__kcal--in span { color: #fa8c16; }
.meal-card__kcal--out { color: #52c41a; }
.meal-card__kcal--out span { color: #52c41a; }
.meal-card__expand { width: 22px; height: 22px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #bbb; transition: transform .25s; }
.meal-card__expand.open { transform: rotate(180deg); }
/* 食物列表 */
.meal-items { display: flex; flex-direction: column; gap: 6px; }
.meal-item { display: flex; align-items: center; padding: 6px 10px; background: #f7f8fa; border-radius: 8px; gap: 8px; }
.meal-item__emoji { font-size: 18px; width: 24px; text-align: center; flex-shrink: 0; }
.meal-item__info { flex: 1; min-width: 0; }
.meal-item__name { font-size: 13px; color: #333; font-weight: 500; display: flex; align-items: center; gap: 6px; }
.meal-item__src { font-size: 9px; font-weight: 600; padding: 1px 6px; border-radius: 6px; white-space: nowrap; line-height: 1.5; }
.meal-item__src--auto { color: #1890ff; background: #e6f7ff; }
.meal-item__src--manual { color: #fa8c16; background: #fff7e6; }
.meal-item__src--wearable { color: #1890ff; background: #e6f7ff; }
.meal-item__src--equip { color: #52c41a; background: #f6ffed; }
.meal-item__src--wechat { color: #597ef7; background: #f0f5ff; }
.meal-item__meta { font-size: 11px; color: #bbb; margin-top: 1px; }
.meal-item__kcal { font-size: 13px; font-weight: 700; color: #fa8c16; white-space: nowrap; flex-shrink: 0; }
.meal-item--exercise .meal-item__kcal { color: #52c41a; }
.meal-empty { text-align: center; padding: 16px 0; color: #ccc; font-size: 13px; }
/* ===== 饮食子分组 ===== */
.diet-sub { border-bottom: 1px solid #f5f5f5; }
.diet-sub:last-child { border-bottom: none; }
.diet-sub__header { display: flex; align-items: center; padding: 10px 0; cursor: pointer; gap: 8px; }
.diet-sub__name { font-size: 14px; font-weight: 600; color: #1a1a1a; flex: 1; }
.diet-sub__kcal { font-size: 13px; font-weight: 600; color: #fa8c16; }
.diet-sub__empty { text-align: center; padding: 10px 0; color: #ccc; font-size: 12px; }
</style>
</head>
<body>
<div class="phone-shell">
<div class="status-bar">
<span class="time">09:40</span>
<div class="icons">
<svg width="16" height="16" fill="none"><rect x="1" y="4" width="3" height="8" rx="1" fill="#333"/><rect x="5" y="3" width="3" height="9" rx="1" fill="#333"/><rect x="9" y="1" width="3" height="11" rx="1" fill="#333"/><rect x="13" y="0" width="3" height="12" rx="1" fill="#333" opacity=".3"/></svg>
<svg width="16" height="16" fill="none"><path d="M8 3C5.5 3 3.2 4 1.5 5.5l1.4 1.4C4.3 5.5 6.1 5 8 5s3.7.5 5.1 1.9l1.4-1.4C12.8 4 10.5 3 8 3zm0 4c-1.4 0-2.6.5-3.5 1.3L6 9.8c.6-.5 1.3-.8 2-.8s1.4.3 2 .8l1.5-1.5C10.6 7.5 9.4 7 8 7zm0 4c-.6 0-1 .4-1 1s.4 1 1 1 1-.4 1-1-.4-1-1-1z" fill="#333"/></svg>
<svg width="22" height="12" fill="none"><rect x=".5" y=".5" width="19" height="11" rx="2" stroke="#333"/><rect x="20" y="3.5" width="1.5" height="5" rx=".75" fill="#333"/><rect x="2" y="2" width="14" height="8" rx="1" fill="#333"/></svg>
</div>
</div>
<div class="nav-bar">
<div class="nav-back" onclick="history.back()">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
</div>
<div class="nav-title">吃动平衡</div>
<div class="nav-placeholder"></div>
</div>
<div class="scroll-content">
<!-- ====== 本周7日日历 ====== -->
<div class="calendar-card">
<div class="calendar-card__header">
<div class="calendar-card__arrow" id="calPrev" onclick="prevWeek()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="15 18 9 12 15 6"/></svg>
</div>
<span class="calendar-card__range" onclick="toggleMonthPicker()">
<span id="calRange">2026年7月23日</span>
<svg class="calendar-card__date-arrow" id="dateArrow" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#999" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</span>
<div class="calendar-card__arrow" id="calNext" onclick="nextWeek()">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg>
</div>
</div>
<!-- 月历弹窗 -->
<div class="month-picker" id="monthPicker">
<div class="month-picker__header">
<div class="month-picker__nav" onclick="event.stopPropagation();prevMonth()"></div>
<span class="month-picker__title" id="monthPickerTitle">2026年7月</span>
<div class="month-picker__nav" onclick="event.stopPropagation();nextMonth()"></div>
</div>
<div class="month-picker__weekdays">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="month-picker__grid" id="monthGrid"></div>
<!-- 图例:来源颜色 -->
<div class="month-picker__legend">
<span class="month-picker__legend-item"><span class="month-picker__legend-ring"></span>推荐热量差 完成进度</span>
</div>
</div>
<div class="cal-week" id="calWeek"></div>
<div class="gauge-wrap">
<button class="recipe-btn recipe-btn--diet" onclick="location.href='diet-advice-v2.html'">今日饮食建议</button>
<button class="recipe-btn recipe-btn--exercise" onclick="location.href='exercise-advice-v2.html'">智能运动计划</button>
<svg viewBox="0 0 340 170" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gapGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#ffc069"/>
<stop offset="35%" stop-color="#fa8c16"/>
<stop offset="100%" stop-color="#52c41a"/>
</linearGradient>
<radialGradient id="glowGrad" cx="50%" cy="70%" r="55%">
<stop offset="0%" stop-color="#e8fbfa" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#fff" stop-opacity="0"/>
</radialGradient>
</defs>
<rect x="0" y="0" width="340" height="170" fill="url(#glowGrad)"/>
<!-- 灰色底轨 -->
<path d="M 90 135 A 80 80 0 0 1 250 135"
fill="none" stroke="#ebeef0" stroke-width="14" stroke-linecap="round"/>
<!-- 进度弧 -->
<path id="gapArc" d="M 90 135 A 80 80 0 0 1 222 75"
fill="none" stroke="url(#gapGrad)" stroke-width="14" stroke-linecap="round"/>
<!-- 刻度圆点 -->
<circle cx="98" cy="103" r="2.5" fill="#cfcfcf"/>
<circle cx="120" cy="75" r="2.5" fill="#cfcfcf"/>
<circle cx="152" cy="60" r="2.5" fill="#cfcfcf"/>
<circle cx="188" cy="60" r="2.5" fill="#cfcfcf"/>
<circle cx="220" cy="75" r="2.5" fill="#cfcfcf"/>
<circle cx="242" cy="103" r="2.5" fill="#cfcfcf"/>
<!-- 端点 -->
<circle cx="90" cy="135" r="5" fill="#bbb" stroke="#fff" stroke-width="2.5"/>
<circle cx="250" cy="135" r="5" fill="#52c41a" stroke="#fff" stroke-width="2.5"/>
<!-- 左侧端点下方:饮食摄入 -->
<text x="90" y="150" font-size="12" fill="#999" text-anchor="middle">饮食摄入</text>
<text x="90" y="165" font-size="18" fill="#fa8c16" text-anchor="middle" font-weight="800"><tspan id="intakeText">1086</tspan><tspan font-size="10" font-weight="400" fill="#bbb">/1950 千卡</tspan></text>
<!-- 右侧端点下方:运动消耗 -->
<text x="250" y="150" font-size="12" fill="#999" text-anchor="middle">运动消耗</text>
<text x="250" y="165" font-size="18" fill="#52c41a" text-anchor="middle" font-weight="800"><tspan id="consumeText">820</tspan><tspan font-size="10" font-weight="400" fill="#bbb">/1000 千卡</tspan></text>
<!-- 中心 -->
<text x="170" y="88" font-size="12" fill="#999" text-anchor="middle">吃动热量差</text>
<text id="gapCenterVal" x="170" y="120" font-size="28" fill="#fa8c16" text-anchor="middle" font-weight="800">-364</text>
<text x="170" y="137" font-size="11" fill="#bbb" text-anchor="middle">/ -500 千卡</text>
</svg>
</div>
</div>
<!-- ====== 饮食卡片(早餐·午餐·晚餐·加餐) ====== -->
<div class="meal-card" id="cardDiet">
<div class="meal-card__header">
<div class="meal-card__left">
<div class="meal-card__icon meal-card__icon--breakfast">🍽️</div>
<span class="meal-card__name">饮食</span>
</div>
<span class="meal-card__kcal meal-card__kcal--in"><span id="kcalDiet">1086</span> kcal</span>
</div>
<!-- 早餐 -->
<div class="diet-sub" id="dietSubBreakfast">
<div class="diet-sub__header" onclick="toggleDietSub('breakfast', this)">
<div class="meal-card__icon meal-card__icon--breakfast" style="width:22px;height:22px;font-size:11px;"></div>
<span class="diet-sub__name">早餐</span>
<span class="diet-sub__kcal" id="kcalBreakfast">320</span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="meal-items" id="groupBreakfast">
<div class="meal-item"><div class="meal-item__emoji">🥚</div><div class="meal-item__info"><div class="meal-item__name">水煮鸡蛋<span class="meal-item__src meal-item__src--auto">智记</span></div><div class="meal-item__meta">×1 · 约50g</div></div><div class="meal-item__kcal">80 kcal</div></div>
<div class="meal-item"><div class="meal-item__emoji">🥛</div><div class="meal-item__info"><div class="meal-item__name">纯牛奶<span class="meal-item__src meal-item__src--auto">智记</span></div><div class="meal-item__meta">×1 · 250ml</div></div><div class="meal-item__kcal">120 kcal</div></div>
<div class="meal-item"><div class="meal-item__emoji">🍞</div><div class="meal-item__info"><div class="meal-item__name">全麦面包<span class="meal-item__src meal-item__src--auto">智记</span></div><div class="meal-item__meta">×2 · 约80g</div></div><div class="meal-item__kcal">120 kcal</div></div>
</div>
</div>
<!-- 午餐 -->
<div class="diet-sub" id="dietSubLunch">
<div class="diet-sub__header" onclick="toggleDietSub('lunch', this)">
<div class="meal-card__icon meal-card__icon--lunch" style="width:22px;height:22px;font-size:11px;"></div>
<span class="diet-sub__name">午餐</span>
<span class="diet-sub__kcal" id="kcalLunch">480</span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="meal-items" id="groupLunch">
<div class="meal-item"><div class="meal-item__emoji">🍚</div><div class="meal-item__info"><div class="meal-item__name">白米饭<span class="meal-item__src meal-item__src--auto">智记</span></div><div class="meal-item__meta">×1 · 约200g</div></div><div class="meal-item__kcal">232 kcal</div></div>
<div class="meal-item"><div class="meal-item__emoji">🐔</div><div class="meal-item__info"><div class="meal-item__name">宫保鸡丁<span class="meal-item__src meal-item__src--manual">自记</span></div><div class="meal-item__meta">×1 · 约150g</div></div><div class="meal-item__kcal">148 kcal</div></div>
<div class="meal-item"><div class="meal-item__emoji">🥬</div><div class="meal-item__info"><div class="meal-item__name">清炒时蔬<span class="meal-item__src meal-item__src--manual">自记</span></div><div class="meal-item__meta">×1 · 约200g</div></div><div class="meal-item__kcal">100 kcal</div></div>
</div>
</div>
<!-- 晚餐 -->
<div class="diet-sub" id="dietSubDinner">
<div class="diet-sub__header" onclick="toggleDietSub('dinner', this)">
<div class="meal-card__icon meal-card__icon--dinner" style="width:22px;height:22px;font-size:11px;">🌙</div>
<span class="diet-sub__name">晚餐</span>
<span class="diet-sub__kcal" id="kcalDinner">220</span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="meal-items" id="groupDinner">
<div class="meal-item"><div class="meal-item__emoji">🥗</div><div class="meal-item__info"><div class="meal-item__name">鸡胸肉沙拉<span class="meal-item__src meal-item__src--auto">智记</span></div><div class="meal-item__meta">×1 · 约250g</div></div><div class="meal-item__kcal">180 kcal</div></div>
<div class="meal-item"><div class="meal-item__emoji">🍠</div><div class="meal-item__info"><div class="meal-item__name">蒸红薯<span class="meal-item__src meal-item__src--auto">智记</span></div><div class="meal-item__meta">×1 · 约100g</div></div><div class="meal-item__kcal">40 kcal</div></div>
</div>
</div>
<!-- 加餐 -->
<div class="diet-sub" id="dietSubSnack">
<div class="diet-sub__header" onclick="toggleDietSub('snack', this)">
<div class="meal-card__icon meal-card__icon--snack" style="width:22px;height:22px;font-size:11px;">🍎</div>
<span class="diet-sub__name">加餐</span>
<span class="diet-sub__kcal" id="kcalSnack">66</span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
<div class="meal-items" id="groupSnack">
<div class="meal-item"><div class="meal-item__emoji">🍎</div><div class="meal-item__info"><div class="meal-item__name">苹果<span class="meal-item__src meal-item__src--manual">自记</span></div><div class="meal-item__meta">×1 · 约200g</div></div><div class="meal-item__kcal">66 kcal</div></div>
</div>
</div>
<!-- 暂无饮食记录 -->
<div class="diet-sub__empty" id="dietEmpty" style="display:none;">暂无饮食记录</div>
</div>
<!-- ====== 运动卡片 ====== -->
<div class="meal-card" id="cardExercise">
<div class="meal-card__header" onclick="toggleMeal('exercise', this)">
<div class="meal-card__left">
<div class="meal-card__icon meal-card__icon--exercise">🏃</div>
<span class="meal-card__name">运动</span>
</div>
<div style="display:flex;align-items:center;gap:8px;">
<span class="meal-card__kcal meal-card__kcal--out"><span id="kcalExercise">-320</span> kcal</span>
<div class="meal-card__expand">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</div>
</div>
</div>
<div class="meal-items" id="groupExercise">
<div class="meal-item meal-item--exercise">
<div class="meal-item__emoji">🏃</div>
<div class="meal-item__info">
<div class="meal-item__name">户外跑步</div>
<div class="meal-item__meta">5.2km · 32分钟</div>
</div>
<div class="meal-item__kcal">-260 kcal</div>
</div>
<div class="meal-item meal-item--exercise">
<div class="meal-item__emoji">🚶</div>
<div class="meal-item__info">
<div class="meal-item__name">步行</div>
<div class="meal-item__meta">6800步 · 今日累计</div>
</div>
<div class="meal-item__kcal">-60 kcal</div>
</div>
</div>
</div>
<div style="height: 20px; flex-shrink: 0;"></div>
</div>
</div>
<script>
/* ===== 日历数据(按日期存储) ===== */
var dayData = {
'2026-07-20': { intake: 1050, consume: 780, gap: -380, macros: {carb:140,carbT:260,protein:52,proteinT:82,fat:28,fatT:60}, meals: {
breakfast: { source:'manual', kcal: 280, items: [{e:'🥚',n:'水煮鸡蛋',m:'×1',k:80},{e:'🥛',n:'纯牛奶',m:'×1 · 250ml',k:120},{e:'🍞',n:'全麦面包',m:'×1',k:80}] },
lunch: { source:'auto', kcal: 450, items: [{e:'🍚',n:'白米饭',m:'×1 · 约200g',k:232},{e:'🐔',n:'红烧鸡块',m:'×1 · 约150g',k:140},{e:'🥬',n:'炒青菜',m:'×1',k:78}] },
dinner: { source:'auto', kcal: 260, items: [{e:'🥗',n:'蔬菜沙拉',m:'×1 · 约200g',k:160},{e:'🍠',n:'蒸红薯',m:'×1',k:100}] },
snack: { source:'manual', kcal: 60, items: [{e:'🍌',n:'香蕉',m:'×1',k:60}] },
exercise: { kcal: -280, items: [{e:'🏃',n:'户外跑步',m:'4.5km · 28分钟',k:-200,src:'wearable'},{e:'🚶',n:'步行',m:'5500步',k:-80,src:'wechat'}] }
}},
'2026-07-21': { intake: 1120, consume: 790, gap: -330, macros: {carb:165,carbT:260,protein:44,proteinT:82,fat:34,fatT:60}, meals: {
breakfast: { source:'manual', kcal: 350, items: [{e:'🥛',n:'豆浆',m:'×1 · 300ml',k:90},{e:'🥮',n:'肉包',m:'×2',k:180},{e:'🥚',n:'茶叶蛋',m:'×1',k:80}] },
lunch: { source:'manual', kcal: 480, items: [{e:'🍜',n:'牛肉面',m:'×1 · 约400g',k:380},{e:'🥒',n:'凉拌黄瓜',m:'×1',k:100}] },
dinner: { source:'manual', kcal: 210, items: [{e:'🐟',n:'清蒸鱼',m:'×1 · 约150g',k:130},{e:'🥦',n:'西兰花',m:'×1',k:80}] },
snack: { source:'manual', kcal: 80, items: [{e:'🥜',n:'坚果',m:'×1把 · 约20g',k:80}] },
exercise: { kcal: -250, items: [{e:'🚴',n:'骑行',m:'8km · 35分钟',k:-250,src:'equip'}] }
}},
'2026-07-22': { intake: 980, consume: 740, gap: -412, macros: {carb:120,carbT:260,protein:58,proteinT:82,fat:22,fatT:60}, meals: {
breakfast: { source:'auto', kcal: 220, items: [{e:'🥚',n:'水煮蛋',m:'×2',k:160},{e:'☕',n:'黑咖啡',m:'×1',k:5},{e:'🍞',n:'全麦吐司',m:'×1',k:55}] },
lunch: { source:'auto', kcal: 420, items: [{e:'🍚',n:'杂粮饭',m:'×1 · 约180g',k:200},{e:'🐔',n:'鸡胸肉',m:'×1 · 约120g',k:120},{e:'🥬',n:'蒜蓉菠菜',m:'×1',k:100}] },
dinner: { source:'auto', kcal: 280, items: [{e:'🥗',n:'凯撒沙拉',m:'×1 · 约250g',k:220},{e:'🍎',n:'苹果',m:'×1',k:60}] },
snack: { source:'auto', kcal: 60, items: [{e:'🥒',n:'黄瓜',m:'×1根',k:15},{e:'🍅',n:'小番茄',m:'×5颗',k:45}] },
exercise: { kcal: -320, items: [{e:'🏊',n:'游泳',m:'40分钟',k:-260,src:'wearable'},{e:'🚶',n:'步行',m:'5000步',k:-60,src:'wechat'}] }
}},
'2026-07-23': { intake: 1086, consume: 820, gap: -364, macros: {carb:155,carbT:260,protein:47,proteinT:82,fat:31,fatT:60}, meals: {
breakfast: { source:'auto', kcal: 320, items: [{e:'🥚',n:'水煮鸡蛋',m:'×1 · 约50g',k:80},{e:'🥛',n:'纯牛奶',m:'×1 · 250ml',k:120},{e:'🍞',n:'全麦面包',m:'×2 · 约80g',k:120}] },
lunch: { source:'manual', kcal: 480, items: [{e:'🍚',n:'白米饭',m:'×1 · 约200g',k:232,src:'auto'},{e:'🐔',n:'宫保鸡丁',m:'×1 · 约150g',k:148},{e:'🥬',n:'清炒时蔬',m:'×1 · 约200g',k:100}] },
dinner: { source:'auto', kcal: 220, items: [{e:'🥗',n:'鸡胸肉沙拉',m:'×1 · 约250g',k:180},{e:'🍠',n:'蒸红薯',m:'×1 · 约100g',k:40}] },
snack: { source:'manual', kcal: 66, items: [{e:'🍎',n:'苹果',m:'×1 · 约200g',k:66}] },
exercise: { kcal: -320, items: [{e:'🏃',n:'户外跑步',m:'5.2km · 32分钟',k:-260,src:'wearable'},{e:'🚶',n:'步行',m:'6800步 · 今日累计',k:-60,src:'wechat'}] }
}},
'2026-07-24': { intake: 0, consume: 0, gap: 0 },
'2026-07-25': { intake: 0, consume: 0, gap: 0 },
'2026-07-26': { intake: 0, consume: 0, gap: 0 }
};
/* ===== 周导航 ===== */
var WEEK_MS = 7 * 24 * 60 * 60 * 1000;
var today = new Date(2026, 6, 23); // 2026-07-23
var currentWeekStart = getMonday(today);
/* 获取某日期所在周的周一 */
function getMonday(d) {
var m = new Date(d);
var day = m.getDay(); // 0=Sun, 1=Mon, ...
var diff = day === 0 ? -6 : 1 - day;
m.setDate(m.getDate() + diff);
m.setHours(0, 0, 0, 0);
return m;
}
/* 格式化日期:YYYY-MM-DD */
function fmtDate(d) {
var y = d.getFullYear();
var m = ('0' + (d.getMonth() + 1)).slice(-2);
var day = ('0' + d.getDate()).slice(-2);
return y + '-' + m + '-' + day;
}
/* 格式化中文日期 */
function fmtDateCN(d) {
return d.getFullYear() + '年' + (d.getMonth() + 1) + '月' + d.getDate() + '日';
}
/* 判断两个日期是否同一天 */
function isSameDay(a, b) {
return a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate();
}
/* 生成进度圆环 SVG */
function ringSVG(progress) {
var r = 13, cx = 15, cy = 15, size = 30;
var circumference = 2 * Math.PI * r;
var offset = circumference * (1 - Math.min(progress, 1));
return '<svg class="cal-day__ring-svg" viewBox="0 0 ' + size + ' ' + size + '">' +
'<circle cx="' + cx + '" cy="' + cy + '" r="' + r + '" fill="none" stroke="#e8e8e8" stroke-width="2.5"/>' +
'<circle cx="' + cx + '" cy="' + cy + '" r="' + r + '" fill="none" stroke="#36cfc9" stroke-width="2.5" ' +
'stroke-dasharray="' + circumference.toFixed(1) + '" stroke-dashoffset="' + offset.toFixed(1) + '" stroke-linecap="round" ' +
'transform="rotate(-90 ' + cx + ' ' + cy + ')"/>' +
'</svg>';
}
/* 生成月历进度圆环 SVG(26×26 小尺寸) */
function monthRingSVG(progress) {
var r = 11, cx = 13, cy = 13, size = 26;
var circumference = 2 * Math.PI * r;
var offset = circumference * (1 - Math.min(progress, 1));
return '<svg class="month-picker__ring-svg" viewBox="0 0 ' + size + ' ' + size + '">' +
'<circle cx="' + cx + '" cy="' + cy + '" r="' + r + '" fill="none" stroke="#e8e8e8" stroke-width="2"/>' +
'<circle cx="' + cx + '" cy="' + cy + '" r="' + r + '" fill="none" stroke="#36cfc9" stroke-width="2" ' +
'stroke-dasharray="' + circumference.toFixed(1) + '" stroke-dashoffset="' + offset.toFixed(1) + '" stroke-linecap="round" ' +
'transform="rotate(-90 ' + cx + ' ' + cy + ')"/>' +
'</svg>';
}
/* 渲染一周日历 */
function renderWeek() {
var container = document.getElementById('calWeek');
container.innerHTML = '';
var weekDayChars = ['日', '一', '二', '三', '四', '五', '六'];
for (var i = 0; i < 7; i++) {
var d = new Date(currentWeekStart);
d.setDate(d.getDate() + i);
var dateStr = fmtDate(d);
var wd = weekDayChars[d.getDay()];
var dayNum = d.getDate();
var isToday = isSameDay(d, today);
var dayEl = document.createElement('div');
dayEl.className = 'cal-day' + (isToday ? ' today' : '');
dayEl.setAttribute('data-date', dateStr);
dayEl.setAttribute('data-wd', wd);
dayEl.onclick = function() { selectDay(this); };
var data = dayData[dateStr];
var hasData = data && data.intake !== 0;
var progress = hasData ? Math.min(Math.abs(data.gap) / 500, 1) : 0;
dayEl.innerHTML =
'<div class="cal-day__wd">' + wd + '</div>' +
'<div class="cal-day__date-wrap">' +
(hasData ? ringSVG(progress) : '') +
'<span class="cal-day__num">' + dayNum + '</span>' +
'</div>';
container.appendChild(dayEl);
}
// 自动选中今天(如果在当前周),否则选中周一
var firstDay = container.querySelector('.cal-day.today');
if (!firstDay) firstDay = container.querySelector('.cal-day');
if (firstDay) selectDay(firstDay);
// 箭头状态:未来周灰显右箭头
var mondayOfThisWeek = getMonday(today);
var nextArrow = document.getElementById('calNext');
if (currentWeekStart.getTime() >= mondayOfThisWeek.getTime()) {
nextArrow.style.opacity = '0.3';
nextArrow.style.pointerEvents = 'none';
} else {
nextArrow.style.opacity = '1';
nextArrow.style.pointerEvents = '';
}
}
function prevWeek() {
currentWeekStart = new Date(currentWeekStart.getTime() - WEEK_MS);
renderWeek();
}
function nextWeek() {
var mondayOfThisWeek = getMonday(today);
if (currentWeekStart.getTime() >= mondayOfThisWeek.getTime()) return;
currentWeekStart = new Date(currentWeekStart.getTime() + WEEK_MS);
renderWeek();
}
/* ===== 月历弹窗 ===== */
var pickerYear = 2026;
var pickerMonth = 6; // 0-based (July)
function toggleMonthPicker() {
var picker = document.getElementById('monthPicker');
var arrow = document.getElementById('dateArrow');
var show = !picker.classList.contains('show');
picker.classList.toggle('show', show);
arrow.classList.toggle('open', show);
if (show) {
// 同步到当前选中日期所在月份
var selDay = document.querySelector('.cal-day.active');
if (selDay) {
var d = selDay.getAttribute('data-date').split('-');
pickerYear = parseInt(d[0]);
pickerMonth = parseInt(d[1]) - 1;
}
renderMonthGrid();
}
}
function prevMonth() {
if (pickerMonth === 0) { pickerMonth = 11; pickerYear--; }
else { pickerMonth--; }
renderMonthGrid();
}
function nextMonth() {
if (pickerMonth === 11) { pickerMonth = 0; pickerYear++; }
else { pickerMonth++; }
renderMonthGrid();
}
function renderMonthGrid() {
document.getElementById('monthPickerTitle').textContent = pickerYear + '年' + (pickerMonth + 1) + '月';
var grid = document.getElementById('monthGrid');
grid.innerHTML = '';
var firstDay = new Date(pickerYear, pickerMonth, 1).getDay(); // 0=Sun
var daysInMonth = new Date(pickerYear, pickerMonth + 1, 0).getDate();
// 填充上月尾巴
var prevDays = new Date(pickerYear, pickerMonth, 0).getDate();
for (var i = firstDay - 1; i >= 0; i--) {
var el = document.createElement('div');
el.className = 'month-picker__day month-picker__day--other';
el.textContent = prevDays - i;
grid.appendChild(el);
}
// 当月日期
var todayStr = fmtDate(today);
var selDay = document.querySelector('.cal-day.active');
var selStr = selDay ? selDay.getAttribute('data-date') : '';
for (var d = 1; d <= daysInMonth; d++) {
var dateStr = pickerYear + '-' + ('0' + (pickerMonth + 1)).slice(-2) + '-' + ('0' + d).slice(-2);
var el = document.createElement('div');
var cls = 'month-picker__day';
if (dateStr === todayStr) cls += ' month-picker__day--today';
if (dateStr === selStr) cls += ' month-picker__day--selected';
el.className = cls;
// 有记录:日期数字外包进度圆环
var data = dayData[dateStr];
var hasData = data && data.intake !== 0;
if (hasData) {
var progress = Math.min(Math.abs(data.gap) / 500, 1);
el.innerHTML = '<span class="month-picker__date-wrap">' +
monthRingSVG(progress) +
d + '</span>';
} else {
el.textContent = d;
}
el.onclick = (function(ds) { return function(e) { e.stopPropagation(); pickDay(ds); }; })(dateStr);
grid.appendChild(el);
}
}
function pickDay(dateStr) {
// 关闭弹窗
document.getElementById('monthPicker').classList.remove('show');
document.getElementById('dateArrow').classList.remove('open');
// 切换到该日期所在的周
var parts = dateStr.split('-');
var d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
currentWeekStart = getMonday(d);
renderWeek();
// 选中该日
var target = document.querySelector('.cal-day[data-date="' + dateStr + '"]');
if (target) selectDay(target);
}
// 点击外部关闭月历
document.addEventListener('click', function(e) {
var picker = document.getElementById('monthPicker');
if (!picker.classList.contains('show')) return;
if (!e.target.closest('.calendar-card__range') && !e.target.closest('#monthPicker')) {
picker.classList.remove('show');
document.getElementById('dateArrow').classList.remove('open');
}
});
/* ===== 计算弧线终点 ===== */
// 半圆弧: cx=170, cy=135, r=80。左端点(90,135)角度π,右端点(250,135)角度0。
// sweep=1 在 y-down SVG 中从左向右经顶部走。
// 进度 progress: 0~1,对应角度 θ = π * (1 - progress)
// x = 170 + 80 * cos(π*(1-p)), y = 135 - 80 * sin(π*(1-p))
function arcPoint(progress) {
var angle = Math.PI * (1 - progress);
var x = 170 + 80 * Math.cos(angle);
var y = 135 - 80 * Math.sin(angle);
return { x: Math.round(x * 10) / 10, y: Math.round(y * 10) / 10 };
}
/* ===== 选中日历天 ===== */
function selectDay(el) {
document.querySelectorAll('.cal-day').forEach(function(d) { d.classList.remove('active'); });
el.classList.add('active');
var dateStr = el.getAttribute('data-date');
// 更新顶部日期显示为选中日期
var parts = dateStr.split('-');
document.getElementById('calRange').textContent = parts[0] + '年' + parseInt(parts[1]) + '月' + parseInt(parts[2]) + '日';
var data = dayData[dateStr];
if (!data) {
// 无数据日,清空显示
data = { intake: 0, consume: 0, gap: 0 };
}
var gapAbs = Math.abs(data.gap);
var maxGap = 500;
var progress = data.gap === 0 ? 0 : Math.min(gapAbs / maxGap, 1);
var pt = arcPoint(progress);
var arcD = 'M 90 135 A 80 80 0 0 1 ' + pt.x + ' ' + pt.y;
var gapArc = document.getElementById('gapArc');
gapArc.setAttribute('d', arcD);
document.getElementById('intakeText').textContent = data.intake === 0 ? '--' : data.intake;
document.getElementById('consumeText').textContent = data.consume === 0 ? '--' : data.consume;
var gapCenter = document.getElementById('gapCenterVal');
gapCenter.textContent = data.gap === 0 ? '--' : ('-' + Math.abs(data.gap));
var colorIntake = data.intake === 0 ? '#bbb' : '#fa8c16';
var colorConsume = data.consume === 0 ? '#bbb' : '#52c41a';
var colorGap = data.gap === 0 ? '#bbb' : '#fa8c16';
document.getElementById('intakeText').setAttribute('fill', colorIntake);
document.getElementById('consumeText').setAttribute('fill', colorConsume);
gapCenter.setAttribute('fill', colorGap);
var rightDot = document.querySelector('.gauge-wrap svg circle[cy="135"][cx="250"]');
rightDot.setAttribute('fill', data.gap === 0 ? '#bbb' : '#52c41a');
// 更新餐食卡片
renderMeals(dateStr);
}
/* ===== 渲染餐食卡片 ===== */
function renderMeals(dateStr) {
var data = dayData[dateStr];
var meals = data && data.meals;
var dietTypes = ['breakfast', 'lunch', 'dinner', 'snack'];
// 检查是否有任何饮食记录
var hasAnyDiet = false;
var totalKcal = 0;
dietTypes.forEach(function(type) {
var md = meals ? meals[type] : null;
if (md && md.items && md.items.length > 0) hasAnyDiet = true;
});
if (hasAnyDiet) {
// 有饮食记录:显示各子分组
document.getElementById('dietEmpty').style.display = 'none';
dietTypes.forEach(function(type) {
var cap = type.charAt(0).toUpperCase() + type.slice(1);
var sub = document.getElementById('dietSub' + cap);
var container = document.getElementById('group' + cap);
var kcalEl = document.getElementById('kcal' + cap);
var md = meals ? meals[type] : null;
if (md && md.items && md.items.length > 0) {
sub.style.display = '';
var html = '';
md.items.forEach(function(item) {
var src = item.src || md.source || 'manual';
var srcLabel = src === 'auto' ? '智记' : '自记';
html += '<div class="meal-item">' +
'<div class="meal-item__emoji">' + item.e + '</div>' +
'<div class="meal-item__info"><div class="meal-item__name">' + item.n + '<span class="meal-item__src meal-item__src--' + src + '">' + srcLabel + '</span></div><div class="meal-item__meta">' + item.m + '</div></div>' +
'<div class="meal-item__kcal">' + item.k + ' kcal</div></div>';
});
container.innerHTML = html;
container.style.display = '';
kcalEl.textContent = md.kcal;
totalKcal += md.kcal;
} else {
sub.style.display = 'none';
}
});
} else {
// 无任何饮食记录:隐藏子分组,显示空状态
dietTypes.forEach(function(type) {
var sub = document.getElementById('dietSub' + type.charAt(0).toUpperCase() + type.slice(1));
if (sub) sub.style.display = 'none';
});
totalKcal = 0;
document.getElementById('dietEmpty').style.display = '';
}
// 更新饮食总热量
document.getElementById('kcalDiet').textContent = totalKcal > 0 ? totalKcal : '--';
// 运动卡片独立处理
var exCard = document.getElementById('cardExercise');
var exContainer = document.getElementById('groupExercise');
var exKcalEl = document.getElementById('kcalExercise');
var exMd = meals ? meals['exercise'] : null;
if (exMd && exMd.items && exMd.items.length > 0) {
exCard.style.display = '';
var html = '';
exMd.items.forEach(function(item) {
var srcTag = '';
if (item.src) {
srcTag = '<span class="meal-item__src meal-item__src--' + item.src + '">' + ({wearable:'穿戴',equip:'器械',wechat:'微信',manual:'自记'}[item.src] || item.src) + '</span>';
}
html += '<div class="meal-item meal-item--exercise">' +
'<div class="meal-item__emoji">' + item.e + '</div>' +
'<div class="meal-item__info"><div class="meal-item__name">' + item.n + srcTag + '</div><div class="meal-item__meta">' + item.m + '</div></div>' +
'<div class="meal-item__kcal">' + item.k + ' kcal</div></div>';
});
exContainer.innerHTML = html;
exContainer.style.display = '';
exKcalEl.textContent = exMd.kcal;
exKcalEl.parentElement.style.display = '';
} else {
exCard.style.display = 'none';
}
}
function toggleDietSub(type, headerEl) {
var items = document.getElementById('group' + type.charAt(0).toUpperCase() + type.slice(1));
var expand = headerEl.querySelector('.meal-card__expand');
if (items.style.display === 'none') {
items.style.display = '';
expand.classList.remove('open');
} else {
items.style.display = 'none';
expand.classList.add('open');
}
}
function toggleMeal(type, headerEl) {
var items = document.getElementById('group' + type.charAt(0).toUpperCase() + type.slice(1));
var expand = headerEl.querySelector('.meal-card__expand');
if (items.style.display === 'none') {
items.style.display = '';
expand.classList.remove('open');
} else {
items.style.display = 'none';
expand.classList.add('open');
}
}
/* ===== 初始化 ===== */
renderWeek();
</script>
</body>
</html>
@@ -28,7 +28,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.scroll-content::-webkit-scrollbar { display: none; }
/* ===== 计划卡片 ===== */
.plan-card { background: #fff; border-radius: 16px; margin: 12px 12px 0; padding: 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.plan-card { background: #fff; border-radius: 16px; margin: 12px 12px 0; padding: 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); cursor: pointer; transition: transform .15s; }
.plan-card__header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 10px; }
.plan-card__name { font-size: 15px; font-weight: 700; color: #1a1a1a; flex: 1; min-width: 0; line-height: 1.4; }
.plan-card__status { font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 8px; white-space: nowrap; flex-shrink: 0; margin-left: 8px; }
@@ -94,12 +94,12 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="scroll-content">
<!-- ===== 活动计划列表 ===== -->
<div id="tabActivity">
<div class="plan-card">
<div class="plan-card__header">
<span class="plan-card__name">2026年二季度体重管理活动</span>
<span class="plan-card__status plan-card__status--active">进行中</span>
</div>
<div id="tabActivity">
<div class="plan-card" onclick="location.href='goal-detail-v2.html?type=activity&state=signed'">
<div class="plan-card__header">
<span class="plan-card__name">2026年二季度体重管理活动</span>
<span class="plan-card__status plan-card__status--active">进行中</span>
</div>
<div class="plan-card__meta">
<span>起止:<em>2026-04-01 ~ 2026-09-30</em></span>
<span>机构:<em>公司机关</em></span>
@@ -122,14 +122,14 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="plan-card__footer">
<span class="plan-card__date">签署时间:2026-04-05 09:15</span>
<a class="plan-card__link" href="goal-detail-v2.html?type=activity&state=signed">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<a class="plan-card__link" href="goal-detail-v2.html?type=activity&state=signed" onclick="event.stopPropagation()">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<div class="plan-card">
<div class="plan-card__header">
<span class="plan-card__name">2026年一季度减重挑战赛</span>
<span class="plan-card__status plan-card__status--done">已结束</span>
<div class="plan-card" onclick="location.href='goal-detail-v2.html?type=activity&state=ended'">
<div class="plan-card__header">
<span class="plan-card__name">2026年一季度减重挑战赛</span>
<span class="plan-card__status plan-card__status--done">已结束</span>
</div>
<div class="plan-card__meta">
<span>起止:<em>2026-01-01 ~ 2026-03-31</em></span>
@@ -153,14 +153,14 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="plan-card__footer">
<span class="plan-card__date">活动已结束 · 减重 7.7kg</span>
<a class="plan-card__link" href="goal-detail-v2.html?type=activity&state=signed">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<a class="plan-card__link" onclick="event.stopPropagation()" href="goal-detail-v2.html?type=activity&state=ended">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<div class="plan-card">
<div class="plan-card__header">
<span class="plan-card__name">全员健康体重管理计划</span>
<span class="plan-card__status plan-card__status--done">已结束</span>
<div class="plan-card" onclick="location.href='goal-detail-v2.html?type=activity&state=ended'">
<div class="plan-card__header">
<span class="plan-card__name">全员健康体重管理计划</span>
<span class="plan-card__status plan-card__status--done">已结束</span>
</div>
<div class="plan-card__meta">
<span>起止:<em>2025-10-01 ~ 2026-06-30</em></span>
@@ -184,19 +184,19 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="plan-card__footer">
<span class="plan-card__date">活动已结束 · 减重 4.5kg</span>
<a class="plan-card__link" href="goal-detail-v2.html?type=activity&state=signed">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<a class="plan-card__link" onclick="event.stopPropagation()" href="goal-detail-v2.html?type=activity&state=ended">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<div class="bottom-safe"></div>
</div>
<div class="bottom-safe"></div>
</div>
<!-- ===== 个人计划列表 ===== -->
<div id="tabPersonal" class="hidden">
<div class="plan-card">
<div class="plan-card__header">
<span class="plan-card__name">2026年二季度个人减脂</span>
<span class="plan-card__status plan-card__status--active">进行中</span>
<!-- ===== 个人计划列表 ===== -->
<div id="tabPersonal" class="hidden">
<div class="plan-card" onclick="location.href='goal-detail-v2.html?type=personal&state=signed'">
<div class="plan-card__header">
<span class="plan-card__name">2026年二季度个人减脂</span>
<span class="plan-card__status plan-card__status--active">进行中</span>
</div>
<div class="plan-card__meta">
<span>起止:<em>2026-04-01 ~ 2026-08-26</em></span>
@@ -219,14 +219,14 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="plan-card__footer">
<span class="plan-card__date">剩余 29 天</span>
<a class="plan-card__link" href="goal-detail-v2.html?type=personal">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<a class="plan-card__link" onclick="event.stopPropagation()" href="goal-detail-v2.html?type=personal&state=signed">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
<div class="plan-card">
<div class="plan-card__header">
<span class="plan-card__name">2025年四季度塑形计划</span>
<span class="plan-card__status plan-card__status--done">已完成</span>
<div class="plan-card" onclick="location.href='goal-detail-v2.html?type=personal&state=ended'">
<div class="plan-card__header">
<span class="plan-card__name">2025年四季度塑形计划</span>
<span class="plan-card__status plan-card__status--done">已完成</span>
</div>
<div class="plan-card__meta">
<span>起止:<em>2025-10-01 ~ 2025-12-31</em></span>
@@ -249,7 +249,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<div class="plan-card__footer">
<span class="plan-card__date">已完成 · 减重 7.5kg · 达成目标 ✅</span>
<a class="plan-card__link" href="goal-detail-v2.html?type=personal">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
<a class="plan-card__link" onclick="event.stopPropagation()" href="goal-detail-v2.html?type=personal&state=ended">详情 <svg width="10" height="10" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></a>
</div>
</div>
@@ -47,9 +47,38 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.goal-card__empty.show { display: block; }
.goal-card__empty-icon { font-size: 40px; margin-bottom: 8px; }
.goal-card__empty-text { font-size: 13px; }
.goal-card__empty-link { display: inline-block; margin-top: 10px; font-size: 12px; color: #36cfc9; font-weight: 600; cursor: pointer; text-decoration: none; }
/* ===== 目标状态横幅 ===== */
.goal-card__empty-link { display: inline-block; margin-top: 10px; font-size: 12px; color: #36cfc9; font-weight: 600; cursor: pointer; text-decoration: none; }
/* ===== 目标卡片:人物+波浪球+标尺 ===== */
.goal-avatars { display: flex; align-items: center; justify-content: space-between; padding: 0 4px; margin: 4px 0 0; }
.goal-avatar-side { width: 78px; position: relative; display: flex; flex-direction: column; align-items: center; }
.goal-avatar__img { width: 64px; height: 96px; }
.goal-avatar__img svg { width: 100%; height: 100%; display: block; }
.goal-ball-wrap { text-align: center; flex: 1; position: relative; }
.goal-ball-wrap__top { font-size: 12px; color: #36cfc9; margin-bottom: 2px; position: absolute; top: 8px; left: 0; right: 0; z-index: 3; }
.goal-ball { width: 150px; height: 150px; margin: 0 auto; border-radius: 50%; position: relative; overflow: hidden; box-shadow: 0 6px 20px rgba(54,207,201,0.18); }
.goal-ball__bg { position: absolute; inset: 0; background: radial-gradient(circle at 50% 30%, #ffffff 0%, #e8fbfa 40%, #b6f0eb 100%); }
.goal-ball__wave { position: absolute; bottom: 0; left: 0; right: 0; height: 58%; }
.goal-ball__wave svg { width: 100%; height: 100%; display: block; }
.goal-ball__rulers { position: absolute; inset: 0; pointer-events: none; }
.goal-ball__rulers::before, .goal-ball__rulers::after { content: ''; position: absolute; top: 8px; bottom: 8px; width: 1px; background: repeating-linear-gradient(to bottom, rgba(54,207,201,0.4) 0 4px, transparent 4px 8px); }
.goal-ball__rulers::before { left: 14px; }
.goal-ball__rulers::after { right: 14px; }
.goal-ball__content { position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; z-index: 2; padding-top: 18px; }
.goal-ball__val { font-size: 36px; font-weight: 800; color: #1a1a1a; line-height: 1; }
.goal-ball__val .goal-ball__unit { font-size: 12px; color: #333; font-weight: 500; margin-left: 2px; }
.goal-ball__meta { font-size: 11px; color: #36a39f; margin-top: 2px; }
.goal-change-row { display: flex; justify-content: space-between; align-items: flex-start; margin-top: 14px; padding: 0 4px; }
.goal-change-row__cell { text-align: center; flex: 1; }
.goal-change-row__label { font-size: 13px; color: #999; margin-bottom: 4px; }
.goal-change-row__val { font-size: 26px; font-weight: 800; color: #1a1a1a; line-height: 1; }
.goal-change-row__val.negative { color: #36cfc9; }
.goal-change-row__sub { font-size: 12px; color: #bbb; margin-top: 4px; }
.goal-change-row__need { font-size: 12px; color: #faad14; margin-top: 4px; }
/* ===== 目标状态横幅 ===== */
.goal-status { display: none; padding: 12px 14px; border-radius: 12px; margin-bottom: 0; }
.goal-status.show { display: flex; align-items: flex-start; gap: 10px; }
.goal-status__icon { font-size: 22px; flex-shrink: 0; }
@@ -73,9 +102,8 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.goal-status--rejected .goal-status__text { color: #8c1a1a; }
.goal-status--rejected .goal-status__btn { background: #ff4d4f; color: #fff; }
/* ===== 标尺 SVG ===== */
.gauge-wrap { width: 100%; }
.gauge-wrap svg { width: 100%; height: auto; display: block; }
/* ===== 目标标尺容器 ===== */
.gauge-wrap { width: 100%; }
/* ===== 吃记录卡片 ===== */
.eat-card { background: #fff; border-radius: 16px; margin: 12px 12px 0; padding: 16px 16px 14px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); cursor: pointer; }
@@ -365,73 +393,83 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<!-- ===== 半圆标尺初始体重-左 | 当前体重- | 目标体重- ===== -->
<div class="gauge-wrap" id="goalGauge">
<svg viewBox="0 0 340 150" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gaugeGrad" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#2dd4bf"/>
<stop offset="100%" stop-color="#5cdbd3"/>
</linearGradient>
<radialGradient id="glowGrad" cx="50%" cy="80%" r="55%">
<stop offset="0%" stop-color="#e8fbfa" stop-opacity="0.3"/>
<stop offset="100%" stop-color="#fff" stop-opacity="0"/>
</radialGradient>
</defs>
<rect x="0" y="0" width="340" height="150" fill="url(#glowGrad)"/>
<!-- ======== 弧 ======== -->
<!-- cx=170, cy=135, r=80, 左端点(90,135) 右端点(250,135) -->
<!-- 灰色底轨 -->
<path d="M 90 135 A 80 80 0 0 1 250 135"
fill="none" stroke="#ebeef0" stroke-width="14" stroke-linecap="round"/>
<!-- 进度弧 62.3%,终点 (200, 61) -->
<path d="M 90 135 A 80 80 0 0 1 200 61"
fill="none" stroke="url(#gaugeGrad)" stroke-width="14" stroke-linecap="round"/>
<!-- ======== 刻度圆点 ======== -->
<circle cx="98" cy="103" r="2.5" fill="#cfcfcf"/>
<circle cx="120" cy="75" r="2.5" fill="#cfcfcf"/>
<circle cx="152" cy="60" r="2.5" fill="#cfcfcf"/>
<circle cx="188" cy="60" r="2.5" fill="#cfcfcf"/>
<circle cx="220" cy="75" r="2.5" fill="#cfcfcf"/>
<circle cx="242" cy="103" r="2.5" fill="#cfcfcf"/>
<!-- 端点 -->
<circle cx="90" cy="135" r="5" fill="#ff6b81" stroke="#fff" stroke-width="2.5"/>
<circle cx="250" cy="135" r="5" fill="#36cfc9" stroke="#fff" stroke-width="2.5"/>
<!-- ======== 当前进度指示器 ======== -->
<circle cx="200" cy="61" r="11" fill="#fff" stroke="#36cfc9" stroke-width="3"/>
<circle cx="200" cy="61" r="5" fill="#36cfc9"/>
<rect x="177" y="31" width="46" height="18" rx="9" fill="#36cfc9"/>
<text x="200" y="44" font-size="10" fill="#fff" text-anchor="middle" font-weight="700">62.6</text>
<!-- ============================================ -->
<!-- 左侧:初始体重(左对齐,距边缘 16) -->
<!-- ============================================ -->
<text x="16" y="90" font-size="12" fill="#999" text-anchor="start">初始体重</text>
<text x="16" y="112" font-size="22" fill="#ff6b81" text-anchor="start" font-weight="800">73.5</text>
<text x="16" y="127" font-size="10" fill="#bbb" text-anchor="start">BMI 28.6</text>
<!-- ============================================ -->
<!-- 右侧:目标体重(右对齐,距边缘 16) -->
<!-- ============================================ -->
<text x="324" y="90" font-size="12" fill="#999" text-anchor="end">目标体重</text>
<text x="324" y="112" font-size="22" fill="#36cfc9" text-anchor="end" font-weight="800">56.0</text>
<text x="324" y="127" font-size="10" fill="#bbb" text-anchor="end">BMI 22.5</text>
<!-- ============================================ -->
<!-- 中心:当前体重(半径居中) -->
<!-- ============================================ -->
<text x="170" y="90" font-size="12" fill="#999" text-anchor="middle">当前体重</text>
<text x="170" y="122" font-size="34" fill="#1a1a1a" text-anchor="middle" font-weight="800">62.6</text>
<text x="170" y="137" font-size="12" fill="#36cfc9" text-anchor="middle" font-weight="600">▼ 10.9</text>
</svg>
</div>
<!-- ===== 人物+波浪球+标尺初始体重-左 | 当前体重- | 目标体重-右 ===== -->
<div class="gauge-wrap" id="goalGauge">
<!-- 人物行:左小人 · 波浪球 · 右小人 -->
<div class="goal-avatars">
<!-- 左侧:初始体重 人物 -->
<div class="goal-avatar-side">
<div class="goal-avatar__img">
<svg viewBox="0 0 64 80" fill="none">
<circle cx="32" cy="13" r="10" fill="#FFD591"/>
<path d="M14 30 Q14 24 20 24 L44 24 Q50 24 50 30 L50 50 Q50 56 44 56 L20 56 Q14 56 14 50 Z" fill="#FF4D4F"/>
<rect x="10" y="22" width="10" height="14" rx="3" fill="#FF4D4F" transform="rotate(-20 15 28)"/>
<rect x="44" y="22" width="10" height="14" rx="3" fill="#FF4D4F" transform="rotate(20 49 28)"/>
<rect x="16" y="52" width="12" height="20" rx="3" fill="#1f1f1f"/>
<rect x="36" y="52" width="12" height="20" rx="3" fill="#1f1f1f"/>
<rect x="14" y="70" width="14" height="6" rx="2" fill="#fadb14"/>
<rect x="36" y="70" width="14" height="6" rx="2" fill="#fadb14"/>
<circle cx="27" cy="11" r="1.4" fill="#333"/>
<circle cx="37" cy="11" r="1.4" fill="#333"/>
</svg>
</div>
</div>
<!-- 中间:波浪球 -->
<div class="goal-ball-wrap">
<div class="goal-ball-wrap__top">当前体重</div>
<div class="goal-ball">
<div class="goal-ball__bg"></div>
<div class="goal-ball__rulers"></div>
<div class="goal-ball__wave">
<svg viewBox="0 0 150 90" preserveAspectRatio="none">
<path d="M0 30 Q25 12 50 30 Q75 48 100 30 Q125 12 150 30 L150 90 L0 90 Z" fill="#36cfc9" opacity="0.85"/>
<path d="M0 40 Q25 22 50 40 Q75 58 100 40 Q125 22 150 40 L150 90 L0 90 Z" fill="#5cdbd3" opacity="0.55"/>
</svg>
</div>
<div class="goal-ball__content">
<span class="goal-ball__val">62.6<span class="goal-ball__unit">公斤</span></span>
<span class="goal-ball__meta">BMI 23.6</span>
<span class="goal-ball__meta">体脂率:25.8%</span>
</div>
</div>
</div>
<!-- 右侧:目标体重 人物 -->
<div class="goal-avatar-side">
<div class="goal-avatar__img">
<svg viewBox="0 0 64 80" fill="none">
<circle cx="32" cy="13" r="10" fill="#FFD591"/>
<path d="M14 30 Q14 24 20 24 L44 24 Q50 24 50 30 L50 50 Q50 56 44 56 L20 56 Q14 56 14 50 Z" fill="#36cfc9"/>
<rect x="10" y="22" width="10" height="14" rx="3" fill="#36cfc9" transform="rotate(-20 15 28)"/>
<rect x="44" y="22" width="10" height="14" rx="3" fill="#36cfc9" transform="rotate(20 49 28)"/>
<rect x="16" y="52" width="12" height="20" rx="3" fill="#1f1f1f"/>
<rect x="36" y="52" width="12" height="20" rx="3" fill="#1f1f1f"/>
<rect x="14" y="70" width="14" height="6" rx="2" fill="#36cfc9"/>
<rect x="36" y="70" width="14" height="6" rx="2" fill="#36cfc9"/>
<circle cx="27" cy="11" r="1.4" fill="#333"/>
<circle cx="37" cy="11" r="1.4" fill="#333"/>
</svg>
</div>
</div>
</div>
<!-- 体重数据行:初始体重 · 体重变化 · 目标体重 -->
<div class="goal-change-row">
<div class="goal-change-row__cell">
<div class="goal-change-row__label">初始体重</div>
<div class="goal-change-row__val">73.5</div>
<div class="goal-change-row__sub">BMI 28.6</div>
</div>
<div class="goal-change-row__cell">
<div class="goal-change-row__label">体重变化</div>
<div class="goal-change-row__val negative">-10.9 <span style="font-size:14px;"></span></div>
<div class="goal-change-row__need">还要减 6.6</div>
</div>
<div class="goal-change-row__cell">
<div class="goal-change-row__label">目标体重</div>
<div class="goal-change-row__val">56.0</div>
<div class="goal-change-row__sub">BMI 22.5</div>
</div>
</div>
</div>
<!-- 未设置目标时的空状态 -->
<div class="goal-card__empty" id="goalEmpty">
@@ -443,7 +481,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<!-- ====== 热量缺口卡片 ====== -->
<div class="gap-card" onclick="location.href='eat-detail-v2.html'">
<div class="gap-card" onclick="location.href='eat-exercise-detail-v2.html'">
<div class="gap-card__title"><span>吃动平衡</span><span><svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="9 18 15 12 9 6"/></svg></span></div>
<!-- 本周日历 -->
@@ -86,10 +86,13 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.summary-compare th { padding: 8px 6px; text-align: center; font-weight: 700; color: #999; font-size: 11px; border-bottom: 2px solid #f0f0f0; }
.summary-compare td { padding: 9px 6px; text-align: center; color: #333; border-bottom: 1px solid #f5f5f5; }
.summary-compare td:first-child { text-align: left; font-weight: 600; color: #666; padding-left: 4px; }
.summary-compare tr:last-child td { border-bottom: none; }
.summary-compare .val-down { color: #36cfc9; font-weight: 700; }
.summary-compare .val-target { color: #faad14; font-weight: 700; }
.summary-compare .val-achieved { color: #36cfc9; font-weight: 700; }
.summary-compare tr:last-child td { border-bottom: none; }
.summary-compare .val-down { color: #36cfc9; font-weight: 700; }
.summary-compare .val-target { color: #faad14; font-weight: 700; }
.summary-compare .val-achieved { color: #36cfc9; font-weight: 700; }
/* 隐藏目标列 */
.summary-compare.hide-target th:nth-child(4),
.summary-compare.hide-target td:nth-child(4) { display: none; }
/* 标签 */
.summary-tag { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 10px; font-weight: 600; margin-left: 4px; }
@@ -136,20 +139,34 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="overview-card__label">截止至 <span id="ovCutoff">2026-08-12</span></span>
<span class="overview-card__label" id="ovDayWrap"><span id="ovDay">52</span> 天(共 <span id="ovTotal">180</span> 天)</span>
</div>
<div class="overview-metrics">
<div class="overview-metric">
<div class="overview-metric__val">-10.9<span class="overview-metric__unit">kg</span></div>
<div class="overview-metric__desc">已减体重</div>
</div>
<div class="overview-metric">
<div class="overview-metric__val">62<span class="overview-metric__unit">%</span></div>
<div class="overview-metric__desc">目标进度</div>
</div>
<div class="overview-metric">
<div class="overview-metric__val">~1.5<span class="overview-metric__unit"></span></div>
<div class="overview-metric__desc">预计达成</div>
</div>
</div>
<div class="overview-metrics" id="ovMetricsActivity">
<div class="overview-metric">
<div class="overview-metric__val">-10.9<span class="overview-metric__unit">kg</span></div>
<div class="overview-metric__desc">已减体重</div>
</div>
<div class="overview-metric">
<div class="overview-metric__val">62<span class="overview-metric__unit">%</span></div>
<div class="overview-metric__desc">目标进度</div>
</div>
<div class="overview-metric">
<div class="overview-metric__val">~1.5<span class="overview-metric__unit"></span></div>
<div class="overview-metric__desc">预计达成</div>
</div>
</div>
<div class="overview-metrics" id="ovMetricsNone" style="display:none;">
<div class="overview-metric">
<div class="overview-metric__val">73.5<span class="overview-metric__unit">kg</span></div>
<div class="overview-metric__desc">初始体重</div>
</div>
<div class="overview-metric">
<div class="overview-metric__val">62.6<span class="overview-metric__unit">kg</span></div>
<div class="overview-metric__desc">当前体重</div>
</div>
<div class="overview-metric">
<div class="overview-metric__val">-10.9<span class="overview-metric__unit">kg</span></div>
<div class="overview-metric__desc">已减体重</div>
</div>
</div>
</div>
<!-- 非活动类型缺省 -->
@@ -357,21 +374,35 @@ function applyPageType(type) {
overviewEmpty.style.display = 'none';
if (ovInfoRow) ovInfoRow.style.display = '';
document.getElementById('ovDayWrap').style.display = '';
document.getElementById('ovMetricsActivity').style.display = '';
document.getElementById('ovMetricsNone').style.display = 'none';
document.getElementById('summarySection').style.display = '';
// 显示目标列
var tables = document.querySelectorAll('.summary-compare');
tables.forEach(function(t) { t.classList.remove('hide-target'); });
} else if (type === 'personal') {
navTitle.textContent = '个人减重目标标题';
overviewCard.style.display = '';
overviewEmpty.style.display = 'none';
if (ovInfoRow) ovInfoRow.style.display = '';
document.getElementById('ovDayWrap').style.display = '';
document.getElementById('ovMetricsActivity').style.display = '';
document.getElementById('ovMetricsNone').style.display = 'none';
document.getElementById('summarySection').style.display = '';
var tables2 = document.querySelectorAll('.summary-compare');
tables2.forEach(function(t) { t.classList.remove('hide-target'); });
} else {
navTitle.textContent = '体重报告';
overviewCard.style.display = '';
overviewEmpty.style.display = 'none';
if (ovInfoRow) ovInfoRow.style.display = 'none';
if (ovInfoRow) ovInfoRow.style.display = '';
document.getElementById('ovDayWrap').style.display = 'none';
document.getElementById('summarySection').style.display = 'none';
document.getElementById('ovMetricsActivity').style.display = 'none';
document.getElementById('ovMetricsNone').style.display = '';
document.getElementById('summarySection').style.display = '';
// 隐藏目标列
var tables3 = document.querySelectorAll('.summary-compare');
tables3.forEach(function(t) { t.classList.add('hide-target'); });
}
}