4742a0bb9d
删除日历 UI、bootstrap 与 /api/stats/calendar 注册;保留日/周/月统计表。内照明心档案日历不受影响。 Co-authored-by: Cursor <cursoragent@cursor.com>
263 lines
8.1 KiB
JavaScript
263 lines
8.1 KiB
JavaScript
/**
|
||
* 中控 iframe 壳:顶栏/统计常驻,tab 内容走 /api/embed/page/<tab>。
|
||
*/
|
||
(function (global) {
|
||
const TAB_PATH = {
|
||
key_monitor: "/key_monitor",
|
||
trade: "/trade",
|
||
strategy: "/strategy",
|
||
strategy_records: "/strategy/records",
|
||
records: "/records",
|
||
stats: "/stats",
|
||
};
|
||
|
||
let navToken = 0;
|
||
let loadingTab = false;
|
||
|
||
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST) */
|
||
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form"]);
|
||
|
||
function isEmbedShell() {
|
||
return document.body && document.body.getAttribute("data-embed-shell") === "1";
|
||
}
|
||
|
||
function getTab() {
|
||
try {
|
||
const t = new URLSearchParams(location.search).get("tab");
|
||
if (t) return t;
|
||
} catch (_) {}
|
||
return document.body.getAttribute("data-page") || "trade";
|
||
}
|
||
|
||
function listWindowQueryString() {
|
||
if (typeof global.listWindowQueryString === "function") {
|
||
return global.listWindowQueryString();
|
||
}
|
||
return "";
|
||
}
|
||
|
||
function setRootLoading(on) {
|
||
const root = document.getElementById("embed-page-root");
|
||
if (root) root.classList.toggle("is-embed-tab-loading", !!on);
|
||
}
|
||
|
||
function setNavActive(tab) {
|
||
document.querySelectorAll(".embed-top-nav [data-embed-tab]").forEach((a) => {
|
||
a.classList.toggle("active", a.getAttribute("data-embed-tab") === tab);
|
||
});
|
||
}
|
||
|
||
function syncUrl(tab, replace) {
|
||
const q = new URLSearchParams(location.search);
|
||
q.set("tab", tab);
|
||
q.set("embed", "1");
|
||
const qs = q.toString();
|
||
const url = "/embed?" + qs;
|
||
if (replace) history.replaceState({ embedTab: tab }, "", url);
|
||
else history.pushState({ embedTab: tab }, "", url);
|
||
}
|
||
|
||
function runPageInit(tab) {
|
||
document.body.setAttribute("data-page", tab);
|
||
if (typeof global.attachListWindowToExports === "function") {
|
||
global.attachListWindowToExports();
|
||
}
|
||
if (tab === "trade") {
|
||
if (typeof global.refreshOrderDefaults === "function") global.refreshOrderDefaults();
|
||
if (global.ManualOrderRrPreview && typeof global.ManualOrderRrPreview.wire === "function") {
|
||
global.ManualOrderRrPreview.wire();
|
||
}
|
||
}
|
||
if (tab === "key_monitor" && global.KeyMonitorForm && typeof global.KeyMonitorForm.init === "function") {
|
||
global.KeyMonitorForm.init();
|
||
}
|
||
if (tab === "records") {
|
||
if (typeof global.loadJournals === "function") global.loadJournals();
|
||
if (typeof global.loadReviews === "function") global.loadReviews();
|
||
if (typeof global.toggleReviewMode === "function") global.toggleReviewMode();
|
||
}
|
||
if (tab === "stats") {
|
||
if (typeof global.initStatsSegmentFromUrl === "function") global.initStatsSegmentFromUrl();
|
||
}
|
||
if (typeof global.refreshPriceSnapshotConditional === "function") {
|
||
global.refreshPriceSnapshotConditional();
|
||
}
|
||
}
|
||
|
||
function injectFragment(html) {
|
||
const root = document.getElementById("embed-page-root");
|
||
if (!root) return;
|
||
root.innerHTML = html;
|
||
root.querySelectorAll("script").forEach((old) => {
|
||
const s = document.createElement("script");
|
||
if (old.src) s.src = old.src;
|
||
else s.textContent = old.textContent;
|
||
old.replaceWith(s);
|
||
});
|
||
}
|
||
|
||
async function loadTab(tab, opts) {
|
||
const options = opts || {};
|
||
if (!tab || loadingTab) return;
|
||
const token = ++navToken;
|
||
loadingTab = true;
|
||
setRootLoading(true);
|
||
try {
|
||
const qs = listWindowQueryString();
|
||
const url = "/api/embed/page/" + encodeURIComponent(tab) + (qs ? "?" + qs : "");
|
||
const r = await fetch(url, { credentials: "same-origin" });
|
||
if (token !== navToken) return;
|
||
const j = await r.json();
|
||
if (!j.ok || !j.html) throw new Error(j.msg || "加载失败");
|
||
injectFragment(j.html);
|
||
setNavActive(tab);
|
||
if (!options.skipUrl) syncUrl(tab, !!options.replace);
|
||
runPageInit(tab);
|
||
} catch (e) {
|
||
if (token === navToken) {
|
||
const flash = document.getElementById("embed-flash");
|
||
if (flash) {
|
||
flash.style.display = "";
|
||
flash.textContent = String(e && e.message ? e.message : e);
|
||
}
|
||
}
|
||
} finally {
|
||
if (token === navToken) {
|
||
loadingTab = false;
|
||
setRootLoading(false);
|
||
}
|
||
}
|
||
}
|
||
|
||
function reloadCurrentTab() {
|
||
return loadTab(getTab(), { replace: true, skipUrl: true });
|
||
}
|
||
|
||
function postFormAndReload(form, label) {
|
||
if (!form) return Promise.resolve();
|
||
if (global.FormSubmitGuard) {
|
||
if (global.FormSubmitGuard.isLocked(form)) {
|
||
global.FormSubmitGuard.setSubmitLabel(form, label || "提交中…");
|
||
} else {
|
||
global.FormSubmitGuard.lock(form, label || "提交中…");
|
||
}
|
||
}
|
||
const fd = new FormData(form);
|
||
return fetch(form.action, {
|
||
method: form.method || "POST",
|
||
body: fd,
|
||
credentials: "same-origin",
|
||
redirect: "manual",
|
||
})
|
||
.then(() => reloadCurrentTab())
|
||
.catch(() => reloadCurrentTab());
|
||
}
|
||
|
||
function patchApplyListWindow() {
|
||
if (typeof global.applyListWindow !== "function") return;
|
||
global.applyListWindow = function embedApplyListWindow() {
|
||
const qs = listWindowQueryString();
|
||
const tab = getTab();
|
||
const q = new URLSearchParams(qs);
|
||
q.set("tab", tab);
|
||
q.set("embed", "1");
|
||
window.location.href = "/embed?" + q.toString();
|
||
};
|
||
}
|
||
|
||
function patchHardNavigations() {
|
||
const resubmitPaths =
|
||
/^\/(del_|delete_|add_|stop_|strategy\/|trend_|roll_|cancel_|place_)/;
|
||
|
||
document.addEventListener(
|
||
"click",
|
||
(ev) => {
|
||
if (!isEmbedShell()) return;
|
||
const a = ev.target.closest("a[href]");
|
||
if (!a || ev.defaultPrevented) return;
|
||
if (a.closest(".embed-top-nav")) return;
|
||
if (a.hasAttribute("download") || a.target === "_blank") return;
|
||
const raw = a.getAttribute("href");
|
||
if (!raw || raw.startsWith("#") || raw.startsWith("javascript:")) return;
|
||
let url;
|
||
try {
|
||
url = new URL(raw, location.href);
|
||
} catch (_) {
|
||
return;
|
||
}
|
||
if (url.origin !== location.origin) return;
|
||
if (url.pathname.startsWith("/export/") || url.pathname.startsWith("/order_focus") || url.pathname.startsWith("/key_focus")) {
|
||
return;
|
||
}
|
||
if (!resubmitPaths.test(url.pathname)) return;
|
||
ev.preventDefault();
|
||
fetch(url.pathname + url.search, { credentials: "same-origin", redirect: "manual" })
|
||
.then(() => reloadCurrentTab())
|
||
.catch(() => reloadCurrentTab());
|
||
},
|
||
false
|
||
);
|
||
|
||
document.addEventListener(
|
||
"submit",
|
||
(ev) => {
|
||
if (!isEmbedShell()) return;
|
||
const form = ev.target;
|
||
if (!(form instanceof HTMLFormElement)) return;
|
||
if (form.method && form.method.toUpperCase() === "GET") return;
|
||
if (CUSTOM_SUBMIT_FORM_IDS.has(form.id)) return;
|
||
ev.preventDefault();
|
||
const fd = new FormData(form);
|
||
fetch(form.action, {
|
||
method: form.method || "POST",
|
||
body: fd,
|
||
credentials: "same-origin",
|
||
redirect: "manual",
|
||
})
|
||
.then(() => reloadCurrentTab())
|
||
.catch(() => reloadCurrentTab());
|
||
},
|
||
true
|
||
);
|
||
}
|
||
|
||
function bindNav() {
|
||
document.querySelectorAll(".embed-top-nav [data-embed-tab]").forEach((a) => {
|
||
a.addEventListener("click", (ev) => {
|
||
ev.preventDefault();
|
||
const tab = a.getAttribute("data-embed-tab");
|
||
if (!tab || tab === getTab()) return;
|
||
void loadTab(tab);
|
||
});
|
||
});
|
||
window.addEventListener("popstate", () => {
|
||
const tab = getTab();
|
||
void loadTab(tab, { replace: true, skipUrl: true });
|
||
});
|
||
}
|
||
|
||
function boot() {
|
||
if (!isEmbedShell()) return;
|
||
patchApplyListWindow();
|
||
patchHardNavigations();
|
||
bindNav();
|
||
runPageInit(getTab());
|
||
try {
|
||
window.parent.postMessage({ type: "instance-frame-ready" }, "*");
|
||
} catch (_) {}
|
||
}
|
||
|
||
global.InstanceEmbed = {
|
||
loadTab,
|
||
reloadCurrentTab,
|
||
getTab,
|
||
postFormAndReload,
|
||
};
|
||
|
||
if (document.readyState === "loading") {
|
||
document.addEventListener("DOMContentLoaded", boot);
|
||
} else {
|
||
boot();
|
||
}
|
||
})(typeof window !== "undefined" ? window : globalThis);
|