fix: reduce embed tab flash with persistent pane show/hide
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<script src="/static/instance_theme.js?v=47"></script>
|
||||
<link rel="stylesheet" href="/static/instance_theme_early.css?v=4">
|
||||
<link rel="stylesheet" href="/static/account_risk_badge.css?v=4">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=3">
|
||||
<link rel="stylesheet" href="/static/instance_page.css?v=4">
|
||||
<link rel="stylesheet" href="/static/instance_theme.css?v=64">
|
||||
<script src="/static/account_risk_badge.js?v=4"></script>
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
@@ -90,6 +90,6 @@ 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_embed.js?v=8"></script>
|
||||
<script src="/static/instance_embed.js?v=9"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user