/** * 策略说明:策略正文(目录 h2 + 章节标识) + 执行清单 Tab. */ (function () { const page = document.getElementById("page-strategy"); if (!page) return; const tabsEl = document.getElementById("strategy-tabs"); const viewTabsEl = document.getElementById("strategy-view-tabs"); const statusEl = document.getElementById("strategy-load-status"); const docBody = document.getElementById("strategy-doc-body"); const docSource = document.getElementById("strategy-doc-source"); const docToc = document.getElementById("strategy-doc-toc"); const panelDoc = document.getElementById("strategy-panel-doc"); const panelChecklist = document.getElementById("strategy-panel-checklist"); const checklistTitle = document.getElementById("strategy-checklist-title"); const checklistBody = document.getElementById("strategy-checklist-body"); const footnotesEl = document.getElementById("strategy-checklist-footnotes"); const btnPrintDoc = document.getElementById("strategy-btn-print-doc"); const btnPrintChecklist = document.getElementById("strategy-btn-print-checklist"); const btnPrintChecklistInline = document.getElementById("strategy-btn-print-checklist-inline"); const btnDownload = document.getElementById("strategy-btn-download"); let activeKey = "playbook_v2"; let activeView = "doc"; let tabsMeta = []; let cache = {}; let bound = false; let scrollSpyObs = null; async function apiFetch(url, opts) { const r = await fetch(url, { credentials: "same-origin", ...(opts || {}) }); const ct = (r.headers.get("content-type") || "").toLowerCase(); if (ct.includes("application/json")) { const data = await r.json(); if (!r.ok) throw new Error((data && data.msg) || r.statusText || "请求失败"); return data; } if (!r.ok) throw new Error(r.statusText || "请求失败"); return r; } function esc(s) { return String(s ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function slugify(text) { return String(text || "") .trim() .toLowerCase() .replace(/\s+/g, "-") .replace(/[^\w\u4e00-\u9fff\-]+/g, "") .replace(/\-+/g, "-") .replace(/^\-|\-$/g, "") .slice(0, 48); } function setView(view) { activeView = view === "checklist" ? "checklist" : "doc"; if (panelDoc) panelDoc.classList.toggle("hidden", activeView !== "doc"); if (panelChecklist) panelChecklist.classList.toggle("hidden", activeView !== "checklist"); if (viewTabsEl) { viewTabsEl.querySelectorAll(".strategy-view-tab").forEach((btn) => { const on = btn.getAttribute("data-view") === activeView; btn.classList.toggle("is-active", on); btn.setAttribute("aria-selected", on ? "true" : "false"); }); } } function renderExchangeTabs() { if (!tabsEl) return; if (!tabsMeta.length) { tabsEl.innerHTML = ""; if (statusEl) statusEl.textContent = "当前无可显示的策略页签(可在系统设置·显示与导航中开启)"; if (docBody) docBody.innerHTML = ""; if (docToc) docToc.innerHTML = ""; if (checklistBody) checklistBody.innerHTML = ""; return; } tabsEl.innerHTML = tabsMeta .map( (t) => `` ) .join(""); tabsEl.querySelectorAll(".strategy-tab").forEach((btn) => { btn.addEventListener("click", () => { const key = btn.getAttribute("data-key"); if (!key || key === activeKey) return; activeKey = key; renderExchangeTabs(); void loadExchange(key); }); }); } function setActiveToc(id) { if (!docToc) return; docToc.querySelectorAll(".strategy-toc-item").forEach((a) => { a.classList.toggle("is-active", a.getAttribute("data-id") === id); }); } function bindScrollSpy(heads) { if (scrollSpyObs) { scrollSpyObs.disconnect(); scrollSpyObs = null; } if (!docBody || !heads.length || typeof IntersectionObserver === "undefined") return; const visible = new Map(); scrollSpyObs = new IntersectionObserver( (entries) => { entries.forEach((en) => { if (en.isIntersecting) visible.set(en.target.id, en.intersectionRatio); else visible.delete(en.target.id); }); let bestId = ""; let bestRatio = -1; visible.forEach((ratio, id) => { if (ratio > bestRatio) { bestRatio = ratio; bestId = id; } }); if (bestId) setActiveToc(bestId); }, { root: docBody, rootMargin: "-8% 0px -70% 0px", threshold: [0, 0.25, 0.5, 1] } ); heads.forEach((h) => scrollSpyObs.observe(h)); } function sectionTag(title) { const t = String(title || ""); if (/总原则|原则/.test(t)) return "原则"; if (/账户|定位|分工/.test(t)) return "账户"; if (/开仓类型|开仓|入场|反转|顺势|波段|假破|结构|对冲|方向单/.test(t)) return "入场"; if (/周期/.test(t)) return "周期"; if (/方向/.test(t)) return "方向"; if (/纪律|出场|笔数|节奏|止损|次数/.test(t)) return "纪律"; if (/持仓|离场|强平|到期/.test(t)) return "离场"; if (/资金|杠杆|计仓|仓位|预算/.test(t)) return "仓位"; if (/系统|字段|对接/.test(t)) return "系统"; if (/修订|记录/.test(t)) return "版本"; if (/边界|关系|行情状态/.test(t)) return "边界"; if (/选择|如何/.test(t)) return "选择"; return "章节"; } function buildDocToc() { if (!docBody || !docToc) return; const heads = Array.from(docBody.querySelectorAll("h2")); if (!heads.length) { docToc.innerHTML = '
暂无目录
'; return; } const used = {}; const items = []; heads.forEach((el, idx) => { const num = String(idx + 1).padStart(2, "0"); const text = (el.textContent || "").trim(); const tag = sectionTag(text); let base = "st-" + num + "-" + slugify(text); if (!base || base === "st-" + num + "-") base = "st-" + num; let id = base; let n = 2; while (used[id] || document.getElementById(id)) { id = base + "-" + n; n += 1; } used[id] = true; el.id = id; if (!el.querySelector(".strategy-sec-mark")) { const mark = document.createElement("span"); mark.className = "strategy-sec-mark"; mark.setAttribute("aria-hidden", "true"); mark.innerHTML = `${esc(num)}` + `${esc(tag)}`; el.insertBefore(mark, el.firstChild); } items.push({ num, id, text, tag }); }); Array.from(docBody.querySelectorAll("h3")).forEach((el, idx) => { if (el.id) return; let base = "st-h3-" + (idx + 1) + "-" + slugify(el.textContent); if (!base || base.endsWith("-")) base = "st-h3-" + (idx + 1); let id = base; let n = 2; while (document.getElementById(id)) { id = base + "-" + n; n += 1; } el.id = id; }); docToc.innerHTML = items .map( (it) => `` + `${esc(it.num)}` + `${esc(it.tag)}` + `${esc(it.text)}` ) .join(""); docToc.querySelectorAll(".strategy-toc-item").forEach((a) => { a.addEventListener("click", (ev) => { ev.preventDefault(); const id = a.getAttribute("data-id"); const target = id ? document.getElementById(id) : null; if (!target || !docBody.contains(target)) return; setActiveToc(id); target.scrollIntoView({ behavior: "smooth", block: "start" }); }); }); if (items[0]) setActiveToc(items[0].id); bindScrollSpy(heads); } function renderChecklist(checklist) { const cl = checklist || {}; const title = cl.title || "开仓检查清单"; if (checklistTitle) checklistTitle.textContent = title; if (!checklistBody) return; const groups = cl.groups || []; if (!groups.length) { checklistBody.innerHTML = '暂无检查清单
'; } else { checklistBody.innerHTML = groups .map((grp) => { const items = (grp.items || []) .map((item) => `