fix: enable threaded Flask for SSE and make embed tab switch instant

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 11:09:34 +08:00
parent 3db84d07e3
commit 799a4d6b8b
5 changed files with 76 additions and 15 deletions
+1 -1
View File
@@ -9420,4 +9420,4 @@ _purge_key_monitors_if_full_margin()
# 启动 # 启动
if __name__ == "__main__": if __name__ == "__main__":
threading.Thread(target=background_task, daemon=True).start() 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)
+64 -9
View File
@@ -1,6 +1,6 @@
/** /**
* 中控 iframe 壳:顶栏/统计常驻,tab 内容走 /api/embed/page/<tab>。 * 中控 iframe 壳:顶栏/统计常驻,tab 内容走 /api/embed/page/<tab>。
* 各 tab 面板常驻 DOM,切换时 show/hide,避免整页替换闪屏 * 各 tab 面板常驻 DOM,切换时 show/hide;脚本延后到首次激活,回访零请求
*/ */
(function (global) { (function (global) {
const TAB_PATH = { const TAB_PATH = {
@@ -17,6 +17,7 @@
let navToken = 0; let navToken = 0;
let loadingTab = false; let loadingTab = false;
const tabPanes = new Map(); const tabPanes = new Map();
const tabBooted = new Set();
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST */ /** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST */
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form", "roll-form"]); const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form", "roll-form"]);
@@ -60,11 +61,17 @@
else history.pushState({ embedTab: tab }, "", url); 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) { function runPageInit(tab, opts) {
const options = opts || {}; const options = opts || {};
const revisit = !!options.revisit; const revisit = !!options.revisit;
document.body.setAttribute("data-page", tab); document.body.setAttribute("data-page", tab);
if (typeof global.attachListWindowToExports === "function") { if (!revisit && typeof global.attachListWindowToExports === "function") {
global.attachListWindowToExports(); global.attachListWindowToExports();
} }
if (tab === "trade") { if (tab === "trade") {
@@ -91,9 +98,6 @@
if (!revisit && tab === "stats") { if (!revisit && tab === "stats") {
if (typeof global.initStatsSegmentFromUrl === "function") global.initStatsSegmentFromUrl(); if (typeof global.initStatsSegmentFromUrl === "function") global.initStatsSegmentFromUrl();
} }
if (revisit && tab === "options" && global.OptionsPanelLive && typeof global.OptionsPanelLive.refreshSoft === "function") {
global.OptionsPanelLive.refreshSoft();
}
if (!revisit) { if (!revisit) {
if (typeof global.refreshPriceSnapshotConditional === "function") { if (typeof global.refreshPriceSnapshotConditional === "function") {
global.refreshPriceSnapshotConditional(); global.refreshPriceSnapshotConditional();
@@ -120,10 +124,20 @@
function showPane(tab) { function showPane(tab) {
tabPanes.forEach((pane, name) => { 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) { function mountPane(tab, html) {
const root = pageRoot(); const root = pageRoot();
if (!root) return null; if (!root) return null;
@@ -137,7 +151,6 @@
const holder = document.createElement("div"); const holder = document.createElement("div");
holder.innerHTML = html; holder.innerHTML = html;
runScripts(holder);
while (holder.firstChild) pane.appendChild(holder.firstChild); while (holder.firstChild) pane.appendChild(holder.firstChild);
root.appendChild(pane); root.appendChild(pane);
@@ -153,11 +166,12 @@
if (!root.childNodes.length) return; if (!root.childNodes.length) return;
const pane = document.createElement("div"); const pane = document.createElement("div");
pane.className = "embed-tab-pane"; pane.className = "embed-tab-pane is-active-pane";
pane.setAttribute("data-embed-pane", tab); pane.setAttribute("data-embed-pane", tab);
Array.from(root.childNodes).forEach((node) => pane.appendChild(node)); Array.from(root.childNodes).forEach((node) => pane.appendChild(node));
root.appendChild(pane); root.appendChild(pane);
tabPanes.set(tab, pane); tabPanes.set(tab, pane);
tabBooted.add(tab);
showPane(tab); showPane(tab);
} }
@@ -189,17 +203,56 @@
.catch(() => {}); .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() { function clearTabCache() {
tabPanes.forEach((pane) => pane.remove()); tabPanes.forEach((pane) => pane.remove());
tabPanes.clear(); tabPanes.clear();
tabBooted.clear();
} }
function activateTab(tab, opts) { function activateTab(tab, opts) {
const options = opts || {}; const options = opts || {};
const revisit = !!options.revisit;
const firstBoot = !tabBooted.has(tab);
showPane(tab); showPane(tab);
setNavActive(tab); setNavActive(tab);
if (!options.skipUrl) syncUrl(tab, !!options.replace); 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) { async function loadTab(tab, opts) {
@@ -237,6 +290,7 @@
const pane = tabPanes.get(tab); const pane = tabPanes.get(tab);
if (pane) pane.remove(); if (pane) pane.remove();
tabPanes.delete(tab); tabPanes.delete(tab);
tabBooted.delete(tab);
return loadTab(tab, { replace: true, skipUrl: true, force: true }); return loadTab(tab, { replace: true, skipUrl: true, force: true });
} }
@@ -354,6 +408,7 @@
initBootPane(); initBootPane();
bindNav(); bindNav();
runPageInit(getTab()); runPageInit(getTab());
preloadAllTabs();
try { try {
window.parent.postMessage({ type: "instance-frame-ready" }, "*"); window.parent.postMessage({ type: "instance-frame-ready" }, "*");
} catch (_) {} } catch (_) {}
+7 -3
View File
@@ -32,8 +32,14 @@
} }
function onLiveEvent(data) { function onLiveEvent(data) {
const reason = data && data.reason;
const ver = Number(data && data.live_version) || 0; 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; localLiveVersion = ver;
refreshTabData(currentTab(), { silent: true }); refreshTabData(currentTab(), { silent: true });
} }
@@ -67,14 +73,12 @@
closeLiveStream(); closeLiveStream();
liveReconnectTimer = setTimeout(function () { liveReconnectTimer = setTimeout(function () {
connectLiveStream(); connectLiveStream();
refreshTabData(currentTab(), { silent: true });
}, 8000); }, 8000);
}; };
} }
function startLive() { function startLive() {
if (!isEmbedShell()) return; if (!isEmbedShell()) return;
refreshTabData(currentTab());
connectLiveStream(); connectLiveStream();
} }
+2
View File
@@ -3,6 +3,8 @@
const root = document.getElementById("options-root"); const root = document.getElementById("options-root");
if (!root) return; if (!root) return;
if (root.getAttribute("data-options-booted") === "1") return;
root.setAttribute("data-options-booted", "1");
const panelCache = (window.__optionsPanelCache = window.__optionsPanelCache || {}); const panelCache = (window.__optionsPanelCache = window.__optionsPanelCache || {});
+2 -2
View File
@@ -90,7 +90,7 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj
<script src="/static/strategy_roll.js?v=6"></script> <script src="/static/strategy_roll.js?v=6"></script>
<script src="/static/key_monitor_form.js?v=2"></script> <script src="/static/key_monitor_form.js?v=2"></script>
{% include 'embed_boot_scripts.html' %} {% include 'embed_boot_scripts.html' %}
<script src="/static/instance_live.js?v=1"></script> <script src="/static/instance_live.js?v=2"></script>
<script src="/static/instance_embed.js?v=10"></script> <script src="/static/instance_embed.js?v=11"></script>
</body> </body>
</html> </html>