From 799a4d6b8b5ca7269e4804ff1c711945957b691a Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 7 Jul 2026 11:09:34 +0800 Subject: [PATCH] fix: enable threaded Flask for SSE and make embed tab switch instant Co-authored-by: Cursor --- crypto_monitor_okx/app.py | 2 +- lib/common/static/instance_embed.js | 73 ++++++++++++++++++++++--- lib/common/static/instance_live.js | 10 +++- lib/common/static/options_panel.js | 2 + lib/instance/templates/embed_shell.html | 4 +- 5 files changed, 76 insertions(+), 15 deletions(-) diff --git a/crypto_monitor_okx/app.py b/crypto_monitor_okx/app.py index 18e2517..13430f4 100644 --- a/crypto_monitor_okx/app.py +++ b/crypto_monitor_okx/app.py @@ -9420,4 +9420,4 @@ _purge_key_monitors_if_full_margin() # 启动 if __name__ == "__main__": threading.Thread(target=background_task, daemon=True).start() - app.run(host=HOST, port=PORT, debug=DEBUG) + app.run(host=HOST, port=PORT, debug=DEBUG, threaded=True) diff --git a/lib/common/static/instance_embed.js b/lib/common/static/instance_embed.js index 85301fd..4e64f1f 100644 --- a/lib/common/static/instance_embed.js +++ b/lib/common/static/instance_embed.js @@ -1,6 +1,6 @@ /** * 中控 iframe 壳:顶栏/统计常驻,tab 内容走 /api/embed/page/。 - * 各 tab 面板常驻 DOM,切换时 show/hide,避免整页替换闪屏。 + * 各 tab 面板常驻 DOM,切换时 show/hide;脚本延后到首次激活,回访零请求。 */ (function (global) { const TAB_PATH = { @@ -17,6 +17,7 @@ let navToken = 0; let loadingTab = false; const tabPanes = new Map(); + const tabBooted = new Set(); /** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST) */ const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form", "roll-form"]); @@ -60,11 +61,17 @@ else history.pushState({ embedTab: tab }, "", url); } + function notifyParentTabSwitch(tab) { + try { + window.parent.postMessage({ type: "instance-frame-navigating", embedShellTab: true, tab: tab }, "*"); + } catch (_) {} + } + function runPageInit(tab, opts) { const options = opts || {}; const revisit = !!options.revisit; document.body.setAttribute("data-page", tab); - if (typeof global.attachListWindowToExports === "function") { + if (!revisit && typeof global.attachListWindowToExports === "function") { global.attachListWindowToExports(); } if (tab === "trade") { @@ -91,9 +98,6 @@ if (!revisit && tab === "stats") { if (typeof global.initStatsSegmentFromUrl === "function") global.initStatsSegmentFromUrl(); } - if (revisit && tab === "options" && global.OptionsPanelLive && typeof global.OptionsPanelLive.refreshSoft === "function") { - global.OptionsPanelLive.refreshSoft(); - } if (!revisit) { if (typeof global.refreshPriceSnapshotConditional === "function") { global.refreshPriceSnapshotConditional(); @@ -120,10 +124,20 @@ function showPane(tab) { tabPanes.forEach((pane, name) => { - pane.hidden = name !== tab; + const on = name === tab; + pane.hidden = !on; + pane.classList.toggle("is-active-pane", on); }); } + function bootPaneScripts(tab) { + if (tabBooted.has(tab)) return; + const pane = tabPanes.get(tab); + if (!pane) return; + runScripts(pane); + tabBooted.add(tab); + } + function mountPane(tab, html) { const root = pageRoot(); if (!root) return null; @@ -137,7 +151,6 @@ const holder = document.createElement("div"); holder.innerHTML = html; - runScripts(holder); while (holder.firstChild) pane.appendChild(holder.firstChild); root.appendChild(pane); @@ -153,11 +166,12 @@ if (!root.childNodes.length) return; const pane = document.createElement("div"); - pane.className = "embed-tab-pane"; + pane.className = "embed-tab-pane is-active-pane"; pane.setAttribute("data-embed-pane", tab); Array.from(root.childNodes).forEach((node) => pane.appendChild(node)); root.appendChild(pane); tabPanes.set(tab, pane); + tabBooted.add(tab); showPane(tab); } @@ -189,17 +203,56 @@ .catch(() => {}); } + function preloadAllTabs() { + const tabs = Object.keys(TAB_PATH); + const current = getTab(); + let idx = 0; + function step() { + if (idx >= tabs.length) return; + const tab = tabs[idx++]; + if (tab === current || tabPanes.has(tab)) { + step(); + return; + } + fetchTabHtml(tab) + .then((html) => { + if (!tabPanes.has(tab)) mountPane(tab, html); + }) + .catch(() => {}) + .finally(() => { + setTimeout(step, 120); + }); + } + const ric = global.requestIdleCallback || function (fn) { + setTimeout(fn, 1200); + }; + ric(step); + } + function clearTabCache() { tabPanes.forEach((pane) => pane.remove()); tabPanes.clear(); + tabBooted.clear(); } function activateTab(tab, opts) { const options = opts || {}; + const revisit = !!options.revisit; + const firstBoot = !tabBooted.has(tab); showPane(tab); setNavActive(tab); if (!options.skipUrl) syncUrl(tab, !!options.replace); - runPageInit(tab, { revisit: !!options.revisit }); + notifyParentTabSwitch(tab); + if (firstBoot) { + bootPaneScripts(tab); + runPageInit(tab, { revisit: false }); + return; + } + if (revisit) { + document.body.setAttribute("data-page", tab); + return; + } + runPageInit(tab, { revisit: false }); } async function loadTab(tab, opts) { @@ -237,6 +290,7 @@ const pane = tabPanes.get(tab); if (pane) pane.remove(); tabPanes.delete(tab); + tabBooted.delete(tab); return loadTab(tab, { replace: true, skipUrl: true, force: true }); } @@ -354,6 +408,7 @@ initBootPane(); bindNav(); runPageInit(getTab()); + preloadAllTabs(); try { window.parent.postMessage({ type: "instance-frame-ready" }, "*"); } catch (_) {} diff --git a/lib/common/static/instance_live.js b/lib/common/static/instance_live.js index 5be5f86..78a2aba 100644 --- a/lib/common/static/instance_live.js +++ b/lib/common/static/instance_live.js @@ -32,8 +32,14 @@ } function onLiveEvent(data) { + const reason = data && data.reason; const ver = Number(data && data.live_version) || 0; - if (!ver || ver === localLiveVersion) return; + if (!ver) return; + if (reason === "connect") { + localLiveVersion = ver; + return; + } + if (ver === localLiveVersion) return; localLiveVersion = ver; refreshTabData(currentTab(), { silent: true }); } @@ -67,14 +73,12 @@ closeLiveStream(); liveReconnectTimer = setTimeout(function () { connectLiveStream(); - refreshTabData(currentTab(), { silent: true }); }, 8000); }; } function startLive() { if (!isEmbedShell()) return; - refreshTabData(currentTab()); connectLiveStream(); } diff --git a/lib/common/static/options_panel.js b/lib/common/static/options_panel.js index 0eb1995..5dabdc0 100644 --- a/lib/common/static/options_panel.js +++ b/lib/common/static/options_panel.js @@ -3,6 +3,8 @@ const root = document.getElementById("options-root"); if (!root) return; + if (root.getAttribute("data-options-booted") === "1") return; + root.setAttribute("data-options-booted", "1"); const panelCache = (window.__optionsPanelCache = window.__optionsPanelCache || {}); diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 932b6d9..bb45d6b 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -90,7 +90,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj {% include 'embed_boot_scripts.html' %} - - + +