From 778a215ad7be1db75c45259f516a342becda8b7a Mon Sep 17 00:00:00 2001 From: dekun Date: Sat, 18 Jul 2026 16:16:34 +0800 Subject: [PATCH] Fix strategy checklist print closing too early. Stop auto-closing the print tab on afterprint (short checklist pages were especially fragile), load print HTML via fetch into a blank window, and use CSS checkboxes instead of Unicode ballot boxes. Co-authored-by: Cursor --- lib/hub/hub_strategy_lib.py | 58 ++++++++++++++++++--------- manual_trading_hub/static/index.html | 2 +- manual_trading_hub/static/strategy.js | 33 ++++++++++++++- tests/test_hub_strategy_lib.py | 7 +++- 4 files changed, 77 insertions(+), 23 deletions(-) diff --git a/lib/hub/hub_strategy_lib.py b/lib/hub/hub_strategy_lib.py index eaf995a..1f37664 100644 --- a/lib/hub/hub_strategy_lib.py +++ b/lib/hub/hub_strategy_lib.py @@ -218,16 +218,22 @@ def strategy_meta_payload() -> dict[str, Any]: return {"ok": True, "exchanges": tabs} -def _checklist_html(checklist: dict[str, Any]) -> str: +def _checklist_html(checklist: dict[str, Any], *, include_title: bool = True) -> str: groups = checklist.get("groups") or [] - parts = [f"

{escape_html(str(checklist.get('title') or '开仓检查清单'))}

"] + parts: list[str] = [] + if include_title: + parts.append(f"

{escape_html(str(checklist.get('title') or '开仓检查清单'))}

") for grp in groups: if not isinstance(grp, dict): continue gtitle = escape_html(str(grp.get("title") or "")) parts.append(f"

{gtitle}

") footnotes = checklist.get("footnotes") or [] if footnotes: @@ -330,15 +336,28 @@ body { padding: 0; } .checklist li { - margin: 7px 0; + display: flex; + align-items: flex-start; + gap: 8px; + margin: 8px 0; padding: 0; } .box { + flex: 0 0 auto; display: inline-block; - width: 1.05em; - margin-right: 6px; - font-size: 1.05em; - line-height: 1.2; + width: 0.95em; + height: 0.95em; + margin-top: 0.2em; + border: 1.5px solid #333; + border-radius: 2px; + background: #fff; + box-sizing: border-box; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} +.item-text { + flex: 1 1 auto; + min-width: 0; } .footnotes { margin: 18px 0 0; @@ -364,16 +383,18 @@ body { def _print_auto_script() -> str: + # 不在 afterprint 里 window.close(): Firefox 会在打开打印框时就触发 afterprint, + # 短页(检查清单)更容易被立刻关掉,表现为「打不开/打不出来」;策略长文相对不易复现. return """""" @@ -398,7 +419,7 @@ def build_print_html(exchange_key: str, part: str = "doc") -> str:

{escape_html(cl_title)}

{escape_html(label)} · {escape_html(version)} · 打印 {escape_html(now)}

-
{_checklist_html(checklist)}
+
{_checklist_html(checklist, include_title=False)}
""" elif part == "doc": page_title = f"{label} · 策略说明" @@ -453,8 +474,9 @@ h1{{font-size:1.35rem;margin:0 0 8px}} .strategy-doc table{{border-collapse:collapse;width:100%;font-size:.88rem}} .strategy-doc th,.strategy-doc td{{border:1px solid #ccc;padding:6px 8px;text-align:left}} .checklist ul{{list-style:none;padding:0;margin:0}} -.checklist li{{margin:8px 0;padding-left:0}} -.box{{display:inline-block;width:1em;margin-right:6px}} +.checklist li{{display:flex;align-items:flex-start;gap:8px;margin:8px 0}} +.box{{flex:0 0 auto;display:inline-block;width:.95em;height:.95em;margin-top:.2em;border:1.5px solid #333;border-radius:2px;background:#fff;box-sizing:border-box}} +.item-text{{flex:1 1 auto}} @media print{{body{{margin:12mm}}}} diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 4da8fd3..c7aeac5 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -1374,7 +1374,7 @@ - + diff --git a/manual_trading_hub/static/strategy.js b/manual_trading_hub/static/strategy.js index b2e7bac..d81ce4b 100644 --- a/manual_trading_hub/static/strategy.js +++ b/manual_trading_hub/static/strategy.js @@ -142,12 +142,41 @@ renderTabs(); } - function printSection(mode) { + async function printSection(mode) { const part = mode === "checklist" ? "checklist" : "doc"; const url = `/api/strategy/${encodeURIComponent(activeKey)}/print?part=${encodeURIComponent(part)}`; - const w = window.open(url, "_blank", "noopener,noreferrer"); + // 同步打开空白页保留用户手势;再 fetch 写入,避免 noopener 丢句柄 / 短页误关窗 + const w = window.open("about:blank", "_blank"); if (!w) { if (statusEl) statusEl.textContent = "请允许弹出窗口以打开打印预览"; + return; + } + try { + w.document.write( + "打印准备中…正在加载打印内容…" + ); + w.document.close(); + } catch (_) {} + try { + const r = await fetch(url, { credentials: "same-origin" }); + const html = await r.text(); + if (!r.ok) { + throw new Error((html && html.slice(0, 120)) || r.statusText || "加载打印页失败"); + } + w.document.open(); + w.document.write(html); + w.document.close(); + if (statusEl) statusEl.textContent = ""; + } catch (e) { + const msg = String(e && e.message ? e.message : e); + try { + w.document.open(); + w.document.write( + `打印页加载失败: ${esc(msg)}` + ); + w.document.close(); + } catch (_) {} + if (statusEl) statusEl.textContent = msg; } } diff --git a/tests/test_hub_strategy_lib.py b/tests/test_hub_strategy_lib.py index 3f3ab79..426127b 100644 --- a/tests/test_hub_strategy_lib.py +++ b/tests/test_hub_strategy_lib.py @@ -33,16 +33,19 @@ class TestHubStrategyLib(unittest.TestCase): def test_export_html_contains_checklist(self): html = build_export_html("gate") self.assertIn("Gate", html) - self.assertIn("☐", html) + self.assertIn('class="box"', html) + self.assertNotIn("☐", html) def test_print_html_doc_and_checklist(self): doc = build_print_html("binance", "doc") cl = build_print_html("binance", "checklist") self.assertIn("window.print", doc) self.assertIn("window.print", cl) + self.assertNotIn("window.close", cl) self.assertIn("币安", doc) self.assertIn("开仓检查清单", cl) - self.assertIn("☐", cl) + self.assertIn('class="box"', cl) + self.assertIn("本账户仅做多", cl) if __name__ == "__main__":