监控面板.html 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. <!DOCTYPE html>
  2. <html lang="zh-CN">
  3. <head>
  4. <meta charset="UTF-8">
  5. <meta name="viewport" content="width=device-width, initial-scale=1.0">
  6. <title>时差改造 · 实时进度监控</title>
  7. <script src="进度数据.js" id="dataScript"></script>
  8. <style>
  9. :root{
  10. --bg:#0f1419; --panel:#1a2129; --panel2:#222b35; --line:#2c3742;
  11. --txt:#e6edf3; --dim:#8b98a5; --done:#3fb950; --prog:#d29922;
  12. --pend:#58a6ff; --todo:#6e7681; --warn:#f85149; --accent:#39d3bb;
  13. }
  14. *{box-sizing:border-box;margin:0;padding:0}
  15. body{background:var(--bg);color:var(--txt);font-family:"Segoe UI","Microsoft YaHei",sans-serif;padding:18px;font-size:14px}
  16. h1{font-size:18px;font-weight:600;display:flex;align-items:center;gap:10px}
  17. .sub{color:var(--dim);font-size:12px;margin-top:2px}
  18. .top{display:flex;justify-content:space-between;align-items:flex-start;flex-wrap:wrap;gap:12px;margin-bottom:14px}
  19. .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)}
  20. .dot{width:9px;height:9px;border-radius:50%;display:inline-block}
  21. .blink{animation:bk 1.4s ease-in-out infinite}
  22. @keyframes bk{0%,100%{opacity:1}50%{opacity:.25}}
  23. .cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(150px,1fr));gap:10px;margin-bottom:16px}
  24. .card{background:var(--panel);border:1px solid var(--line);border-radius:10px;padding:12px 14px}
  25. .card .k{color:var(--dim);font-size:12px}
  26. .card .v{font-size:22px;font-weight:700;margin-top:4px}
  27. .bar{height:10px;background:var(--panel2);border-radius:6px;overflow:hidden;margin-top:8px}
  28. .bar > i{display:block;height:100%;background:linear-gradient(90deg,var(--accent),var(--done))}
  29. .ms{background:var(--panel);border:1px solid var(--line);border-radius:10px;margin-bottom:10px;overflow:hidden}
  30. .ms-h{padding:9px 14px;background:var(--panel2);display:flex;justify-content:space-between;align-items:center;cursor:pointer;font-weight:600}
  31. .ms-h .mini{color:var(--dim);font-size:12px;font-weight:400}
  32. table{width:100%;border-collapse:collapse}
  33. td{padding:7px 14px;border-top:1px solid var(--line);font-size:13px}
  34. td.st{width:30px;text-align:center;font-size:15px}
  35. td.id{width:78px;color:var(--dim);font-family:monospace}
  36. .env{color:var(--prog);font-size:11px;border:1px solid var(--prog);border-radius:4px;padding:0 5px;margin-left:6px}
  37. .s-done{color:var(--done)} .s-prog{color:var(--prog)} .s-pend{color:var(--pend)} .s-todo{color:var(--todo)}
  38. .row-done{opacity:.6} .row-prog{background:rgba(210,153,34,.08)}
  39. .sec-title{font-size:14px;font-weight:600;margin:18px 0 8px;color:var(--accent)}
  40. .risk-高{color:var(--warn);font-weight:600} .risk-中{color:var(--prog)} .risk-低{color:var(--dim)}
  41. .foot{color:var(--dim);font-size:12px;margin-top:18px;text-align:center}
  42. .legend{color:var(--dim);font-size:12px;display:flex;gap:14px;flex-wrap:wrap;margin-top:6px}
  43. </style>
  44. </head>
  45. <body>
  46. <div class="top">
  47. <div>
  48. <h1>🔬 时差培养箱合并改造 · 实时进度</h1>
  49. <div class="sub" id="phase"></div>
  50. <div class="legend">
  51. <span class="s-done">☑ 完成</span><span class="s-prog">◐ 进行中</span>
  52. <span class="s-pend">⚠ 代码完成待验证</span><span class="s-todo">☐ 未开始</span>
  53. <span class="env">环境</span>=需真机/服务验证
  54. </div>
  55. </div>
  56. <div class="heartbeat" id="hb"><span class="dot blink" id="hbdot"></span><span id="hbtxt">检测中…</span></div>
  57. </div>
  58. <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">
  59. <div style="flex:2;min-width:280px">
  60. <div style="color:var(--accent);font-size:12px;font-weight:700">🔄 正在做</div>
  61. <div id="liveNow" style="font-size:17px;font-weight:700;margin-top:3px;line-height:1.4">–</div>
  62. </div>
  63. <div style="flex:2;min-width:280px">
  64. <div style="color:var(--prog);font-size:12px;font-weight:700">⏭ 下一步</div>
  65. <div id="liveNext" style="font-size:15px;margin-top:3px;line-height:1.4;color:var(--txt)">–</div>
  66. </div>
  67. <div style="text-align:right;min-width:120px">
  68. <div id="liveAgo" style="font-size:13px;font-weight:600">–</div>
  69. <div style="color:var(--dim);font-size:11px;margin-top:2px">每 5 秒自动刷新</div>
  70. </div>
  71. </div>
  72. <div class="cards">
  73. <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>
  74. <div class="card"><div class="k">当前任务</div><div class="v" id="cur" style="font-size:18px">–</div><div class="sub" id="curnote"></div></div>
  75. <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>
  76. <div class="card"><div class="k">待验证项</div><div class="v" id="cpend" style="color:var(--pend)">0</div><div class="sub">需环境/真机验证</div></div>
  77. </div>
  78. <div id="planbox"></div>
  79. <div class="sec-title">📋 里程碑分解(M0–M7)</div>
  80. <div id="msbox"></div>
  81. <div class="sec-title">🧪 待验证清单(测试阶段照单验)</div>
  82. <div class="ms"><table id="pendtbl"></table></div>
  83. <div class="foot" id="foot"></div>
  84. <script>
  85. function render(){
  86. var D = window.PROGRESS_DATA;
  87. if(!D){ document.body.innerHTML="<p style='color:#f85149'>未找到 进度数据.js</p>"; return; }
  88. var SY={ "☑":"s-done","◐":"s-prog","⚠":"s-pend","☐":"s-todo" };
  89. var SW={ "☑":1,"⚠":0.9,"◐":0.5,"☐":0 }; // 进度权重
  90. // 计数与百分比
  91. var total=0, done=0, weighted=0, pend=0;
  92. D.milestones.forEach(function(m){ m.tasks.forEach(function(t){ total++; weighted+=(SW[t.status]||0); if(t.status==="☑")done++; }); });
  93. pend = (D.pending||[]).filter(function(v){return v.status==="☐";}).length;
  94. var pct = total? Math.round(weighted/total*100):0;
  95. document.getElementById("pct").textContent = pct+"%";
  96. document.getElementById("barfill").style.width = pct+"%";
  97. document.getElementById("cdone").textContent = done;
  98. document.getElementById("ctotal").textContent = total;
  99. document.getElementById("cpend").textContent = pend;
  100. document.getElementById("cur").textContent = D.currentTask||"–";
  101. document.getElementById("curnote").textContent = D.note||"";
  102. document.getElementById("phase").textContent = (D.phase||"") ;
  103. // 计划任务条
  104. if(D.planTasks){
  105. var ph='<div class="ms"><div class="ms-h">本批执行计划 <span class="mini">Task 1–9</span></div><table>';
  106. D.planTasks.forEach(function(t){
  107. ph+='<tr><td class="st '+SY[t.status]+'">'+t.status+'</td><td class="id">'+t.id+'</td><td>'+t.name+'</td></tr>';
  108. });
  109. document.getElementById("planbox").innerHTML = ph+'</table></div>';
  110. }
  111. // 里程碑
  112. var html="";
  113. D.milestones.forEach(function(m){
  114. var md=0,mt=m.tasks.length;
  115. m.tasks.forEach(function(t){ if(t.status==="☑")md++; });
  116. html+='<div class="ms"><div class="ms-h">'+m.id+' · '+m.name+' <span class="mini">'+md+'/'+mt+'</span></div><table>';
  117. m.tasks.forEach(function(t){
  118. var rc = t.status==="☑"?"row-done":(t.status==="◐"?"row-prog":"");
  119. 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>';
  120. });
  121. html+='</table></div>';
  122. });
  123. document.getElementById("msbox").innerHTML = html;
  124. // 待验证清单
  125. var pt='<tr style="color:var(--dim)"><td class="id">编号</td><td>关联</td><td>待验证点</td><td>依赖环境</td><td>风险</td><td class="st">状态</td></tr>';
  126. (D.pending||[]).forEach(function(v){
  127. 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>';
  128. });
  129. document.getElementById("pendtbl").innerHTML = pt;
  130. // 停滞/心跳检测:比较 generatedAt 与当前时间
  131. 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)+" 分钟前"; }
  132. var gen = new Date((D.generatedAt||"").replace(" ","T"));
  133. var diff = Date.now()-gen.getTime();
  134. var hb=document.getElementById("hb"), dot=document.getElementById("hbdot"), txt=document.getElementById("hbtxt");
  135. var col,label;
  136. if(isNaN(diff)){ col="#6e7681"; label="时间未知"; dot.classList.remove("blink"); }
  137. else if(diff < 4*60000){ col="#3fb950"; label="运行中 · "+fmt(diff); }
  138. else if(diff < 12*60000){ col="#d29922"; label="可能暂停/等待输入 · "+fmt(diff); }
  139. else { col="#f85149"; label="疑似停滞 · "+fmt(diff)+"(建议发送“继续”)"; dot.classList.remove("blink"); }
  140. dot.style.background=col; txt.textContent=label; hb.style.borderColor=col; txt.style.color=col;
  141. document.getElementById("foot").textContent = "数据生成于 "+(D.generatedAt||"?")+" · 本页每 5 秒自动刷新 · "+(D.project||"");
  142. // 实时状态条:正在做 / 下一步 / 距上次更新
  143. var nowEl=document.getElementById("liveNow"), nextEl=document.getElementById("liveNext"), agoEl=document.getElementById("liveAgo");
  144. if(nowEl) nowEl.textContent = D.currentStep || D.currentTask || "–";
  145. if(nextEl) nextEl.textContent = D.nextStep || "–";
  146. if(agoEl){ agoEl.textContent = isNaN(diff)? "更新时间未知" : ("更新于 "+fmt(diff)); agoEl.style.color = col; }
  147. }
  148. // 免缓存重载 进度数据.js 后局部重渲染(无整页闪烁;file:// 下回退为不带 query 重读)
  149. function reloadData(){
  150. var old=document.getElementById("dataScript");
  151. var s=document.createElement("script"); s.id="dataScript";
  152. s.src="进度数据.js?_="+Date.now();
  153. s.onload=function(){ if(old&&old.parentNode)old.parentNode.removeChild(old); try{render();}catch(e){} };
  154. 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); };
  155. document.body.appendChild(s);
  156. }
  157. render();
  158. setInterval(reloadData, 5000);
  159. </script>
  160. </body>
  161. </html>