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 <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-18 16:16:34 +08:00
parent faf21da955
commit d816f04603
4 changed files with 77 additions and 23 deletions
+40 -18
View File
@@ -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"<h2>{escape_html(str(checklist.get('title') or '开仓检查清单'))}</h2>"]
parts: list[str] = []
if include_title:
parts.append(f"<h2>{escape_html(str(checklist.get('title') or '开仓检查清单'))}</h2>")
for grp in groups:
if not isinstance(grp, dict):
continue
gtitle = escape_html(str(grp.get("title") or ""))
parts.append(f"<h3>{gtitle}</h3><ul class=\"checklist\">")
for item in grp.get("items") or []:
parts.append(f"<li><span class=\"box\" aria-hidden=\"true\">☐</span> {escape_html(str(item))}</li>")
# 用 CSS 方框代替 ☐,避免部分打印机/预览吞掉 Unicode 勾选符
parts.append(
f"<li><span class=\"box\" aria-hidden=\"true\"></span>"
f"<span class=\"item-text\">{escape_html(str(item))}</span></li>"
)
parts.append("</ul>")
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 """<script>
(function () {
function done() {
try { window.close(); } catch (e) {}
function go() {
try { window.focus(); } catch (e) {}
window.setTimeout(function () {
try { window.print(); } catch (e) {}
}, 250);
}
window.addEventListener("afterprint", done, { once: true });
window.addEventListener("load", function () {
window.setTimeout(function () { window.print(); }, 120);
window.setTimeout(done, 60000);
});
if (document.readyState === "complete") go();
else window.addEventListener("load", go, { once: true });
})();
</script>"""
@@ -398,7 +419,7 @@ def build_print_html(exchange_key: str, part: str = "doc") -> str:
<h1>{escape_html(cl_title)}</h1>
<p class="doc-meta">{escape_html(label)} · {escape_html(version)} · 打印 {escape_html(now)}</p>
</header>
<div class="doc-body">{_checklist_html(checklist)}</div>
<div class="doc-body">{_checklist_html(checklist, include_title=False)}</div>
</article>"""
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}}}}
</style>
</head>
+1 -1
View File
@@ -1374,7 +1374,7 @@
<script src="/assets/quotes.js?v=20260717-quotes-feed"></script>
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
<script src="/assets/dashboard.js?v=20260717-dash-kpi-merge"></script>
<script src="/assets/strategy.js?v=3"></script>
<script src="/assets/strategy.js?v=4"></script>
<script src="/assets/help.js?v=1"></script>
<script src="/assets/logs.js?v=1"></script>
<script src="/assets/ai_review_render.js?v=3"></script>
+31 -2
View File
@@ -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(
"<!DOCTYPE html><title>打印准备中…</title><body style=\"font-family:sans-serif;padding:24px;color:#222\">正在加载打印内容…</body>"
);
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(
`<!DOCTYPE html><body style="font-family:sans-serif;padding:24px;color:#222">打印页加载失败: ${esc(msg)}</body>`
);
w.document.close();
} catch (_) {}
if (statusEl) statusEl.textContent = msg;
}
}
+5 -2
View File
@@ -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__":