diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index 35d2164..b1bddc6 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index f115bca..0eb6b4b 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index f115bca..0eb6b4b 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index e815816..4a52c24 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -3,7 +3,7 @@ - + diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index f9974da..dd12c7f 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -1189,6 +1189,10 @@ body.market-chart-fs-open { display: none !important; } +.instance-frame-shell.is-instance-nav-loading .instance-frame { + visibility: hidden; +} + .instance-frame-toolbar { flex: 0 0 auto; display: flex; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 2e19be5..d879461 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -429,6 +429,11 @@ return j.url; } + function setInstanceFrameNavLoading(loading) { + const shell = document.getElementById("instance-frame-shell"); + if (shell) shell.classList.toggle("is-instance-nav-loading", !!loading); + } + async function openInstance(exchangeId, nextPath, opts) { const options = opts || {}; const newTab = !!options.newTab; @@ -481,7 +486,10 @@ ); instanceFrameUrl = url; const frame = document.getElementById("instance-frame"); - if (frame) frame.src = url; + if (frame) { + setInstanceFrameNavLoading(true); + frame.src = url; + } } catch (e) { showToast(String(e), true); } @@ -498,6 +506,7 @@ closeExchangeFullscreen(); instanceFrameUrl = url; if (titleEl) titleEl.textContent = title || "实例"; + setInstanceFrameNavLoading(true); frame.src = url; shell.classList.remove("hidden"); shell.setAttribute("aria-hidden", "false"); @@ -528,6 +537,7 @@ if (shell) { shell.classList.add("hidden"); shell.setAttribute("aria-hidden", "true"); + shell.classList.remove("is-instance-nav-loading"); } document.body.classList.remove("hub-instance-frame-open"); } @@ -3332,6 +3342,23 @@ const back = document.getElementById("instance-frame-back"); const refresh = document.getElementById("instance-frame-refresh"); const newTab = document.getElementById("instance-frame-newtab"); + const frame = document.getElementById("instance-frame"); + if (frame && frame.dataset.hubNavBound !== "1") { + frame.dataset.hubNavBound = "1"; + frame.addEventListener("load", () => setInstanceFrameNavLoading(false)); + } + if (!window.__hubInstanceFrameMsgBound) { + window.__hubInstanceFrameMsgBound = true; + window.addEventListener("message", (ev) => { + const d = ev.data; + if (!d || typeof d !== "object") return; + if (d.type === "instance-frame-navigating") { + setInstanceFrameNavLoading(true); + } else if (d.type === "instance-frame-ready" || d.type === "instance-theme-ready") { + setInstanceFrameNavLoading(false); + } + }); + } if (back) back.onclick = () => closeInstanceFrame(); if (refresh) refresh.onclick = () => refreshInstanceFrame(); if (newTab) { diff --git a/static/instance_theme.js b/static/instance_theme.js index 1992aac..8923243 100644 --- a/static/instance_theme.js +++ b/static/instance_theme.js @@ -329,41 +329,33 @@ } } - /** 仅中控 iframe 内:fetch + document.write 换页,避免 iframe 整页卸载白屏。单独打开实例仍走浏览器正常跳转。 */ + function notifyParentFrameNavStart() { + if (!isHubLinked()) return; + try { + window.parent.postMessage({ type: "instance-frame-navigating", theme: get() }, "*"); + } catch (_) {} + } + + function notifyParentFrameReady() { + if (!isHubLinked()) return; + try { + window.parent.postMessage({ type: "instance-frame-ready", theme: get() }, "*"); + } catch (_) {} + } + + /** 中控 iframe:顶栏用正常跳转(与单独打开实例一致),由中控 shell 遮罩盖住 iframe 加载过程。 */ function initHubEmbedInFrameNav() { if (!isHubLinked()) return; - let navToken = 0; - function isSoftNavLink(a) { if (!a || !a.getAttribute) return false; + if (a.hasAttribute("download") || a.target === "_blank") return false; return !!a.closest(".top-nav, .strategy-subnav"); } - async function navigateInFrame(href, opts) { - const token = ++navToken; - try { - const r = await fetch(href, { credentials: "same-origin" }); - if (token !== navToken) return; - if (!r.ok) { - location.href = href; - return; - } - const html = await r.text(); - if (token !== navToken) return; - let path = href; - try { - const u = new URL(href, location.href); - path = u.pathname + u.search + u.hash; - } catch (_) {} - if (opts && opts.replace) history.replaceState(null, "", path); - else history.pushState(null, "", path); - document.open(); - document.write(html); - document.close(); - } catch (_) { - if (token === navToken) location.href = href; - } + function navigateTopNav(href) { + notifyParentFrameNavStart(); + location.assign(href); } document.addEventListener( @@ -384,14 +376,12 @@ const nextHref = target.pathname + target.search + target.hash; if (target.pathname === location.pathname && target.search === location.search) return; ev.preventDefault(); - void navigateInFrame(nextHref); + navigateTopNav(nextHref); }, true ); - window.addEventListener("popstate", () => { - void navigateInFrame(location.pathname + location.search + location.hash, { replace: true }); - }); + window.addEventListener("pagehide", notifyParentFrameNavStart); } function purgeLegacySoftNavCache() { @@ -418,6 +408,7 @@ apply(get(), { skipStore: true }); window.addEventListener("message", (ev) => initFromHubMessage(ev.data)); initHubEmbedInFrameNav(); + notifyParentFrameReady(); try { window.parent.postMessage({ type: "instance-theme-ready" }, "*"); } catch (_) {} @@ -447,6 +438,7 @@ syncInlineStyles(get()); patchHubNavLinks(get()); observeDynamicLists(); + if (isHubLinked()) notifyParentFrameReady(); }; if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", onReady);