# -*- coding: utf-8 -*- """中转管理端加"批量删除"(重写版)。 上一版的坑: 1. JS 里的换行我写成 \\n,但 relay.py 里是 f-string, \\n 会被还原成真换行 -> JS 字符串跨行 -> 语法错误。 正确写法是补丁里用 \\\\n,这样 relay.py 源码里是 \\n, f-string 渲染后 JS 拿到的是 反斜杠+n 两个字符。 2. rows.append 的锚点带太多上下文,容易被空格差异搞挂。 换成最小锚点。 """ from __future__ import annotations import ast import sys from pathlib import Path RELAY = Path(r"C:\Users\shu\Desktop\deepseekagent\LanRemote\relay" r"\relay.py") # ---------------------------------------------------------------- 后端 BACKEND_ANCHOR = ''' if path in ("/v1/admin/delete", "/v1/admin/delete_all"):''' BACKEND_NEW = ''' if path == "/v1/admin/delete_many": # 批量删除:勾几台删几台。 # 和"删除全部"的区别:不动没勾的,也不清孤儿绑定。 body = self._json_body() wanted = body.get("deviceIds") or [] if not isinstance(wanted, list): self._json(400, {"ok": False, "error": "deviceIds 要是数组"}) return wanted = [str(item).strip() for item in wanted if str(item).strip()] if not wanted: self._json(400, {"ok": False, "error": "没有勾选任何设备"}) return results = [RELAY.delete_device(device_id=item) for item in wanted] done = [r for r in results if r.get("ok")] failed = [r for r in results if not r.get("ok")] log(f"管理操作:批量删除 {len(done)}/{len(wanted)} 台设备") self._json(200, { "ok": True, "requested": len(wanted), "deleted": len(done), "failed": len(failed), "devices": done, "errors": failed[:10], }) return ''' + BACKEND_ANCHOR # ---------------------------------------------------------------- 行里加复选框(最小锚点) ROW_ANCHOR = ''' rows.append( "" ''' ROW_NEW = ''' rows.append( "" # 批量删除用的复选框。value 放 deviceId。 "" f'' "" ''' # ---------------------------------------------------------------- 表头全选 HEADER_ANCHOR = "" HEADER_NEW = ("
设备
' "") # ---------------------------------------------------------------- 顶部按钮 BAR_ANCHOR = " " BAR_NEW = (" \n" " ") # ---------------------------------------------------------------- 样式 CSS_ANCHOR = (" button.del:hover{{background:#5a1c22;border-color:#a33;" "color:#ffa198}}") CSS_NEW = CSS_ANCHOR + """ button.del:disabled{{opacity:.45;cursor:not-allowed}} .pickcell{{width:34px;text-align:center;padding-left:10px;padding-right:0}} input[type=checkbox]{{width:15px;height:15px;cursor:pointer;accent-color:#2f81f7}} tr.picked td{{background:#1f2733}}""" # ---------------------------------------------------------------- 前端 JS # 注意:这里写 \\\\n,落到 relay.py 里是 \\n, # 再经 f-string 渲染,JS 拿到的才是 反斜杠+n。 JS_ANCHOR = '''document.querySelectorAll('button.del[data-device]').forEach(b => {{ b.onclick = () => del(b.dataset.device, b.dataset.name, b.dataset.code); }});''' JS_NEW = JS_ANCHOR + """ // ---------------- 批量删除 ---------------- const picks = () => Array.from(document.querySelectorAll('input.pick')); const picked = () => picks().filter(c => c.checked).map(c => c.value); function refreshPickState() {{ const chosen = picked().length; const btn = document.getElementById('delPicked'); if (btn) {{ btn.disabled = chosen === 0; btn.textContent = chosen ? ('删除选中 (' + chosen + ')') : '删除选中'; }} picks().forEach(c => {{ const tr = c.closest('tr'); if (tr) tr.classList.toggle('picked', c.checked); }}); const all = document.getElementById('pickAll'); if (all) {{ const total = picks().length; all.checked = total > 0 && chosen === total; all.indeterminate = chosen > 0 && chosen < total; }} }} picks().forEach(c => c.addEventListener('change', refreshPickState)); const pickAll = document.getElementById('pickAll'); if (pickAll) pickAll.addEventListener('change', () => {{ picks().forEach(c => {{ c.checked = pickAll.checked; }}); refreshPickState(); }}); refreshPickState(); const delPicked = document.getElementById('delPicked'); if (delPicked) delPicked.onclick = async () => {{ const ids = picked(); if (!ids.length) return; if (!confirm('删除选中的 ' + ids.length + ' 台设备?\\\\n\\\\n' + '会断开它们现在的手表连接,并清掉它们占的配对码。')) return; delPicked.disabled = true; try {{ const r = await fetch('/v1/admin/delete_many', {{ method: 'POST', headers: {{'Content-Type': 'application/json'}}, body: JSON.stringify({{deviceIds: ids}}) }}); const j = await r.json(); if (j.ok) {{ alert('已删除 ' + j.deleted + ' 台设备' + (j.failed ? (',' + j.failed + ' 台失败') : '')); location.reload(); }} else {{ alert('删除失败:' + (j.error || '未知错误')); delPicked.disabled = false; }} }} catch (e) {{ alert('请求出错:' + e); delPicked.disabled = false; }} }};""" PAIRS = [ (BACKEND_ANCHOR, BACKEND_NEW), (ROW_ANCHOR, ROW_NEW), (HEADER_ANCHOR, HEADER_NEW), (BAR_ANCHOR, BAR_NEW), (CSS_ANCHOR, CSS_NEW), (JS_ANCHOR, JS_NEW), ] def main() -> int: text = RELAY.read_text(encoding="utf-8") if "delete_many" in text: print(" 已经加过了") return 0 ok = True for old, new in PAIRS: if old not in text: print(f" [失败] 锚点没找到 -> {old.strip()[:70]!r}") ok = False continue if text.count(old) != 1: print(f" [警告] 锚点出现 {text.count(old)} 次,只替换第一处") text = text.replace(old, new, 1) RELAY.write_text(text, encoding="utf-8", newline="\n") if ok: print(" 已加:/v1/admin/delete_many + 复选框 + 全选 + 删除选中") try: ast.parse(RELAY.read_text(encoding="utf-8")) print(" relay.py 语法 OK") except SyntaxError as exc: print(f" relay.py 语法错误: 行 {exc.lineno}: {exc.msg}") ok = False return 0 if ok else 1 if __name__ == "__main__": sys.exit(main())
" '设备