fix: reduce embed tab flash with persistent pane show/hide

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 10:53:42 +08:00
parent 86188e6611
commit 444088baf3
3 changed files with 82 additions and 41 deletions
+78 -37
View File
@@ -1,5 +1,6 @@
/**
* 中控 iframe 壳:顶栏/统计常驻,tab 内容走 /api/embed/page/<tab>。
* 各 tab 面板常驻 DOM,切换时 show/hide,避免整页替换闪屏。
*/
(function (global) {
const TAB_PATH = {
@@ -15,7 +16,7 @@
let navToken = 0;
let loadingTab = false;
const tabHtmlCache = new Map();
const tabPanes = new Map();
/** 自带校验后 form.submit() 的表单,勿在捕获阶段再 fetch 一份(会双发 POST */
const CUSTOM_SUBMIT_FORM_IDS = new Set(["add-order-form", "key-form", "roll-form"]);
@@ -39,9 +40,8 @@
return "";
}
function setRootLoading(on) {
const root = document.getElementById("embed-page-root");
if (root) root.classList.toggle("is-embed-tab-loading", !!on);
function pageRoot() {
return document.getElementById("embed-page-root");
}
function setNavActive(tab) {
@@ -68,7 +68,7 @@
if (tab === "trade") {
if (typeof global.refreshOrderDefaults === "function") global.refreshOrderDefaults();
if (typeof global.initOrderEntryModelSelect === "function") {
var root = document.getElementById("embed-page-root") || document;
const root = pageRoot() || document;
global.initOrderEntryModelSelect(root);
}
if (global.ManualOrderRrPreview && typeof global.ManualOrderRrPreview.wire === "function") {
@@ -93,20 +93,17 @@
global.refreshPriceSnapshotConditional();
}
if (global.SymbolLivePrice && typeof global.SymbolLivePrice.init === "function") {
const root = document.getElementById("embed-page-root") || document;
const root = pageRoot() || document;
global.SymbolLivePrice.init(root);
}
if (global.JournalUploadSlots && typeof global.JournalUploadSlots.init === "function") {
const root = document.getElementById("embed-page-root") || document;
const root = pageRoot() || document;
global.JournalUploadSlots.init(root);
}
}
function injectFragment(html) {
const root = document.getElementById("embed-page-root");
if (!root) return;
root.innerHTML = html;
root.querySelectorAll("script").forEach((old) => {
function runScripts(container) {
container.querySelectorAll("script").forEach((old) => {
const s = document.createElement("script");
if (old.src) s.src = old.src;
else s.textContent = old.textContent;
@@ -114,6 +111,49 @@
});
}
function showPane(tab) {
tabPanes.forEach((pane, name) => {
pane.hidden = name !== tab;
});
}
function mountPane(tab, html) {
const root = pageRoot();
if (!root) return null;
const existing = tabPanes.get(tab);
if (existing) existing.remove();
const pane = document.createElement("div");
pane.className = "embed-tab-pane";
pane.setAttribute("data-embed-pane", tab);
pane.hidden = true;
const holder = document.createElement("div");
holder.innerHTML = html;
runScripts(holder);
while (holder.firstChild) pane.appendChild(holder.firstChild);
root.appendChild(pane);
tabPanes.set(tab, pane);
return pane;
}
function initBootPane() {
const root = pageRoot();
if (!root || tabPanes.size > 0) return;
const tab = getTab();
if (root.querySelector("[data-embed-pane]")) return;
if (!root.childNodes.length) return;
const pane = document.createElement("div");
pane.className = "embed-tab-pane";
pane.setAttribute("data-embed-pane", tab);
Array.from(root.childNodes).forEach((node) => pane.appendChild(node));
root.appendChild(pane);
tabPanes.set(tab, pane);
showPane(tab);
}
function embedPageUrl(tab) {
const qs = listWindowQueryString();
let url = "/api/embed/page/" + encodeURIComponent(tab);
@@ -134,44 +174,44 @@
}
function warmTabCache(tab) {
if (!tab || tabHtmlCache.has(tab)) return;
if (!tab || tabPanes.has(tab) || loadingTab) return;
fetchTabHtml(tab)
.then((html) => {
tabHtmlCache.set(tab, html);
if (!tabPanes.has(tab)) mountPane(tab, html);
})
.catch(() => {});
}
function clearTabCache() {
tabHtmlCache.clear();
tabPanes.forEach((pane) => pane.remove());
tabPanes.clear();
}
function activateTab(tab, opts) {
const options = opts || {};
showPane(tab);
setNavActive(tab);
if (!options.skipUrl) syncUrl(tab, !!options.replace);
runPageInit(tab);
}
async function loadTab(tab, opts) {
const options = opts || {};
if (!tab || loadingTab) return;
const token = ++navToken;
const force = !!options.force;
const cached = !force && tabHtmlCache.get(tab);
if (!tab) return;
if (cached) {
injectFragment(cached);
setNavActive(tab);
if (!options.skipUrl) syncUrl(tab, !!options.replace);
runPageInit(tab);
warmTabCache(tab);
if (tabPanes.has(tab) && !options.force) {
activateTab(tab, options);
return;
}
if (loadingTab) return;
const token = ++navToken;
loadingTab = true;
setRootLoading(true);
try {
const html = await fetchTabHtml(tab);
if (token !== navToken) return;
tabHtmlCache.set(tab, html);
injectFragment(html);
setNavActive(tab);
if (!options.skipUrl) syncUrl(tab, !!options.replace);
runPageInit(tab);
mountPane(tab, html);
activateTab(tab, options);
} catch (e) {
if (token === navToken) {
const flash = document.getElementById("embed-flash");
@@ -181,16 +221,16 @@
}
}
} finally {
if (token === navToken) {
loadingTab = false;
setRootLoading(false);
}
if (token === navToken) loadingTab = false;
}
}
function reloadCurrentTab() {
tabHtmlCache.delete(getTab());
return loadTab(getTab(), { replace: true, skipUrl: true, force: true });
const tab = getTab();
const pane = tabPanes.get(tab);
if (pane) pane.remove();
tabPanes.delete(tab);
return loadTab(tab, { replace: true, skipUrl: true, force: true });
}
function postFormAndReload(form, label) {
@@ -304,6 +344,7 @@
if (!isEmbedShell()) return;
patchApplyListWindow();
patchHardNavigations();
initBootPane();
bindNav();
runPageInit(getTab());
try {