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__":
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>。
* 各 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 (_) {}
+7 -3
View File
@@ -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();
}
+2
View File
@@ -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 || {});
+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/key_monitor_form.js?v=2"></script>
{% include 'embed_boot_scripts.html' %}
<script src="/static/instance_live.js?v=1"></script>
<script src="/static/instance_embed.js?v=10"></script>
<script src="/static/instance_live.js?v=2"></script>
<script src="/static/instance_embed.js?v=11"></script>
</body>
</html>