Speed up top nav with turbo routing and external base CSS.
Remove view-transition lag, swap main content without full reload, prefetch pages, and tear down SSE timers on leave. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+14
-2
@@ -31,8 +31,20 @@
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function stopPolling() {
|
||||
if (keyTimer) {
|
||||
clearInterval(keyTimer);
|
||||
keyTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
stopPolling();
|
||||
pollKeyPrices();
|
||||
keyTimer = setInterval(pollKeyPrices, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(startPolling);
|
||||
else document.addEventListener('DOMContentLoaded', startPolling);
|
||||
if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(stopPolling);
|
||||
})();
|
||||
|
||||
+22
-14
@@ -689,7 +689,12 @@
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function cleanupMarketPage() {
|
||||
stopKlineStream();
|
||||
destroyChart();
|
||||
}
|
||||
|
||||
function bootMarketPage() {
|
||||
if (!window.LightweightCharts) {
|
||||
if (emptyEl) emptyEl.textContent = '图表库加载失败,请刷新页面';
|
||||
return;
|
||||
@@ -699,15 +704,6 @@
|
||||
bindAutoButton();
|
||||
bindChartOptions();
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.closest('[data-theme-pick]') && lastData) {
|
||||
setTimeout(function () {
|
||||
destroyChart();
|
||||
renderChart(lastData, { forceFull: true });
|
||||
}, 80);
|
||||
}
|
||||
});
|
||||
|
||||
var active = document.querySelector('.period-tab.active');
|
||||
if (active) currentPeriod = active.getAttribute('data-period') || '15m';
|
||||
|
||||
@@ -732,10 +728,22 @@
|
||||
} else {
|
||||
updateRefreshHint(false);
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', function () {
|
||||
stopKlineStream();
|
||||
destroyChart();
|
||||
if (!window.__QIHUO_MARKET_THEME__) {
|
||||
window.__QIHUO_MARKET_THEME__ = true;
|
||||
document.addEventListener('click', function (e) {
|
||||
if (e.target.closest('[data-theme-pick]') && lastData) {
|
||||
setTimeout(function () {
|
||||
destroyChart();
|
||||
renderChart(lastData, { forceFull: true });
|
||||
}, 80);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootMarketPage);
|
||||
else document.addEventListener('DOMContentLoaded', bootMarketPage);
|
||||
if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(cleanupMarketPage);
|
||||
window.addEventListener('pagehide', cleanupMarketPage);
|
||||
})();
|
||||
|
||||
@@ -42,6 +42,10 @@
|
||||
}
|
||||
|
||||
function prefetchNav(href) {
|
||||
if (window.qihuoPrefetchPage) {
|
||||
window.qihuoPrefetchPage(href);
|
||||
return;
|
||||
}
|
||||
if (!href || href.indexOf(window.location.origin) !== 0) return;
|
||||
if (href === window.location.href) return;
|
||||
var links = document.head.querySelectorAll('link[rel="prefetch"]');
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function (global) {
|
||||
global.qihuoOnPageLoad = function (fn) {
|
||||
global.addEventListener('qihuo:page-load', fn);
|
||||
};
|
||||
|
||||
global.qihuoOnPageLeave = function (fn) {
|
||||
global.addEventListener('qihuo:page-leave', fn);
|
||||
};
|
||||
|
||||
global.qihuoEmitPageLoad = function () {
|
||||
global.dispatchEvent(new Event('qihuo:page-load'));
|
||||
};
|
||||
})();
|
||||
+11
-2
@@ -39,11 +39,20 @@
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
function stopPolling() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
if (timer) clearInterval(timer);
|
||||
stopPolling();
|
||||
pollPrices();
|
||||
timer = setInterval(pollPrices, 1000);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', startPolling);
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(startPolling);
|
||||
else document.addEventListener('DOMContentLoaded', startPolling);
|
||||
if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(stopPolling);
|
||||
})();
|
||||
|
||||
+14
-2
@@ -72,8 +72,20 @@
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function stopPolling() {
|
||||
if (posTimer) {
|
||||
clearInterval(posTimer);
|
||||
posTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function startPolling() {
|
||||
stopPolling();
|
||||
pollPositions();
|
||||
posTimer = setInterval(pollPositions, 1000);
|
||||
});
|
||||
}
|
||||
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(startPolling);
|
||||
else document.addEventListener('DOMContentLoaded', startPolling);
|
||||
if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(stopPolling);
|
||||
})();
|
||||
|
||||
+5
-2
@@ -163,11 +163,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function bootReviewPage() {
|
||||
bindForm();
|
||||
bindModal();
|
||||
recalc();
|
||||
});
|
||||
}
|
||||
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootReviewPage);
|
||||
else document.addEventListener('DOMContentLoaded', bootReviewPage);
|
||||
|
||||
window.recalc = recalc;
|
||||
})();
|
||||
|
||||
+7
-3
@@ -147,13 +147,17 @@
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function bootStatsPage() {
|
||||
var viewSel = document.getElementById('stats-view-select');
|
||||
if (viewSel) {
|
||||
if (viewSel && !viewSel.dataset.statsBound) {
|
||||
viewSel.dataset.statsBound = '1';
|
||||
viewSel.addEventListener('change', function () {
|
||||
renderBreakdown(this.value);
|
||||
});
|
||||
}
|
||||
loadStats();
|
||||
});
|
||||
}
|
||||
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootStatsPage);
|
||||
else document.addEventListener('DOMContentLoaded', bootStatsPage);
|
||||
})();
|
||||
|
||||
+7
-2
@@ -261,12 +261,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
function initSymbolForms() {
|
||||
document.querySelectorAll('.symbol-wrap').forEach(initSymbolInput);
|
||||
|
||||
document.querySelectorAll('form').forEach(function (form) {
|
||||
if (!form.querySelector('.symbol-wrap')) return;
|
||||
if (form.id === 'market-form') return;
|
||||
if (form.dataset.symbolGuard) return;
|
||||
form.dataset.symbolGuard = '1';
|
||||
form.addEventListener('submit', function (e) {
|
||||
const ths = form.querySelector('input[name="symbol"]')
|
||||
|| form.querySelector('.symbol-ths-code');
|
||||
@@ -282,5 +284,8 @@
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(initSymbolForms);
|
||||
else document.addEventListener('DOMContentLoaded', initSymbolForms);
|
||||
})();
|
||||
|
||||
+32
-14
@@ -49,14 +49,6 @@
|
||||
var productCategories = window.PRODUCT_CATEGORIES || [];
|
||||
var POS_CACHE_KEY = 'qihuo_trading_live_v5';
|
||||
|
||||
function runWhenReady(fn) {
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', fn);
|
||||
} else {
|
||||
fn();
|
||||
}
|
||||
}
|
||||
|
||||
function fmtNum(v, digits) {
|
||||
if (v === null || v === undefined) return '--';
|
||||
return Number(v).toFixed(digits === undefined ? 2 : digits);
|
||||
@@ -1595,7 +1587,27 @@
|
||||
.catch(function () {});
|
||||
}
|
||||
|
||||
runWhenReady(function () {
|
||||
function cleanupTradePage() {
|
||||
if (positionSource) {
|
||||
positionSource.close();
|
||||
positionSource = null;
|
||||
}
|
||||
if (recommendSource) {
|
||||
recommendSource.close();
|
||||
recommendSource = null;
|
||||
}
|
||||
if (quoteTimer) {
|
||||
clearTimeout(quoteTimer);
|
||||
quoteTimer = null;
|
||||
}
|
||||
if (calcTimer) {
|
||||
clearTimeout(calcTimer);
|
||||
calcTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function bootTradePage() {
|
||||
if (!list && !orderList) return;
|
||||
updateCtpConnectButtonState();
|
||||
setPriceType('limit');
|
||||
if (isFixedMode() && lotsCalc) {
|
||||
@@ -1624,14 +1636,20 @@
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) { if (data.ok) renderRecommendations(data); })
|
||||
.catch(function () {});
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (document.visibilityState === 'visible' && !positionSource) {
|
||||
connectPositionStream();
|
||||
}
|
||||
});
|
||||
updateSessionUi();
|
||||
updateRRDisplay();
|
||||
scheduleQuote();
|
||||
scheduleAutoCalc();
|
||||
}
|
||||
|
||||
document.addEventListener('visibilitychange', function () {
|
||||
if (document.visibilityState === 'visible' && list && !positionSource) {
|
||||
connectPositionStream();
|
||||
}
|
||||
});
|
||||
|
||||
if (window.qihuoOnPageLoad) window.qihuoOnPageLoad(bootTradePage);
|
||||
else bootTradePage();
|
||||
if (window.qihuoOnPageLeave) window.qihuoOnPageLeave(cleanupTradePage);
|
||||
window.addEventListener('pagehide', cleanupTradePage);
|
||||
})();
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
/* Copyright (c) 2025-2026 马建军. All rights reserved.
|
||||
* 专有软件 — 未经授权禁止复制、传播、转售。
|
||||
* 详见 LICENSE.zh-CN.txt
|
||||
*/
|
||||
(function () {
|
||||
var PERMANENT_CSS = ['/static/css/base.css', '/static/css/tech.css', '/static/css/responsive.css'];
|
||||
var CORE_SCRIPT_MARKERS = ['theme.js', 'symbol.js', 'page.js', 'nav.js', 'pwa.js', 'turbonav.js'];
|
||||
var pageCache = new Map();
|
||||
var MAX_CACHE = 10;
|
||||
var inflight = null;
|
||||
var currentUrl = normalizeUrl(window.location.href);
|
||||
|
||||
function normalizeUrl(href) {
|
||||
var u = new URL(href, window.location.origin);
|
||||
u.hash = '';
|
||||
return u.href;
|
||||
}
|
||||
|
||||
function isPermanentStylesheet(el) {
|
||||
var href = el.getAttribute('href') || '';
|
||||
return PERMANENT_CSS.some(function (p) { return href.indexOf(p) !== -1; });
|
||||
}
|
||||
|
||||
function isCoreScript(el) {
|
||||
var src = el.getAttribute('src') || '';
|
||||
if (!src) return false;
|
||||
return CORE_SCRIPT_MARKERS.some(function (m) { return src.indexOf(m) !== -1; });
|
||||
}
|
||||
|
||||
function shouldTurboClick(e, link) {
|
||||
if (!link || !link.href) return false;
|
||||
if (e.defaultPrevented) return false;
|
||||
if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return false;
|
||||
if (link.target && link.target !== '_self') return false;
|
||||
if (link.hasAttribute('download')) return false;
|
||||
if (link.origin !== window.location.origin) return false;
|
||||
if (normalizeUrl(link.href) === currentUrl) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function parseHtml(html) {
|
||||
return new DOMParser().parseFromString(html, 'text/html');
|
||||
}
|
||||
|
||||
function fetchPage(url, signal) {
|
||||
var key = normalizeUrl(url);
|
||||
if (pageCache.has(key)) {
|
||||
return Promise.resolve(pageCache.get(key));
|
||||
}
|
||||
return fetch(key, {
|
||||
credentials: 'same-origin',
|
||||
headers: { Accept: 'text/html' },
|
||||
signal: signal
|
||||
}).then(function (res) {
|
||||
if (!res.ok) throw new Error('HTTP ' + res.status);
|
||||
return res.text();
|
||||
}).then(function (html) {
|
||||
var doc = parseHtml(html);
|
||||
pageCache.set(key, doc);
|
||||
if (pageCache.size > MAX_CACHE) {
|
||||
var first = pageCache.keys().next().value;
|
||||
pageCache.delete(first);
|
||||
}
|
||||
return doc;
|
||||
});
|
||||
}
|
||||
|
||||
function collectPageCss(doc) {
|
||||
var out = [];
|
||||
doc.querySelectorAll('head link[rel="stylesheet"], head style').forEach(function (el) {
|
||||
if (el.tagName === 'LINK' && isPermanentStylesheet(el)) return;
|
||||
out.push(el);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
function collectPageScripts(doc) {
|
||||
var out = [];
|
||||
var pastCore = false;
|
||||
doc.body.querySelectorAll(':scope > script').forEach(function (el) {
|
||||
if (isCoreScript(el)) {
|
||||
if ((el.getAttribute('src') || '').indexOf('pwa.js') !== -1) pastCore = true;
|
||||
return;
|
||||
}
|
||||
if (!pastCore) return;
|
||||
out.push(el);
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
function removePageAssets() {
|
||||
document.querySelectorAll('head link[rel="stylesheet"]').forEach(function (el) {
|
||||
if (!isPermanentStylesheet(el)) el.remove();
|
||||
});
|
||||
document.querySelectorAll('head style').forEach(function (el) { el.remove(); });
|
||||
document.querySelectorAll('body script[data-page-js]').forEach(function (el) { el.remove(); });
|
||||
}
|
||||
|
||||
function applyPageCss(items) {
|
||||
items.forEach(function (srcEl) {
|
||||
var el = srcEl.cloneNode(true);
|
||||
if (el.tagName === 'LINK') {
|
||||
el.setAttribute('data-page-css', '');
|
||||
} else {
|
||||
el.setAttribute('data-page-css', '');
|
||||
}
|
||||
document.head.appendChild(el);
|
||||
});
|
||||
}
|
||||
|
||||
function runPageScripts(items) {
|
||||
return items.reduce(function (chain, srcEl) {
|
||||
return chain.then(function () {
|
||||
return new Promise(function (resolve) {
|
||||
var s = document.createElement('script');
|
||||
s.setAttribute('data-page-js', '');
|
||||
var src = srcEl.getAttribute('src');
|
||||
if (src) {
|
||||
var bust = (src.indexOf('?') >= 0 ? '&' : '?') + '_turbo=' + Date.now();
|
||||
s.src = src + bust;
|
||||
s.async = false;
|
||||
s.onload = function () { resolve(); };
|
||||
s.onerror = function () { resolve(); };
|
||||
document.body.appendChild(s);
|
||||
} else {
|
||||
s.textContent = srcEl.textContent;
|
||||
document.body.appendChild(s);
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}, Promise.resolve());
|
||||
}
|
||||
|
||||
function syncNavActive(doc, url) {
|
||||
var nav = document.getElementById('site-nav');
|
||||
var fetchedNav = doc.getElementById('site-nav');
|
||||
if (!nav || !fetchedNav) return;
|
||||
nav.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
|
||||
fetchedNav.querySelectorAll('a[href].active').forEach(function (fa) {
|
||||
var href = fa.getAttribute('href');
|
||||
if (!href) return;
|
||||
var local = nav.querySelector('a[href="' + href + '"]')
|
||||
|| nav.querySelector('a[href="' + new URL(href, window.location.origin).pathname + '"]');
|
||||
if (local) local.classList.add('active');
|
||||
});
|
||||
}
|
||||
|
||||
function setLoading(on) {
|
||||
var main = document.querySelector('.main');
|
||||
if (main) main.classList.toggle('nav-loading', on);
|
||||
}
|
||||
|
||||
function applyDocument(doc, url, fromPopstate) {
|
||||
var main = document.querySelector('.main');
|
||||
var fetchedMain = doc.querySelector('.main');
|
||||
if (!main || !fetchedMain) {
|
||||
window.location.href = url;
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
window.dispatchEvent(new Event('qihuo:page-leave'));
|
||||
removePageAssets();
|
||||
applyPageCss(collectPageCss(doc));
|
||||
main.innerHTML = fetchedMain.innerHTML;
|
||||
document.title = doc.title || document.title;
|
||||
syncNavActive(doc, url);
|
||||
|
||||
return runPageScripts(collectPageScripts(doc)).then(function () {
|
||||
currentUrl = normalizeUrl(url);
|
||||
if (!fromPopstate) {
|
||||
history.pushState({ turbo: true }, '', currentUrl);
|
||||
}
|
||||
setLoading(false);
|
||||
window.scrollTo(0, 0);
|
||||
if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
|
||||
});
|
||||
}
|
||||
|
||||
function navigateTo(url, opts) {
|
||||
opts = opts || {};
|
||||
var target = normalizeUrl(url);
|
||||
if (target === currentUrl && !opts.force) return Promise.resolve();
|
||||
|
||||
if (inflight) inflight.abort();
|
||||
var ctrl = new AbortController();
|
||||
inflight = ctrl;
|
||||
setLoading(true);
|
||||
|
||||
return fetchPage(target, ctrl.signal).then(function (doc) {
|
||||
if (ctrl.signal.aborted) return;
|
||||
inflight = null;
|
||||
applyDocument(doc, target, !!opts.fromPopstate);
|
||||
}).catch(function (err) {
|
||||
if (ctrl.signal.aborted) return;
|
||||
inflight = null;
|
||||
setLoading(false);
|
||||
window.location.href = target;
|
||||
});
|
||||
}
|
||||
|
||||
function prefetchPage(href) {
|
||||
var target = normalizeUrl(href);
|
||||
if (target === currentUrl || pageCache.has(target)) return;
|
||||
fetchPage(target).catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
window.qihuoNavigate = navigateTo;
|
||||
window.qihuoPrefetchPage = prefetchPage;
|
||||
|
||||
document.addEventListener('click', function (e) {
|
||||
var link = e.target.closest('#site-nav a[href]');
|
||||
if (!link || !shouldTurboClick(e, link)) return;
|
||||
e.preventDefault();
|
||||
var navEl = document.getElementById('site-nav');
|
||||
if (navEl) {
|
||||
navEl.querySelectorAll('a.active').forEach(function (a) { a.classList.remove('active'); });
|
||||
link.classList.add('active');
|
||||
}
|
||||
navigateTo(link.href);
|
||||
}, true);
|
||||
|
||||
window.addEventListener('popstate', function () {
|
||||
var target = normalizeUrl(window.location.href);
|
||||
if (target === currentUrl) return;
|
||||
navigateTo(target, { fromPopstate: true, force: true });
|
||||
});
|
||||
|
||||
if (window.qihuoEmitPageLoad) window.qihuoEmitPageLoad();
|
||||
})();
|
||||
Reference in New Issue
Block a user