perf: embed shell soft nav, tab cache, and lighter options page boot

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-07 10:42:01 +08:00
parent 8913138dc3
commit 30e40dc34c
6 changed files with 150 additions and 31 deletions
+31 -2
View File
@@ -290,7 +290,7 @@ def resolve_path(path_value):
app = Flask(__name__) app = Flask(__name__)
app.secret_key = os.getenv("FLASK_SECRET_KEY", "crypto_monitor_2026_secret_key") app.secret_key = os.getenv("FLASK_SECRET_KEY", "crypto_monitor_2026_secret_key")
from lib.instance.instance_embed_lib import attach_embed_templates from lib.instance.instance_embed_lib import attach_embed_templates, redirect_to_embed_shell_if_enabled
attach_embed_templates(app, _REPO_ROOT) attach_embed_templates(app, _REPO_ROOT)
@@ -6510,7 +6510,11 @@ def render_main_page(page="trade", embed_mode=None):
options_trading_usdc = None options_trading_usdc = None
options_funding_usdc = None options_funding_usdc = None
options_funding_usdt = None options_funding_usdt = None
if OKX_OPTIONS_ENABLED and exchange_options.apiKey: if (
OKX_OPTIONS_ENABLED
and exchange_options.apiKey
and embed_mode != "fragment"
):
try: try:
from lib.exchange.okx_options_lib import ( from lib.exchange.okx_options_lib import (
fetch_options_funding_usdc, fetch_options_funding_usdc,
@@ -6601,6 +6605,13 @@ def render_main_page(page="trade", embed_mode=None):
hard_limit=DAILY_OPEN_HARD_LIMIT, hard_limit=DAILY_OPEN_HARD_LIMIT,
extra_blocks=not risk_status.get("can_trade", True), extra_blocks=not risk_status.get("can_trade", True),
) )
key_rule_ctx = {}
if page in ("key_monitor", "trade") or page in (
"strategy",
"strategy_trend",
"strategy_roll",
"strategy_records",
):
key_rule_ctx = key_monitor_rule_template_context( key_rule_ctx = key_monitor_rule_template_context(
kline_timeframe=KLINE_TIMEFRAME, kline_timeframe=KLINE_TIMEFRAME,
key_breakout_amp_min_pct=KEY_BREAKOUT_AMP_MIN_PCT, key_breakout_amp_min_pct=KEY_BREAKOUT_AMP_MIN_PCT,
@@ -6768,36 +6779,54 @@ def index():
@app.route("/key_monitor") @app.route("/key_monitor")
@login_required @login_required
def key_monitor_page(): def key_monitor_page():
redir = redirect_to_embed_shell_if_enabled("key_monitor")
if redir is not None:
return redir
return render_main_page("key_monitor") return render_main_page("key_monitor")
@app.route("/trade") @app.route("/trade")
@login_required @login_required
def trade_page(): def trade_page():
redir = redirect_to_embed_shell_if_enabled("trade")
if redir is not None:
return redir
return render_main_page("trade") return render_main_page("trade")
@app.route("/records") @app.route("/records")
@login_required @login_required
def records_page(): def records_page():
redir = redirect_to_embed_shell_if_enabled("records")
if redir is not None:
return redir
return render_main_page("records") return render_main_page("records")
@app.route("/stats") @app.route("/stats")
@login_required @login_required
def stats_page(): def stats_page():
redir = redirect_to_embed_shell_if_enabled("stats")
if redir is not None:
return redir
return render_main_page("stats") return render_main_page("stats")
@app.route("/settings") @app.route("/settings")
@login_required @login_required
def settings_page(): def settings_page():
redir = redirect_to_embed_shell_if_enabled("settings")
if redir is not None:
return redir
return render_main_page("settings") return render_main_page("settings")
@app.route("/options") @app.route("/options")
@login_required @login_required
def options_main_page(): def options_main_page():
redir = redirect_to_embed_shell_if_enabled("options")
if redir is not None:
return redir
return render_main_page("options") return render_main_page("options")
+56 -7
View File
@@ -7,6 +7,7 @@
trade: "/trade", trade: "/trade",
strategy: "/strategy", strategy: "/strategy",
strategy_records: "/strategy/records", strategy_records: "/strategy/records",
options: "/options",
records: "/records", records: "/records",
stats: "/stats", stats: "/stats",
settings: "/settings", settings: "/settings",
@@ -14,6 +15,7 @@
let navToken = 0; let navToken = 0;
let loadingTab = false; let loadingTab = false;
const tabHtmlCache = new Map();
/** 自带校验后 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"]);
@@ -112,20 +114,61 @@
}); });
} }
function embedPageUrl(tab) {
const qs = listWindowQueryString();
let url = "/api/embed/page/" + encodeURIComponent(tab);
const parts = [];
if (qs) parts.push(qs);
parts.push("embed=1");
return url + "?" + parts.join("&");
}
async function fetchTabHtml(tab) {
const r = await fetch(embedPageUrl(tab), {
credentials: "same-origin",
headers: { "X-Instance-Soft-Nav": "1" },
});
const j = await r.json();
if (!j.ok || !j.html) throw new Error(j.msg || "加载失败");
return j.html;
}
function warmTabCache(tab) {
if (!tab || tabHtmlCache.has(tab)) return;
fetchTabHtml(tab)
.then((html) => {
tabHtmlCache.set(tab, html);
})
.catch(() => {});
}
function clearTabCache() {
tabHtmlCache.clear();
}
async function loadTab(tab, opts) { async function loadTab(tab, opts) {
const options = opts || {}; const options = opts || {};
if (!tab || loadingTab) return; if (!tab || loadingTab) return;
const token = ++navToken; const token = ++navToken;
const force = !!options.force;
const cached = !force && tabHtmlCache.get(tab);
if (cached) {
injectFragment(cached);
setNavActive(tab);
if (!options.skipUrl) syncUrl(tab, !!options.replace);
runPageInit(tab);
warmTabCache(tab);
return;
}
loadingTab = true; loadingTab = true;
setRootLoading(true); setRootLoading(true);
try { try {
const qs = listWindowQueryString(); const html = await fetchTabHtml(tab);
const url = "/api/embed/page/" + encodeURIComponent(tab) + (qs ? "?" + qs : "");
const r = await fetch(url, { credentials: "same-origin" });
if (token !== navToken) return; if (token !== navToken) return;
const j = await r.json(); tabHtmlCache.set(tab, html);
if (!j.ok || !j.html) throw new Error(j.msg || "加载失败"); injectFragment(html);
injectFragment(j.html);
setNavActive(tab); setNavActive(tab);
if (!options.skipUrl) syncUrl(tab, !!options.replace); if (!options.skipUrl) syncUrl(tab, !!options.replace);
runPageInit(tab); runPageInit(tab);
@@ -146,7 +189,8 @@
} }
function reloadCurrentTab() { function reloadCurrentTab() {
return loadTab(getTab(), { replace: true, skipUrl: true }); tabHtmlCache.delete(getTab());
return loadTab(getTab(), { replace: true, skipUrl: true, force: true });
} }
function postFormAndReload(form, label) { function postFormAndReload(form, label) {
@@ -172,6 +216,7 @@
function patchApplyListWindow() { function patchApplyListWindow() {
if (typeof global.applyListWindow !== "function") return; if (typeof global.applyListWindow !== "function") return;
global.applyListWindow = function embedApplyListWindow() { global.applyListWindow = function embedApplyListWindow() {
clearTabCache();
const qs = listWindowQueryString(); const qs = listWindowQueryString();
const tab = getTab(); const tab = getTab();
const q = new URLSearchParams(qs); const q = new URLSearchParams(qs);
@@ -239,6 +284,9 @@
function bindNav() { function bindNav() {
document.querySelectorAll(".embed-top-nav [data-embed-tab]").forEach((a) => { document.querySelectorAll(".embed-top-nav [data-embed-tab]").forEach((a) => {
a.addEventListener("mouseenter", () => {
warmTabCache(a.getAttribute("data-embed-tab"));
});
a.addEventListener("click", (ev) => { a.addEventListener("click", (ev) => {
ev.preventDefault(); ev.preventDefault();
const tab = a.getAttribute("data-embed-tab"); const tab = a.getAttribute("data-embed-tab");
@@ -268,6 +316,7 @@
reloadCurrentTab, reloadCurrentTab,
getTab, getTab,
postFormAndReload, postFormAndReload,
clearTabCache,
}; };
if (document.readyState === "loading") { if (document.readyState === "loading") {
+29 -4
View File
@@ -4,10 +4,12 @@
const root = document.getElementById("options-root"); const root = document.getElementById("options-root");
if (!root) return; if (!root) return;
const panelCache = (window.__optionsPanelCache = window.__optionsPanelCache || {});
const state = { const state = {
underlying: root.dataset.defaultUnderly || "ETH", underlying: root.dataset.defaultUnderly || "ETH",
optType: "C", optType: "C",
chain: null, chain: panelCache.chain || null,
selectedInst: null, selectedInst: null,
}; };
@@ -254,6 +256,9 @@
return; return;
} }
state.chain = d; state.chain = d;
panelCache.chain = d;
panelCache.underlying = state.underlying;
panelCache.optType = state.optType;
state.selectedInst = null; state.selectedInst = null;
parkOrderPanel(); parkOrderPanel();
renderExpiries(); renderExpiries();
@@ -457,6 +462,28 @@
refreshHistory(); refreshHistory();
} }
function bootOptionsPanel() {
updateSizeInputs();
const hasCache =
panelCache.chain &&
panelCache.underlying === state.underlying &&
panelCache.optType === state.optType;
if (hasCache) {
state.chain = panelCache.chain;
renderExpiries();
renderStrikes();
refreshAllPositions();
requestAnimationFrame(function () {
loadChain();
});
return;
}
requestAnimationFrame(function () {
loadChain();
refreshAllPositions();
});
}
document.querySelectorAll(".opt-uly-btn").forEach(function (btn) { document.querySelectorAll(".opt-uly-btn").forEach(function (btn) {
btn.addEventListener("click", function () { btn.addEventListener("click", function () {
document.querySelectorAll(".opt-uly-btn").forEach(function (b) { b.classList.remove("active"); }); document.querySelectorAll(".opt-uly-btn").forEach(function (b) { b.classList.remove("active"); });
@@ -495,7 +522,5 @@
}); });
}); });
updateSizeInputs(); bootOptionsPanel();
loadChain();
refreshAllPositions();
})(); })();
+16
View File
@@ -72,6 +72,22 @@ def embed_shell_enabled() -> bool:
return (os.getenv("HUB_EMBED_SHELL") or "1").strip().lower() in ("1", "true", "yes", "on") return (os.getenv("HUB_EMBED_SHELL") or "1").strip().lower() in ("1", "true", "yes", "on")
def redirect_to_embed_shell_if_enabled(page: str):
"""直连 /trade 等整页路由时,重定向到 embed 壳(顶栏常驻、tab 软切换)。"""
from flask import redirect, request, urlencode
if not embed_shell_enabled():
return None
if (request.args.get("embed") or "").strip() == "1":
return None
if (request.path or "").rstrip("/") == "/embed":
return None
q = {k: v for k, v in request.args.items()}
q["tab"] = page
q["embed"] = "1"
return redirect("/embed?" + urlencode(q))
def rewrite_embed_dest(path: str, hub_theme: str | None = None) -> str: def rewrite_embed_dest(path: str, hub_theme: str | None = None) -> str:
"""embed=1 打开时:/trade → /embed?tab=trade&embed=1""" """embed=1 打开时:/trade → /embed?tab=trade&embed=1"""
if not embed_shell_enabled(): if not embed_shell_enabled():
+1 -1
View File
@@ -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/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_embed.js?v=7"></script> <script src="/static/instance_embed.js?v=8"></script>
</body> </body>
</html> </html>
+1 -1
View File
@@ -121,4 +121,4 @@
</div> </div>
</div> </div>
</div> </div>
<script src="/static/options_panel.js?v=7"></script> <script src="/static/options_panel.js?v=8"></script>