diff --git a/crypto_monitor_binance/templates/index.html b/crypto_monitor_binance/templates/index.html index b087eec..14925b9 100644 --- a/crypto_monitor_binance/templates/index.html +++ b/crypto_monitor_binance/templates/index.html @@ -3,8 +3,8 @@ - - + + diff --git a/crypto_monitor_gate/templates/index.html b/crypto_monitor_gate/templates/index.html index 39e304f..9f2b5c6 100644 --- a/crypto_monitor_gate/templates/index.html +++ b/crypto_monitor_gate/templates/index.html @@ -3,8 +3,8 @@ - - + + diff --git a/crypto_monitor_gate_bot/templates/index.html b/crypto_monitor_gate_bot/templates/index.html index 39e304f..9f2b5c6 100644 --- a/crypto_monitor_gate_bot/templates/index.html +++ b/crypto_monitor_gate_bot/templates/index.html @@ -3,8 +3,8 @@ - - + + diff --git a/crypto_monitor_okx/templates/index.html b/crypto_monitor_okx/templates/index.html index 1c14aa9..c63918a 100644 --- a/crypto_monitor_okx/templates/index.html +++ b/crypto_monitor_okx/templates/index.html @@ -3,8 +3,8 @@ - - + + diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index efd6c72..f4c637e 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -1092,6 +1092,10 @@ body.market-chart-fs-open { background: var(--bg); } +.instance-frame-shell.is-instance-nav-loading .instance-frame { + visibility: hidden; +} + .exchange-fullscreen { position: fixed; inset: 0; diff --git a/manual_trading_hub/static/app.js b/manual_trading_hub/static/app.js index 55a117a..1998a68 100644 --- a/manual_trading_hub/static/app.js +++ b/manual_trading_hub/static/app.js @@ -371,13 +371,20 @@ 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; const next = nextPath || "/"; try { const embedded = isHubEmbedded(); - const url = await fetchInstanceOpenUrl(exchangeId, next, { embed: embedded }); + const url = await fetchInstanceOpenUrl(exchangeId, next, { + embed: embedded || !newTab, + }); if (newTab) { window.open(url, "_blank", "noopener"); return; @@ -417,11 +424,14 @@ const url = await fetchInstanceOpenUrl( instanceFrameCtx.exchangeId, instanceFrameCtx.nextPath, - { embed: isHubEmbedded() } + { embed: true } ); 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); } @@ -438,6 +448,7 @@ closeExchangeFullscreen(); instanceFrameUrl = url; if (titleEl) titleEl.textContent = title || "实例"; + setInstanceFrameNavLoading(true); frame.src = url; shell.classList.remove("hidden"); shell.setAttribute("aria-hidden", "false"); @@ -466,6 +477,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"); } @@ -3129,6 +3141,22 @@ 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/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index dd7f425..bc349b6 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -15,7 +15,7 @@ - + @@ -654,6 +654,6 @@ - + diff --git a/static/instance_theme.js b/static/instance_theme.js index 6c5846a..2623f49 100644 --- a/static/instance_theme.js +++ b/static/instance_theme.js @@ -283,10 +283,92 @@ apply(data.theme, { skipStore: true }); } + 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" }, "*"); + } catch (_) {} + } + + /** 中控 iframe 内:拦截顶栏导航,fetch 后 document.write 原地换页,避免 iframe 卸载白屏 */ + function initHubEmbedInFrameNav() { + if (!isHubLinked()) return; + + let navToken = 0; + + function isSoftNavLink(a) { + if (!a || !a.getAttribute) return false; + return !!a.closest(".top-nav, .strategy-subnav"); + } + + async function navigateInFrame(href, opts) { + const token = ++navToken; + notifyParentFrameNavStart(); + 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; + document.open(); + document.write(html); + document.close(); + 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); + } catch (_) { + if (token === navToken) location.href = href; + } + } + + document.addEventListener( + "click", + (ev) => { + const a = ev.target.closest("a[href]"); + if (!a || !isSoftNavLink(a) || ev.defaultPrevented) return; + if (ev.button !== 0 || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey) return; + const rawHref = a.getAttribute("href"); + if (!rawHref || rawHref.startsWith("#") || rawHref.startsWith("javascript:")) return; + let target; + try { + target = new URL(rawHref, location.href); + } catch (_) { + return; + } + if (target.origin !== location.origin) return; + const nextHref = target.pathname + target.search + target.hash; + if (target.pathname === location.pathname && target.search === location.search) return; + ev.preventDefault(); + void navigateInFrame(nextHref); + }, + true + ); + + window.addEventListener("popstate", () => { + void navigateInFrame(location.pathname + location.search + location.hash, { replace: true }); + }); + } + function boot() { if (isHubLinked()) { apply(get(), { skipStore: true }); window.addEventListener("message", (ev) => initFromHubMessage(ev.data)); + initHubEmbedInFrameNav(); + notifyParentFrameReady(); try { window.parent.postMessage({ type: "instance-theme-ready" }, "*"); } catch (_) {} diff --git a/static/instance_theme_early.css b/static/instance_theme_early.css index a122d9b..ea20632 100644 --- a/static/instance_theme_early.css +++ b/static/instance_theme_early.css @@ -1,4 +1,14 @@ /* 紧接 instance_theme.js 之后加载,避免亮色下先闪暗色底 */ +html { + background: #0b0d14; + color-scheme: dark; +} + +html[data-theme="light"] { + background: #d8e2ec; + color-scheme: light; +} + html[data-theme="light"] body { background: #d8e2ec !important; color: #1a2838 !important;