feat: 体重管理模块交互完善—多状态卡片显隐、导航链路连通、SITE_MAP补全

- employee-app/home: 体重管理模块标题栏/卡片/功能列表入口指向weight-main.html
- basic-info: 返回按钮智能跳转,根据referrer判断来源
- goal-commitment: setGoalType改为URL参数跳转刷新,新增applyGoalTypeState函数
- goal-management: 7种状态新增hideActCard/hideSelfCard/showInfoCard字段控制卡片显隐,personal/只读态专属逻辑完善
- weight-main(新): 体重管理主入口页(英雄区/三态卡片/吃动平衡/体重记录/排行榜/推荐视频)
- balance-history(新): 吃动平衡历史页(月视图日历+柱状图+交互明细)
- index.html: entry改为weight-main.html,SITE_MAP补全promise-signing/my-promise/balance-history

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
liuyunqin
2026-06-12 08:50:01 +08:00
co-authored by Claude Opus 4.8
parent 2ddd649a69
commit e26e596c11
8 changed files with 1735 additions and 18 deletions
+3 -3
View File
@@ -171,13 +171,13 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<!-- 体重管理模块 -->
<div class="weight-module">
<div class="weight-header">
<div class="weight-header" onclick="location.href='../weight-management/weight-main.html'" style="cursor:pointer;">
<span class="weight-header__watermark">WEIGHT</span>
<span class="weight-header__tab">体重管理</span>
<span class="weight-header__info">体重管理活动 <strong>58</strong> 天,第 <strong>8</strong></span>
</div>
<!-- 当前/目标/初始 卡片 -->
<div class="weight-card">
<div class="weight-card" onclick="location.href='../weight-management/weight-main.html'" style="cursor:pointer;">
<div class="weight-avatars">
<div class="weight-avatar-side">
<div class="weight-avatar__img">
@@ -389,7 +389,7 @@ var funcItems = [
{ name: '应急就医', color: '#FF4D4F', icon: '🚑', href: '../emergency-medical/index.html' },
{ name: '一线医疗', color: '#52C41A', icon: '🏥', href: '../frontline-medical/index.html' },
{ name: '健康体检', color: '#FAAD14', icon: '📋', href: '../health-checkup/index.html' },
{ name: '体重管理', color: '#722ED1', icon: '⚖️', href: '../weight-management/index.html' },
{ name: '体重管理', color: '#722ED1', icon: '⚖️', href: '../weight-management/weight-main.html' },
{ name: '营养管理', color: '#52C41A', icon: '🥗', href: '../nutrition/index.html' },
{ name: '运动管理', color: '#FA8C16', icon: '🏃', href: '../exercise/index.html' },
{ name: '健康监测', color: '#13C2C2', icon: '💓', href: '../health-monitor/index.html' },
@@ -0,0 +1,308 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>吃动平衡历史 - 员工端 | 健康CQ原型</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; position: relative; 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; margin-right: 28px; }
.scroll-content { flex: 1; overflow-y: auto; background: #f5f7fa; }
.scroll-content::-webkit-scrollbar { display: none; }
.bottom-safe { height: 20px; flex-shrink: 0; }
/* ===== 日历导航 ===== */
.cal-nav { display: flex; align-items: center; justify-content: space-between; padding: 10px 14px 6px; background: #fff; margin: 10px 12px 0; border-radius: 16px 16px 0 0; }
.cal-nav__arrow { width: 26px; height: 26px; border-radius: 50%; background: #f7f8fa; display: flex; align-items: center; justify-content: center; cursor: pointer; border: none; }
.cal-nav__arrow svg { width: 14px; height: 14px; stroke: #333; }
.cal-nav__month { font-size: 14px; font-weight: 700; color: #1a1a1a; }
/* ===== 日历 ===== */
.cal-grid { background: #fff; margin: 0 12px; padding: 0 12px 10px; border-radius: 0 0 16px 16px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.cal-grid__wd { display: grid; grid-template-columns: repeat(7, 1fr); margin-bottom: 3px; }
.cal-grid__wd span { font-size: 10px; color: #999; text-align: center; }
.cal-grid__days { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.cal-day { display: flex; flex-direction: column; align-items: center; padding: 1px 0 2px; border-radius: 6px; cursor: pointer; min-height: 34px; }
.cal-day:hover { background: #f0f8f8; }
.cal-day.today { background: #e8f8f7; }
.cal-day.sel { background: #e8f8f7; border: 1.5px solid #36cfc9; }
.cal-day__num { font-size: 12px; font-weight: 700; color: #333; line-height: 1.2; }
.cal-day.today .cal-day__num, .cal-day.sel .cal-day__num { color: #36cfc9; }
.cal-day--empty { min-height: 34px; }
.cal-ring-wrap { display: flex; align-items: center; justify-content: center; margin-top: 2px; width: 24px; height: 24px; position: relative; }
.cal-ring--o { position: absolute; width: 21px; height: 21px; }
.cal-ring--o svg { width: 100%; height: 100%; }
.cal-ring--i { position: absolute; width: 14px; height: 14px; }
.cal-ring--i svg { width: 100%; height: 100%; }
.cal-day-diff { font-size: 6px; color: #bbb; margin-top: 0; text-align: center; line-height: 1.2; }
.cal-day-diff span { color: #36cfc9; }
/* ===== 柱状图 ===== */
.bar-section { background: #fff; border-radius: 16px; margin: 10px 12px 0; padding: 12px 10px 12px; box-shadow: 0 4px 14px rgba(0,0,0,0.04); }
.bar-section__title { font-size: 13px; font-weight: 700; color: #1a1a1a; margin-bottom: 8px; padding-left: 2px; }
.bar-area { position: relative; width: 100%; height: 220px; }
.bar-area svg { width: 100%; height: 100%; display: block; }
.bar-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
.bar-hit { position: absolute; pointer-events: auto; cursor: pointer; border-radius: 3px 3px 0 0; }
.bar-hit:hover { opacity: 0.85; }
.bar-legend { display: flex; justify-content: center; gap: 14px; margin-top: 8px; }
.bar-legend__item { display: flex; align-items: center; gap: 4px; font-size: 11px; color: #666; }
.bar-legend__sw { width: 10px; height: 7px; border-radius: 2px; flex-shrink: 0; }
.bar-legend__sw--in { background: #5470c6; }
.bar-legend__sw--ex { background: #fac858; }
/* Tooltip */
.tip { display: none; position: absolute; background: rgba(30,30,30,0.9); color: #fff; border-radius: 10px; padding: 10px 12px; font-size: 11px; line-height: 1.7; pointer-events: none; z-index: 10; min-width: 130px; box-shadow: 0 4px 12px rgba(0,0,0,0.2); }
.tip::after { content: ''; position: absolute; top: 100%; left: 50%; transform: translateX(-50%); border: 5px solid transparent; border-top-color: rgba(30,30,30,0.9); }
.tip__date { font-weight: 700; font-size: 12px; margin-bottom: 4px; }
.tip__row { display: flex; justify-content: space-between; }
.tip__lbl { color: #ccc; }
.tip__val { font-weight: 600; }
.tip__val--b { color: #7eb8f7; }
.tip__val--y { color: #f7d794; }
.tip__div { border-top: 1px solid rgba(255,255,255,0.15); margin: 4px 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="location.href='weight-main.html'">
<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>
<div class="scroll-content">
<!-- 日历导航 -->
<div class="cal-nav">
<button class="cal-nav__arrow" onclick="changeMonth(-1)">
<svg 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>
</button>
<span class="cal-nav__month" id="nav-month">2026年8月</span>
<button class="cal-nav__arrow" onclick="changeMonth(1)">
<svg 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>
</button>
</div>
<!-- 日历 -->
<div class="cal-grid">
<div class="cal-grid__wd">
<span></span><span></span><span></span><span></span><span></span><span></span><span></span>
</div>
<div class="cal-grid__days" id="cal-days"></div>
</div>
<!-- 柱状图 -->
<div class="bar-section">
<div class="bar-section__title">本月吃动趋势</div>
<div class="bar-area" id="bar-area">
<div class="tip" id="tip"><div class="tip__date"></div><div class="tip__body"></div></div>
<svg viewBox="0 0 360 208" preserveAspectRatio="none" id="bar-svg">
<defs>
<linearGradient id="ig" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#5470c6"/><stop offset="100%" stop-color="#3a56a4"/></linearGradient>
<linearGradient id="eg" x1="0" y1="0" x2="0" y2="1"><stop offset="0%" stop-color="#fac858"/><stop offset="100%" stop-color="#e6a817"/></linearGradient>
</defs>
<!-- Y轴标签 -->
<text x="2" y="16" font-size="9" fill="#999">2500</text>
<text x="2" y="48" font-size="9" fill="#999">2000</text>
<text x="2" y="79" font-size="9" fill="#999">1500</text>
<text x="2" y="111" font-size="9" fill="#999">1000</text>
<text x="2" y="142" font-size="9" fill="#999">500</text>
<!-- Y轴刻度线 -->
<line x1="34" y1="12" x2="38" y2="12" stroke="#e0e0e0" stroke-width="1"/>
<line x1="34" y1="44" x2="38" y2="44" stroke="#e0e0e0" stroke-width="1"/>
<line x1="34" y1="75" x2="38" y2="75" stroke="#e0e0e0" stroke-width="1"/>
<line x1="34" y1="107" x2="38" y2="107" stroke="#e0e0e0" stroke-width="1"/>
<line x1="34" y1="138" x2="38" y2="138" stroke="#e0e0e0" stroke-width="1"/>
<!-- X轴线 (y=170) -->
<line x1="38" y1="170" x2="350" y2="170" stroke="#f0f0f0" stroke-width="1"/>
<g id="bars"></g>
</svg>
<div class="bar-overlay" id="bar-overlay"></div>
</div>
<div class="bar-legend">
<div class="bar-legend__item"><div class="bar-legend__sw bar-legend__sw--in"></div><span>摄入</span></div>
<div class="bar-legend__item"><div class="bar-legend__sw bar-legend__sw--ex"></div><span>消耗</span></div>
</div>
</div>
<div class="bottom-safe"></div>
</div>
</div>
<script>
var currentYear = 2026, currentMonth = 8, selectedDay = null;
var dayData = {};
var mock = [
{d:1,i:1920,e:1680,c:1100,f:490,p:330,bf:520,l:820,dn:580,ba:1100,s:320,w:260},
{d:2,i:2100,e:1750,c:1200,f:540,p:360,bf:580,l:900,dn:620,ba:1120,s:350,w:280},
{d:3,i:1850,e:1920,c:1050,f:470,p:330,bf:480,l:780,dn:590,ba:1130,s:480,w:310},
{d:4,i:2050,e:1700,c:1160,f:520,p:370,bf:550,l:870,dn:630,ba:1110,s:310,w:280},
{d:5,i:1780,e:1850,c:1010,f:450,p:320,bf:460,l:750,dn:570,ba:1140,s:420,w:290},
{d:6,i:2200,e:1600,c:1260,f:560,p:380,bf:620,l:940,dn:640,ba:1100,s:260,w:240},
{d:7,i:1950,e:1780,c:1110,f:495,p:345,bf:510,l:830,dn:610,ba:1120,s:360,w:300},
{d:8,i:1880,e:1960,c:1070,f:475,p:335,bf:490,l:800,dn:590,ba:1150,s:500,w:310},
{d:9,i:1960,e:1720,c:1120,f:500,p:340,bf:530,l:840,dn:590,ba:1110,s:330,w:280},
{d:10,i:2080,e:1800,c:1180,f:530,p:370,bf:560,l:890,dn:630,ba:1130,s:370,w:300},
{d:11,i:1820,e:1900,c:1030,f:460,p:330,bf:470,l:770,dn:580,ba:1140,s:450,w:310},
{d:12,i:1940,e:1760,c:1100,f:490,p:350,bf:520,l:830,dn:590,ba:1120,s:340,w:300},
{d:13,i:1900,e:1820,c:1080,f:480,p:340,bf:500,l:810,dn:590,ba:1130,s:380,w:310},
];
mock.forEach(function(m){
dayData['2026-08-'+(m.d<10?'0'+m.d:m.d)] = m;
});
/* 日历 */
function renderCal(){
var c = document.getElementById('cal-days');
c.innerHTML = '';
document.getElementById('nav-month').textContent = currentYear+'年'+currentMonth+'月';
var fd = new Date(currentYear, currentMonth-1, 1).getDay();
var dim = new Date(currentYear, currentMonth, 0).getDate();
var off = fd===0?6:fd-1;
var today = new Date();
var tk = today.getFullYear()+'-'+String(today.getMonth()+1).padStart(2,'0')+'-'+String(today.getDate()).padStart(2,'0');
for(var i=0;i<off;i++){var em=document.createElement('div');em.className='cal-day--empty';c.appendChild(em);}
for(var d=1;d<=dim;d++){
var dk = currentYear+'-'+String(currentMonth).padStart(2,'0')+'-'+String(d).padStart(2,'0');
var data = dayData[dk];
var el = document.createElement('div');
el.className='cal-day';
if(dk===tk) el.classList.add('today');
if(selectedDay===d) el.classList.add('sel');
el.setAttribute('data-d',d);el.setAttribute('data-k',dk);
el.onclick=function(){selDay(+this.getAttribute('data-d'),this.getAttribute('data-k'));};
var n=document.createElement('span');n.className='cal-day__num';n.textContent=d;el.appendChild(n);
if(data){
var ip=Math.min(data.i/2500,1),ep=Math.min(data.e/2500,1);
var rw=document.createElement('div');rw.className='cal-ring-wrap';
var o=document.createElement('div');o.className='cal-ring--o';
var cc=2*Math.PI*8;
o.innerHTML='<svg viewBox="0 0 20 20"><circle cx="10" cy="10" r="8" fill="none" stroke="#e0e0e0" stroke-width="2"/><circle cx="10" cy="10" r="8" fill="none" stroke="#5470c6" stroke-width="2" stroke-dasharray="'+(cc*ip).toFixed(1)+' '+ (cc*(1-ip)).toFixed(1)+'" stroke-linecap="round"/></svg>';
rw.appendChild(o);
var inn=document.createElement('div');inn.className='cal-ring--i';
var cc2=2*Math.PI*8;
inn.innerHTML='<svg viewBox="0 0 20 20"><circle cx="10" cy="10" r="8" fill="none" stroke="#e0e0e0" stroke-width="2"/><circle cx="10" cy="10" r="8" fill="none" stroke="#fac858" stroke-width="2" stroke-dasharray="'+(cc2*ep).toFixed(1)+' '+(cc2*(1-ep)).toFixed(1)+'" stroke-linecap="round"/></svg>';
rw.appendChild(inn);el.appendChild(rw);
var ad=data.i-data.e;
var df=document.createElement('div');df.className='cal-day-diff';
df.innerHTML='<span>-500</span><span>'+ad+'</span>';el.appendChild(df);
}
c.appendChild(el);
}
}
function selDay(d,k){
selectedDay=d;
document.querySelectorAll('.cal-day').forEach(function(el){el.classList.remove('sel');if(+el.getAttribute('data-d')===d)el.classList.add('sel');});
renderBars();
/* 自动弹出选中日的摄入详情 */
var intBar=document.querySelector('.bar-hit[data-k="'+k+'"]');
if(intBar) showTipIn(intBar);
}
function selectDayFromBar(dk,type){
var d=+dk.split("-")[2];
selectedDay=d;
document.querySelectorAll(".cal-day").forEach(function(el){el.classList.remove("sel");if(+el.getAttribute("data-d")===d)el.classList.add("sel");});
renderBars();
var h=document.querySelector('.bar-hit[data-k="'+dk+'"]');
if(h){ if(type==="in")showTipIn(h); else showTipEx(h); }
}
function changeMonth(delta){
currentMonth+=delta;
if(currentMonth>12){currentMonth=1;currentYear++;}
if(currentMonth<1){currentMonth=12;currentYear--;}
selectedDay=null;renderCal();renderBars();
}
/* 柱状图 */
function renderBars(){
var g=document.getElementById('bars'),ov=document.getElementById('bar-overlay');
g.innerHTML='';ov.innerHTML='';
var dim=new Date(currentYear,currentMonth,0).getDate();
var md=dim;
// SVG: viewBox 0 0 360 208, preserveAspectRatio=none 完全填满bar-area
// X轴线在 y=170,Y轴标签/刻度在左侧
var chartLeft=38, chartRight=350, chartTop=12, chartBot=170;
var chartW=chartRight-chartLeft, chartH=chartBot-chartTop; // 312 x 158
var pairW=chartW/md, gap=3, barW=(pairW-gap)/2;
var maxY=2500;
for(var d=1;d<=md;d++){
var k=currentYear+'-'+String(currentMonth).padStart(2,'0')+'-'+String(d).padStart(2,'0');
var data=dayData[k];
var xs=chartLeft+(d-1)*pairW, hl=selectedDay===d;
if(data){
var iH=(data.i/maxY)*chartH, iY=chartTop+chartH-iH;
var iX=xs+1, iC=hl?'#5470c6':'url(#ig)';
if(hl) g.innerHTML+='<rect x="'+(iX-1)+'" y="'+iY+'" width="'+(barW+2)+'" height="'+iH+'" rx="2" fill="'+iC+'" opacity="0.25"/>';
g.innerHTML+='<rect x="'+iX+'" y="'+iY+'" width="'+barW+'" height="'+iH+'" rx="2" fill="'+iC+'"/>';
var eH=(data.e/maxY)*chartH, eY=chartTop+chartH-eH;
var eX=xs+barW+gap;
g.innerHTML+='<rect x="'+eX+'" y="'+eY+'" width="'+barW+'" height="'+eH+'" rx="2" fill="url(#eg)"/>';
// 点击区:完整 viewBox 百分比 (0..360 宽度, 0..208 高度)
var lpV=iX/360*100, tpV=iY/208*100, wpV=barW/360*100, hpI=iH/208*100, hpE=eH/208*100;
var ih=document.createElement('div');ih.className='bar-hit';
ih.style.cssText='left:'+lpV.toFixed(2)+'%;top:'+tpV.toFixed(2)+'%;width:'+wpV.toFixed(2)+'%;height:'+hpI.toFixed(2)+'%;background:'+(hl?'#5470c6':'rgba(84,112,198,0.5)');
ih.setAttribute('data-k',k);ih.onclick=function(ev){ev.stopPropagation();var dk=this.getAttribute("data-k");selectDayFromBar(dk,"in");};ov.appendChild(ih);
var eh=document.createElement('div');eh.className='bar-hit';
var elpV=eX/360*100, etpV=eY/208*100; eh.style.cssText='left:'+elpV.toFixed(2)+'%;top:'+etpV.toFixed(2)+'%;width:'+wpV.toFixed(2)+'%;height:'+hpE.toFixed(2)+'%;background:rgba(250,200,88,0.5)';
eh.setAttribute('data-k',k);eh.onclick=function(ev){ev.stopPropagation();var dk=this.getAttribute("data-k");selectDayFromBar(dk,"ex");};ov.appendChild(eh);
}
// X轴日期(每隔几天显示一次标签,避免拥挤)
var step=md>20?5:md>10?3:1;
if(d===1||d%step===0||d===md){
var lx=xs+pairW/2;
g.innerHTML+='<text x="'+lx+'" y="200" font-size="8" fill="#bbb" text-anchor="middle">'+currentMonth+'/'+d+'</text>';
}
}
}
function showTip(el,dl,html){
var t=document.getElementById('tip');t.querySelector('.tip__date').textContent=dl;
t.querySelector('.tip__body').innerHTML=html;t.style.display='block';
t.style.left='50%';t.style.top='2px';t.style.transform='translateX(-50%) translateY(-100%)';
setTimeout(function(){document.addEventListener('click',closeTip,{once:true});},10);
}
function closeTip(e){if(!e.target.closest('.bar-hit')&&!e.target.closest('.tip'))document.getElementById('tip').style.display='none';}
function showTipIn(el){
var k=el.getAttribute('data-k'),data=dayData[k];if(!data)return;
var p=k.split('-');
showTip(el,p[0]+'年'+(+p[1])+'月'+(+p[2])+'日',
'<div class="tip__div"></div>'+
'<div class="tip__row"><span class="tip__lbl">碳水</span><span class="tip__val tip__val--b">'+data.c+' 千卡</span></div>'+
'<div class="tip__row"><span class="tip__lbl">脂肪</span><span class="tip__val tip__val--b">'+data.f+' 千卡</span></div>'+
'<div class="tip__row"><span class="tip__lbl">蛋白质</span><span class="tip__val tip__val--b">'+data.p+' 千卡</span></div>'+
'<div class="tip__div"></div>'+
'<div class="tip__row"><span class="tip__lbl">早餐</span><span class="tip__val">'+data.bf+' 千卡</span></div>'+
'<div class="tip__row"><span class="tip__lbl">午餐</span><span class="tip__val">'+data.l+' 千卡</span></div>'+
'<div class="tip__row"><span class="tip__lbl">晚餐</span><span class="tip__val">'+data.dn+' 千卡</span></div>');
}
function showTipEx(el){
var k=el.getAttribute('data-k'),data=dayData[k];if(!data)return;
var p=k.split('-');
showTip(el,p[0]+'年'+(+p[1])+'月'+(+p[2])+'日',
'<div class="tip__div"></div>'+
'<div class="tip__row"><span class="tip__lbl">基础代谢</span><span class="tip__val tip__val--y">'+data.ba+' 千卡</span></div>'+
'<div class="tip__row"><span class="tip__lbl">运动消耗</span><span class="tip__val tip__val--y">'+data.s+' 千卡</span></div>'+
'<div class="tip__row"><span class="tip__lbl">步行消耗</span><span class="tip__val tip__val--y">'+data.w+' 千卡</span></div>');
}
renderCal();renderBars();
</script>
</body>
</html>
+11 -1
View File
@@ -92,7 +92,7 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
<!-- 顶部导航 -->
<div class="nav-bar">
<div class="nav-back" onclick="history.back()">
<div class="nav-back" onclick="goBack()">
<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>
@@ -209,6 +209,16 @@ body { background: #f0f2f5; display: flex; justify-content: center; align-items:
</div>
</div>
<script>
/* 返回来源页面:优先根据 referrer 判断来源,否则默认目标承诺页 */
function goBack() {
var ref = document.referrer;
if (ref && ref.indexOf('goal-management.html') !== -1) {
location.href = 'goal-management.html';
} else {
location.href = 'goal-commitment.html';
}
}
var selectedActivity = null;
var mask = document.getElementById('mask');
var sheet = document.getElementById('sheet');
@@ -0,0 +1,96 @@
# 体重管理 - 变更记录
> 本文档记录「体重管理」模块原型的所有变更,按时间倒序排列。
---
## [2026-06-12] feat: 体重管理模块交互完善—多状态卡片显隐、导航链路连通、SITE_MAP 补全
**涉及文件(7个):**
- `employee-app/home/index.html`(修改)
- `employee-app/weight-management/basic-info.html`(修改)
- `employee-app/weight-management/goal-commitment.html`(修改)
- `employee-app/weight-management/goal-management.html`(修改)
- `employee-app/weight-management/weight-main.html`(新增)
- `employee-app/weight-management/balance-history.html`(新增)
- `index.html`(修改)
**变更说明:**
**employee-app/home/index.html**
- 体重管理模块标题栏和卡片添加点击跳转至 `weight-main.html`
- 功能列表"体重管理"入口链接从 `index.html` 改为 `weight-main.html`
**basic-info.html**
- 返回按钮从 `history.back()` 改为智能返回 `goBack()`:根据 referrer 判断,来自 goal-management 则跳回 goal-management,否则跳回 goal-commitment
**goal-commitment.html**
- `setGoalType()` 重构为通过 URL 参数跳转刷新页面(`?type=`),避免单页面内状态切换残留
- 新增 `applyGoalTypeState()` 函数,页面加载时根据 URL type 参数初始化 UI 状态
- 切换类型菜单项高亮逻辑从 setGoalType 移入 applyGoalTypeState
**goal-management.html**
- 7 种状态配置新增 `hideActCard``hideSelfCard``showInfoCard` 字段控制卡片显隐
- `personal` 状态:隐藏活动目标卡片、隐藏个人信息卡片
- `wait/pass/reject/signed` 状态:隐藏自我目标卡片、隐藏个人信息卡片
- 自我目标/活动目标切换时底部按钮跟随切换(确认目标↔提交审核/重新提交)
- `personal` 状态专属初始化:显示身高(175cm)、隐藏计算按钮、数据源芯片全部置灰不可操作
- 只读态(wait/pass/reject/signed):活动目标管理卡片不置灰(仅下拉不可操作),个人信息卡片显示身高、数据源芯片全部置灰
- `unlockPersonal()` 解锁时追加 personal 专属逻辑:显示活动卡片、点亮计算按钮和数据源芯片
**weight-main.html(全新页面):**
- 体重管理主入口页,包含:顶部英雄区(活动天数/排名)、当前/目标/初始体重三态卡片、吃动平衡卡(摄入/消耗同心圆+热量差+查看历史按钮)、体重记录卡(三个Tab统计卡片+体重折线图)、排行榜/建议入口、推荐视频模块
**balance-history.html(全新页面):**
- 吃动平衡历史记录页:月视图日历(同心圆热量显示)、柱状图区域(吃动趋势)、交互点击查看营养明细/消耗明细
**index.html**
- 体重管理模块 entry 从 `goal-commitment.html` 改为 `weight-main.html`
- SITE_MAP 新增 `weight-main.html``promise-signing.html``my-promise.html``balance-history.html` 条目
---
## [2026-06-11] feat: 吃动平衡历史页面 + 排行榜/推荐视频/建议入口模块
**涉及文件(2个):**
- `weight-main.html`(修改)
- `balance-history.html`(新增)
**变更说明:**
**weight-main.html**
- **排行榜 & 建议入口**:新增于体重记录卡片下方,左右布局
- 左侧排行榜:活动标题、我的排名(居中"减重排名 第X名 XXX分")、列表头(排名/用户/减重分)、前4名列表(1-3名星形奖牌、4-5名数字、用户"名·**"+部门、减重分右对齐)
- 右侧三个建议卡片:膳食建议(1950千卡)、运动建议(12000步)、体重报告("健康,从今天开始"),`min-height: 72px` 与左侧对齐
- **推荐视频模块**:排行榜下方全宽板块,标题"推荐视频",2列网格布局
- 每个卡片:彩色渐变预览图+播放按钮+播放次数、作者(预览图左下角)、标题(单行省略号)、所属机构
- **吃动平衡"查看历史"按钮**:添加跳转至 `balance-history.html`
**balance-history.html(全新页面):**
- 标题"历史记录",返回 `weight-main.html`
- **月视图日历**:顶部左右箭头切换上下月,每个日期下方显示同心圆(外圈摄入/内圈消耗)+ 推荐热量差/实际热量差
- **柱状图区域**:本月吃动趋势,每天两根柱状图(摄入=蓝色、消耗=黄色)
- **交互**:点击日历日期→对应天摄入柱高亮+tooltip(日期/碳水/脂肪/蛋白质/早中晚摄入);点击柱状图条目→摄入显示营养明细,消耗显示基础代谢/运动/步行消耗
---
## [2026-06-11] feat: 体重记录卡片—统计卡片+体重变化折线图
**涉及文件(1个修改):**
- `weight-main.html`
**变更说明:**
- **体重记录卡片**:新增于吃动平衡卡片下方,包含标题"体重记录"和右上角"查看历史"按钮
- **三个 Tab**:体重变化(默认选中)、体脂变化、腰围变化
- **体重变化 Tab**
- 统计卡片行:平均体重(68.2kg)、最大体重(73.5kg)、最小体重(62.6kg)、体重变化(-10.9kg)
- 折线图(参考用户图片样式):
- 红色虚线:初始体重线(73.50kg)
- 青绿色虚线:目标体重线(68.00kg)
- 浅青绿色背景区域:推荐范围(65~68kg)
- 实线+圆点:实际每日体重折线趋势
- 底部图例标注三种参考线含义
- **体脂变化 Tab**:统计卡片(平均体脂/最大体脂/最小体脂/体脂变化)+ 占位提示
- **腰围变化 Tab**:统计卡片(平均腰围/最大腰围/最小腰围/腰围变化)+ 占位提示
---
@@ -314,7 +314,13 @@ var INTRO_VALUES = {
curWeight:'90kg', tgtWeight:'77kg', curFat:'28%', tgtFat:'25%', curWaist:'100cm', tgtWaist:'86cm', loseWeight:'13kg', days:'90'
};
/* 右上角菜单切换类型 → 跳转刷新页面,URL 同步更新 */
function setGoalType(type) {
location.href = 'goal-commitment.html?type=' + type;
}
/* 页面加载后根据 type 参数初始化 UI 状态 */
function applyGoalTypeState(type) {
var cfg = STATUS_MAP[type];
if (!cfg) return;
document.getElementById('cardActivity').style.display = cfg.activity ? 'block' : 'none';
@@ -368,10 +374,10 @@ function setGoalType(type) {
document.getElementById('introLoseWeight').textContent = '--kg';
document.getElementById('introDays').textContent = '--';
}
// 高亮当前类型菜单项
document.querySelectorAll('.nav-menu__item').forEach(function(el){
el.classList.toggle('active', el.dataset.type === type);
});
document.getElementById('typeMenu').classList.remove('show');
}
// 优先读取 URL 参数中的状态,否则默认未选目标-无活动
@@ -384,7 +390,7 @@ function getQueryParam(key) {
return '';
}
var defaultType = getQueryParam('type') || 'unset-hasact';
setGoalType(defaultType);
applyGoalTypeState(defaultType);
document.getElementById('goalMgmtLink').href = 'goal-management.html?type=' + defaultType;
function openReasonModal() {
@@ -529,11 +529,11 @@ function getQueryParam(key) {
var STATE_CONFIG = {
'unset-noact': { mode:'activity', hideDetail:true, hideSelect:false, readOnly:false, selfLock:false, btnMode:'normal', status:'' },
'unset-hasact':{ mode:'activity', hideDetail:false, hideSelect:false, readOnly:false, selfLock:false, btnMode:'normal', status:'' },
'personal': { mode:'self', hideDetail:true, hideSelect:true, readOnly:false, selfLock:true, btnMode:'modify', status:'' },
'wait': { mode:'activity', hideDetail:false, hideSelect:false, readOnly:true, selfLock:false, btnMode:'none', status:'<span class="gm-status-bar--wait">管理员审核中,请耐心等待...</span>' },
'pass': { mode:'activity', hideDetail:false, hideSelect:false, readOnly:true, selfLock:false, btnMode:'none', status:'<span class="gm-status-bar--pass">管理员已审核通过,请尽快签订承诺书参加活动</span>' },
'reject': { mode:'activity', hideDetail:false, hideSelect:false, readOnly:false, selfLock:false, btnMode:'resubmit', status:'<span class="gm-status-bar--reject">管理员审核驳回,<a class="gm-status-bar__link" onclick="openRejectModal()">查看原因</a>,请重新调整目标</span>' },
'signed': { mode:'activity', hideDetail:false, hideSelect:false, readOnly:true, selfLock:false, btnMode:'none', status:'<span class="gm-status-bar--signed">已签署承诺书并参与活动</span>' }
'personal': { mode:'self', hideDetail:true, hideSelect:false, hideActCard:true, showInfoCard:false, readOnly:false, selfLock:true, btnMode:'modify', status:'' },
'wait': { mode:'activity', hideDetail:false, hideSelect:false, hideSelfCard:true, showInfoCard:false, readOnly:true, selfLock:false, btnMode:'none', status:'<span class="gm-status-bar--wait">管理员审核中,请耐心等待...</span>' },
'pass': { mode:'activity', hideDetail:false, hideSelect:false, hideSelfCard:true, showInfoCard:false, readOnly:true, selfLock:false, btnMode:'none', status:'<span class="gm-status-bar--pass">管理员已审核通过,请尽快签订承诺书参加活动</span>' },
'reject': { mode:'activity', hideDetail:false, hideSelect:false, showInfoCard:false, hideSelfCard:true, readOnly:false, selfLock:false, btnMode:'resubmit', status:'<span class="gm-status-bar--reject">管理员审核驳回,<a class="gm-status-bar__link" onclick="openRejectModal()">查看原因</a>,请重新调整目标</span>' },
'signed': { mode:'activity', hideDetail:false, hideSelect:false, showInfoCard:false, hideSelfCard:true, readOnly:true, selfLock:false, btnMode:'none', status:'<span class="gm-status-bar--signed">已签署承诺书并参与活动</span>' }
};
var currentState = getQueryParam('type') || 'unset-hasact';
var currentCfg = STATE_CONFIG[currentState] || STATE_CONFIG['unset-hasact'];
@@ -556,6 +556,13 @@ function selectMode(mode) {
// 切换天数显示
document.getElementById('actDays').style.display = isActivity ? 'inline' : 'none';
document.getElementById('selfDays').style.display = isActivity ? 'none' : 'inline-flex';
/* 切换卡片时底部按钮跟随切换:活动目标→提交审核/重新提交,自我目标→确认目标 */
if ((currentState === 'unset-noact' || currentState === 'unset-hasact' || currentState === 'personal' || currentState === 'reject') && !personalLocked) {
document.getElementById('btnSubmitReview').style.display = isActivity ? 'block' : 'none';
document.getElementById('btnConfirmGoal').style.display = isActivity ? 'none' : 'block';
document.getElementById('btnResubmit').style.display = 'none';
}
}
/* —— 初始化页面状态 —— */
@@ -568,13 +575,25 @@ function initState() {
detailLink.style.display = cfg.hideDetail ? 'none' : 'inline';
var actSelect = document.getElementById('actSelect');
actSelect.style.display = cfg.hideSelect ? 'none' : 'flex';
if (currentState === 'unset-noact') {
if (currentState === 'unset-noact' || currentState === 'personal') {
document.getElementById('actSelectText').textContent = '请选择您要参加的活动';
actSelect.classList.remove('gm-select--filled');
} else {
var act = ACTIVITIES.find(function(a){ return a.id === selectedActivityId; });
document.getElementById('actSelectText').textContent = act ? act.name : '请选择您要参加的活动';
}
// 隐藏活动目标管理卡片(个人目标初始态)
if (cfg.hideActCard) {
document.getElementById('cardActivity').style.display = 'none';
}
// 隐藏自我目标管理卡片(审核中/已通过/已签署等状态)
if (cfg.hideSelfCard) {
document.getElementById('cardSelf').style.display = 'none';
}
// 隐藏个人信息卡片(personal/wait/pass/reject/signed
if (cfg.showInfoCard === false) {
document.querySelectorAll('.gm-card')[2].style.display = 'none';
}
// 自我目标管理 - 锁定态
if (cfg.selfLock) {
@@ -594,6 +613,20 @@ function initState() {
document.getElementById('btnConfirmGoal').style.display = 'none';
document.getElementById('btnSubmitReview').style.display = 'none';
document.getElementById('btnModifyRow').style.display = 'flex';
/* personal 专属初始化:隐藏活动卡片、显示身高、隐藏计算按钮、数据源置灰 */
if (currentState === 'personal') {
// 个人信息卡片显示身高数据
document.querySelectorAll('.gm-info-cell')[0].querySelector('.gm-info-cell__value').textContent = '175 cm';
// 隐藏"开始计算"按钮
document.querySelector('.gm-cta').style.display = 'none';
// 初始数据录入卡片数据源按钮全部置灰
document.querySelectorAll('.gm-chip').forEach(function(c) {
c.classList.remove('active');
c.style.pointerEvents = 'none';
c.style.opacity = '0.5';
});
}
} else {
personalLocked = false;
document.getElementById('btnModifyRow').style.display = 'none';
@@ -601,15 +634,20 @@ function initState() {
// 只读态(审核中/已通过/已签署)
if (cfg.readOnly && !cfg.selfLock) {
document.getElementById('cardActivity').classList.add('gm-card--disabled');
document.getElementById('cardActivity').classList.remove('gm-card--selectable');
/* 活动目标管理卡片不置灰,仅下拉选择不可操作(固定显示当前活动) */
document.getElementById('cardSelf').classList.add('gm-card--disabled');
document.getElementById('cardSelf').classList.remove('gm-card--selectable');
document.querySelectorAll('.gm-data-row').forEach(function(r){ r.classList.add('gm-locked'); });
document.querySelectorAll('.gm-target-row__edit').forEach(function(e){ e.style.pointerEvents = 'none'; e.style.color = '#c8d0d8'; });
document.querySelector('.gm-cta').style.pointerEvents = 'none';
document.querySelector('.gm-cta').style.background = '#c8d0d8';
document.querySelector('.gm-cta').style.display = 'none';
document.getElementById('actSelect').style.pointerEvents = 'none';
/* 只读态:个人信息卡片显示身高、数据源按钮全部置灰 */
document.querySelectorAll('.gm-info-cell')[0].querySelector('.gm-info-cell__value').textContent = '175 cm';
document.querySelectorAll('.gm-chip').forEach(function(c) {
c.classList.remove('active');
c.style.pointerEvents = 'none';
c.style.opacity = '0.5';
});
}
// 底部按钮和状态栏
@@ -679,6 +717,18 @@ function unlockPersonal() {
document.querySelectorAll('.gm-target-row__edit').forEach(function(e){ e.style.pointerEvents = 'auto'; e.style.color = '#36cfc9'; });
document.querySelector('.gm-cta').style.pointerEvents = 'auto';
document.querySelector('.gm-cta').style.background = 'linear-gradient(135deg, #36cfc9, #1890ff)';
/* personal 专属解锁:展示活动卡片、点亮计算按钮和数据源 */
if (currentState === 'personal') {
var actCard = document.getElementById('cardActivity');
actCard.style.display = 'block';
actCard.classList.remove('gm-card--disabled');
document.querySelector('.gm-cta').style.display = 'block';
document.querySelectorAll('.gm-chip').forEach(function(c) {
c.style.pointerEvents = 'auto';
c.style.opacity = '1';
});
}
}
document.querySelectorAll('.gm-data-row').forEach(function(row) {
File diff suppressed because it is too large Load Diff
+6 -2
View File
@@ -563,13 +563,17 @@ const SITE_MAP = [
{ file:'report-query.html', label:'报告查询详情' },
{ file:'project-detail.html', label:'体检项目详情' }
] },
{ name:'体重管理', dir:'employee-app/weight-management', entry:'goal-commitment.html', pages:[
{ name:'体重管理', dir:'employee-app/weight-management', entry:'weight-main.html', pages:[
{ file:'weight-main.html', label:'体重管理主页' },
{ file:'goal-commitment.html', label:'目标承诺' },
{ file:'basic-info.html', label:'基本信息' },
{ file:'indicators-diseases.html', label:'指标慢病' },
{ file:'goal-management.html', label:'目标管理' },
{ file:'activity-detail.html', label:'活动详情' },
{ file:'contraindications.html', label:'禁忌证' }
{ file:'contraindications.html', label:'禁忌证' },
{ file:'promise-signing.html', label:'承诺签订' },
{ file:'my-promise.html', label:'我的承诺书' },
{ file:'balance-history.html', label:'吃动平衡历史' }
] },
{ name:'营养管理', dir:'employee-app/nutrition', pages:[] },
{ name:'运动管理', dir:'employee-app/exercise', pages:[] },