feat: 新增体重管理v2运动与目标页面,优化多个v2页面及蓝牙秤小程序

This commit is contained in:
liuyunqin
2026-07-27 17:49:06 +08:00
parent 8108f5520e
commit 2a1e15d928
18 changed files with 4060 additions and 107 deletions
+1 -1
View File
@@ -97,7 +97,7 @@
<!-- [交互] 导航栏 -->
<div class="nav-bar">
<span class="nav-back" onclick="history.back()"></span>
<span class="nav-back" onclick="window.location.href='../home/index.html'"></span>
<span class="nav-title">应急就医</span>
</div>
@@ -126,6 +126,9 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.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; }
@@ -496,28 +499,28 @@ var dayData = {
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},{e:'🚶',n:'步行',m:'5500步',k:-80}] }
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}] }
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},{e:'🚶',n:'步行',m:'5000步',k:-60}] }
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},{e:'🚶',n:'步行',m:'6800步 · 今日累计',k:-60}] }
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 },
@@ -863,9 +866,13 @@ function renderMeals(dateStr) {
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 + '</div><div class="meal-item__meta">' + item.m + '</div></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;
@@ -0,0 +1,621 @@
<!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: 12px 12px 8px; 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: 8px; }
.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: 38px; 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; }
.month-picker__day:hover { background: #f5f5f5; }
.month-picker__day--other { color: #ddd; }
.month-picker__day--today { background: #f0fff0; color: #52c41a; font-weight: 700; }
.month-picker__day--selected { background: #52c41a; color: #fff; font-weight: 700; }
.cal-week { display: flex; gap: 4px; }
.cal-day { flex: 1; text-align: center; padding: 6px 0; border-radius: 10px; cursor: pointer; background: #f7f8fa; transition: background .2s; }
.cal-day.active { background: #f0fff0; }
.cal-day.today .cal-day__num { color: #1a1a1a; font-weight: 800; }
.cal-day.active .cal-day__num { color: #52c41a; font-weight: 800; }
.cal-day.active .cal-day__wd { color: #52c41a; font-weight: 600; }
.cal-day__wd { font-size: 10px; color: #999; margin-bottom: 3px; }
.cal-day__date-wrap { position: relative; display: inline-flex; align-items: center; justify-content: center; width: 28px; height: 28px; margin: 0 auto; }
.cal-day__num { font-size: 14px; font-weight: 700; color: #333; position: relative; z-index: 2; }
.cal-day__ring { position: absolute; inset: 0; border-radius: 50%; border: 2.5px solid #52c41a; z-index: 1; opacity: 0.35; }
.cal-day.active .cal-day__ring { opacity: 1; }
.cal-day.no-plan { opacity: 0.4; }
/* ===== 运动建议摘要卡片 ===== */
.advice-summary { background: linear-gradient(160deg, #f0fff0, #f6ffed); border-radius: 16px; margin: 10px 12px 0; padding: 14px 16px; border: 1px solid #b7ebc4; }
.advice-summary__header { display: flex; align-items: center; gap: 6px; cursor: pointer; user-select: none; }
.advice-summary__icon { font-size: 16px; flex-shrink: 0; }
.advice-summary__title { font-size: 14px; font-weight: 700; color: #1a1a1a; flex: 1; }
.advice-summary__arrow { flex-shrink: 0; color: #999; transition: transform .25s; }
.advice-summary__arrow.collapsed { transform: rotate(180deg); }
.advice-summary__body { margin-top: 8px; transition: all .25s; }
.advice-summary__body.collapsed { display: none; }
.advice-summary__text { font-size: 13px; color: #555; line-height: 1.7; }
/* ===== 周计划目标概览(紧凑行内) ===== */
.week-goal-card { background: #fff; border-radius: 16px; margin: 10px 12px 0; padding: 14px 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.week-goal-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.week-goal-card__title { font-size: 15px; font-weight: 800; color: #1a1a1a; }
.week-goal-card__tag { font-size: 11px; font-weight: 600; color: #52c41a; background: #f0fff0; padding: 3px 10px; border-radius: 10px; }
.goal-stats { display: flex; gap: 8px; }
.goal-stat { flex: 1; background: #f7f8fa; border-radius: 12px; padding: 10px 8px; text-align: center; }
.goal-stat__icon { font-size: 20px; margin-bottom: 4px; }
.goal-stat__val { font-size: 15px; font-weight: 800; color: #1a1a1a; }
.goal-stat__label { font-size: 10px; color: #999; margin-top: 2px; }
.goal-stat--highlight { background: linear-gradient(160deg, #f0fff0, #e6ffe6); }
.goal-stat--highlight .goal-stat__val { color: #52c41a; }
/* ===== 慢病提示行(嵌入运动建议卡片内) ===== */
.advice-warning { display: flex; align-items: flex-start; gap: 10px; padding: 10px 12px; background: #fff2f0; border-radius: 8px; margin-top: 8px; border: 1px solid #ffccc7; }
.advice-warning__icon { width: 22px; height: 22px; border-radius: 50%; background: #ff4d4f; color: #fff; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 11px; font-weight: 800; }
.advice-warning__text { font-size: 12px; color: #666; line-height: 1.65; flex: 1; }
.advice-warning__text strong { color: #333; }
.advice-warning--orange { background: #fffbe6; border-color: #ffe58f; }
.advice-warning--orange .advice-warning__icon { background: #faad14; }
/* ===== 每日运动计划 ===== */
.day-plan-card { background: #fff; border-radius: 16px; margin: 10px 12px 0; padding: 16px 16px 14px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.day-plan-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.day-plan-card__title { font-size: 15px; font-weight: 800; color: #1a1a1a; }
.day-plan-card__date { display: flex; align-items: center; gap: 6px; }
.day-plan-card__date-badge { font-size: 11px; font-weight: 700; padding: 3px 10px; border-radius: 8px; background: #f0fff0; color: #52c41a; }
.day-plan-card__date-badge--rest { background: #fffbe6; color: #fa8c16; }
/* 当日概览 */
.day-overview { padding: 12px; background: #f9fafb; border-radius: 12px; }
.day-overview__total { font-size: 14px; font-weight: 800; color: #1a1a1a; margin-bottom: 2px; }
.day-overview__total em { font-style: normal; color: #52c41a; }
.day-overview__total em.rest { color: #fa8c16; }
.day-overview__sub { font-size: 11px; color: #999; margin-bottom: 8px; }
.day-overview__tags { display: flex; gap: 6px; flex-wrap: wrap; }
.day-overview__tag { font-size: 10px; font-weight: 600; padding: 3px 8px; border-radius: 6px; }
.day-overview__tag--aerobic { background: #fff2e8; color: #ff8a3d; }
.day-overview__tag--strength { background: #f0f5ff; color: #5470c6; }
.day-overview__tag--flex { background: #f6ffed; color: #52c41a; }
.day-overview__tag--rest { background: #fffbe6; color: #fa8c16; }
/* 运动项列表 */
.plan-list { margin-top: 10px; display: flex; flex-direction: column; gap: 8px; }
.plan-item { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: #f9fafb; border-radius: 12px; }
.plan-item__icon-wrap { width: 40px; height: 40px; border-radius: 12px; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.plan-item__icon-wrap--aerobic { background: #fff2e8; }
.plan-item__icon-wrap--strength { background: #f0f5ff; }
.plan-item__icon-wrap--flex { background: #f6ffed; }
.plan-item__icon-wrap--walk { background: #f9f0ff; }
.plan-item__info { flex: 1; min-width: 0; }
.plan-item__name { font-size: 14px; font-weight: 600; color: #333; }
.plan-item__desc { font-size: 11px; color: #999; margin-top: 2px; line-height: 1.4; }
.plan-item__meta { display: flex; gap: 12px; margin-top: 2px; }
.plan-item__meta-item { font-size: 11px; color: #bbb; }
.plan-item__meta-item em { font-style: normal; color: #52c41a; font-weight: 600; }
.plan-item__kcal { font-size: 14px; font-weight: 700; color: #52c41a; flex-shrink: 0; text-align: right; }
.plan-item__kcal-unit { font-size: 10px; font-weight: 400; color: #bbb; }
/* 休息日样式 */
.plan-item--rest { background: #fffbe6; }
.plan-item--rest .plan-item__name { color: #fa8c16; }
.plan-item--rest .plan-item__kcal { color: #fa8c16; }
.plan-item__icon-wrap--rest { background: #fffbe6; }
/* 空白日 */
.day-plan-card__empty { text-align: center; padding: 32px 0; color: #ccc; }
.day-plan-card__empty-icon { font-size: 40px; margin-bottom: 8px; }
.day-plan-card__empty-text { font-size: 13px; }
.bottom-safe { height: 16px; flex-shrink: 0; }
</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">
<!-- ===== 1. 周日历:唯一的日期选择器 ===== -->
<div class="calendar-card">
<div class="calendar-card__header">
<div class="calendar-card__arrow" 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月27日</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>
<div class="cal-week" id="calWeek"></div>
</div>
<!-- ===== 2. 运动建议摘要(可折叠) ===== -->
<div class="advice-summary">
<div class="advice-summary__header" onclick="toggleAdviceSummary()">
<span class="advice-summary__icon">💡</span>
<span class="advice-summary__title">本周运动建议</span>
<svg class="advice-summary__arrow" id="adviceArrow" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><polyline points="18 15 12 9 6 15"/></svg>
</div>
<div class="advice-summary__body" id="adviceBody">
<p class="advice-summary__text">
根据你的身体数据和运动目标,本周建议<strong>每周运动 5 天</strong>,每天总消耗目标约 <strong>600-1000 千卡</strong>。以<strong>有氧运动为主</strong>(每周3-4次),配合<strong>力量训练</strong>(每周2次,隔天进行),穿插<strong>柔韧拉伸</strong>帮助肌肉恢复。周六安排趣味球类运动提高参与度,周日为<strong>主动恢复日</strong>,以低强度活动为主。运动时注意监测心率,保持在 (220-年龄)×60%~70% 区间。
</p>
<div class="advice-warning">
<div class="advice-warning__icon">!</div>
<div class="advice-warning__text"><strong>高血压注意:</strong>避免憋气用力动作(如大重量举重),运动中监测心率不超过(220-年龄)×70%。首选快走、游泳、太极等中低强度有氧运动。</div>
</div>
<div class="advice-warning advice-warning--orange">
<div class="advice-warning__icon">!</div>
<div class="advice-warning__text"><strong>高尿酸提醒:</strong>避免剧烈无氧运动(产生乳酸抑制尿酸排泄),运动后及时补水2000ml以上。急性发作期应暂停运动,待症状缓解后从低强度开始恢复。</div>
</div>
</div>
</div>
<!-- ===== 3. 周目标概览 ===== -->
<div class="week-goal-card">
<div class="week-goal-card__header">
<span class="week-goal-card__title">本周目标概览</span>
<span class="week-goal-card__tag" id="weekRangeTag">7/27 - 8/2</span>
</div>
<div class="goal-stats">
<div class="goal-stat">
<div class="goal-stat__icon">📅</div>
<div class="goal-stat__val" id="statDays">5</div>
<div class="goal-stat__label">运动天数</div>
</div>
<div class="goal-stat">
<div class="goal-stat__icon">🔥</div>
<div class="goal-stat__val" id="statTotalKcal">4780</div>
<div class="goal-stat__label">周消耗(kcal)</div>
</div>
<div class="goal-stat goal-stat--highlight">
<div class="goal-stat__icon">⏱️</div>
<div class="goal-stat__val" id="statDuration">310</div>
<div class="goal-stat__label">周时长(分钟)</div>
</div>
<div class="goal-stat">
<div class="goal-stat__icon">🧘</div>
<div class="goal-stat__val" id="statRestDays">2</div>
<div class="goal-stat__label">休息日</div>
</div>
</div>
</div>
<!-- ===== 4. 每日运动安排(由顶部日历驱动) ===== -->
<div class="day-plan-card">
<div class="day-plan-card__header">
<span class="day-plan-card__title">运动安排详情</span>
<span class="day-plan-card__date" id="dayDateBadgeWrap">
<span class="day-plan-card__date-badge" id="dayDateBadge">7月27日 周一</span>
</span>
</div>
<!-- 当日概览 -->
<div class="day-overview" id="dayOverview"></div>
<!-- 运动项列表 -->
<div class="plan-list" id="planList"></div>
<!-- 空状态 -->
<div class="day-plan-card__empty" id="dayEmpty" style="display:none;">
<div class="day-plan-card__empty-icon">📋</div>
<div class="day-plan-card__empty-text">该日期暂无运动计划</div>
</div>
</div>
<div class="bottom-safe"></div>
</div>
</div>
<script>
/* ===== 一周运动计划数据 ===== */
var weekPlans = {
'2026-07-27': { rest: false, totalKcal: 980, totalMin: 70, tags: ['有氧运动','力量训练'], items: [
{ icon:'🏃', cls:'aerobic', name:'户外慢跑', desc:'配速 7-8 min/km,时长 30 分钟', duration:'30分钟', intensity:'中等', kcal:'320' },
{ icon:'💪', cls:'strength', name:'上肢力量训练', desc:'俯卧撑 3组×15 + 哑铃推举 3组×12 + 划船 3组×12', duration:'30分钟', intensity:'中高', kcal:'200' },
{ icon:'🚶', cls:'walk', name:'全天步行', desc:'目标步数 10000 步以上,利用通勤和午休时间', duration:'全天', intensity:'低', kcal:'460' }
]},
'2026-07-28': { rest: false, totalKcal: 820, totalMin: 65, tags: ['有氧运动','柔韧拉伸'], items: [
{ icon:'🏊', cls:'aerobic', name:'游泳', desc:'蛙泳/自由泳交替,中等速度,时长 35 分钟', duration:'35分钟', intensity:'中等', kcal:'350' },
{ icon:'🧘', cls:'flex', name:'哈他瑜伽', desc:'基础序列:拜日式 + 站姿 + 坐姿扭转,缓解放松', duration:'25分钟', intensity:'低', kcal:'120' },
{ icon:'🚶', cls:'walk', name:'全天步行', desc:'目标步数 8000 步以上', duration:'全天', intensity:'低', kcal:'350' }
]},
'2026-07-29': { rest: false, totalKcal: 1050, totalMin: 75, tags: ['有氧运动','力量训练'], items: [
{ icon:'🚴', cls:'aerobic', name:'动感单车', desc:'中等阻力间歇骑行,1分钟冲刺+2分钟恢复 ×10组', duration:'40分钟', intensity:'中高', kcal:'420' },
{ icon:'💪', cls:'strength', name:'下肢力量训练', desc:'深蹲 4组×15 + 弓步蹲 3组×12(左右)+ 臀桥 3组×20', duration:'30分钟', intensity:'中高', kcal:'240' },
{ icon:'🚶', cls:'walk', name:'全天步行', desc:'目标步数 9000 步以上', duration:'全天', intensity:'低', kcal:'390' }
]},
'2026-07-30': { rest: false, totalKcal: 760, totalMin: 60, tags: ['有氧运动','核心训练'], items: [
{ icon:'🏃', cls:'aerobic', name:'快走', desc:'配速 10 min/km,保持心率120-135,时长 40 分钟', duration:'40分钟', intensity:'低中', kcal:'280' },
{ icon:'🧘', cls:'flex', name:'核心训练', desc:'平板支撑 3组×45秒 + 卷腹 3组×20 + 仰卧举腿 3组×15', duration:'15分钟', intensity:'中等', kcal:'160' },
{ icon:'🚶', cls:'walk', name:'全天步行', desc:'目标步数 8000 步以上', duration:'全天', intensity:'低', kcal:'320' }
]},
'2026-07-31': { rest: false, totalKcal: 970, totalMin: 70, tags: ['有氧运动','力量训练'], items: [
{ icon:'🏃', cls:'aerobic', name:'间歇跑', desc:'快跑 2分钟 + 慢跑 3分钟 循环 ×6组,总时长 30 分钟', duration:'30分钟', intensity:'高', kcal:'380' },
{ icon:'💪', cls:'strength', name:'全身力量循环', desc:'深蹲 + 俯卧撑 + 划船 + 肩推 各15次 ×3组,组间休息60秒', duration:'30分钟', intensity:'中高', kcal:'240' },
{ icon:'🚶', cls:'walk', name:'全天步行', desc:'目标步数 9000 步以上', duration:'全天', intensity:'低', kcal:'350' }
]},
'2026-08-01': { rest: false, totalKcal: 1100, totalMin: 80, tags: ['球类运动','柔韧拉伸'], items: [
{ icon:'🏸', cls:'aerobic', name:'羽毛球', desc:'双打 / 单打,注意移动节奏,时长 50 分钟', duration:'50分钟', intensity:'中高', kcal:'480' },
{ icon:'🧘', cls:'flex', name:'运动后拉伸', desc:'全身静态拉伸:腿、髋、肩、背,每个动作20-30秒', duration:'15分钟', intensity:'低', kcal:'90' },
{ icon:'🚶', cls:'walk', name:'全天步行', desc:'目标步数 10000 步以上', duration:'全天', intensity:'低', kcal:'530' }
]},
'2026-08-02': { rest: true, totalKcal: 450, totalMin: 50, tags: ['主动恢复'], items: [
{ icon:'🚶', cls:'walk', name:'悠闲散步', desc:'公园/绿道慢走,放松身心,不追求速度和距离', duration:'30分钟', intensity:'极低', kcal:'180' },
{ icon:'🧘', cls:'flex', name:'恢复拉伸 + 泡沫轴', desc:'阴瑜伽体式 3-5个 + 泡沫轴放松大腿、小腿、背部', duration:'20分钟', intensity:'极低', kcal:'90' },
{ icon:'🚶', cls:'walk', name:'全天步行', desc:'目标步数 7000 步以上,以舒适为主', duration:'全天', intensity:'低', kcal:'180' }
]}
};
/* ===== 周导航 ===== */
var WEEK_MS = 7 * 24 * 60 * 60 * 1000;
var today = new Date(2026, 6, 27); // 2026-07-27
var currentWeekStart = getMonday(today);
var selectedDate = fmtDate(today);
var weekDayLabels = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'];
function getMonday(d) {
var m = new Date(d);
var day = m.getDay();
var diff = day === 0 ? -6 : 1 - day;
m.setDate(m.getDate() + diff);
m.setHours(0, 0, 0, 0);
return m;
}
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 isSameDay(a, b) {
return a.getFullYear() === b.getFullYear() &&
a.getMonth() === b.getMonth() &&
a.getDate() === b.getDate();
}
/* ===== 渲染一周日历 ===== */
function renderWeek() {
var container = document.getElementById('calWeek');
container.innerHTML = '';
var weekDayChars = ['日', '一', '二', '三', '四', '五', '六'];
// 更新周范围标签
var weekEnd = new Date(currentWeekStart);
weekEnd.setDate(weekEnd.getDate() + 6);
document.getElementById('weekRangeTag').textContent =
(currentWeekStart.getMonth() + 1) + '/' + currentWeekStart.getDate() +
' - ' + (weekEnd.getMonth() + 1) + '/' + weekEnd.getDate();
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 hasPlan = !!weekPlans[dateStr];
var dayEl = document.createElement('div');
dayEl.className = 'cal-day' + (isToday ? ' today' : '') + (!hasPlan ? ' no-plan' : '');
dayEl.setAttribute('data-date', dateStr);
dayEl.onclick = function() { selectDay(this); };
dayEl.innerHTML =
'<div class="cal-day__wd">' + wd + '</div>' +
'<div class="cal-day__date-wrap">' +
(hasPlan ? '<div class="cal-day__ring"></div>' : '') +
'<span class="cal-day__num">' + dayNum + '</span>' +
'</div>';
container.appendChild(dayEl);
}
// 箭头状态:未来周禁止前进
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 = '';
}
// 更新周统计(必须在 selectDay 之前,因为 selectDay 不更新统计)
updateWeekStats();
// 选中当前日期(如果在本周内),否则 fallback
var selParts = selectedDate.split('-');
var selD = new Date(parseInt(selParts[0]), parseInt(selParts[1]) - 1, parseInt(selParts[2]));
var selMonday = getMonday(selD);
var target = null;
if (selMonday.getTime() === currentWeekStart.getTime()) {
target = container.querySelector('.cal-day[data-date="' + selectedDate + '"]');
}
if (!target) {
target = container.querySelector('.cal-day.today');
if (!target) target = container.querySelector('.cal-day:not(.no-plan)');
if (!target) target = container.querySelector('.cal-day');
}
if (target) selectDay(target);
}
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();
}
function selectDay(el) {
document.querySelectorAll('.cal-day').forEach(function(d) { d.classList.remove('active'); });
el.classList.add('active');
selectedDate = el.getAttribute('data-date');
var parts = selectedDate.split('-');
document.getElementById('calRange').textContent = parts[0] + '年' + parseInt(parts[1]) + '月' + parseInt(parts[2]) + '日';
renderDayPlan();
}
/* ===== 月历弹窗 ===== */
var pickerYear = 2026;
var pickerMonth = 6;
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) {
pickerYear = today.getFullYear();
pickerMonth = today.getMonth();
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();
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);
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 === selectedDate) cls += ' month-picker__day--selected';
el.className = cls;
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);
selectedDate = dateStr;
renderWeek();
}
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');
}
});
/* ===== 运动建议折叠 ===== */
function toggleAdviceSummary() {
var body = document.getElementById('adviceBody');
var arrow = document.getElementById('adviceArrow');
body.classList.toggle('collapsed');
arrow.classList.toggle('collapsed');
}
/* ===== 周统计更新 ===== */
function updateWeekStats() {
var monday = new Date(currentWeekStart);
var activeDays = 0, totalKcal = 0, totalMin = 0, restDays = 0;
for (var i = 0; i < 7; i++) {
var d = new Date(monday);
d.setDate(d.getDate() + i);
var plan = weekPlans[fmtDate(d)];
if (!plan) continue;
if (plan.rest) { restDays++; }
else { activeDays++; totalKcal += plan.totalKcal; totalMin += plan.totalMin; }
}
document.getElementById('statDays').textContent = activeDays;
document.getElementById('statTotalKcal').textContent = totalKcal;
document.getElementById('statDuration').textContent = totalMin;
document.getElementById('statRestDays').textContent = restDays;
}
/* ===== 渲染每日运动安排 ===== */
function renderDayPlan() {
var plan = weekPlans[selectedDate];
var overviewEl = document.getElementById('dayOverview');
var planListEl = document.getElementById('planList');
var emptyEl = document.getElementById('dayEmpty');
var badgeEl = document.getElementById('dayDateBadge');
// 更新日期徽章
var parts = selectedDate.split('-');
var d = new Date(parseInt(parts[0]), parseInt(parts[1]) - 1, parseInt(parts[2]));
var label = (d.getMonth() + 1) + '月' + d.getDate() + '日 ' + weekDayLabels[d.getDay()];
badgeEl.textContent = label;
badgeEl.className = 'day-plan-card__date-badge';
// 无计划状态
if (!plan) {
overviewEl.innerHTML = '';
planListEl.innerHTML = '';
emptyEl.style.display = '';
return;
}
emptyEl.style.display = 'none';
var isRest = plan.rest;
if (isRest) {
badgeEl.className = 'day-plan-card__date-badge day-plan-card__date-badge--rest';
}
// 类型标签
var tagsHtml = '';
plan.tags.forEach(function(tag) {
var tagCls = 'rest';
if (tag.indexOf('有氧') !== -1) tagCls = 'aerobic';
else if (tag.indexOf('力量') !== -1) tagCls = 'strength';
else if (tag.indexOf('柔韧') !== -1 || tag.indexOf('核心') !== -1) tagCls = 'flex';
tagsHtml += '<span class="day-overview__tag day-overview__tag--' + tagCls + '">' + tag + '</span>';
});
var restLabel = isRest ? ' 🛌 休息日' : '';
var kcalClass = isRest ? ' class="rest"' : '';
overviewEl.innerHTML =
'<div class="day-overview__total">今日目标消耗 <em' + kcalClass + '>' + plan.totalKcal + ' 千卡</em>' + restLabel + '</div>' +
'<div class="day-overview__sub">运动时长约 ' + plan.totalMin + ' 分钟</div>' +
'<div class="day-overview__tags">' + tagsHtml + '</div>';
// 运动项
var itemsHtml = '';
plan.items.forEach(function(item) {
var itemCls = isRest ? 'plan-item plan-item--rest' : 'plan-item';
var iconCls = item.cls;
itemsHtml += '<div class="' + itemCls + '">' +
'<div class="plan-item__icon-wrap plan-item__icon-wrap--' + iconCls + '">' + item.icon + '</div>' +
'<div class="plan-item__info">' +
'<div class="plan-item__name">' + item.name + '</div>' +
'<div class="plan-item__desc">' + item.desc + '</div>' +
'<div class="plan-item__meta">' +
'<span class="plan-item__meta-item">⏱ <em>' + item.duration + '</em></span>' +
'<span class="plan-item__meta-item">📊 ' + item.intensity + '强度</span>' +
'</div>' +
'</div>' +
'<div class="plan-item__kcal">' + item.kcal + '<span class="plan-item__kcal-unit"> kcal</span></div>' +
'</div>';
});
planListEl.innerHTML = itemsHtml;
}
/* ===== 初始化 ===== */
renderWeek();
</script>
</body>
</html>
@@ -0,0 +1,706 @@
<!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; position: relative; }
.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; }
/* ===== 时段选择 + 搜索行 ===== */
.filter-row { background: #fff; padding: 10px 12px; display: flex; align-items: center; gap: 8px; border-bottom: 1px solid #f0f0f0; }
.filter-row__period { flex-shrink: 0; position: relative; }
.filter-row__period-btn { height: 34px; padding: 0 28px 0 12px; border: 1.5px solid #e8e8e8; border-radius: 8px; background: #fff; font-size: 13px; font-weight: 600; color: #333; cursor: pointer; display: flex; align-items: center; gap: 4px; white-space: nowrap; appearance: none; }
.filter-row__period-btn:active { background: #f7f8fa; }
.filter-row__period-arrow { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); pointer-events: none; color: #bbb; }
.filter-row__period-arrow svg { width: 12px; height: 12px; display: block; }
.filter-row__search { flex: 1; min-width: 0; position: relative; }
.filter-row__search-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); color: #bbb; pointer-events: none; }
.filter-row__search-icon svg { width: 15px; height: 15px; display: block; }
.filter-row__search-input { width: 100%; height: 34px; border: 1.5px solid #e8e8e8; border-radius: 8px; padding: 0 10px 0 32px; font-size: 13px; color: #333; background: #f7f8fa; outline: none; }
.filter-row__search-input:focus { border-color: #52c41a; background: #fff; }
.filter-row__search-input::placeholder { color: #bbb; }
/* 时段下拉菜单 */
.period-dropdown { display: none; position: absolute; top: 38px; left: 0; background: #fff; border-radius: 10px; box-shadow: 0 6px 24px rgba(0,0,0,0.12); z-index: 100; min-width: 100px; overflow: hidden; }
.period-dropdown.show { display: block; }
.period-dropdown__item { padding: 10px 14px; font-size: 13px; color: #333; cursor: pointer; white-space: nowrap; border-bottom: 1px solid #f5f5f5; }
.period-dropdown__item:last-child { border-bottom: none; }
.period-dropdown__item:hover { background: #f7f8fa; }
.period-dropdown__item.active { color: #52c41a; font-weight: 700; }
.period-mask { display: none; position: fixed; inset: 0; z-index: 50; }
.period-mask.show { display: block; }
/* ===== 内容主区域 ===== */
.scroll-content { flex: 1; overflow: hidden; display: flex; flex-direction: column; background: #f5f7fa; }
/* ===== 双列布局 ===== */
.record-layout { flex: 1; display: flex; gap: 8px; padding: 12px; overflow: hidden; }
.record-layout__left { width: 92px; flex-shrink: 0; }
.record-layout__right { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
/* ===== 分类列表 ===== */
.category-list { background: #fff; border-radius: 12px; overflow: hidden; height: 100%; display: flex; flex-direction: column; }
.category-list__title { font-size: 12px; font-weight: 700; color: #999; padding: 10px 10px 6px; letter-spacing: 1px; flex-shrink: 0; }
.category-list__items { flex: 1; overflow-y: auto; }
.category-list__items::-webkit-scrollbar { display: none; }
.category-item { padding: 12px 10px; font-size: 13px; color: #666; text-align: center; cursor: pointer; border-left: 3px solid transparent; transition: all .2s; }
.category-item.active { color: #52c41a; font-weight: 700; background: #f0fff0; border-left-color: #52c41a; }
/* ===== 运动列表 ===== */
.exe-list-wrap { flex: 1; overflow: hidden; display: flex; flex-direction: column; }
.exe-list { background: #fff; border-radius: 12px; flex: 1; overflow-y: auto; }
.exe-list::-webkit-scrollbar { display: none; }
.exe-list__empty { display: none; padding: 40px 0; text-align: center; color: #ccc; font-size: 13px; }
.exe-list__empty.show { display: block; }
.exe-item { display: flex; align-items: center; padding: 10px 10px; border-bottom: 1px solid #f5f5f5; gap: 8px; }
.exe-item:last-child { border-bottom: none; }
.exe-item__thumb { width: 48px; height: 48px; border-radius: 8px; background: #f7f8fa; flex-shrink: 0; display: flex; align-items: center; justify-content: center; overflow: hidden; position: relative; }
.exe-item__thumb-placeholder { width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; font-size: 22px; color: #bbb; }
.exe-item__info { flex: 1; min-width: 0; }
.exe-item__name { font-size: 14px; font-weight: 600; color: #1a1a1a; margin-bottom: 3px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.exe-item__meta { font-size: 11px; color: #999; }
.exe-item__meta em { font-style: normal; font-weight: 600; color: #fa8c16; }
.exe-item__actions { display: flex; align-items: center; gap: 6px; flex-shrink: 0; }
.exe-item__btn { width: 28px; height: 28px; border-radius: 50%; border: 1.5px solid #e8e8e8; background: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 16px; color: #666; line-height: 1; transition: all .15s; }
.exe-item__btn:active { background: #f5f5f5; }
.exe-item__btn--plus { color: #52c41a; border-color: #b7ebc4; background: #f6fff6; }
/* ===== 底部固定栏 ===== */
.bottom-bar { height: 68px; background: #fff; border-top: 1px solid #f0f0f0; display: flex; align-items: center; padding: 0 16px; flex-shrink: 0; gap: 12px; }
.bottom-bar__bag { width: 40px; height: 40px; border-radius: 10px; background: #f0fff0; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; position: relative; transition: background .2s; }
.bottom-bar__bag svg { width: 22px; height: 22px; color: #52c41a; }
.bottom-bar__bag-badge { position: absolute; top: -4px; right: -4px; min-width: 18px; height: 18px; border-radius: 9px; background: #ff4d4f; color: #fff; font-size: 10px; font-weight: 700; display: none; align-items: center; justify-content: center; padding: 0 4px; line-height: 1; }
.bottom-bar__bag-badge.show { display: flex; }
.bottom-bar__submit { flex: 1; height: 40px; border-radius: 20px; border: none; background: linear-gradient(160deg, #73d13d, #389e0d); color: #fff; font-size: 15px; font-weight: 700; cursor: pointer; box-shadow: 0 2px 8px rgba(82,196,26,0.3); transition: opacity .2s, transform .15s; }
.bottom-bar__submit:active { transform: scale(0.97); }
.bottom-bar__submit:disabled { opacity: 0.4; cursor: not-allowed; box-shadow: none; }
.bottom-bar__scan { width: 40px; height: 40px; border-radius: 10px; background: #f7f8fa; display: flex; align-items: center; justify-content: center; cursor: pointer; flex-shrink: 0; }
.bottom-bar__scan svg { width: 22px; height: 22px; color: #666; }
.bottom-bar__scan:active { background: #e8e8e8; }
/* ===== 时长录入弹窗 ===== */
.dur-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.45); z-index: 50; }
.dur-mask.show { display: block; }
.dur-panel { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 20px 20px 0 0; padding: 20px 16px 30px; z-index: 51; transform: translateY(100%); transition: transform .25s ease; }
.dur-panel.show { transform: translateY(0); }
.dur-panel__handle { width: 36px; height: 4px; background: #ddd; border-radius: 2px; margin: 0 auto 16px; }
.dur-panel__header { display: flex; align-items: center; gap: 10px; margin-bottom: 16px; }
.dur-panel__thumb { width: 52px; height: 52px; border-radius: 10px; background: #f7f8fa; display: flex; align-items: center; justify-content: center; font-size: 28px; flex-shrink: 0; }
.dur-panel__info { flex: 1; }
.dur-panel__name { font-size: 16px; font-weight: 700; color: #1a1a1a; margin-bottom: 2px; }
.dur-panel__kcal-ref { font-size: 12px; color: #999; }
.dur-panel__kcal-ref em { font-style: normal; color: #fa8c16; font-weight: 600; }
.dur-panel__label { font-size: 13px; font-weight: 600; color: #666; margin-bottom: 8px; }
.dur-input-row { display: flex; align-items: center; gap: 0; margin-bottom: 12px; }
.dur-input-row__btn { width: 40px; height: 40px; border: 1.5px solid #e8e8e8; background: #fafafa; font-size: 20px; color: #666; cursor: pointer; display: flex; align-items: center; justify-content: center; }
.dur-input-row__btn:first-child { border-radius: 10px 0 0 10px; }
.dur-input-row__btn:last-child { border-radius: 0 10px 10px 0; }
.dur-input-row__btn:active { background: #f0f0f0; }
.dur-input-row__input { width: 80px; height: 40px; border: 1.5px solid #e8e8e8; border-left: none; border-right: none; text-align: center; font-size: 20px; font-weight: 700; color: #1a1a1a; outline: none; }
.dur-input-row__input:focus { border-color: #52c41a; }
.dur-input-row__unit { font-size: 13px; color: #999; margin-left: 8px; }
.dur-panel__calorie { text-align: center; font-size: 13px; color: #666; margin-bottom: 16px; padding: 10px; background: #f7f8fa; border-radius: 10px; }
.dur-panel__calorie em { font-style: normal; font-weight: 800; color: #fa8c16; font-size: 18px; }
.dur-panel__confirm { width: 100%; height: 46px; border: none; border-radius: 23px; background: linear-gradient(160deg, #73d13d, #389e0d); color: #fff; font-size: 16px; font-weight: 700; cursor: pointer; }
.dur-panel__confirm:active { opacity: 0.85; }
/* ===== 已选运动弹窗 ===== */
.bag-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.45); z-index: 50; }
.bag-mask.show { display: block; }
.bag-panel { position: absolute; bottom: 0; left: 0; right: 0; background: #fff; border-radius: 20px 20px 0 0; max-height: 70%; display: flex; flex-direction: column; z-index: 51; transform: translateY(100%); transition: transform .25s ease; }
.bag-panel.show { transform: translateY(0); }
.bag-panel__head { padding: 16px 16px 8px; flex-shrink: 0; }
.bag-panel__handle { width: 36px; height: 4px; background: #ddd; border-radius: 2px; margin: 0 auto 12px; }
.bag-panel__title { font-size: 15px; font-weight: 700; color: #1a1a1a; text-align: center; margin-bottom: 8px; }
.bag-panel__body { flex: 1; overflow: hidden; display: flex; gap: 8px; padding: 0 16px; }
.bag-panel__periods { width: 68px; flex-shrink: 0; display: flex; flex-direction: column; gap: 4px; padding-bottom: 4px; }
.bag-period-tab { padding: 10px 6px; font-size: 13px; color: #666; text-align: center; cursor: pointer; border-radius: 8px; transition: all .2s; position: relative; font-weight: 500; }
.bag-period-tab.active { color: #52c41a; font-weight: 700; background: #f0fff0; }
.bag-period-tab__count { display: none; position: absolute; top: 2px; right: 2px; min-width: 16px; height: 16px; border-radius: 8px; background: #ff4d4f; color: #fff; font-size: 10px; font-weight: 700; align-items: center; justify-content: center; padding: 0 4px; line-height: 1; }
.bag-period-tab__count.show { display: flex; }
.bag-panel__list-wrap { flex: 1; min-width: 0; display: flex; flex-direction: column; overflow: hidden; }
.bag-panel__summary { font-size: 13px; color: #666; padding-bottom: 8px; flex-shrink: 0; }
.bag-panel__summary em { font-style: normal; font-weight: 800; color: #fa8c16; }
.bag-panel__list { flex: 1; overflow-y: auto; }
.bag-panel__list::-webkit-scrollbar { display: none; }
.bag-panel__empty { text-align: center; padding: 30px 0; color: #ccc; font-size: 13px; }
.bag-item { display: flex; align-items: center; padding: 10px 0; border-bottom: 1px solid #f5f5f5; gap: 8px; }
.bag-item:last-child { border-bottom: none; }
.bag-item__thumb { width: 36px; height: 36px; border-radius: 8px; background: #f7f8fa; display: flex; align-items: center; justify-content: center; font-size: 20px; flex-shrink: 0; }
.bag-item__info { flex: 1; min-width: 0; }
.bag-item__name { font-size: 13px; font-weight: 600; color: #1a1a1a; margin-bottom: 2px; }
.bag-item__meta { font-size: 10px; color: #999; display: flex; gap: 8px; }
.bag-item__duration { font-weight: 600; color: #666; }
.bag-item__kcal { color: #fa8c16; font-weight: 600; }
.bag-item__src { font-size: 9px; font-weight: 600; padding: 1px 6px; border-radius: 6px; white-space: nowrap; line-height: 1.5; }
.bag-item__src--wearable { color: #1890ff; background: #e6f7ff; }
.bag-item__src--equip { color: #52c41a; background: #f6ffed; }
.bag-item__src--wechat { color: #597ef7; background: #f0f5ff; }
.bag-item__src--manual { color: #fa8c16; background: #fff7e6; }
.bag-item__remove { width: 24px; height: 24px; border-radius: 50%; border: none; background: #fff1f0; color: #ff4d4f; cursor: pointer; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 12px; line-height: 1; }
.bag-item__remove:active { background: #ffccc7; }
.bag-panel__footer { padding: 12px 16px 24px; flex-shrink: 0; }
.bag-panel__submit { width: 100%; height: 46px; border: none; border-radius: 23px; background: linear-gradient(160deg, #73d13d, #389e0d); color: #fff; font-size: 16px; font-weight: 700; cursor: pointer; }
.bag-panel__submit:active { opacity: 0.85; }
/* Toast */
.toast { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%) scale(0.9); background: rgba(0,0,0,0.78); color: #fff; font-size: 14px; padding: 10px 22px; border-radius: 20px; z-index: 100; pointer-events: none; opacity: 0; transition: all .25s ease; white-space: nowrap; }
.toast.show { opacity: 1; transform: translate(-50%, -50%) scale(1); }
</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" id="pageTitle">记录运动</div>
<div class="nav-placeholder"></div>
</div>
<!-- 搜索行 -->
<div class="filter-row">
<div class="filter-row__search">
<div class="filter-row__search-icon">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
</div>
<input class="filter-row__search-input" id="searchInput" type="text" placeholder="搜索运动..." oninput="filterExercises()">
</div>
</div>
<!-- 内容区:双列布局 -->
<div class="scroll-content">
<div class="record-layout">
<!-- 左侧:分类列表 -->
<div class="record-layout__left">
<div class="category-list">
<div class="category-list__title">运动分类</div>
<div class="category-list__items" id="categoryList"></div>
</div>
</div>
<!-- 右侧:运动列表 -->
<div class="record-layout__right">
<div class="exe-list-wrap">
<div class="exe-list" id="exeList"></div>
<div class="exe-list__empty" id="exeEmpty">暂无匹配运动</div>
</div>
</div>
</div>
</div>
<!-- 底部固定栏 -->
<div class="bottom-bar">
<div class="bottom-bar__bag" id="bagBtn" onclick="toggleBagPanel()">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>
<div class="bottom-bar__bag-badge" id="bagBadge"></div>
</div>
<button class="bottom-bar__submit" id="submitBtn" disabled onclick="submitCheckin()">完成记录</button>
</div>
<!-- 时长录入弹窗 -->
<div class="dur-mask" id="durMask" onclick="closeDurPanel()">
<div class="dur-panel" id="durPanel" onclick="event.stopPropagation()">
<div class="dur-panel__handle"></div>
<div class="dur-panel__header">
<div class="dur-panel__thumb" id="durThumb"></div>
<div class="dur-panel__info">
<div class="dur-panel__name" id="durName"></div>
<div class="dur-panel__kcal-ref"><em id="durKcalRef"></em> 千卡/30分钟</div>
</div>
</div>
<div class="dur-panel__label">录入时长</div>
<div class="dur-input-row">
<button class="dur-input-row__btn" onclick="adjustDuration(-5)"></button>
<input class="dur-input-row__input" type="number" id="durInput" value="30" min="1" max="300" oninput="updateDurCalorie()">
<button class="dur-input-row__btn" onclick="adjustDuration(5)">+</button>
<span class="dur-input-row__unit">分钟</span>
</div>
<div class="dur-panel__calorie">
预估消耗 <em id="durCalorie">0</em> 千卡
</div>
<button class="dur-panel__confirm" onclick="confirmAddExercise()">确认添加</button>
</div>
</div>
<!-- 已选运动弹窗 -->
<div class="bag-mask" id="bagMask" onclick="closeBagPanel()">
<div class="bag-panel" id="bagPanel" onclick="event.stopPropagation()">
<div class="bag-panel__head">
<div class="bag-panel__handle"></div>
<div class="bag-panel__title">已记录运动</div>
</div>
<div class="bag-panel__body">
<div class="bag-panel__list-wrap" style="flex:1;">
<div class="bag-panel__summary" id="bagSummary"></div>
<div class="bag-panel__list" id="bagItemList"></div>
</div>
</div>
<div class="bag-panel__footer">
<button class="bag-panel__submit" onclick="submitCheckin()">完成记录</button>
</div>
</div>
</div>
<!-- Toast -->
<div class="toast" id="toast"></div>
</div>
<script>
/* ===== 运动数据 ===== */
var exerciseData = {
common: {
name: '常用',
items: [
{ id: 'c1', name: '户外跑步', kcal: 300, emoji: '🏃' },
{ id: 'c2', name: '快走', kcal: 150, emoji: '🚶' },
{ id: 'c3', name: '骑行', kcal: 250, emoji: '🚴' },
{ id: 'c4', name: '跳绳', kcal: 280, emoji: '🪢' },
{ id: 'c5', name: '瑜伽', kcal: 120, emoji: '🧘' },
{ id: 'c6', name: '游泳', kcal: 350, emoji: '🏊' },
{ id: 'c7', name: '器械训练', kcal: 200, emoji: '🏋️' },
{ id: 'c8', name: '步行', kcal: 60, emoji: '🚶' }
]
},
cardio: {
name: '有氧运动',
items: [
{ id: 'a1', name: '户外跑步', kcal: 300, emoji: '🏃' },
{ id: 'a2', name: '快走', kcal: 150, emoji: '🚶' },
{ id: 'a3', name: '慢跑', kcal: 220, emoji: '🏃' },
{ id: 'a4', name: '骑行', kcal: 250, emoji: '🚴' },
{ id: 'a5', name: '跳绳', kcal: 280, emoji: '🪢' },
{ id: 'a6', name: '游泳', kcal: 350, emoji: '🏊' },
{ id: 'a7', name: '爬楼梯', kcal: 320, emoji: '🪜' },
{ id: 'a8', name: '椭圆机', kcal: 260, emoji: '🔄' },
{ id: 'a9', name: '划船机', kcal: 280, emoji: '🚣' },
{ id: 'a10', name: '动感单车', kcal: 330, emoji: '🚲' }
]
},
strength: {
name: '力量训练',
items: [
{ id: 's1', name: '器械训练', kcal: 200, emoji: '🏋️' },
{ id: 's2', name: '俯卧撑', kcal: 180, emoji: '💪' },
{ id: 's3', name: '深蹲', kcal: 200, emoji: '🦵' },
{ id: 's4', name: '哑铃训练', kcal: 220, emoji: '🏋️' },
{ id: 's5', name: '引体向上', kcal: 250, emoji: '💪' },
{ id: 's6', name: '平板支撑', kcal: 100, emoji: '🧎' },
{ id: 's7', name: '仰卧起坐', kcal: 150, emoji: '🦾' },
{ id: 's8', name: '杠铃训练', kcal: 260, emoji: '🏋️' }
]
},
ball: {
name: '球类运动',
items: [
{ id: 'b1', name: '篮球', kcal: 320, emoji: '🏀' },
{ id: 'b2', name: '羽毛球', kcal: 260, emoji: '🏸' },
{ id: 'b3', name: '乒乓球', kcal: 200, emoji: '🏓' },
{ id: 'b4', name: '网球', kcal: 300, emoji: '🎾' },
{ id: 'b5', name: '足球', kcal: 340, emoji: '⚽' },
{ id: 'b6', name: '排球', kcal: 220, emoji: '🏐' },
{ id: 'b7', name: '高尔夫', kcal: 140, emoji: '⛳' },
{ id: 'b8', name: '台球', kcal: 80, emoji: '🎱' }
]
},
daily: {
name: '日常活动',
items: [
{ id: 'd1', name: '步行', kcal: 60, emoji: '🚶' },
{ id: 'd2', name: '家务劳动', kcal: 100, emoji: '🧹' },
{ id: 'd3', name: '爬楼梯', kcal: 320, emoji: '🪜' },
{ id: 'd4', name: '遛狗', kcal: 80, emoji: '🐕' },
{ id: 'd5', name: '园艺', kcal: 120, emoji: '🌱' },
{ id: 'd6', name: '广场舞', kcal: 180, emoji: '💃' },
{ id: 'd7', name: '太极', kcal: 100, emoji: '☯️' },
{ id: 'd8', name: '广播体操', kcal: 130, emoji: '🤸' }
]
},
flexibility: {
name: '柔韧平衡',
items: [
{ id: 'f1', name: '瑜伽', kcal: 120, emoji: '🧘' },
{ id: 'f2', name: '普拉提', kcal: 150, emoji: '🧘' },
{ id: 'f3', name: '拉伸', kcal: 80, emoji: '🤸' },
{ id: 'f4', name: '平衡训练', kcal: 90, emoji: '🦶' },
{ id: 'f5', name: '太极', kcal: 100, emoji: '☯️' },
{ id: 'f6', name: '舞蹈', kcal: 220, emoji: '💃' },
{ id: 'f7', name: '健身操', kcal: 200, emoji: '🤸' },
{ id: 'f8', name: '八段锦', kcal: 90, emoji: '🧘' }
]
}
};
/* ===== 当前状态 ===== */
var currentCategory = 'common';
var cart = {};
var searchText = '';
var currentExeId = null;
var STORAGE_KEY = 'exercise_checkin_v2_cart';
var STORAGE_VERSION_KEY = 'exercise_checkin_v2_ver';
var DATA_VERSION = 2;
/* ===== 初始化 ===== */
setPageTitle();
loadCart();
renderCategories();
selectCategory('common');
updateBottomBar();
function setPageTitle() {
var now = new Date();
var y = now.getFullYear();
var m = ('0' + (now.getMonth() + 1)).slice(-2);
var d = ('0' + now.getDate()).slice(-2);
document.getElementById('pageTitle').textContent = '添加' + y + '/' + m + '/' + d + '运动';
}
/* ===== 持久化:localStorage ===== */
function getDefaultCart() {
return {
'c1': [{ duration: 32, kcal: 320, source: 'wearable' }],
'd1': [{ duration: 45, kcal: 90, source: 'wechat' }],
's1': [{ duration: 25, kcal: 167, source: 'equip' }],
'c5': [{ duration: 20, kcal: 80, source: 'manual' }]
};
}
function saveCart() {
try {
localStorage.setItem(STORAGE_KEY, JSON.stringify(cart));
} catch(e) {}
}
function loadCart() {
try {
var savedVer = localStorage.getItem(STORAGE_VERSION_KEY);
var saved = localStorage.getItem(STORAGE_KEY);
if (saved && savedVer == DATA_VERSION) {
cart = JSON.parse(saved);
} else {
cart = getDefaultCart();
saveCart();
localStorage.setItem(STORAGE_VERSION_KEY, DATA_VERSION);
}
} catch(e) {
cart = getDefaultCart();
}
}
/* ===== 渲染分类列表 ===== */
function renderCategories() {
var container = document.getElementById('categoryList');
var html = '';
var keys = ['common', 'cardio', 'strength', 'ball', 'daily', 'flexibility'];
keys.forEach(function(key) {
var cat = exerciseData[key];
html += '<div class="category-item' + (key === currentCategory ? ' active' : '') + '" data-cat="' + key + '" onclick="selectCategory(\'' + key + '\')">' + cat.name + '</div>';
});
container.innerHTML = html;
}
/* ===== 切换分类 ===== */
function selectCategory(catKey) {
currentCategory = catKey;
var items = document.querySelectorAll('.category-item');
items.forEach(function(el) {
el.classList.toggle('active', el.dataset.cat === catKey);
});
renderExercises();
}
/* ===== 查找运动数据 ===== */
function findExercise(exeId) {
var keys = Object.keys(exerciseData);
for (var i = 0; i < keys.length; i++) {
var items = exerciseData[keys[i]].items;
for (var j = 0; j < items.length; j++) {
if (items[j].id === exeId) return items[j];
}
}
return null;
}
/* ===== 渲染运动列表 ===== */
function renderExercises() {
var container = document.getElementById('exeList');
var empty = document.getElementById('exeEmpty');
var cat = exerciseData[currentCategory];
var exercises = cat.items;
if (searchText.trim()) {
exercises = exercises.filter(function(e) {
return e.name.indexOf(searchText.trim()) !== -1;
});
}
if (exercises.length === 0) {
container.innerHTML = '';
empty.classList.add('show');
return;
}
empty.classList.remove('show');
var html = '';
exercises.forEach(function(exe) {
html += '<div class="exe-item">' +
'<div class="exe-item__thumb">' +
'<div class="exe-item__thumb-placeholder">' + exe.emoji + '</div>' +
'</div>' +
'<div class="exe-item__info">' +
'<div class="exe-item__name">' + exe.name + '</div>' +
'<div class="exe-item__meta"><em>' + exe.kcal + ' 千卡</em> / 30分钟</div>' +
'</div>' +
'<button class="exe-item__btn exe-item__btn--plus" onclick="openDurPanel(\'' + exe.id + '\')">+</button>' +
'</div>';
});
container.innerHTML = html;
}
/* ===== 搜索过滤 ===== */
function filterExercises() {
searchText = document.getElementById('searchInput').value;
renderExercises();
}
/* ===== 时长录入弹窗 ===== */
function openDurPanel(exeId) {
currentExeId = exeId;
var exe = findExercise(exeId);
if (!exe) return;
document.getElementById('durThumb').textContent = exe.emoji;
document.getElementById('durName').textContent = exe.name;
document.getElementById('durKcalRef').textContent = exe.kcal;
document.getElementById('durInput').value = 30;
document.getElementById('durMask').classList.add('show');
document.getElementById('durPanel').classList.add('show');
updateDurCalorie();
}
function closeDurPanel() {
document.getElementById('durMask').classList.remove('show');
document.getElementById('durPanel').classList.remove('show');
currentExeId = null;
}
function adjustDuration(delta) {
var input = document.getElementById('durInput');
var val = parseInt(input.value) || 30;
val += delta;
if (val < 1) val = 1;
if (val > 300) val = 300;
input.value = val;
updateDurCalorie();
}
function updateDurCalorie() {
var exe = findExercise(currentExeId);
if (!exe) return;
var minutes = parseInt(document.getElementById('durInput').value) || 0;
var kcal = Math.round(exe.kcal * minutes / 30);
document.getElementById('durCalorie').textContent = kcal;
}
function confirmAddExercise() {
if (!currentExeId) return;
var exe = findExercise(currentExeId);
if (!exe) return;
var minutes = parseInt(document.getElementById('durInput').value) || 30;
var kcal = Math.round(exe.kcal * minutes / 30);
if (!cart[currentExeId]) cart[currentExeId] = [];
cart[currentExeId].push({ duration: minutes, kcal: kcal, source: 'manual' });
closeDurPanel();
renderExercises();
updateBottomBar();
saveCart();
showToast('已添加「' + exe.name + '」' + minutes + '分钟,' + kcal + ' 千卡');
}
/* ===== 移除运动条目 ===== */
function removeCartItem(exeId) {
if (!cart[exeId] || cart[exeId].length === 0) return;
cart[exeId].pop();
if (cart[exeId].length === 0) delete cart[exeId];
renderExercises();
updateBottomBar();
saveCart();
if (document.getElementById('bagMask').classList.contains('show')) {
renderBagItems();
}
}
/* ===== 更新底部栏 ===== */
function updateBottomBar() {
var totalItems = 0;
Object.keys(cart).forEach(function(id) {
cart[id].forEach(function(e) { totalItems++; });
});
var badge = document.getElementById('bagBadge');
if (totalItems > 0) {
badge.textContent = totalItems;
badge.classList.add('show');
} else {
badge.classList.remove('show');
badge.textContent = '';
}
var submitBtn = document.getElementById('submitBtn');
submitBtn.disabled = totalItems === 0;
}
/* ===== 已选运动弹窗 ===== */
function toggleBagPanel() {
if (Object.keys(cart).length === 0) {
showToast('请先选择运动');
return;
}
var mask = document.getElementById('bagMask');
if (mask.classList.contains('show')) {
closeBagPanel();
} else {
openBagPanel();
}
}
function openBagPanel() {
document.getElementById('bagMask').classList.add('show');
document.getElementById('bagPanel').classList.add('show');
renderBagItems();
}
function closeBagPanel() {
document.getElementById('bagMask').classList.remove('show');
document.getElementById('bagPanel').classList.remove('show');
}
function renderBagItems() {
var container = document.getElementById('bagItemList');
var summaryEl = document.getElementById('bagSummary');
var allIds = Object.keys(cart);
var totalItems = 0;
var totalKcal = 0;
allIds.forEach(function(id) {
cart[id].forEach(function(e) {
totalItems++;
totalKcal += e.kcal;
});
});
if (allIds.length === 0) {
summaryEl.innerHTML = '';
container.innerHTML = '<div class="bag-panel__empty">暂无记录</div>';
return;
}
summaryEl.innerHTML = '共 <em>' + totalItems + '</em> 项,消耗 <em>' + totalKcal + '</em> 千卡';
var srcLabels = { wearable: '穿戴', equip: '器械', wechat: '微信', manual: '自记' };
var html = '';
allIds.forEach(function(id) {
var exe = findExercise(id);
var entries = cart[id];
entries.forEach(function(e, idx) {
var src = e.source || 'manual';
var srcLabel = srcLabels[src] || '自记';
var isManual = src === 'manual';
html += '<div class="bag-item">' +
'<div class="bag-item__thumb">' + (exe ? exe.emoji : '🏃') + '</div>' +
'<div class="bag-item__info">' +
'<div class="bag-item__name">' + (exe ? exe.name : id) + '<span class="bag-item__src bag-item__src--' + src + '">' + srcLabel + '</span></div>' +
'<div class="bag-item__meta">' +
'<span class="bag-item__duration">' + e.duration + '分钟</span>' +
'<span class="bag-item__kcal">' + e.kcal + ' 千卡</span>' +
'</div>' +
'</div>' +
(isManual ? '<button class="bag-item__remove" onclick="removeBagItem(\'' + id + '\', ' + idx + ')">✕</button>' : '') +
'</div>';
});
});
container.innerHTML = html;
}
function removeBagItem(exeId, idx) {
if (!cart[exeId]) return;
cart[exeId].splice(idx, 1);
if (cart[exeId].length === 0) delete cart[exeId];
renderExercises();
updateBottomBar();
saveCart();
renderBagItems();
if (Object.keys(cart).length === 0) closeBagPanel();
}
/* ===== 完成记录 ===== */
function submitCheckin() {
var totalKcal = 0;
var totalItems = 0;
var totalDuration = 0;
Object.keys(cart).forEach(function(id) {
cart[id].forEach(function(e) {
totalKcal += e.kcal;
totalDuration += e.duration;
totalItems++;
});
});
if (totalItems === 0) {
showToast('请先选择运动');
return;
}
showToast('已记录 ' + totalItems + ' 项运动,共 ' + totalDuration + '分钟 ' + totalKcal + ' 千卡');
cart = getDefaultCart();
saveCart();
setTimeout(function() {
history.back();
}, 1200);
}
/* ===== 扫码 ===== */
function scanExercise() {
showToast('扫码功能开发中');
}
/* ===== Toast ===== */
var toastTimer;
function showToast(msg) {
var toast = document.getElementById('toast');
toast.textContent = msg;
toast.classList.add('show');
clearTimeout(toastTimer);
toastTimer = setTimeout(function() {
toast.classList.remove('show');
}, 1800);
}
</script>
</body>
</html>
File diff suppressed because it is too large Load Diff
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

@@ -0,0 +1,457 @@
<!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; position: relative; }
.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-more { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; position: relative; }
.nav-more__dots { display: flex; flex-direction: column; gap: 3px; align-items: center; }
.nav-more__dots span { width: 4px; height: 4px; border-radius: 50%; background: #333; }
.type-menu { display: none; position: absolute; top: 32px; right: 0; background: #fff; border-radius: 12px; box-shadow: 0 6px 24px rgba(0,0,0,0.12); z-index: 20; min-width: 160px; overflow: hidden; }
.type-menu.show { display: block; }
.type-menu__item { padding: 13px 16px; font-size: 14px; color: #333; cursor: pointer; display: flex; align-items: center; justify-content: space-between; border-bottom: 1px solid #f5f5f5; }
.type-menu__item:last-child { border-bottom: none; }
.type-menu__item.active { color: #36cfc9; font-weight: 700; }
.type-menu__item.active::after { content: '✓'; font-size: 12px; color: #36cfc9; }
.type-mask { display: none; position: absolute; inset: 0; z-index: 10; }
.type-mask.show { display: block; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
/* ===== 状态横幅 ===== */
.status-banner { padding: 14px 16px; margin: 12px 12px 0; border-radius: 14px; display: flex; align-items: center; gap: 10px; font-size: 14px; font-weight: 700; }
.status-banner--review { background: #fffbe6; color: #faad14; border: 1px solid #ffe58f; }
.status-banner--approved { background: #e6f7ff; color: #1890ff; border: 1px solid #91d5ff; }
.status-banner--rejected { background: #fff1f0; color: #ff4d4f; border: 1px solid #ffa39e; }
.status-banner--signed { background: #f6ffed; color: #52c41a; border: 1px solid #b7eb8f; }
.status-banner__icon { font-size: 22px; flex-shrink: 0; }
/* ===== 通用卡片 ===== */
.card { background: #fff; border-radius: 16px; margin: 10px 12px 0; padding: 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.card__title { font-size: 15px; font-weight: 800; color: #1a1a1a; margin-bottom: 10px; }
.card__link { font-size: 12px; color: #36cfc9; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; gap: 2px; white-space: nowrap; }
/* 信息行 */
.info-row { display: flex; gap: 8px; }
.info-item { flex: 1; background: #f7f8fa; border-radius: 10px; padding: 10px 12px; }
.info-item__label { font-size: 11px; color: #999; margin-bottom: 4px; }
.info-item__value { font-size: 13px; font-weight: 600; color: #1a1a1a; }
/* 目标信息数据行 */
.goal-data { background: #f7f8fa; border-radius: 12px; padding: 14px; }
.goal-data__row { display: flex; align-items: center; padding: 6px 0; }
.goal-data__row + .goal-data__row { border-top: 1px dashed #e8e8e8; }
.goal-data__label { width: 48px; font-size: 12px; color: #999; flex-shrink: 0; }
.goal-data__init { flex: 1; text-align: center; font-size: 15px; font-weight: 700; color: #1a1a1a; }
.goal-data__arrow { width: 28px; text-align: center; font-size: 16px; color: #ccc; flex-shrink: 0; }
.goal-data__target { flex: 1; text-align: center; font-size: 15px; font-weight: 700; color: #36cfc9; }
.goal-data__unit { font-size: 10px; font-weight: 400; color: #999; margin-left: 2px; }
.goal-data__header { display: flex; align-items: center; margin-bottom: 10px; }
.goal-data__header-label { flex: 1; text-align: center; font-size: 11px; font-weight: 700; color: #999; }
.goal-data__header-spacer { width: 48px; flex-shrink: 0; }
.goal-data__header-arrow { width: 28px; flex-shrink: 0; }
/* 驳回信息 */
.reject-box { background: #fff1f0; border-left: 3px solid #ff4d4f; border-radius: 8px; padding: 12px 14px; margin-top: 8px; }
.reject-box__reason { font-size: 13px; color: #333; line-height: 1.7; }
.reject-box__meta { font-size: 11px; color: #999; margin-top: 6px; display: flex; justify-content: space-between; }
/* 待签署按钮 */
.sign-bar { padding: 16px 12px 12px; flex-shrink: 0; }
.sign-btn { width: 100%; height: 46px; border-radius: 23px; border: none; font-size: 16px; font-weight: 700; cursor: pointer; color: #fff; letter-spacing: 4px; }
.sign-btn--primary { background: linear-gradient(135deg, #36cfc9, #2db8b0); box-shadow: 0 4px 14px rgba(54,207,201,0.3); }
.sign-btn:active { opacity: 0.85; }
/* 状态切换(原型用) */
.state-tabs { display: flex; gap: 6px; margin: 12px 12px 0; flex-wrap: wrap; }
.state-tab { padding: 4px 10px; border-radius: 10px; font-size: 11px; font-weight: 600; cursor: pointer; background: #f0f0f0; color: #999; border: none; }
.state-tab.active { background: #36cfc9; color: #fff; }
.hidden { display: none !important; }
.bottom-safe { height: 16px; flex-shrink: 0; }
</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" id="navTitle">目标详情</div>
<div class="nav-more" id="navMore" onclick="toggleTypeMenu(event)">
<div class="nav-more__dots"><span></span><span></span><span></span></div>
<div class="type-menu" id="typeMenu">
<div class="type-menu__item active" data-type="activity" onclick="switchView('activity', event)">活动目标</div>
<div class="type-menu__item" data-type="personal" onclick="switchView('personal', event)">个人目标</div>
</div>
</div>
</div>
<div class="type-mask" id="typeMask" onclick="closeTypeMenu()"></div>
<div class="scroll-content">
<!-- ===== 活动目标模式 ===== -->
<div id="viewActivity">
<!-- 状态切换(原型预览用) -->
<div class="state-tabs">
<button class="state-tab active" data-state="review" onclick="switchState('review')">审核中</button>
<button class="state-tab" data-state="approved" onclick="switchState('approved')">已通过待签署</button>
<button class="state-tab" data-state="rejected" onclick="switchState('rejected')">已驳回</button>
<button class="state-tab" data-state="signed" onclick="switchState('signed')">已签署</button>
</div>
<!-- 审核中状态 -->
<div id="stateReview">
<div class="status-banner status-banner--review">
<span class="status-banner__icon"></span>
<div>
<div style="font-size:14px;font-weight:700;">目标审核中,请耐心等待</div>
<div style="font-size:11px;font-weight:400;opacity:0.6;margin-top:3px;">提交时间:2026-04-02 10:30</div>
</div>
</div>
<!-- 活动信息 -->
<div class="card">
<div class="card__title" style="display:flex;align-items:center;justify-content:space-between;"><span>活动信息</span><a class="card__link" href="activity-detail.html" 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 class="info-row">
<div class="info-item"><div class="info-item__label">活动名称</div><div class="info-item__value">2026年二季度体重管理活动</div></div>
</div>
<div class="info-row" style="margin-top:8px;">
<div class="info-item"><div class="info-item__label">起止日期</div><div class="info-item__value">2026-04-01 ~ 2026-09-30</div></div>
<div class="info-item"><div class="info-item__label">组织机构</div><div class="info-item__value">公司机关</div></div>
</div>
</div>
<!-- 目标信息 -->
<div class="card">
<div class="card__title">目标信息</div>
<div class="goal-data">
<div class="goal-data__header">
<span class="goal-data__header-spacer"></span>
<span class="goal-data__header-label">初始</span>
<span class="goal-data__header-arrow"></span>
<span class="goal-data__header-label">目标</span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体重</span>
<span class="goal-data__init">73.5<span class="goal-data__unit">kg</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">65.0<span class="goal-data__unit">kg</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体脂率</span>
<span class="goal-data__init">28.6<span class="goal-data__unit">%</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">22.0<span class="goal-data__unit">%</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">腰围</span>
<span class="goal-data__init">88<span class="goal-data__unit">cm</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">78<span class="goal-data__unit">cm</span></span>
</div>
</div>
</div>
</div>
<!-- 已通过待签署状态 -->
<div id="stateApproved" class="hidden">
<div class="status-banner status-banner--approved" style="display:flex;align-items:center;gap:10px;">
<span style="display:flex;align-items:center;gap:10px;flex:1;min-width:0;">
<span class="status-banner__icon"></span>
<div>
<div style="font-size:14px;font-weight:700;">审核已通过,请签署目标承诺书</div>
<div style="font-size:11px;font-weight:400;opacity:0.7;margin-top:3px;">审核人:李医生 · 2026-04-03 14:30</div>
</div>
</span>
<button class="sign-btn sign-btn--primary" onclick="alert('已签署')" style="width:auto;height:36px;border-radius:18px;font-size:13px;letter-spacing:2px;padding:0 16px;flex-shrink:0;">签署</button>
</div>
<div class="card">
<div class="card__title" style="display:flex;align-items:center;justify-content:space-between;"><span>活动信息</span><a class="card__link" href="activity-detail.html" 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 class="info-row">
<div class="info-item"><div class="info-item__label">活动名称</div><div class="info-item__value">2026年二季度体重管理活动</div></div>
</div>
<div class="info-row" style="margin-top:8px;">
<div class="info-item"><div class="info-item__label">起止日期</div><div class="info-item__value">2026-04-01 ~ 2026-09-30</div></div>
<div class="info-item"><div class="info-item__label">组织机构</div><div class="info-item__value">公司机关</div></div>
</div>
</div>
<div class="card">
<div class="card__title">目标信息</div>
<div class="goal-data">
<div class="goal-data__header">
<span class="goal-data__header-spacer"></span>
<span class="goal-data__header-label">初始</span>
<span class="goal-data__header-arrow"></span>
<span class="goal-data__header-label">目标</span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体重</span>
<span class="goal-data__init">73.5<span class="goal-data__unit">kg</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">65.0<span class="goal-data__unit">kg</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体脂率</span>
<span class="goal-data__init">28.6<span class="goal-data__unit">%</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">22.0<span class="goal-data__unit">%</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">腰围</span>
<span class="goal-data__init">88<span class="goal-data__unit">cm</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">78<span class="goal-data__unit">cm</span></span>
</div>
</div>
</div>
</div>
<!-- 已驳回状态 -->
<div id="stateRejected" class="hidden">
<div class="status-banner status-banner--rejected" style="display:flex;align-items:flex-start;gap:10px;">
<span class="status-banner__icon"></span>
<div style="flex:1;min-width:0;">
<div style="font-size:14px;font-weight:700;">目标已被驳回,请修改后重新提交</div>
<div style="font-size:12px;color:#333;line-height:1.6;margin-top:4px;opacity:0.85;">目标体重设定偏低,建议参考BMI健康范围适当调整。请将目标体重调整至62-68kg区间后重新提交。</div>
<div style="font-size:11px;margin-top:6px;opacity:0.6;">审核人:李医生 · 2026-04-03 14:30</div>
<button class="sign-btn sign-btn--primary" onclick="location.href='goal-management-v2.html?actId=1&actName=2026年二季度体重管理活动&actPeriod=2026-04-01 ~ 2026-09-30&actOrg=公司机关'" style="width:auto;height:32px;border-radius:16px;font-size:12px;letter-spacing:1px;padding:0 14px;margin-top:8px;">重新提交</button>
</div>
</div>
<div class="card">
<div class="card__title" style="display:flex;align-items:center;justify-content:space-between;"><span>活动信息</span><a class="card__link" href="activity-detail.html" 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 class="info-row">
<div class="info-item"><div class="info-item__label">活动名称</div><div class="info-item__value">2026年二季度体重管理活动</div></div>
</div>
<div class="info-row" style="margin-top:8px;">
<div class="info-item"><div class="info-item__label">起止日期</div><div class="info-item__value">2026-04-01 ~ 2026-09-30</div></div>
<div class="info-item"><div class="info-item__label">组织机构</div><div class="info-item__value">公司机关</div></div>
</div>
</div>
<div class="card">
<div class="card__title">目标信息</div>
<div class="goal-data">
<div class="goal-data__header">
<span class="goal-data__header-spacer"></span>
<span class="goal-data__header-label">初始</span>
<span class="goal-data__header-arrow"></span>
<span class="goal-data__header-label">目标</span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体重</span>
<span class="goal-data__init">73.5<span class="goal-data__unit">kg</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target" style="color:#ff4d4f;">65.0<span class="goal-data__unit">kg</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体脂率</span>
<span class="goal-data__init">28.6<span class="goal-data__unit">%</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">22.0<span class="goal-data__unit">%</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">腰围</span>
<span class="goal-data__init">88<span class="goal-data__unit">cm</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">78<span class="goal-data__unit">cm</span></span>
</div>
</div>
</div>
</div>
<!-- 已签署状态 -->
<div id="stateSigned" class="hidden">
<div class="status-banner status-banner--signed" style="display:flex;align-items:center;gap:10px;">
<span style="display:flex;align-items:center;gap:10px;flex:1;min-width:0;">
<span class="status-banner__icon">🎉</span>
<div>
<div style="font-size:14px;font-weight:700;">目标已签署,活动进行中</div>
<div style="font-size:11px;font-weight:400;opacity:0.7;margin-top:3px;">签署时间:2026-04-05 09:15</div>
</div>
</span>
<button class="sign-btn sign-btn--primary" onclick="location.href='my-promise.html'" style="width:auto;height:32px;border-radius:16px;font-size:12px;letter-spacing:1px;padding:0 14px;flex-shrink:0;">承诺书</button>
</div>
<div class="card">
<div class="card__title" style="display:flex;align-items:center;justify-content:space-between;"><span>活动信息</span><a class="card__link" href="activity-detail.html" 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 class="info-row">
<div class="info-item"><div class="info-item__label">活动名称</div><div class="info-item__value">2026年二季度体重管理活动</div></div>
</div>
<div class="info-row" style="margin-top:8px;">
<div class="info-item"><div class="info-item__label">起止日期</div><div class="info-item__value">2026-04-01 ~ 2026-09-30</div></div>
<div class="info-item"><div class="info-item__label">组织机构</div><div class="info-item__value">公司机关</div></div>
</div>
</div>
<div class="card">
<div class="card__title">目标信息</div>
<div class="goal-data">
<div class="goal-data__header">
<span class="goal-data__header-spacer"></span>
<span class="goal-data__header-label">初始</span>
<span class="goal-data__header-arrow"></span>
<span class="goal-data__header-label">目标</span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体重</span>
<span class="goal-data__init">73.5<span class="goal-data__unit">kg</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">65.0<span class="goal-data__unit">kg</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体脂率</span>
<span class="goal-data__init">28.6<span class="goal-data__unit">%</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">22.0<span class="goal-data__unit">%</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">腰围</span>
<span class="goal-data__init">88<span class="goal-data__unit">cm</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">78<span class="goal-data__unit">cm</span></span>
</div>
</div>
</div>
</div>
</div>
<!-- ===== 个人目标模式 ===== -->
<div id="viewPersonal" class="hidden">
<div class="card">
<div class="card__title">个人目标信息</div>
<div class="info-row">
<div class="info-item"><div class="info-item__label">目标名称</div><div class="info-item__value">2026年二季度个人减脂</div></div>
</div>
<div class="info-row" style="margin-top:8px;">
<div class="info-item"><div class="info-item__label">开始日期</div><div class="info-item__value">2026-04-01</div></div>
<div class="info-item"><div class="info-item__label">结束日期</div><div class="info-item__value">2026-08-26</div></div>
</div>
</div>
<div class="card">
<div class="card__title">目标信息</div>
<div class="goal-data">
<div class="goal-data__header">
<span class="goal-data__header-spacer"></span>
<span class="goal-data__header-label">初始</span>
<span class="goal-data__header-arrow"></span>
<span class="goal-data__header-label">目标</span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体重</span>
<span class="goal-data__init">73.5<span class="goal-data__unit">kg</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">62.0<span class="goal-data__unit">kg</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">体脂率</span>
<span class="goal-data__init">28.6<span class="goal-data__unit">%</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">20.0<span class="goal-data__unit">%</span></span>
</div>
<div class="goal-data__row">
<span class="goal-data__label">腰围</span>
<span class="goal-data__init">88<span class="goal-data__unit">cm</span></span>
<span class="goal-data__arrow"></span>
<span class="goal-data__target">75<span class="goal-data__unit">cm</span></span>
</div>
</div>
</div>
</div>
<div class="sign-bar">
<button class="sign-btn sign-btn--primary" onclick="location.href='goal-management-v2.html?type=personal&name=2026年二季度个人减脂&startDate=2026-04-01&endDate=2026-08-26'">修改目标</button>
</div>
<div class="bottom-safe"></div>
</div>
</div>
<script>
/* ===== 读取 URL 参数 ===== */
function getParam(name) {
var m = location.search.match(new RegExp('[?&]' + name + '=([^&]*)'));
return m ? decodeURIComponent(m[1]) : null;
}
var currentView = getParam('type') || 'activity';
var currentState = getParam('state') || 'review';
/* ===== 初始化视图 ===== */
function initView() {
document.getElementById('navTitle').textContent = currentView === 'activity' ? '活动目标详情' : '个人目标详情';
document.querySelectorAll('.type-menu__item').forEach(function(item) {
item.classList.toggle('active', item.dataset.type === currentView);
});
document.getElementById('viewActivity').classList.toggle('hidden', currentView !== 'activity');
document.getElementById('viewPersonal').classList.toggle('hidden', currentView === 'activity');
if (currentView === 'activity') {
switchState(currentState);
}
}
/* ===== 类型菜单 ===== */
function toggleTypeMenu(e) {
e.stopPropagation();
document.getElementById('typeMenu').classList.toggle('show');
document.getElementById('typeMask').classList.toggle('show');
}
function closeTypeMenu() {
document.getElementById('typeMenu').classList.remove('show');
document.getElementById('typeMask').classList.remove('show');
}
function switchView(type, e) {
if (e) e.stopPropagation();
currentView = type;
closeTypeMenu();
initView();
}
document.addEventListener('click', function(e) {
if (!e.target.closest('#navMore')) closeTypeMenu();
});
/* ===== 活动状态切换 ===== */
function switchState(state) {
currentState = state;
document.querySelectorAll('.state-tab').forEach(function(t) {
t.classList.toggle('active', t.dataset.state === state);
});
document.getElementById('stateReview').classList.toggle('hidden', state !== 'review');
document.getElementById('stateApproved').classList.toggle('hidden', state !== 'approved');
document.getElementById('stateRejected').classList.toggle('hidden', state !== 'rejected');
document.getElementById('stateSigned').classList.toggle('hidden', state !== 'signed');
}
initView();
</script>
</body>
</html>
@@ -0,0 +1,545 @@
<!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; position: relative; }
.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; }
/* ===== 顶部类型选择(勾选式) ===== */
.type-options { display: flex; gap: 10px; margin: 12px 12px 0; }
.type-option { flex: 1; background: #fff; border-radius: 14px; padding: 14px; cursor: pointer; border: 2px solid transparent; box-shadow: 0 2px 8px rgba(0,0,0,0.03); transition: all .25s; position: relative; }
.type-option.active { border-color: #36cfc9; box-shadow: 0 4px 16px rgba(54,207,201,0.15); }
.type-option__radio { position: absolute; top: 10px; right: 10px; width: 18px; height: 18px; border-radius: 50%; border: 2px solid #d9d9d9; display: flex; align-items: center; justify-content: center; transition: all .25s; }
.type-option.active .type-option__radio { border-color: #36cfc9; background: #36cfc9; }
.type-option__radio::after { content: ''; width: 5px; height: 5px; border-radius: 50%; background: #fff; opacity: 0; transform: scale(0); transition: all .2s ease; }
.type-option.active .type-option__radio::after { opacity: 1; transform: scale(1); }
.type-option__head { display: flex; align-items: center; gap: 8px; margin-bottom: 6px; }
.type-option__icon { font-size: 22px; line-height: 1; }
.type-option__title { font-size: 14px; font-weight: 700; color: #1a1a1a; }
.type-option.active .type-option__title { color: #36cfc9; }
/* ===== 通用卡片 ===== */
.card { background: #fff; border-radius: 16px; margin: 10px 12px 0; padding: 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.card__title { font-size: 15px; font-weight: 800; color: #1a1a1a; margin-bottom: 12px; }
.card__link { font-size: 12px; color: #36cfc9; font-weight: 600; text-decoration: none; display: inline-flex; align-items: center; gap: 2px; }
/* 卡片内分区 */
.data-section { background: #f7f8fa; border-radius: 12px; padding: 12px; margin-bottom: 10px; }
.data-section:last-child { margin-bottom: 0; }
.data-section__title { font-size: 12px; font-weight: 700; color: #999; margin-bottom: 10px; letter-spacing: .5px; }
/* 选择器 */
.selector { display: flex; align-items: center; justify-content: space-between; height: 42px; padding: 0 14px; background: #f7f8fa; border-radius: 10px; font-size: 14px; color: #bbb; cursor: pointer; transition: border-color .2s; border: 1.5px solid transparent; }
.selector:hover { border-color: #36cfc9; }
.selector--filled { color: #1a1a1a; font-weight: 600; }
.selector__arrow { font-size: 12px; color: #bbb; }
/* 活动信息行 */
.info-row { display: flex; gap: 8px; margin-top: 8px; }
.info-item { flex: 1; background: #f7f8fa; border-radius: 10px; padding: 10px 12px; }
.info-item__label { font-size: 11px; color: #999; margin-bottom: 4px; }
.info-item__value { font-size: 13px; font-weight: 600; color: #1a1a1a; }
/* 三列数据 */
.data-grid { display: flex; gap: 10px; }
.data-col { flex: 1; }
.data-col__header { font-size: 12px; font-weight: 700; color: #999; margin-bottom: 8px; padding-bottom: 6px; border-bottom: 2px solid #f0f0f0; letter-spacing: .5px; }
.data-row { display: flex; align-items: center; justify-content: space-between; padding: 8px 0; }
.data-row__label { font-size: 12px; color: #666; }
.data-row__value { font-size: 14px; font-weight: 700; color: #1a1a1a; text-align: right; }
.data-row__value--empty { color: #ccc; font-weight: 400; font-size: 13px; }
.data-row__unit { font-size: 10px; font-weight: 400; color: #999; margin-left: 2px; }
/* 输入行 */
.input-row { display: flex; align-items: center; gap: 8px; }
.input-row__label { font-size: 13px; color: #333; white-space: nowrap; flex-shrink: 0; width: 56px; }
.input-row__input { flex: 1; height: 38px; padding: 0 12px; background: #fff; border-radius: 8px; border: 1.5px solid #e8e8e8; font-size: 14px; font-weight: 600; color: #1a1a1a; outline: none; }
.input-row__input::placeholder { color: #ccc; font-weight: 400; }
.input-row__input:focus { border-color: #36cfc9; }
.input-row__input:read-only { background: #f5f5f5; border-color: #eee; color: #999; }
.input-row__unit { font-size: 12px; color: #999; flex-shrink: 0; width: 24px; text-align: center; }
.input-row__select { flex: 1; height: 38px; padding: 0 12px; background: #fff; border-radius: 8px; border: 1.5px solid #e8e8e8; font-size: 14px; font-weight: 600; color: #1a1a1a; outline: none; appearance: none; cursor: pointer; }
.input-row__select:focus { border-color: #36cfc9; }
.input-row__select:disabled { background: #f5f5f5; border-color: #eee; color: #999; cursor: default; }
/* ===== 活动选择弹窗 ===== */
.sheet-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.4); z-index: 50; }
.sheet-mask.show { display: block; }
.sheet { position: absolute; left: 0; right: 0; bottom: 0; background: #fff; border-radius: 20px 20px 0 0; z-index: 51; padding: 10px 0 18px; transform: translateY(100%); transition: transform .3s ease; max-height: 70%; display: flex; flex-direction: column; }
.sheet.show { transform: translateY(0); }
.sheet__handle { width: 38px; height: 4px; background: #d8dde3; border-radius: 2px; margin: 0 auto 8px; flex-shrink: 0; }
.sheet__title { font-size: 16px; font-weight: 700; color: #1a1a1a; text-align: center; padding: 6px 0 12px; border-bottom: 1px solid #f1f3f5; flex-shrink: 0; }
.sheet__list { flex: 1; overflow-y: auto; padding: 4px 0; }
.sheet__list::-webkit-scrollbar { display: none; }
.sheet-item { padding: 14px 18px; border-bottom: 1px solid #f5f7fa; display: flex; align-items: center; justify-content: space-between; cursor: pointer; }
.sheet-item__main { flex: 1; min-width: 0; }
.sheet-item__name { font-size: 14px; font-weight: 600; color: #1a1a1a; }
.sheet-item__period { font-size: 12px; color: #999; margin-top: 4px; display: flex; align-items: center; justify-content: space-between; }
.sheet-item__org { font-size: 11px; color: #bbb; }
.sheet-item__check { color: #36cfc9; font-weight: 800; opacity: 0; }
.sheet-item.selected .sheet-item__check { opacity: 1; }
.sheet__empty { padding: 48px 0; text-align: center; color: #ccc; font-size: 14px; }
.sheet__confirm-bar { padding: 12px 16px 0; flex-shrink: 0; }
.sheet__confirm { display: block; width: 100%; height: 44px; background: linear-gradient(135deg, #36cfc9, #2db8b0); color: #fff; border: none; border-radius: 22px; font-size: 15px; font-weight: 700; cursor: pointer; letter-spacing: 4px; }
/* ===== 底部按钮 ===== */
.bottom-bar { padding: 16px 12px 12px; flex-shrink: 0; }
.bottom-btn { width: 100%; height: 46px; border-radius: 23px; border: none; font-size: 16px; font-weight: 700; cursor: pointer; background: linear-gradient(135deg, #36cfc9, #2db8b0); color: #fff; box-shadow: 0 4px 14px rgba(54,207,201,0.3); letter-spacing: 4px; transition: all .2s; }
.bottom-btn:active { transform: scale(0.97); }
/* ===== 确认弹窗 ===== */
.confirm-mask { display: none; position: absolute; inset: 0; background: rgba(0,0,0,0.45); z-index: 60; align-items: center; justify-content: center; }
.confirm-mask.show { display: flex; }
.confirm-dialog { width: 310px; background: #fff; border-radius: 18px; padding: 24px 20px 20px; }
.confirm-dialog__title { font-size: 17px; font-weight: 800; color: #1a1a1a; text-align: center; margin-bottom: 16px; }
.confirm-dialog__section { margin-bottom: 12px; }
.confirm-dialog__section:last-child { margin-bottom: 0; }
.confirm-dialog__label { font-size: 12px; font-weight: 700; color: #999; margin-bottom: 6px; }
.confirm-dialog__row { display: flex; justify-content: space-between; font-size: 13px; color: #333; padding: 4px 0; }
.confirm-dialog__row strong { color: #1a1a1a; }
.confirm-dialog__btns { display: flex; gap: 10px; margin-top: 18px; }
.confirm-dialog__btn { flex: 1; height: 40px; border-radius: 20px; border: none; font-size: 14px; font-weight: 700; cursor: pointer; }
.confirm-dialog__btn--cancel { background: #f0f2f5; color: #999; }
.confirm-dialog__btn--confirm { background: linear-gradient(135deg, #36cfc9, #2db8b0); color: #fff; }
/* ===== 隐藏 ===== */
.hidden { display: none !important; }
.bottom-safe { height: 12px; flex-shrink: 0; }
</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">
<!-- ===== 顶部类型选择(勾选式) ===== -->
<div class="type-options">
<div class="type-option active" data-type="activity" onclick="switchType('activity')">
<div class="type-option__radio"></div>
<div class="type-option__head">
<span class="type-option__icon">🏆</span>
<span class="type-option__title">活动目标</span>
</div>
</div>
<div class="type-option" data-type="personal" onclick="switchType('personal')">
<div class="type-option__radio"></div>
<div class="type-option__head">
<span class="type-option__icon">🎯</span>
<span class="type-option__title">个人目标</span>
</div>
</div>
</div>
<!-- ====== 活动目标模式 ====== -->
<div id="sectionActivity">
<!-- 选择活动卡片 -->
<div class="card">
<div class="card__title" style="display:flex;align-items:center;justify-content:space-between;">
<span>选择活动</span>
<a class="card__link hidden" id="actDetailLink" href="activity-detail.html" 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 class="selector" id="activitySelector" onclick="openActivitySheet()">
<span id="activitySelectorText">请选择体重管理活动</span>
<span class="selector__arrow"></span>
</div>
<!-- 活动基本信息 -->
<div class="info-row hidden" id="activityInfo">
<div class="info-item">
<div class="info-item__label">起止日期</div>
<div class="info-item__value" id="actPeriod">--</div>
</div>
<div class="info-item">
<div class="info-item__label">组织机构</div>
<div class="info-item__value" id="actOrg">--</div>
</div>
</div>
</div>
<!-- 数据设定卡片 -->
<div class="card">
<div class="card__title">数据设定</div>
<!-- 基础信息 -->
<div class="data-section">
<div class="data-section__title">基础信息</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">性别</span>
<select class="input-row__select" disabled><option></option><option></option></select>
</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">出生年月</span>
<input class="input-row__input" type="text" value="1990-05" readonly>
</div>
<div class="input-row">
<span class="input-row__label">身高</span>
<input class="input-row__input" type="number" value="172" placeholder="请输入">
<span class="input-row__unit">cm</span>
</div>
</div>
<!-- 初始数据 -->
<div class="data-section">
<div class="data-section__title">初始数据</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体重</span>
<input class="input-row__input" type="number" value="73.5" placeholder="请输入" step="0.1">
<span class="input-row__unit">kg</span>
</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体脂率</span>
<input class="input-row__input" type="number" value="28.6" placeholder="请输入" step="0.1">
<span class="input-row__unit">%</span>
</div>
<div class="input-row">
<span class="input-row__label">腰围</span>
<input class="input-row__input" type="number" value="88" placeholder="请输入" step="0.1">
<span class="input-row__unit">cm</span>
</div>
</div>
<!-- 目标数据 -->
<div class="data-section">
<div class="data-section__title">目标数据</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体重</span>
<input class="input-row__input" type="number" value="65.0" placeholder="请输入" step="0.1">
<span class="input-row__unit">kg</span>
</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体脂率</span>
<input class="input-row__input" type="number" value="22.0" placeholder="请输入" step="0.1">
<span class="input-row__unit">%</span>
</div>
<div class="input-row">
<span class="input-row__label">腰围</span>
<input class="input-row__input" type="number" value="78" placeholder="请输入" step="0.1">
<span class="input-row__unit">cm</span>
</div>
</div>
</div>
</div>
<!-- ====== 个人目标模式 ====== -->
<div id="sectionPersonal" class="hidden">
<!-- 个人目标卡片 -->
<div class="card">
<div class="card__title">个人目标</div>
<div class="input-row" style="margin-bottom:10px;">
<span class="input-row__label">目标名称</span>
<input class="input-row__input" id="personalName" type="text" placeholder="请输入目标名称" value="2026年二季度个人减脂">
</div>
<div class="input-row" style="margin-bottom:10px;">
<span class="input-row__label">开始日期</span>
<input class="input-row__input" id="personalStartDate" type="date" value="2026-07-27">
</div>
<div class="input-row">
<span class="input-row__label">结束日期</span>
<input class="input-row__input" id="personalEndDate" type="date" value="2026-08-27">
</div>
</div>
<!-- 数据设定卡片 -->
<div class="card">
<div class="card__title">数据设定</div>
<!-- 基础信息 -->
<div class="data-section">
<div class="data-section__title">基础信息</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">性别</span>
<select class="input-row__select" disabled><option></option><option></option></select>
</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">出生年月</span>
<input class="input-row__input" type="text" value="1990-05" readonly>
</div>
<div class="input-row">
<span class="input-row__label">身高</span>
<input class="input-row__input" type="number" value="172" placeholder="请输入">
<span class="input-row__unit">cm</span>
</div>
</div>
<!-- 初始数据 -->
<div class="data-section">
<div class="data-section__title">初始数据</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体重</span>
<input class="input-row__input" type="number" value="73.5" placeholder="请输入" step="0.1">
<span class="input-row__unit">kg</span>
</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体脂率</span>
<input class="input-row__input" type="number" value="28.6" placeholder="请输入" step="0.1">
<span class="input-row__unit">%</span>
</div>
<div class="input-row">
<span class="input-row__label">腰围</span>
<input class="input-row__input" type="number" value="88" placeholder="请输入" step="0.1">
<span class="input-row__unit">cm</span>
</div>
</div>
<!-- 目标数据 -->
<div class="data-section">
<div class="data-section__title">目标数据</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体重</span>
<input class="input-row__input" type="number" value="62.0" placeholder="请输入" step="0.1">
<span class="input-row__unit">kg</span>
</div>
<div class="input-row" style="margin-bottom:8px;">
<span class="input-row__label">体脂率</span>
<input class="input-row__input" type="number" value="20.0" placeholder="请输入" step="0.1">
<span class="input-row__unit">%</span>
</div>
<div class="input-row">
<span class="input-row__label">腰围</span>
<input class="input-row__input" type="number" value="75" placeholder="请输入" step="0.1">
<span class="input-row__unit">cm</span>
</div>
</div>
</div>
</div>
<div class="bottom-safe"></div>
</div>
<!-- 底部按钮 -->
<div class="bottom-bar">
<button class="bottom-btn" onclick="submitGoal()">提 交</button>
</div>
<!-- ===== 活动选择弹窗 ===== -->
<div class="sheet-mask" id="actMask" onclick="closeActivitySheet()"></div>
<div class="sheet" id="actSheet">
<div class="sheet__handle"></div>
<div class="sheet__title">选择体重管理活动</div>
<div class="sheet__list" id="actList">
<div class="sheet-item" data-id="1" data-name="2026年二季度体重管理活动" data-period="2026-04-01 ~ 2026-09-30" data-org="公司机关" onclick="selectActivity(this)">
<div class="sheet-item__main">
<div class="sheet-item__name">2026年二季度体重管理活动</div>
<div class="sheet-item__period">2026-04-01 ~ 2026-09-30<span class="sheet-item__org">公司机关</span></div>
</div>
<span class="sheet-item__check"></span>
</div>
<div class="sheet-item" data-id="2" data-name="2026年一季度减重挑战赛" data-period="2026-01-01 ~ 2026-03-31" data-org="第一采油厂" onclick="selectActivity(this)">
<div class="sheet-item__main">
<div class="sheet-item__name">2026年一季度减重挑战赛</div>
<div class="sheet-item__period">2026-01-01 ~ 2026-03-31<span class="sheet-item__org">第一采油厂</span></div>
</div>
<span class="sheet-item__check"></span>
</div>
<div class="sheet-item" data-id="3" data-name="全员健康体重管理计划" data-period="2025-10-01 ~ 2026-06-30" data-org="第一采气厂" onclick="selectActivity(this)">
<div class="sheet-item__main">
<div class="sheet-item__name">全员健康体重管理计划</div>
<div class="sheet-item__period">2025-10-01 ~ 2026-06-30<span class="sheet-item__org">第一采气厂</span></div>
</div>
<span class="sheet-item__check"></span>
</div>
</div>
<div class="sheet__confirm-bar">
<button class="sheet__confirm" onclick="confirmActivity()">确 定</button>
</div>
</div>
<!-- ===== 提交确认弹窗 ===== -->
<div class="confirm-mask" id="confirmMask" onclick="closeConfirm()"></div>
<div class="confirm-mask" id="confirmDialog" style="z-index:61;">
<div class="confirm-dialog" onclick="event.stopPropagation()">
<div class="confirm-dialog__title">确认提交</div>
<div class="confirm-dialog__section">
<div class="confirm-dialog__label">活动信息</div>
<div class="confirm-dialog__row"><span>活动名称</span><strong id="cfmActName">--</strong></div>
<div class="confirm-dialog__row"><span>起止日期</span><span id="cfmActPeriod">--</span></div>
<div class="confirm-dialog__row"><span>组织机构</span><span id="cfmActOrg">--</span></div>
</div>
<div class="confirm-dialog__section">
<div class="confirm-dialog__label">初始数据</div>
<div class="confirm-dialog__row"><span>体重</span><span>73.5 kg</span></div>
<div class="confirm-dialog__row"><span>体脂率</span><span>28.6 %</span></div>
<div class="confirm-dialog__row"><span>腰围</span><span>88 cm</span></div>
</div>
<div class="confirm-dialog__section">
<div class="confirm-dialog__label">目标数据</div>
<div class="confirm-dialog__row"><span>体重</span><span>65.0 kg</span></div>
<div class="confirm-dialog__row"><span>体脂率</span><span>22.0 %</span></div>
<div class="confirm-dialog__row"><span>腰围</span><span>78 cm</span></div>
</div>
<div class="confirm-dialog__btns">
<button class="confirm-dialog__btn confirm-dialog__btn--cancel" onclick="closeConfirm()">取消</button>
<button class="confirm-dialog__btn confirm-dialog__btn--confirm" onclick="doSubmit()">确认提交</button>
</div>
</div>
</div>
</div>
<script>
/* ===== 类型切换 ===== */
function switchType(type) {
document.querySelectorAll('.type-option').forEach(function(t) {
t.classList.toggle('active', t.dataset.type === type);
});
document.getElementById('sectionActivity').classList.toggle('hidden', type !== 'activity');
document.getElementById('sectionPersonal').classList.toggle('hidden', type !== 'personal');
}
/* ===== 活动选择 ===== */
var selectedActId = null;
function openActivitySheet() {
document.getElementById('actMask').classList.add('show');
document.getElementById('actSheet').classList.add('show');
}
function closeActivitySheet() {
document.getElementById('actMask').classList.remove('show');
document.getElementById('actSheet').classList.remove('show');
}
function selectActivity(el) {
document.querySelectorAll('.sheet-item').forEach(function(item) { item.classList.remove('selected'); });
el.classList.add('selected');
selectedActId = el.dataset.id;
}
function confirmActivity() {
if (!selectedActId) return;
var el = document.querySelector('.sheet-item[data-id="' + selectedActId + '"]');
if (!el) return;
var name = el.dataset.name;
var period = el.dataset.period;
var org = el.dataset.org;
// 更新选择器文字
var selector = document.getElementById('activitySelector');
selector.classList.add('selector--filled');
document.getElementById('activitySelectorText').textContent = name;
// 显示活动信息
document.getElementById('activityInfo').classList.remove('hidden');
document.getElementById('actPeriod').textContent = period;
document.getElementById('actOrg').textContent = org;
// 显示活动详情链接
document.getElementById('actDetailLink').classList.remove('hidden');
closeActivitySheet();
}
/* ===== 提交 ===== */
function submitGoal() {
var type = document.querySelector('.type-option.active').dataset.type;
if (type === 'activity') {
if (!selectedActId) { alert('请先选择活动'); return; }
var el = document.querySelector('.sheet-item[data-id="' + selectedActId + '"]');
document.getElementById('cfmActName').textContent = el.dataset.name;
document.getElementById('cfmActPeriod').textContent = el.dataset.period;
document.getElementById('cfmActOrg').textContent = el.dataset.org;
}
if (type === 'personal') {
var name = document.getElementById('personalName').value.trim();
if (!name) { alert('请输入目标名称'); return; }
var sd = document.getElementById('personalStartDate').value;
var ed = document.getElementById('personalEndDate').value;
if (!sd) { alert('请选择开始日期'); return; }
if (!ed) { alert('请选择结束日期'); return; }
if (ed <= sd) { alert('结束日期必须晚于开始日期'); return; }
}
document.getElementById('confirmDialog').classList.add('show');
document.getElementById('confirmMask').classList.add('show');
}
function closeConfirm() {
document.getElementById('confirmDialog').classList.remove('show');
document.getElementById('confirmMask').classList.remove('show');
}
function doSubmit() {
closeConfirm();
var type = document.querySelector('.type-option.active').dataset.type;
location.href = 'goal-detail-v2.html?type=' + type;
}
/* ===== URL 参数回填 ===== */
function getParam(name) {
var m = location.search.match(new RegExp('[?&]' + name + '=([^&]*)'));
return m ? decodeURIComponent(m[1]) : null;
}
(function initFill() {
var type = getParam('type');
// 个人目标回填
if (type === 'personal') {
switchType('personal');
var name = getParam('name');
var sd = getParam('startDate');
var ed = getParam('endDate');
if (name) document.getElementById('personalName').value = name;
if (sd) document.getElementById('personalStartDate').value = sd;
if (ed) document.getElementById('personalEndDate').value = ed;
return;
}
// 活动目标回填
var actId = getParam('actId');
if (!actId) return;
var actName = getParam('actName');
var actPeriod = getParam('actPeriod');
var actOrg = getParam('actOrg');
if (!actName) return;
selectedActId = actId;
var selector = document.getElementById('activitySelector');
selector.classList.add('selector--filled');
document.getElementById('activitySelectorText').textContent = actName;
document.getElementById('activityInfo').classList.remove('hidden');
document.getElementById('actPeriod').textContent = actPeriod;
document.getElementById('actOrg').textContent = actOrg;
document.getElementById('actDetailLink').classList.remove('hidden');
})();
</script>
</body>
</html>
@@ -122,7 +122,11 @@ var device = new URLSearchParams(location.search).get('device') || 'fat';
/* 返回来源页面 */
function goBack() {
location.href = 'weight-history.html';
if (document.referrer) {
location.href = document.referrer;
} else {
history.back();
}
}
/* 头部设备标签 */
@@ -15,7 +15,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.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-add { width: 28px; height: 28px; display: flex; align-items: center; justify-content: center; cursor: pointer; color: #36cfc9; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
@@ -270,9 +269,6 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<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-add" onclick="addRecord()">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
</div>
</div>
<div class="scroll-content">
@@ -365,11 +361,12 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="record-card__header">
<div class="record-card__title">变化趋势</div>
<div class="time-range-tabs">
<div class="time-range-tab active" onclick="switchTimeRange(this, 'month')"></div>
<div class="time-range-tab" onclick="switchTimeRange(this, 'quarter')"></div>
<div class="time-range-tab" onclick="switchTimeRange(this, 'activity')">活动期间</div>
<div class="time-range-tab" onclick="switchTimeRange(this, 'all')">全部</div>
</div>
<div class="time-range-tab active" onclick="switchTimeRange(this, 'week')"></div>
<div class="time-range-tab" onclick="switchTimeRange(this, 'month')"></div>
<div class="time-range-tab" onclick="switchTimeRange(this, 'quarter')"></div>
<div class="time-range-tab" onclick="switchTimeRange(this, 'year')"></div>
<div class="time-range-tab" onclick="switchTimeRange(this, 'activity')">活动期间</div>
</div>
</div>
<div class="record-tabs">
<div class="record-tab active" onclick="switchRecordTab(this, 'weight')">体重变化</div>
@@ -1181,17 +1178,18 @@ function submitRecord() {
// 刷新界面
renderWeek();
// 重新激活当前时间范围,刷新记录列表
var activeTab = document.querySelector('.time-range-tab.active');
if (activeTab) {
var range = 'month';
if (activeTab.textContent === '') range = 'quarter';
else if (activeTab.textContent === '活动期间') range = 'activity';
else if (activeTab.textContent === '全部') range = 'all';
renderRecordList(range);
} else {
renderRecordList('month');
}
}
var activeTab = document.querySelector('.time-range-tab.active');
if (activeTab) {
var range = 'week';
if (activeTab.textContent === '') range = 'month';
else if (activeTab.textContent === '') range = 'quarter';
else if (activeTab.textContent === '') range = 'year';
else if (activeTab.textContent === '活动期间') range = 'activity';
renderRecordList(range);
} else {
renderRecordList('week');
}
}
function renderRecordFields() {
var body = document.getElementById('recordSheetBody');
@@ -1349,12 +1347,18 @@ function switchTimeRange(el, range) {
function renderRecordList(range) {
var now = today;
var cutoff;
if (range === 'month') {
if (range === 'week') {
cutoff = new Date(now);
cutoff.setDate(cutoff.getDate() - 7);
} else if (range === 'month') {
cutoff = new Date(now);
cutoff.setMonth(cutoff.getMonth() - 1);
} else if (range === 'quarter') {
cutoff = new Date(now);
cutoff.setMonth(cutoff.getMonth() - 3);
} else if (range === 'year') {
cutoff = new Date(now);
cutoff.setFullYear(cutoff.getFullYear() - 1);
} else if (range === 'activity') {
// 活动期间:演示用最近60天
cutoff = new Date(now);
@@ -1517,7 +1521,7 @@ document.addEventListener('click', function(e) {
/* ===== 初始化 ===== */
// renderWeek → selectDay → renderDayDetail → renderFatMetric/renderBmiMetric 已形成完整调用链
renderWeek();
renderRecordList('month');
renderRecordList('week');
</script>
</body>
</html>
@@ -7,7 +7,7 @@
<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; }
.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; position: relative; }
.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; }
@@ -43,6 +43,35 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.goal-card__sub { display: flex; align-items: center; justify-content: space-between; margin-bottom: 0; }
.goal-card__activity { font-size: 13px; color: #666; font-weight: 500; }
.goal-card__rank { font-size: 13px; font-weight: 600; color: #faad14; display: flex; align-items: center; gap: 2px; }
.goal-card__empty { display: none; text-align: center; padding: 32px 0 20px; color: #bbb; }
.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-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; }
.goal-status__body { flex: 1; min-width: 0; }
.goal-status__title { font-size: 14px; font-weight: 700; line-height: 1.3; }
.goal-status__sub { font-size: 11px; margin-top: 4px; }
.goal-status__text { font-size: 12px; line-height: 1.6; margin-top: 4px; }
.goal-status__btn { padding: 5px 14px; border-radius: 14px; border: none; font-size: 11px; font-weight: 700; cursor: pointer; margin-top: 8px; }
.goal-status--review { background: #fffbe6; border: 1px solid #ffe58f; }
.goal-status--review .goal-status__title { color: #faad14; }
.goal-status--review .goal-status__sub { color: #d4a00c; }
.goal-status--review .goal-status__text { color: #8c6d00; }
.goal-status--review .goal-status__btn { background: #faad14; color: #fff; }
.goal-status--approved { background: #e6f7ff; border: 1px solid #91d5ff; }
.goal-status--approved .goal-status__title { color: #1890ff; }
.goal-status--approved .goal-status__sub { color: #0d7ad9; }
.goal-status--approved .goal-status__btn { background: #1890ff; color: #fff; }
.goal-status--rejected { background: #fff1f0; border: 1px solid #ffa39e; }
.goal-status--rejected .goal-status__title { color: #ff4d4f; }
.goal-status--rejected .goal-status__sub { color: #d9363e; }
.goal-status--rejected .goal-status__text { color: #8c1a1a; }
.goal-status--rejected .goal-status__btn { background: #ff4d4f; color: #fff; }
/* ===== 标尺 SVG ===== */
.gauge-wrap { width: 100%; }
@@ -141,6 +170,53 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.gap-stat__val--good { color: #52c41a; }
.gap-stat__unit { font-size: 11px; font-weight: 400; color: #999; }
/* ===== 排行榜卡片 ===== */
.rank-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; }
.rank-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.rank-card__title { font-size: 16px; font-weight: 800; color: #1a1a1a; }
.rank-card__arrow { color: #bbb; display: flex; align-items: center; gap: 2px; font-size: 11px; }
.rank-card__my { display: flex; align-items: center; gap: 10px; padding: 10px 12px; background: linear-gradient(135deg, #e8fbfa, #f0fffe); border-radius: 10px; margin-bottom: 10px; border: 1px solid #b6f0eb; }
.rank-card__my-rank { width: 40px; height: 40px; border-radius: 50%; background: #fff; display: flex; flex-direction: column; align-items: center; justify-content: center; flex-shrink: 0; font-size: 18px; font-weight: 800; color: #36cfc9; line-height: 1; box-shadow: 0 2px 6px rgba(54,207,201,0.15); }
.rank-card__my-rank span { font-size: 8px; color: #999; font-weight: 500; margin-top: -1px; }
.rank-card__my-info { flex: 1; min-width: 0; }
.rank-card__my-name { font-size: 14px; font-weight: 700; color: #1a1a1a; }
.rank-card__my-dept { font-size: 11px; color: #999; margin-top: 1px; }
.rank-card__my-score { font-size: 14px; font-weight: 700; color: #333; text-align: right; flex-shrink: 0; }
.rank-card__my-score em { font-style: normal; font-size: 10px; color: #999; font-weight: 400; }
.rank-card__top3 { display: flex; flex-direction: column; gap: 4px; }
.rank-card__top-item { display: flex; align-items: center; gap: 8px; padding: 6px 10px; border-radius: 8px; font-size: 12px; }
.rank-card__top-item:nth-child(1) { background: #fffbe6; }
.rank-card__top-item:nth-child(2) { background: #f7f8fa; }
.rank-card__top-item:nth-child(3) { background: #fdf4ed; }
.rank-card__top-num { width: 20px; height: 20px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 11px; flex-shrink: 0; }
.rank-card__top-item:nth-child(1) .rank-card__top-num { background: #fadb14; color: #fff; }
.rank-card__top-item:nth-child(2) .rank-card__top-num { background: #d9d9d9; color: #fff; }
.rank-card__top-item:nth-child(3) .rank-card__top-num { background: #ffbb96; color: #fff; }
.rank-card__top-name { flex: 1; color: #333; font-weight: 500; }
.rank-card__top-val { color: #666; font-weight: 600; }
/* ===== 体重报告卡片 ===== */
.report-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; }
.report-card__header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 10px; }
.report-card__title { font-size: 16px; font-weight: 800; color: #1a1a1a; }
.report-card__arrow { color: #bbb; display: flex; align-items: center; gap: 2px; font-size: 11px; }
.report-card__meta { display: flex; align-items: center; gap: 12px; font-size: 11px; color: #999; margin-bottom: 12px; }
.report-card__meta em { font-style: normal; font-weight: 600; color: #666; }
.report-card__weight-row { display: flex; align-items: center; gap: 6px; margin-bottom: 10px; }
.report-card__weight-item { flex: 1; text-align: center; }
.report-card__weight-item-val { font-size: 18px; font-weight: 800; color: #1a1a1a; line-height: 1; }
.report-card__weight-item-label { font-size: 10px; color: #999; margin-top: 3px; }
.report-card__weight-arrow { color: #bbb; font-size: 14px; flex-shrink: 0; margin-top: -6px; }
.report-card__stats-row { display: flex; gap: 10px; margin-bottom: 10px; }
.report-card__stat { flex: 1; background: #f7f8fa; border-radius: 10px; padding: 8px 8px; text-align: center; }
.report-card__stat-val { font-size: 15px; font-weight: 800; color: #1a1a1a; line-height: 1; }
.report-card__stat-val--good { color: #52c41a; }
.report-card__stat-label { font-size: 10px; color: #999; margin-top: 3px; }
.report-card__progress { margin-top: 2px; }
.report-card__progress-bar { width: 100%; height: 8px; background: #f0f0f0; border-radius: 4px; overflow: hidden; }
.report-card__progress-fill { height: 100%; background: linear-gradient(90deg, #36cfc9, #2ec4b6); border-radius: 4px; transition: width .5s ease; }
.report-card__progress-label { display: flex; justify-content: space-between; font-size: 10px; color: #999; margin-top: 4px; }
/* ===== 推荐视频卡片 ===== */
.video-card { background: #fff; border-radius: 16px; margin: 12px 12px 0; padding: 16px 12px 14px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.video-card__title { font-size: 16px; font-weight: 800; color: #1a1a1a; margin-bottom: 12px; padding-left: 4px; }
@@ -157,6 +233,56 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
.video-item__org { font-size: 10px; color: #bbb; margin-top: 3px; }
.bottom-safe { height: 20px; flex-shrink: 0; }
/* ===== 底部弹窗(记录体重) ===== */
.sheet-mask { position: absolute; inset: 0; background: rgba(0,0,0,0.4); z-index: 20; opacity: 0; pointer-events: none; transition: opacity 0.25s; }
.sheet-mask.show { opacity: 1; pointer-events: auto; }
.sheet { position: absolute; left: 0; right: 0; bottom: 0; background: #fff; border-radius: 20px 20px 0 0; z-index: 21; padding: 10px 0 22px; transform: translateY(100%); transition: transform 0.3s ease; max-height: 88%; display: flex; flex-direction: column; }
.sheet.show { transform: translateY(0); }
.sheet__handle { width: 40px; height: 4px; background: #d8dde3; border-radius: 2px; margin: 0 auto 8px; flex-shrink: 0; }
.sheet__head { display: flex; align-items: center; gap: 12px; padding: 8px 22px 16px; border-bottom: 1px solid #f1f3f5; flex-shrink: 0; }
.sheet__icon { width: 40px; height: 40px; border-radius: 10px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; background: linear-gradient(135deg, #5ad9d3, #36cfc9); }
.sheet__icon svg { width: 22px; height: 22px; }
.sheet__meta { flex: 1; min-width: 0; }
.sheet__title { font-size: 17px; font-weight: 700; color: #1a1a1a; }
.sheet__hint { font-size: 13px; color: #9aa3ad; margin-top: 4px; }
.sheet__body { flex: 1; overflow-y: auto; padding: 8px 0 12px; }
.sheet__body::-webkit-scrollbar { display: none; }
/* 字段 */
.sheet-field { padding: 14px 16px 8px; position: relative; }
.sheet-field__head { display: flex; align-items: center; justify-content: space-between; margin-bottom: 2px; padding-left: 2px; }
.sheet-field__label { font-size: 15px; font-weight: 700; color: #1a1a1a; }
.sheet-field__value { text-align: center; margin: 4px 0 10px; min-height: 40px; }
.sheet-field__num { font-size: 32px; font-weight: 800; color: #36cfc9; letter-spacing: 1px; }
.sheet-field__unit { font-size: 14px; color: #9aa3ad; margin-left: 6px; vertical-align: middle; }
.sheet-field--disabled .sheet-field__value,
.sheet-field--disabled .ruler { display: none; }
.sheet-field--disabled { padding-bottom: 4px; }
/* 尺子 */
.ruler { position: relative; height: 64px; margin: 0 16px; cursor: ew-resize; touch-action: none; user-select: none; -webkit-user-select: none; }
.ruler__pointer { position: absolute; left: 50%; top: 0; transform: translateX(-50%); display: flex; flex-direction: column; align-items: center; z-index: 2; pointer-events: none; }
.ruler__pointer-flag { width: 0; height: 0; border-left: 7px solid transparent; border-right: 7px solid transparent; border-top: 9px solid #36cfc9; }
.ruler__pointer-line { width: 2px; height: 18px; background: #36cfc9; margin-top: -1px; }
.ruler__scale { position: absolute; left: 0; right: 0; top: 16px; height: 22px; overflow: hidden; }
.ruler__tick { position: absolute; bottom: 0; width: 1px; background: #b6e3dd; }
.ruler__tick--minor { height: 8px; }
.ruler__tick--major { height: 16px; }
.ruler__labels { position: absolute; left: 0; right: 0; top: 42px; height: 16px; font-size: 11px; color: #9aa3ad; }
.ruler__label { position: absolute; transform: translateX(-50%); }
/* Switch 开关 */
.switch { position: relative; width: 44px; height: 26px; flex-shrink: 0; cursor: pointer; }
.switch__track { display: block; width: 100%; height: 100%; background: #e0e0e0; border-radius: 13px; transition: background .25s; }
.switch__track::after { content: ''; position: absolute; top: 2px; left: 2px; width: 22px; height: 22px; background: #fff; border-radius: 50%; transition: transform .25s; box-shadow: 0 1px 4px rgba(0,0,0,0.15); }
.switch.on .switch__track { background: #36cfc9; }
.switch.on .switch__track::after { transform: translateX(18px); }
.sheet__submit-bar { padding: 14px 16px 0; flex-shrink: 0; }
.sheet__submit { display: block; width: 100%; height: 46px; background: linear-gradient(135deg, #5ad9d3, #36cfc9); color: #fff; border: none; border-radius: 23px; font-size: 16px; font-weight: 600; cursor: pointer; letter-spacing: 6px; }
.sheet__submit:active { opacity: 0.85; }
</style>
</head>
<body>
@@ -179,7 +305,10 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="nav-more" id="navMore" onclick="toggleTypeMenu(event)">
<div class="nav-more__dots"><span></span><span></span><span></span></div>
<div class="type-menu" id="typeMenu">
<div class="type-menu__item active" data-type="hasact" onclick="setPageType('hasact', event)">已设置-参加活动</div>
<div class="type-menu__item active" data-type="hasact-signed" onclick="setPageType('hasact-signed', event)">已设置-参加活动 · 已签署</div>
<div class="type-menu__item" data-type="hasact-review" onclick="setPageType('hasact-review', event)">已设置-参加活动 · 审核中</div>
<div class="type-menu__item" data-type="hasact-approved" onclick="setPageType('hasact-approved', event)">已设置-参加活动 · 已通过待签署</div>
<div class="type-menu__item" data-type="hasact-rejected" onclick="setPageType('hasact-rejected', event)">已设置-参加活动 · 已驳回</div>
<div class="type-menu__item" data-type="personal" onclick="setPageType('personal', event)">已设置-个人目标</div>
<div class="type-menu__item" data-type="none" onclick="setPageType('none', event)">未设置目标</div>
</div>
@@ -193,7 +322,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="goal-card">
<div class="goal-card__header">
<span class="goal-card__title">体重管理目标</span>
<a class="goal-card__days" id="daysLink" href="goal-management.html">
<a class="goal-card__days" id="daysLink" href="goal-management-v2.html">
当前第52/180天
<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>
</a>
@@ -207,8 +336,34 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</span>
</div>
<!-- 目标状态横幅 -->
<div class="goal-status goal-status--review" id="goalStatusReview">
<span class="goal-status__icon"></span>
<div class="goal-status__body">
<div class="goal-status__title">目标审核中,请耐心等待</div>
<div class="goal-status__sub">提交时间:2026-04-02 10:30</div>
</div>
</div>
<div class="goal-status goal-status--approved" id="goalStatusApproved">
<span class="goal-status__icon"></span>
<div class="goal-status__body">
<div class="goal-status__title">审核已通过,请签署目标承诺书</div>
<div class="goal-status__sub">审核人:李医生 · 2026-04-03 14:30</div>
<button class="goal-status__btn" onclick="event.stopPropagation();location.href='promise-signing.html'">去签署 →</button>
</div>
</div>
<div class="goal-status goal-status--rejected" id="goalStatusRejected">
<span class="goal-status__icon"></span>
<div class="goal-status__body">
<div class="goal-status__title">目标已被驳回,请修改后重新提交</div>
<div class="goal-status__text">目标体重设定偏低,建议参考BMI健康范围适当调整至62-68kg区间后重新提交。</div>
<div class="goal-status__sub">审核人:李医生 · 2026-04-03 14:30</div>
<button class="goal-status__btn" onclick="event.stopPropagation();location.href='goal-management-v2.html?actId=1&actName=2026年二季度体重管理活动&actPeriod=2026-04-01 ~ 2026-09-30&actOrg=公司机关'">重新提交</button>
</div>
</div>
<!-- ===== 半圆标尺(初始体重-左 | 当前体重-内 | 目标体重-右) ===== -->
<div class="gauge-wrap">
<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%">
@@ -275,6 +430,13 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</svg>
</div>
<!-- 未设置目标时的空状态 -->
<div class="goal-card__empty" id="goalEmpty">
<div class="goal-card__empty-icon">🎯</div>
<div class="goal-card__empty-text">请前往目标管理页面设置目标</div>
<a class="goal-card__empty-link" href="goal-management-v2.html">去设置 →</a>
</div>
</div>
<!-- ====== 热量缺口卡片 ====== -->
@@ -327,14 +489,14 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<span class="gap-stat__val gap-stat__val--good">-364<span class="gap-stat__unit"> / -500 千卡</span></span>
</div>
<div style="display:flex;gap:8px;">
<div class="gap-stat" style="flex:1;">
<span class="gap-stat__label">总消耗</span>
<span class="gap-stat__val">820<span class="gap-stat__unit"> / 1000 千卡</span></span>
</div>
<div class="gap-stat" style="flex:1;">
<span class="gap-stat__label">总摄入</span>
<span class="gap-stat__val">1086<span class="gap-stat__unit"> / 1950 千卡</span></span>
</div>
<div class="gap-stat" style="flex:1;">
<span class="gap-stat__label">总消耗</span>
<span class="gap-stat__val">820<span class="gap-stat__unit"> / 1000 千卡</span></span>
</div>
</div>
</div>
</div>
@@ -342,7 +504,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- ====== 吃记录卡片 ====== -->
<div class="eat-card" onclick="location.href='eat-detail-v2.html'">
<div class="eat-card__header">
<span class="eat-card__title">吃记录</span>
<span class="eat-card__title">吃记录 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></span>
<div style="display:flex;align-items:center;gap:8px;">
<button style="padding:5px 12px;border-radius:14px;border:none;background:linear-gradient(160deg,#6ee16e,#3ec46a);color:#fff;font-size:11px;font-weight:600;cursor:pointer;box-shadow:0 2px 6px rgba(62,196,106,0.3);" onclick="event.stopPropagation();location.href='diet-advice-v2.html'">今日饮食建议</button>
<button class="eat-card__add" onclick="event.stopPropagation(); location.href='eat-checkin-v2.html'">
@@ -398,14 +560,17 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- ====== 运动记录卡片 ====== -->
<div class="move-card" onclick="location.href='exercise-detail-v2.html'">
<div class="move-card__header">
<span class="move-card__title">动记录</span>
<button class="move-card__add" onclick="event.stopPropagation();location.href='exercise-checkin.html'">
<div class="move-card__header">
<span class="move-card__title">动记录 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></span>
<div style="display:flex;align-items:center;gap:8px;">
<button style="padding:5px 12px;border-radius:14px;border:none;background:linear-gradient(160deg,#ffb066,#ff8a3d);color:#fff;font-size:11px;font-weight:600;cursor:pointer;box-shadow:0 2px 6px rgba(255,138,61,0.3);" onclick="event.stopPropagation();location.href='exercise-advice-v2.html'">智能运动计划</button>
<button class="move-card__add" onclick="event.stopPropagation();location.href='exercise-checkin-v2.html'">
<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>
</button>
</div>
</div>
<div class="move-body">
<div class="move-body">
<!-- 左侧:圆形进度圈 -->
<div class="move-ring">
<svg viewBox="0 0 130 130">
@@ -443,13 +608,13 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- ====== 体重记录卡片 ====== -->
<div class="weight-card" style="cursor:pointer;" onclick="location.href='weight-detail-v2.html'">
<div class="weight-card__header">
<span class="weight-card__title">体重记录</span>
<span class="weight-card__title">体重记录 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="#bbb" stroke-width="2.5"><polyline points="9 18 15 12 9 6"/></svg></span>
<div style="display:flex;align-items:center;gap:8px;">
<button style="padding:5px 12px;border-radius:14px;border:none;background:linear-gradient(160deg,#b08cff,#8b6cff);color:#fff;font-size:12px;font-weight:600;cursor:pointer;box-shadow:0 2px 8px rgba(139,108,255,0.3);display:flex;align-items:center;gap:4px;" onclick="event.stopPropagation();location.href='../bluetooth-scale-miniprogram/main.html'">
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M14.24 12.01l2.32 2.32c.28-.72.44-1.51.44-2.33 0-.82-.16-1.59-.43-2.31l-2.33 2.32zm5.29-5.3l-1.26 1.26c.63 1.21.98 2.57.98 4.02s-.36 2.82-.98 4.02l1.2 1.2c.97-1.54 1.54-3.36 1.54-5.31-.01-1.89-.55-3.67-1.48-5.19zm-3.82 1L10 2H9v7.59L4.41 5 3 6.41 8.59 12 3 17.59 4.41 19 9 14.41V22h1l5.71-5.71-4.3-4.29 4.3-4.29zM11 5.83l1.88 1.88L11 9.59V5.83zm1.88 10.46L11 18.17v-3.76l1.88 1.88z"/></svg>
蓝牙称
</button>
<button class="weight-card__add" onclick="event.stopPropagation();location.href='weight-checkin.html'">
<button class="weight-card__add" onclick="event.stopPropagation();openRecordSheet()">
<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>
@@ -501,6 +666,91 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<!-- ====== 排行榜卡片 ====== -->
<div class="rank-card" onclick="location.href='weight-ranking.html'">
<div class="rank-card__header">
<span class="rank-card__title">🏆 排行榜</span>
<span class="rank-card__arrow">查看全部 <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></span>
</div>
<div class="rank-card__my" id="rankMy">
<div class="rank-card__my-rank">12<span></span></div>
<div class="rank-card__my-info">
<div class="rank-card__my-name"></div>
<div class="rank-card__my-dept">技术研发部</div>
</div>
<div class="rank-card__my-score">86<em></em></div>
</div>
<div class="rank-card__top3">
<div class="rank-card__top-item">
<span class="rank-card__top-num">1</span>
<span class="rank-card__top-name">张明</span>
<span class="rank-card__top-val">98 分</span>
</div>
<div class="rank-card__top-item">
<span class="rank-card__top-num">2</span>
<span class="rank-card__top-name">李婷</span>
<span class="rank-card__top-val">95 分</span>
</div>
<div class="rank-card__top-item">
<span class="rank-card__top-num">3</span>
<span class="rank-card__top-name">王磊</span>
<span class="rank-card__top-val">92 分</span>
</div>
</div>
</div>
<!-- ====== 体重报告卡片 ====== -->
<div class="report-card" onclick="location.href='weight-report.html'">
<div class="report-card__header">
<span class="report-card__title">📊 体重报告</span>
<span class="report-card__arrow">查看详情 <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></span>
</div>
<div class="report-card__meta" id="reportMeta">
<span>截止至 <em>2026-08-26</em></span>
<span>|</span>
<span id="reportDayNum"><em>62天</em></span>
</div>
<div class="report-card__weight-row">
<div class="report-card__weight-item">
<div class="report-card__weight-item-val">65.2</div>
<div class="report-card__weight-item-label">初始体重 kg</div>
</div>
<span class="report-card__weight-arrow"></span>
<div class="report-card__weight-item">
<div class="report-card__weight-item-val">62.6</div>
<div class="report-card__weight-item-label">当前体重 kg</div>
</div>
<span class="report-card__weight-arrow" id="reportTargetArrow"></span>
<div class="report-card__weight-item" id="reportTargetWeight">
<div class="report-card__weight-item-val">60.0</div>
<div class="report-card__weight-item-label">目标体重 kg</div>
</div>
</div>
<div class="report-card__stats-row">
<div class="report-card__stat">
<div class="report-card__stat-val report-card__stat-val--good">-2.6<span style="font-size:10px;font-weight:400;">kg</span></div>
<div class="report-card__stat-label">已减体重</div>
</div>
<div class="report-card__stat" id="reportTargetGap">
<div class="report-card__stat-val">2.6<span style="font-size:10px;font-weight:400;">kg</span></div>
<div class="report-card__stat-label">距目标</div>
</div>
<div class="report-card__stat" id="reportTargetProgress">
<div class="report-card__stat-val">50<span style="font-size:10px;font-weight:400;">%</span></div>
<div class="report-card__stat-label">目标进度</div>
</div>
</div>
<div class="report-card__progress" id="reportProgressBar">
<div class="report-card__progress-bar">
<div class="report-card__progress-fill" style="width:50%;"></div>
</div>
<div class="report-card__progress-label">
<span>0%</span>
<span>目标 60.0kg</span>
</div>
</div>
</div>
<!-- ====== 推荐视频卡片 ====== -->
<div class="video-card">
<div class="video-card__title">推荐视频</div>
@@ -559,6 +809,30 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="bottom-safe"></div>
</div>
<!-- ====== 记录体重底部弹窗 ====== -->
<div class="sheet-mask" id="recordSheetMask" onclick="closeRecordSheet()"></div>
<div class="sheet" id="recordSheet">
<div class="sheet__handle"></div>
<div class="sheet__head">
<div class="sheet__icon">
<svg viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="5" width="16" height="15" rx="2"/>
<path d="M9 5 V3 H15 V5"/>
<line x1="9" y1="14" x2="12" y2="10"/>
<line x1="12" y1="10" x2="15" y2="14"/>
</svg>
</div>
<div class="sheet__meta">
<div class="sheet__title">记录体重</div>
<div class="sheet__hint">滑动尺子调整数值</div>
</div>
</div>
<div class="sheet__body" id="recordSheetBody"></div>
<div class="sheet__submit-bar">
<button class="sheet__submit" onclick="submitRecord()">保 存</button>
</div>
</div>
</div>
<script>
@@ -592,30 +866,136 @@ function setPageType(type, e) {
var activityEl = document.getElementById('activityName');
var rankEl = document.getElementById('rankBadge');
var daysEl = document.getElementById('daysLink');
var gaugeEl = document.getElementById('goalGauge');
var emptyEl = document.getElementById('goalEmpty');
var rankMyEl = document.getElementById('rankMy');
var reportDayNum = document.getElementById('reportDayNum');
var reportTargetWeight = document.getElementById('reportTargetWeight');
var reportTargetArrow = document.getElementById('reportTargetArrow');
var reportTargetGap = document.getElementById('reportTargetGap');
var reportTargetProgress = document.getElementById('reportTargetProgress');
var reportProgressBar = document.getElementById('reportProgressBar');
if (type === 'hasact') {
// 已设置-参加活动:全部显示
activityEl.textContent = '2026年二季度体重管理活动';
var statusReviewEl = document.getElementById('goalStatusReview');
var statusApprovedEl = document.getElementById('goalStatusApproved');
var statusRejectedEl = document.getElementById('goalStatusRejected');
// 隐藏所有状态横幅
function hideAllStatus() {
statusReviewEl.classList.remove('show');
statusApprovedEl.classList.remove('show');
statusRejectedEl.classList.remove('show');
}
// 公共:已设置基础样式
function setHasActBase() {
activityEl.style.display = '';
rankEl.style.display = '';
daysEl.innerHTML = '当前第52/180天 <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>';
daysEl.style.color = '#36cfc9';
daysEl.style.cursor = 'pointer';
emptyEl.classList.remove('show');
rankMyEl.style.display = '';
reportDayNum.style.display = '';
reportTargetWeight.style.display = '';
reportTargetArrow.style.display = '';
reportTargetGap.style.display = '';
reportTargetProgress.style.display = '';
reportProgressBar.style.display = '';
document.getElementById('reportMeta').querySelector('span:nth-child(2)').style.display = '';
}
// 公共:非已签署状态下排行榜和报告卡片样式(与未设置目标一致)
function setNonSignedCards() {
rankMyEl.style.display = 'none';
reportDayNum.style.display = 'none';
document.getElementById('reportMeta').querySelector('span:nth-child(2)').style.display = 'none';
reportTargetWeight.style.display = 'none';
reportTargetArrow.style.display = 'none';
reportTargetGap.style.display = 'none';
reportTargetProgress.style.display = 'none';
reportProgressBar.style.display = 'none';
}
if (type === 'hasact-signed') {
activityEl.textContent = '2026年二季度体重管理活动';
daysEl.href = 'goal-detail-v2.html?type=activity&state=signed';
daysEl.innerHTML = '当前第52/180天 <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>';
setHasActBase();
hideAllStatus();
gaugeEl.style.display = '';
} else if (type === 'hasact-review') {
activityEl.textContent = '2026年二季度体重管理活动';
daysEl.href = 'goal-detail-v2.html?type=activity&state=review';
daysEl.innerHTML = '审核中 <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>';
setHasActBase();
hideAllStatus();
statusReviewEl.classList.add('show');
gaugeEl.style.display = 'none';
rankEl.style.display = 'none';
setNonSignedCards();
} else if (type === 'hasact-approved') {
activityEl.textContent = '2026年二季度体重管理活动';
daysEl.href = 'goal-detail-v2.html?type=activity&state=approved';
daysEl.innerHTML = '待签署 <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>';
setHasActBase();
hideAllStatus();
statusApprovedEl.classList.add('show');
gaugeEl.style.display = 'none';
rankEl.style.display = 'none';
setNonSignedCards();
} else if (type === 'hasact-rejected') {
activityEl.textContent = '2026年二季度体重管理活动';
daysEl.href = 'goal-detail-v2.html?type=activity&state=rejected';
daysEl.innerHTML = '已驳回 <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>';
setHasActBase();
hideAllStatus();
statusRejectedEl.classList.add('show');
gaugeEl.style.display = 'none';
rankEl.style.display = 'none';
setNonSignedCards();
} else if (type === 'personal') {
// 已设置-个人目标:改活动标题,隐藏排名
hideAllStatus();
activityEl.textContent = '2026年二季度个人体重管理目标';
activityEl.style.display = '';
rankEl.style.display = 'none';
daysEl.href = 'goal-detail-v2.html?type=personal';
daysEl.innerHTML = '当前第52/180天 <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>';
daysEl.style.color = '#36cfc9';
daysEl.style.cursor = 'pointer';
// 标尺显示
gaugeEl.style.display = '';
emptyEl.classList.remove('show');
// 排行榜 - 隐藏我的排行
rankMyEl.style.display = 'none';
// 报告卡片 - 完整显示
reportDayNum.style.display = '';
reportTargetWeight.style.display = '';
reportTargetArrow.style.display = '';
reportTargetGap.style.display = '';
reportTargetProgress.style.display = '';
reportProgressBar.style.display = '';
document.getElementById('reportMeta').querySelector('span:nth-child(2)').style.display = '';
} else if (type === 'none') {
// 未设置目标:隐藏活动标题、排名,天数置灰
hideAllStatus();
activityEl.style.display = 'none';
rankEl.style.display = 'none';
daysEl.href = 'goal-management-v2.html';
daysEl.innerHTML = '未设置目标 <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>';
daysEl.style.color = '#bbb';
daysEl.style.cursor = 'default';
daysEl.style.cursor = 'pointer';
// 标尺隐藏,显示空状态
gaugeEl.style.display = 'none';
emptyEl.classList.add('show');
// 排行榜 - 隐藏我的排行
rankMyEl.style.display = 'none';
// 报告卡片 - 隐藏目标相关元素
reportDayNum.style.display = 'none';
document.getElementById('reportMeta').querySelector('span:nth-child(2)').style.display = 'none';
reportTargetWeight.style.display = 'none';
reportTargetArrow.style.display = 'none';
reportTargetGap.style.display = 'none';
reportTargetProgress.style.display = 'none';
reportProgressBar.style.display = 'none';
}
}
@@ -649,6 +1029,203 @@ function selectGapDay(el) {
rows[2].innerHTML = data.intake + '<span class="gap-stat__unit"> / 1950 千卡</span>';
}
}
/* ===== 记录体重底部弹窗 ===== */
var weightDayData = {
'2026-07-22': { records: [ {time:'07:33', weight:62.6, fat:25.8, bmi:24.4, waist:76.5, source:'stadiometer'} ] }
};
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;
}
var RECORD_FIELDS = [
{ label: '记录体重', unit: 'kg', value: 62.6, step: 0.1, majorEvery: 10, min: 30, max: 150, key: 'weight' },
{ label: '记录体脂率', unit: '%', value: 25.8, step: 0.1, majorEvery: 5, min: 3, max: 60, key: 'fat', enabled: true },
{ label: '记录腰围', unit: 'cm', value: 76.5, step: 0.1, majorEvery: 10, min: 40, max: 160, key: 'waist', enabled: true }
];
function openRecordSheet() {
renderRecordFields();
document.getElementById('recordSheetMask').classList.add('show');
document.getElementById('recordSheet').classList.add('show');
}
function closeRecordSheet() {
document.getElementById('recordSheetMask').classList.remove('show');
document.getElementById('recordSheet').classList.remove('show');
}
function submitRecord() {
var w = RECORD_FIELDS[0].value;
var f = RECORD_FIELDS[1].enabled ? RECORD_FIELDS[1].value : null;
var wa = RECORD_FIELDS[2].enabled ? RECORD_FIELDS[2].value : null;
var bmi = w / (1.60 * 1.60);
var now = new Date();
var hh = ('0' + now.getHours()).slice(-2);
var mm = ('0' + now.getMinutes()).slice(-2);
var dateStr = fmtDate(now);
if (!weightDayData[dateStr]) weightDayData[dateStr] = { records: [] };
var rec = {
time: hh + ':' + mm,
weight: parseFloat(w.toFixed(1)),
bmi: parseFloat(bmi.toFixed(1)),
source: 'manual'
};
if (f !== null) rec.fat = parseFloat(f.toFixed(1));
if (wa !== null) rec.waist = parseFloat(wa.toFixed(1));
weightDayData[dateStr].records.push(rec);
// 更新主页体重卡片显示
var latestWeightEl = document.querySelector('.weight-latest__val');
var latestDateEl = document.querySelector('.weight-latest__date');
if (latestWeightEl) latestWeightEl.textContent = w.toFixed(1);
if (latestDateEl) latestDateEl.textContent = dateStr;
closeRecordSheet();
}
function renderRecordFields() {
var body = document.getElementById('recordSheetBody');
body.innerHTML = '';
RECORD_FIELDS.forEach(function(f, idx) {
var wrap = document.createElement('div');
wrap.className = 'sheet-field';
if (idx > 0 && !f.enabled) wrap.classList.add('sheet-field--disabled');
wrap.setAttribute('data-idx', idx);
var labelHtml;
if (idx === 0) {
labelHtml = '<div class="sheet-field__label">' + f.label + '</div>';
} else {
labelHtml =
'<div class="sheet-field__head">' +
'<div class="sheet-field__label">' + f.label + '</div>' +
'<div class="switch' + (f.enabled ? ' on' : '') + '" onclick="toggleField(event, ' + idx + ')">' +
'<span class="switch__track"></span>' +
'</div>' +
'</div>';
}
wrap.innerHTML = labelHtml +
'<div class="sheet-field__value">' +
'<span class="sheet-field__num" id="fieldNum' + idx + '">' + formatRulerNum(f.value, f.step) + '</span>' +
'<span class="sheet-field__unit">' + f.unit + '</span>' +
'</div>' +
'<div class="ruler" data-idx="' + idx + '">' +
'<div class="ruler__pointer">' +
'<div class="ruler__pointer-flag"></div>' +
'<div class="ruler__pointer-line"></div>' +
'</div>' +
'<div class="ruler__scale"></div>' +
'<div class="ruler__labels"></div>' +
'</div>';
body.appendChild(wrap);
var ruler = wrap.querySelector('.ruler');
renderRuler(ruler, f, idx);
bindRulerDrag(ruler, f, idx);
});
}
function toggleField(e, idx) {
e.stopPropagation();
var f = RECORD_FIELDS[idx];
f.enabled = !f.enabled;
var wrap = document.querySelector('.sheet-field[data-idx="' + idx + '"]');
if (wrap) {
wrap.classList.toggle('sheet-field--disabled', !f.enabled);
var sw = wrap.querySelector('.switch');
if (sw) sw.classList.toggle('on', f.enabled);
}
}
function formatRulerNum(v, step) {
if (step >= 1) return String(Math.round(v));
if (step >= 0.1) return v.toFixed(1);
return v.toFixed(2);
}
function updateFieldValue(idx) {
var f = RECORD_FIELDS[idx];
var numEl = document.getElementById('fieldNum' + idx);
if (numEl) numEl.textContent = formatRulerNum(f.value, f.step);
}
function renderRuler(ruler, cfg, idx) {
var scale = ruler.querySelector('.ruler__scale');
var labels = ruler.querySelector('.ruler__labels');
scale.innerHTML = '';
labels.innerHTML = '';
var pxPerStep = 8;
var rulerWidth = ruler.clientWidth || 358;
var totalTicks = Math.floor(rulerWidth / pxPerStep);
var centerIndex = Math.floor(totalTicks / 2);
for (var i = 0; i < totalTicks; i++) {
var offsetSteps = i - centerIndex;
var tick = document.createElement('div');
tick.className = 'ruler__tick';
var isMajor = (Math.abs(offsetSteps) % cfg.majorEvery) === 0;
tick.classList.add(isMajor ? 'ruler__tick--major' : 'ruler__tick--minor');
tick.style.left = (i * pxPerStep) + 'px';
scale.appendChild(tick);
if (isMajor) {
var lbl = document.createElement('div');
lbl.className = 'ruler__label';
var labelVal = cfg.value + offsetSteps * cfg.step;
lbl.textContent = formatRulerNum(labelVal, cfg.step);
lbl.style.left = (i * pxPerStep) + 'px';
labels.appendChild(lbl);
}
}
}
function bindRulerDrag(ruler, cfg, idx) {
var dragging = false;
var startX = 0;
var startValue = 0;
var pxPerStep = 8;
function onStart(e) {
e.preventDefault();
dragging = true;
startX = e.touches ? e.touches[0].clientX : e.clientX;
startValue = cfg.value;
}
function onMove(e) {
if (!dragging) return;
e.preventDefault();
var clientX = e.touches ? e.touches[0].clientX : e.clientX;
var deltaX = clientX - startX;
var steps = Math.round(deltaX / pxPerStep);
var newVal = startValue + steps * cfg.step;
if (newVal < cfg.min) newVal = cfg.min;
if (newVal > cfg.max) newVal = cfg.max;
cfg.value = parseFloat(newVal.toFixed(1));
updateFieldValue(idx);
renderRuler(ruler, cfg, idx);
}
function onEnd(e) {
if (!dragging) return;
dragging = false;
}
ruler.addEventListener('mousedown', onStart);
ruler.addEventListener('touchstart', onStart, { passive: false });
document.addEventListener('mousemove', onMove);
document.addEventListener('touchmove', onMove, { passive: false });
document.addEventListener('mouseup', onEnd);
document.addEventListener('touchend', onEnd);
}
setPageType('hasact-signed');
</script>
</body>
</html>
@@ -104,7 +104,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<!-- 导航栏 -->
<div class="nav-bar">
<!-- [交互] 点击返回体重管理主页 -->
<div class="nav-back" onclick="location.href='weight-main.html'">
<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>
@@ -83,7 +83,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<div class="nav-bar">
<div class="nav-back" id="navBack" onclick="location.href='weight-main.html' + (currentPageType ? '?type=' + currentPageType : '')">
<div class="nav-back" id="navBack" 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" id="navTitle">体重报告</div>
@@ -102,23 +102,56 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
<div class="type-mask" id="typeMask" onclick="closeTypeMenu()"></div>
<div class="scroll-content">
<!-- 总览卡片(已参加活动) -->
<!-- 总览卡片 -->
<div class="overview-card" id="overviewCard">
<div class="overview-card__title">您的体重报告</div>
<div class="overview-card__info">
<div class="overview-card__row" id="ovInfoRow">
<span class="overview-card__label">截止至 <span id="ovCutoff">20260812</span></span>
<span class="overview-card__label"><span id="ovDay">52</span> 天(共 <span id="ovTotal">180</span> 天)</span>
<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>
<div class="overview-card__metrics">
<div class="overview-card__metric">
<span class="overview-card__metric-label">已减重</span>
<span class="overview-card__metric-val" id="ovLoss">10.9<span class="overview-card__metric-unit">kg</span></span>
<!-- 体重对比行 -->
<div style="display:flex;align-items:center;gap:6px;margin-top:14px;position:relative;z-index:1;">
<div style="flex:1;text-align:center;">
<div style="font-size:16px;font-weight:800;">73.5</div>
<div style="font-size:10px;opacity:0.7;">初始体重 kg</div>
</div>
<div class="overview-card__metric">
<span class="overview-card__metric-label">减重率</span>
<span class="overview-card__metric-val" id="ovRate">14.8<span class="overview-card__metric-unit">%</span></span>
<span style="color:rgba(255,255,255,0.4);"></span>
<div style="flex:1;text-align:center;">
<div style="font-size:16px;font-weight:800;">62.6</div>
<div style="font-size:10px;opacity:0.7;">当前体重 kg</div>
</div>
<span style="color:rgba(255,255,255,0.4);" id="ovTargetArrow"></span>
<div style="flex:1;text-align:center;" id="ovTargetWeight">
<div style="font-size:16px;font-weight:800;">56.0</div>
<div style="font-size:10px;opacity:0.7;">目标体重 kg</div>
</div>
</div>
<!-- 统计行 -->
<div style="display:flex;gap:8px;margin-top:12px;position:relative;z-index:1;">
<div style="flex:1;background:rgba(255,255,255,0.15);border-radius:10px;padding:8px 6px;text-align:center;">
<div style="font-size:14px;font-weight:800;">-10.9<span style="font-size:10px;font-weight:400;opacity:0.8;">kg</span></div>
<div style="font-size:10px;opacity:0.7;">已减体重</div>
</div>
<div style="flex:1;background:rgba(255,255,255,0.15);border-radius:10px;padding:8px 6px;text-align:center;" id="ovTargetGap">
<div style="font-size:14px;font-weight:800;">6.6<span style="font-size:10px;font-weight:400;opacity:0.8;">kg</span></div>
<div style="font-size:10px;opacity:0.7;">距目标</div>
</div>
<div style="flex:1;background:rgba(255,255,255,0.15);border-radius:10px;padding:8px 6px;text-align:center;" id="ovTargetProgress">
<div style="font-size:14px;font-weight:800;">62<span style="font-size:10px;font-weight:400;opacity:0.8;">%</span></div>
<div style="font-size:10px;opacity:0.7;">目标进度</div>
</div>
</div>
<!-- 进度条 -->
<div style="margin-top:10px;position:relative;z-index:1;" id="ovProgressBar">
<div style="width:100%;height:6px;background:rgba(255,255,255,0.2);border-radius:3px;overflow:hidden;">
<div style="width:62%;height:100%;background:rgba(255,255,255,0.6);border-radius:3px;"></div>
</div>
<div style="display:flex;justify-content:space-between;font-size:10px;opacity:0.6;margin-top:3px;">
<span>0%</span><span>目标 56.0kg</span>
</div>
</div>
</div>
@@ -221,7 +254,7 @@ document.addEventListener('click', function(e) {
/* 切换页面类型 */
function setPageType(type) {
if (type === currentPageType) { closeTypeMenu(); return; }
location.href = 'weight-report.html?type=' + type;
location.replace('weight-report.html?type=' + type);
}
/* ===== 根据页面类型初始化 ===== */
@@ -237,53 +270,35 @@ function applyPageType(type) {
});
if (type === 'hasact') {
// 已参加活动:显示活动名称标题和完整总览卡片
navTitle.textContent = '2026年二季度体重管理活动';
overviewCard.style.display = '';
overviewEmpty.style.display = 'none';
if (ovInfoRow) ovInfoRow.style.display = '';
document.getElementById('ovDayWrap').style.display = '';
document.getElementById('tabActivity').style.display = '';
updateOverview();
} 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('tabActivity').style.display = '';
updateOverview();
} else {
// 未参加未设置:隐藏标题,显示总览卡片但隐藏截止至/活动进度,隐藏"活动期间"tab
navTitle.textContent = '';
navTitle.textContent = '体重报告';
overviewCard.style.display = '';
overviewEmpty.style.display = 'none';
if (ovInfoRow) ovInfoRow.style.display = 'none';
document.getElementById('ovTargetArrow').style.display = 'none';
document.getElementById('ovTargetWeight').style.display = 'none';
document.getElementById('ovTargetGap').style.display = 'none';
document.getElementById('ovTargetProgress').style.display = 'none';
document.getElementById('ovProgressBar').style.display = 'none';
document.getElementById('ovDayWrap').style.display = 'none';
document.getElementById('tabActivity').style.display = 'none';
updateOverview();
}
}
/* ===== 更新总览数据 ===== */
function updateOverview() {
var firstRecord = RECORDS[RECORDS.length - 1]; // 最早的记录
var latestRecord = RECORDS[0]; // 最新的记录
var startWeight = firstRecord.weight; // 73.5
var currentWeight = latestRecord.weight; // 62.6
var loss = startWeight - currentWeight; // 10.9
var rate = (loss / startWeight) * 100; // 14.83
// 计算活动天数
var startDate = ACTIVITY_START;
var todayDate = TODAY;
var elapsedDays = Math.floor((todayDate - startDate) / (1000 * 60 * 60 * 24)) + 1;
var totalDays = Math.floor((ACTIVITY_END - ACTIVITY_START) / (1000 * 60 * 60 * 24)) + 1;
document.getElementById('ovDay').textContent = elapsedDays;
document.getElementById('ovTotal').textContent = totalDays;
document.getElementById('ovLoss').innerHTML = loss.toFixed(1) + '<span class="overview-card__metric-unit">kg</span>';
document.getElementById('ovRate').innerHTML = rate.toFixed(1) + '<span class="overview-card__metric-unit">%</span>';
}
/* ===== Tab 切换 ===== */
var activeTab = 'week';
+1 -1
View File
@@ -575,7 +575,7 @@ const SITE_MAP = [
{ file:'report-gastro.html', label:'胃肠镜报告详情' },
{ file:'project-detail.html', label:'体检项目详情' }
] },
{ name:'体重管理', dir:'employee-app/weight-management', entry:'weight-main.html', prd:'employee-app/weight-management/doc/PRD.md', done:true, pages:[
{ name:'体重管理', dir:'employee-app/weight-management', entry:'weight-main-v2.html', prd:'employee-app/weight-management/doc/PRD.md', done:true, pages:[
{ file:'doc/design-overview.html', label:'页面总览' },
{ file:'weight-main.html', label:'体重管理主页' },
{ file:'weight-ranking.html', label:'排行榜' },
@@ -170,7 +170,7 @@
/* 同步弹窗 */
.sync-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
.sync-overlay--show { display: flex; }
@@ -203,7 +203,7 @@
/* 删除确认弹窗 */
.delete-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
.delete-overlay--show { display: flex; }
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 40 KiB

@@ -396,11 +396,11 @@
}
.toast--show { opacity: 1; transform: translateX(-50%) translateY(0); }
/* 确认弹窗 */
.confirm-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
/* 确认弹窗 */
.confirm-overlay {
position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
.confirm-overlay--show { display: flex; }
.confirm-dialog {
width: 280px; background: #fff; border-radius: 16px; padding: 28px 24px 20px;
@@ -418,11 +418,11 @@
.confirm-dialog__btn--confirm { background: var(--danger); color: #fff; }
.confirm-dialog__btn--confirm:active { background: #d9363e; }
/* 连接弹窗 */
.connect-overlay {
position: fixed; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
/* 连接弹窗 */
.connect-overlay {
position: absolute; inset: 0; background: rgba(0,0,0,0.5); z-index: 1000;
display: none; align-items: center; justify-content: center;
}
.connect-overlay--show { display: flex; }
.connect-dialog {
width: 260px; background: #fff; border-radius: 16px; padding: 32px 24px 24px;
@@ -155,6 +155,10 @@
<span class="form-label">身高<span style="color:#ff4d4f;">*</span></span>
<input class="form-input" id="inputHeight" type="number" placeholder="请输入身高"><span class="form-unit">cm</span>
</div>
<div class="form-item">
<span class="form-label">体重</span>
<input class="form-input" id="inputWeight" type="number" placeholder="选填"><span class="form-unit">kg</span>
</div>
</div>
<button class="btn-primary" id="submitBtn" onclick="submitProfile()">完成并进入首页</button>
@@ -174,10 +178,12 @@
var gender = params.get('gender');
var age = params.get('age');
var height = params.get('height');
var weight = params.get('weight');
if (name) document.getElementById('inputName').value = name;
if (gender) selectGender(gender);
if (age) document.getElementById('inputAge').value = age;
if (height) document.getElementById('inputHeight').value = height;
if (weight) document.getElementById('inputWeight').value = weight;
}
})();