From 62b015337aa72ec8afba97b69e7517d078caa0f8 Mon Sep 17 00:00:00 2001 From: ylytdeng Date: Sun, 17 May 2026 17:50:08 +0800 Subject: [PATCH] =?UTF-8?q?refactor(monitor=5Fweb):=20Web=20UI=20=E5=B7=A5?= =?UTF-8?q?=E5=85=B7=E7=AE=B1=E6=8C=89=E4=BA=A7=E5=93=81=E5=88=86=20tab,?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E6=B7=B7=E6=9D=82=E4=B8=AA=E4=BA=BA/?= =?UTF-8?q?=E4=BC=81=E5=BE=AE/=E5=B7=A5=E5=85=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## 反馈 用户实测后说"产品没设计好,企微和个人微信应该分开"。原版把 8 个按钮平 铺成两行,虽有"个人微信:" / "朋友圈/企微:" label 但只是文字注释,视觉 分组弱,工作流顺序也不明确 (新用户不知道该先点哪个再点哪个)。 ## 重设计 (3 个 tab + 工作流 Step 分组) ``` [📱 个人微信] [🏢 企业微信] [🔧 工具] <- tab 切换 📱 个人微信 (active) ⚠️ 前置: 微信 PC 版正在运行且已登录 ───────────────────────────────────────── STEP 1 — 解密 [① 提取密钥 + 解密数据库] (primary) [② 提取图片密钥] STEP 2 — 导出/解码 (可独立运行,需先 Step 1) [③ 导出全部聊天 (JSON)] [④ 批量解密 .dat 图片] [⑤ 朋友圈解密 + 导出] [实时日志区] ``` ``` 🏢 企业微信 ⚠️ 前置: 企业微信 PC 版正在运行且已登录 (独立于个人微信) ───────────────────────────────────────── STEP 1 — 解密 [① 提取密钥 + 解密数据库] (primary) STEP 2 — 导出 [② 导出聊天 (CSV/HTML/JSON)] [实时日志区] ``` ``` 🔧 工具 💡 跟微信/企微进程无关,只读已解密产物 ───────────────────────────────────────── 语音 / 转码 [语音转 MP3 (需 ffmpeg in PATH)] [实时日志区] ``` ## 改动细节 ### CSS - 加 `.tool-tabs` / `.tool-tab` / `.tool-pane` (tab 头 + 内容切换) - 加 `.tool-prereq` (黄色边框前置条件提示) - 加 `.tool-step` + `.tool-step-label` (大写小灰字步骤标签) - 加 `.primary` 给 Step 1 主操作按钮更显眼的边框 ### JS - `switchToolTab(name)` 切换 tab,记录到 `window.__activeToolPane` - `runTool` 把日志写到当前激活 pane 的 `toolLog_` 而不是单一全局 - SSE `tool_log` 监听同样按 `__activeToolPane` dispatch 到对应日志框 - DOMContentLoaded 时同时绑 tab click + 任务 button click ### Backend - TOOL_TASKS 不动 (still 8 个),只是前端重新组织 - 一次只跑一个任务的约束 (_tool_lock) 不变 ## 优势 - 工作流明确: 新用户进来直接看到"前置 + Step 1 → Step 2" - 视觉分组: tab 切换比平铺一目了然 - 跟现有 SSE / settings panel 不冲突 - 浏览器 hard refresh 即可看到新版 Tests: 185/185 通过 --- monitor_web.py | 114 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 88 insertions(+), 26 deletions(-) diff --git a/monitor_web.py b/monitor_web.py index 638319c..bb9566f 100644 --- a/monitor_web.py +++ b/monitor_web.py @@ -1750,15 +1750,27 @@ a.msg-link{text-decoration:none;color:inherit} /* 工具面板 (Web 版替代 tkinter app_gui.py) */ .tools-btn{background:none;border:1px solid rgba(255,255,255,.15);color:#888;font-size:13px;cursor:pointer;padding:4px 10px;border-radius:6px;transition:all .2s;margin-left:6px} .tools-btn:hover{color:#4fc3f7;border-color:rgba(79,195,247,.4)} -#toolsPanel{display:none;background:#0f0f17;border-top:1px solid rgba(255,255,255,.08);padding:14px 24px;flex-shrink:0;max-height:50vh;overflow:auto} -#toolsPanel.show{display:flex;flex-direction:column;gap:10px} -.tools-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center} -.tool-task-btn{background:linear-gradient(135deg,#1e293b,#0f172a);border:1px solid rgba(79,195,247,.3);color:#bdd4ff;padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;transition:all .2s} +#toolsPanel{display:none;background:#0f0f17;border-top:1px solid rgba(255,255,255,.08);padding:0;flex-shrink:0;max-height:60vh;overflow:hidden;flex-direction:column} +#toolsPanel.show{display:flex} +/* Tab 头 */ +.tool-tabs{display:flex;background:#0a0a0f;border-bottom:1px solid rgba(255,255,255,.06);padding:0 24px;gap:2px;flex-shrink:0} +.tool-tab{background:none;border:none;color:#666;font-size:13px;padding:12px 20px;cursor:pointer;border-bottom:2px solid transparent;transition:all .2s;font-family:inherit} +.tool-tab:hover{color:#aaa} +.tool-tab.active{color:#4fc3f7;border-bottom-color:#4fc3f7;font-weight:600} +/* Tab 内容 */ +.tool-pane{display:none;padding:16px 24px;overflow:auto;flex:1} +.tool-pane.active{display:block} +.tool-prereq{font-size:11px;color:#777;margin-bottom:10px;padding:6px 10px;background:rgba(255,170,60,.06);border-left:2px solid rgba(255,170,60,.4);border-radius:0 4px 4px 0} +.tool-step{margin-bottom:12px} +.tool-step-label{font-size:11px;color:#666;margin-bottom:6px;text-transform:uppercase;letter-spacing:.5px} +.tools-row{display:flex;flex-wrap:wrap;gap:8px;align-items:center;margin-bottom:4px} +.tool-task-btn{background:linear-gradient(135deg,#1e293b,#0f172a);border:1px solid rgba(79,195,247,.3);color:#bdd4ff;padding:8px 16px;border-radius:8px;font-size:13px;cursor:pointer;transition:all .2s;font-family:inherit} .tool-task-btn:hover:not(:disabled){background:linear-gradient(135deg,#2a3a5a,#1a2540);transform:translateY(-1px)} .tool-task-btn:disabled{opacity:.5;cursor:not-allowed} -.tool-log-wrap{background:#000;border:1px solid rgba(255,255,255,.08);border-radius:6px;padding:10px 12px;font-family:Consolas,"Courier New",monospace;font-size:12px;color:#cfd8dc;line-height:1.4;max-height:300px;overflow:auto;white-space:pre-wrap;word-break:break-all} +.tool-task-btn.primary{background:linear-gradient(135deg,#1e3a5a,#0f2540);border-color:rgba(79,195,247,.5)} +.tool-log-wrap{background:#000;border:1px solid rgba(255,255,255,.08);border-radius:6px;padding:10px 12px;font-family:Consolas,"Courier New",monospace;font-size:12px;color:#cfd8dc;line-height:1.4;max-height:240px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin-top:12px} .tool-log-wrap:empty::before{content:"点击上方按钮开始任务,日志会实时显示";color:#555;font-style:italic} -.tool-status{display:inline-block;font-size:12px;padding:3px 10px;border-radius:10px;margin-left:8px} +.tool-status{display:inline-block;font-size:12px;padding:3px 10px;border-radius:10px;margin-left:8px;vertical-align:middle} .tool-status.running{background:rgba(79,195,247,.15);color:#4fc3f7;border:1px solid rgba(79,195,247,.3)} .tool-status.ok{background:rgba(76,175,80,.15);color:#81c784;border:1px solid rgba(76,175,80,.3)} .tool-status.err{background:rgba(244,67,54,.15);color:#ef9a9a;border:1px solid rgba(244,67,54,.3)} @@ -1773,22 +1785,60 @@ a.msg-link{text-decoration:none;color:inherit}
-
-
个人微信:
- - - - +
+ + + +
-
-
朋友圈/企微:
- - - - - + +
+
⚠️ 前置:微信 PC 版正在运行且已登录
+
+
Step 1 — 解密
+
+ + +
+
+
+
Step 2 — 导出/解码(可独立运行,需先 Step 1)
+
+ + + +
+
+
+
+ +
+
⚠️ 前置:企业微信 PC 版正在运行且已登录(独立于个人微信)
+
+
Step 1 — 解密
+
+ +
+
+
+
Step 2 — 导出
+
+ +
+
+
+
+ +
+
💡 跟微信/企微进程无关,只读已解密产物
+
+
语音 / 转码
+
+ +
+
+
-
@@ -1929,13 +1979,21 @@ function toggleTools(){ const p=document.getElementById('toolsPanel'); p.classList.toggle('show'); } +window.__activeToolPane='wechat'; +function switchToolTab(name){ + window.__activeToolPane=name; + document.querySelectorAll('.tool-tab').forEach(t=>t.classList.toggle('active', t.dataset.pane===name)); + document.querySelectorAll('.tool-pane').forEach(p=>p.classList.toggle('active', p.dataset.pane===name)); +} async function runTool(task, btn){ const s=document.getElementById('toolStatus'); document.querySelectorAll('.tool-task-btn').forEach(b=>b.disabled=true); - document.getElementById('toolLog').textContent=''; + // 清当前 pane 的日志框 + const L=document.getElementById('toolLog_'+window.__activeToolPane); + if(L) L.textContent=''; s.style.display='inline-block'; s.className='tool-status running'; - s.textContent='⏳ 运行中...'; + s.textContent='⏳ 运行中: '+btn.textContent.trim(); try{ const r=await fetch('/api/tool',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({task:task})}); const d=await r.json(); @@ -1950,11 +2008,14 @@ async function runTool(task, btn){ document.querySelectorAll('.tool-task-btn').forEach(b=>b.disabled=false); } } -// 工具按钮 click handler 绑定 +// 工具按钮 + tab 切换 click handler 绑定 document.addEventListener('DOMContentLoaded',()=>{ document.querySelectorAll('.tool-task-btn').forEach(b=>{ b.addEventListener('click',()=>runTool(b.dataset.task, b)); }); + document.querySelectorAll('.tool-tab').forEach(t=>{ + t.addEventListener('click',()=>switchToolTab(t.dataset.pane)); + }); }); function beep(){ try{ @@ -2079,9 +2140,10 @@ function connectSSE(){ }); es.addEventListener('tool_log', ev=>{ const d=JSON.parse(ev.data); - const L=document.getElementById('toolLog'); - L.textContent += d.line; - L.scrollTop = L.scrollHeight; + // 写到当前激活 pane 的日志框 + const pane=window.__activeToolPane||'wechat'; + const L=document.getElementById('toolLog_'+pane); + if(L){L.textContent += d.line; L.scrollTop = L.scrollHeight;} }); es.addEventListener('tool_done', ev=>{ const d=JSON.parse(ev.data);