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:
+40
-18
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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__":
|
||||
|
||||
Reference in New Issue
Block a user