From 444088baf36687108a61383fe97c7cdd88bf03b7 Mon Sep 17 00:00:00 2001 From: dekun Date: Tue, 7 Jul 2026 10:53:42 +0800 Subject: [PATCH] fix: reduce embed tab flash with persistent pane show/hide Co-authored-by: Cursor --- lib/common/static/instance_embed.js | 115 ++++++++++++++++-------- lib/common/static/instance_page.css | 4 +- lib/instance/templates/embed_shell.html | 4 +- 3 files changed, 82 insertions(+), 41 deletions(-) diff --git a/lib/common/static/instance_embed.js b/lib/common/static/instance_embed.js index 1e7aafd..1f38644 100644 --- a/lib/common/static/instance_embed.js +++ b/lib/common/static/instance_embed.js @@ -1,5 +1,6 @@ /** * 中控 iframe 壳:顶栏/统计常驻,tab 内容走 /api/embed/page/。 + * 各 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 { diff --git a/lib/common/static/instance_page.css b/lib/common/static/instance_page.css index 9f00c12..179206f 100644 --- a/lib/common/static/instance_page.css +++ b/lib/common/static/instance_page.css @@ -236,5 +236,5 @@ .stats-period-block:last-child{border-bottom:none;margin-bottom:0;padding-bottom:0} .stats-period-block h3{font-size:1rem;color:#dbe4ff;margin-bottom:4px} .stats-period-block .sub{font-size:.78rem;color:#8892b0;margin-bottom:10px;line-height:1.4} -#embed-page-root{transition:opacity .12s ease} -#embed-page-root.is-embed-tab-loading{opacity:.55;pointer-events:none} +#embed-page-root{min-height:120px;position:relative} +.embed-tab-pane[hidden]{display:none!important} diff --git a/lib/instance/templates/embed_shell.html b/lib/instance/templates/embed_shell.html index 1aa7ef1..114e0d8 100644 --- a/lib/instance/templates/embed_shell.html +++ b/lib/instance/templates/embed_shell.html @@ -6,7 +6,7 @@ - + @@ -90,6 +90,6 @@ const ORDER_ENTRY_MODEL_CODE_TO_CATEGORY = {{ entry_model_code_to_category | toj {% include 'embed_boot_scripts.html' %} - +