| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175 |
- <!DOCTYPE html>
- <html lang="zh-CN">
- <head>
- <meta charset="UTF-8">
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
- <title>时差改造 · 实时进度监控</title>
- <script src="进度数据.js" id="dataScript"></script>
- <style>
- :root{
- --bg:#0f1419; --panel:#1a2129; --panel2:#222b35; --line:#2c3742;
- --txt:#e6edf3; --dim:#8b98a5; --done:#3fb950; --prog:#d29922;
- --pend:#58a6ff; --todo:#6e7681; --warn:#f85149; --accent:#39d3bb;
- }
- *{box-sizing:border-box;margin:0;padding:0}
- body{background:var(--bg);color:var(--txt);font-family:"Segoe UI","Microsoft YaHei",sans-serif;padding:18px;font-size:14px}
- h1{font-size:18px;font-weight:600;display:flex;align-items:center;gap:10px}
- .sub{color:var(--dim);font-size:12px;margin-top:2px}
- .top{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:12px;margin-bottom:14px}
- .heartbeat{padding:6px 12px;border-radius:20px;font-size:13px;font-weight:600;display:flex;align-items:center;gap:7px;border:1px solid var(--line)}
- .dot{width:9px;height:9px;border-radius:50%;display:inline-block}
- .blink{animation:bk 1.4s ease-in-out infinite}
- @keyframes bk{0%,100%{opacity:1}50%{opacity:.25}}
- .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-bottom:16px}
- .card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 14px}
- .card .k{color:var(--dim);font-size:12px}
- .card .v{font-size:22px;font-weight:700;margin-top:4px}
- .bar{height:10px;background:var(--panel2);border-radius:6px;overflow:hidden;margin-top:8px}
- .bar > i{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--done))}
- .ms{background:var(--panel);border:1px solid var(--line);border-radius:10px;margin-bottom:10px;overflow:hidden}
- .ms-h{padding:9px 14px;background:var(--panel2);display:flex;justify-content:space-between;align-items:center;cursor:pointer;font-weight:600}
- .ms-h .mini{color:var(--dim);font-size:12px;font-weight:400}
- table{width:100%;border-collapse:collapse}
- td{padding:7px 14px;border-top:1px solid var(--line);font-size:13px}
- td.st{width:30px;text-align:center;font-size:15px}
- td.id{width:78px;color:var(--dim);font-family:monospace}
- .env{color:var(--prog);font-size:11px;border:1px solid var(--prog);border-radius:4px;padding:0 5px;margin-left:6px}
- .s-done{color:var(--done)} .s-prog{color:var(--prog)} .s-pend{color:var(--pend)} .s-todo{color:var(--todo)}
- .row-done{opacity:.6} .row-prog{background:rgba(210,153,34,.08)}
- .sec-title{font-size:14px;font-weight:600;margin:18px 0 8px;color:var(--accent)}
- .risk-高{color:var(--warn);font-weight:600} .risk-中{color:var(--prog)} .risk-低{color:var(--dim)}
- .foot{color:var(--dim);font-size:12px;margin-top:18px;text-align:center}
- .legend{color:var(--dim);font-size:12px;display:flex;gap:14px;flex-wrap:wrap;margin-top:6px}
- </style>
- </head>
- <body>
- <div class="top">
- <div>
- <h1>🔬 时差培养箱合并改造 · 实时进度</h1>
- <div class="sub" id="phase"></div>
- <div class="legend">
- <span class="s-done">☑ 完成</span><span class="s-prog">◐ 进行中</span>
- <span class="s-pend">⚠ 代码完成待验证</span><span class="s-todo">☐ 未开始</span>
- <span class="env">环境</span>=需真机/服务验证
- </div>
- </div>
- <div class="heartbeat" id="hb"><span class="dot blink" id="hbdot"></span><span id="hbtxt">检测中…</span></div>
- </div>
- <div id="livebar" style="background:linear-gradient(90deg,#16313a,#1a2129);border:1px solid var(--accent);border-radius:10px;padding:12px 16px;margin-bottom:14px;display:flex;flex-wrap:wrap;gap:18px;align-items:center">
- <div style="flex:2;min-width:280px">
- <div style="color:var(--accent);font-size:12px;font-weight:700">🔄 正在做</div>
- <div id="liveNow" style="font-size:17px;font-weight:700;margin-top:3px;line-height:1.4">–</div>
- </div>
- <div style="flex:2;min-width:280px">
- <div style="color:var(--prog);font-size:12px;font-weight:700">⏭ 下一步</div>
- <div id="liveNext" style="font-size:15px;margin-top:3px;line-height:1.4;color:var(--txt)">–</div>
- </div>
- <div style="text-align:right;min-width:120px">
- <div id="liveAgo" style="font-size:13px;font-weight:600">–</div>
- <div style="color:var(--dim);font-size:11px;margin-top:2px">每 5 秒自动刷新</div>
- </div>
- </div>
- <div class="cards">
- <div class="card"><div class="k">总进度</div><div class="v" id="pct">–</div><div class="bar"><i id="barfill" style="width:0%"></i></div></div>
- <div class="card"><div class="k">当前任务</div><div class="v" id="cur" style="font-size:18px">–</div><div class="sub" id="curnote"></div></div>
- <div class="card"><div class="k">已完成 / 总任务</div><div class="v"><span id="cdone">0</span><span style="color:var(--dim);font-size:15px"> / <span id="ctotal">0</span></span></div></div>
- <div class="card"><div class="k">待验证项</div><div class="v" id="cpend" style="color:var(--pend)">0</div><div class="sub">需环境/真机验证</div></div>
- </div>
- <div id="planbox"></div>
- <div class="sec-title">📋 里程碑分解(M0–M7)</div>
- <div id="msbox"></div>
- <div class="sec-title">🧪 待验证清单(测试阶段照单验)</div>
- <div class="ms"><table id="pendtbl"></table></div>
- <div class="foot" id="foot"></div>
- <script>
- function render(){
- var D = window.PROGRESS_DATA;
- if(!D){ document.body.innerHTML="<p style='color:#f85149'>未找到 进度数据.js</p>"; return; }
- var SY={ "☑":"s-done","◐":"s-prog","⚠":"s-pend","☐":"s-todo" };
- var SW={ "☑":1,"⚠":0.9,"◐":0.5,"☐":0 }; // 进度权重
- // 计数与百分比
- var total=0, done=0, weighted=0, pend=0;
- D.milestones.forEach(function(m){ m.tasks.forEach(function(t){ total++; weighted+=(SW[t.status]||0); if(t.status==="☑")done++; }); });
- pend = (D.pending||[]).filter(function(v){return v.status==="☐";}).length;
- var pct = total? Math.round(weighted/total*100):0;
- document.getElementById("pct").textContent = pct+"%";
- document.getElementById("barfill").style.width = pct+"%";
- document.getElementById("cdone").textContent = done;
- document.getElementById("ctotal").textContent = total;
- document.getElementById("cpend").textContent = pend;
- document.getElementById("cur").textContent = D.currentTask||"–";
- document.getElementById("curnote").textContent = D.note||"";
- document.getElementById("phase").textContent = (D.phase||"") ;
- // 计划任务条
- if(D.planTasks){
- var ph='<div class="ms"><div class="ms-h">本批执行计划 <span class="mini">Task 1–9</span></div><table>';
- D.planTasks.forEach(function(t){
- ph+='<tr><td class="st '+SY[t.status]+'">'+t.status+'</td><td class="id">'+t.id+'</td><td>'+t.name+'</td></tr>';
- });
- document.getElementById("planbox").innerHTML = ph+'</table></div>';
- }
- // 里程碑
- var html="";
- D.milestones.forEach(function(m){
- var md=0,mt=m.tasks.length;
- m.tasks.forEach(function(t){ if(t.status==="☑")md++; });
- html+='<div class="ms"><div class="ms-h">'+m.id+' · '+m.name+' <span class="mini">'+md+'/'+mt+'</span></div><table>';
- m.tasks.forEach(function(t){
- var rc = t.status==="☑"?"row-done":(t.status==="◐"?"row-prog":"");
- html+='<tr class="'+rc+'"><td class="st '+SY[t.status]+'">'+t.status+'</td><td class="id">'+t.id+'</td><td>'+t.name+(t.env?'<span class="env">环境</span>':'')+'</td></tr>';
- });
- html+='</table></div>';
- });
- document.getElementById("msbox").innerHTML = html;
- // 待验证清单
- var pt='<tr style="color:var(--dim)"><td class="id">编号</td><td>关联</td><td>待验证点</td><td>依赖环境</td><td>风险</td><td class="st">状态</td></tr>';
- (D.pending||[]).forEach(function(v){
- pt+='<tr><td class="id">'+v.id+'</td><td class="id">'+v.task+'</td><td>'+v.point+'</td><td style="color:var(--dim)">'+v.env+'</td><td class="risk-'+v.risk+'">'+v.risk+'</td><td class="st '+SY[v.status]+'">'+v.status+'</td></tr>';
- });
- document.getElementById("pendtbl").innerHTML = pt;
- // 停滞/心跳检测:比较 generatedAt 与当前时间
- function fmt(ms){ var m=Math.floor(ms/60000); if(m<1)return"刚刚"; if(m<60)return m+" 分钟前"; var h=Math.floor(m/60); return h+" 小时"+(m%60)+" 分钟前"; }
- var gen = new Date((D.generatedAt||"").replace(" ","T"));
- var diff = Date.now()-gen.getTime();
- var hb=document.getElementById("hb"), dot=document.getElementById("hbdot"), txt=document.getElementById("hbtxt");
- var col,label;
- if(isNaN(diff)){ col="#6e7681"; label="时间未知"; dot.classList.remove("blink"); }
- else if(diff < 4*60000){ col="#3fb950"; label="运行中 · "+fmt(diff); }
- else if(diff < 12*60000){ col="#d29922"; label="可能暂停/等待输入 · "+fmt(diff); }
- else { col="#f85149"; label="疑似停滞 · "+fmt(diff)+"(建议发送“继续”)"; dot.classList.remove("blink"); }
- dot.style.background=col; txt.textContent=label; hb.style.borderColor=col; txt.style.color=col;
- document.getElementById("foot").textContent = "数据生成于 "+(D.generatedAt||"?")+" · 本页每 5 秒自动刷新 · "+(D.project||"");
- // 实时状态条:正在做 / 下一步 / 距上次更新
- var nowEl=document.getElementById("liveNow"), nextEl=document.getElementById("liveNext"), agoEl=document.getElementById("liveAgo");
- if(nowEl) nowEl.textContent = D.currentStep || D.currentTask || "–";
- if(nextEl) nextEl.textContent = D.nextStep || "–";
- if(agoEl){ agoEl.textContent = isNaN(diff)? "更新时间未知" : ("更新于 "+fmt(diff)); agoEl.style.color = col; }
- }
- // 免缓存重载 进度数据.js 后局部重渲染(无整页闪烁;file:// 下回退为不带 query 重读)
- function reloadData(){
- var old=document.getElementById("dataScript");
- var s=document.createElement("script"); s.id="dataScript";
- s.src="进度数据.js?_="+Date.now();
- s.onload=function(){ if(old&&old.parentNode)old.parentNode.removeChild(old); try{render();}catch(e){} };
- s.onerror=function(){ var s2=document.createElement("script"); s2.id="dataScript"; s2.src="进度数据.js"; s2.onload=function(){ if(old&&old.parentNode)old.parentNode.removeChild(old); try{render();}catch(e){} }; document.body.appendChild(s2); };
- document.body.appendChild(s);
- }
- render();
- setInterval(reloadData, 5000);
- </script>
- </body>
- </html>
|