feat(hub): show cached monitor board when returning from market area

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-03 21:40:29 +08:00
parent 7900a68d07
commit b5f66a0db2
3 changed files with 121 additions and 20 deletions
+11
View File
@@ -160,6 +160,17 @@ a:hover {
background: rgba(255, 77, 109, 0.1); background: rgba(255, 77, 109, 0.1);
} }
.sys-pill.syncing {
opacity: 0.85;
animation: sys-pill-pulse 1.2s ease-in-out infinite;
}
@keyframes sys-pill-pulse {
50% {
opacity: 0.55;
}
}
.top-nav { .top-nav {
display: flex; display: flex;
gap: 4px; gap: 4px;
+109 -19
View File
@@ -6,6 +6,10 @@
let tpslPending = null; let tpslPending = null;
let lastMonitorRows = []; let lastMonitorRows = [];
let expandedExchangeId = sessionStorage.getItem("hub_expanded_ex") || ""; let expandedExchangeId = sessionStorage.getItem("hub_expanded_ex") || "";
const HUB_MONITOR_BOARD_CACHE_KEY = "hub_monitor_board_v1";
const HUB_MONITOR_CACHE_MAX_AGE_MS = 6 * 60 * 60 * 1000;
let monitorBoardFetchSeq = 0;
let lastMonitorBoardUpdatedAt = "";
async function apiFetch(url, opts) { async function apiFetch(url, opts) {
const r = await fetch(url, opts); const r = await fetch(url, opts);
@@ -344,11 +348,78 @@
monitorTimer = null; monitorTimer = null;
} }
function saveMonitorBoardCache(rows, updatedAt) {
try {
sessionStorage.setItem(
HUB_MONITOR_BOARD_CACHE_KEY,
JSON.stringify({
version: 1,
updated_at: updatedAt || "",
rows: rows || [],
saved_at: Date.now(),
})
);
} catch (_) {}
}
function loadMonitorBoardFromCache() {
try {
const raw = sessionStorage.getItem(HUB_MONITOR_BOARD_CACHE_KEY);
if (!raw) return null;
const data = JSON.parse(raw);
if (!data || !Array.isArray(data.rows) || !data.rows.length) return null;
const age = Date.now() - Number(data.saved_at || 0);
if (!Number.isFinite(age) || age > HUB_MONITOR_CACHE_MAX_AGE_MS) {
sessionStorage.removeItem(HUB_MONITOR_BOARD_CACHE_KEY);
return null;
}
return data;
} catch (_) {
return null;
}
}
function restoreMonitorBoardFromCache() {
const cached = loadMonitorBoardFromCache();
if (!cached) return false;
lastMonitorRows = cached.rows;
lastMonitorBoardUpdatedAt = cached.updated_at || "";
applyMonitorBoardUi(cached.rows, lastMonitorBoardUpdatedAt, { stale: true });
return true;
}
function applyMonitorBoardUi(rows, updatedAt, opts) {
const options = opts || {};
const tsRaw = updatedAt || lastMonitorBoardUpdatedAt || "";
if (updatedAt) lastMonitorBoardUpdatedAt = updatedAt;
const online = (rows || []).filter((x) => x.http_ok && (x.agent || {}).ok !== false).length;
const pill = document.getElementById("sys-status");
if (pill) {
pill.textContent = rows.length ? `LINK ${online}/${rows.length}` : "NO DATA";
pill.classList.toggle("warn", rows.length && online < rows.length);
if (options.stale) pill.classList.add("syncing");
else pill.classList.remove("syncing");
}
const upd = document.getElementById("monitor-updated");
if (upd) {
const ts = tsRaw.replace("T", " ");
upd.textContent = options.stale
? ts
? `缓存 ${ts} · 刷新中…`
: "刷新中…"
: ts
? `UPD ${ts}`
: "";
}
renderMonitorGrid(rows || []);
}
function startMonitorPoll() { function startMonitorPoll() {
stopMonitorPoll(); stopMonitorPoll();
loadMonitorBoard(); const hadCache = restoreMonitorBoardFromCache();
loadMonitorBoard({ background: hadCache });
if (document.getElementById("auto-monitor").checked) { if (document.getElementById("auto-monitor").checked) {
monitorTimer = setInterval(loadMonitorBoard, 5000); monitorTimer = setInterval(() => loadMonitorBoard({ background: true }), 5000);
} }
} }
@@ -502,32 +573,33 @@
return `<span class="pos-breakeven-badge">已保本</span>`; return `<span class="pos-breakeven-badge">已保本</span>`;
} }
async function loadMonitorBoard() { async function loadMonitorBoard(opts) {
const options = opts || {};
const background = !!options.background;
const box = document.getElementById("monitor-grid"); const box = document.getElementById("monitor-grid");
const showLoading = !lastMonitorRows.length; const seq = ++monitorBoardFetchSeq;
const showLoading = !background && !lastMonitorRows.length;
if (showLoading && box) { if (showLoading && box) {
box.innerHTML = box.innerHTML =
'<div class="board-loading"><span class="board-loading-spin" aria-hidden="true"></span>正在聚合四所数据…</div>'; '<div class="board-loading"><span class="board-loading-spin" aria-hidden="true"></span>正在聚合四所数据…</div>';
} else if (background && lastMonitorRows.length) {
applyMonitorBoardUi(lastMonitorRows, null, { stale: true });
} }
try { try {
const r = await apiFetch("/api/monitor/board"); const r = await apiFetch("/api/monitor/board");
const data = await r.json(); const data = await r.json();
if (seq !== monitorBoardFetchSeq) return;
lastMonitorRows = data.rows || []; lastMonitorRows = data.rows || [];
const online = lastMonitorRows.filter( saveMonitorBoardCache(lastMonitorRows, data.updated_at);
(x) => x.http_ok && (x.agent || {}).ok !== false applyMonitorBoardUi(lastMonitorRows, data.updated_at, { stale: false });
).length;
const pill = document.getElementById("sys-status");
if (pill) {
pill.textContent = lastMonitorRows.length
? `LINK ${online}/${lastMonitorRows.length}`
: "NO DATA";
pill.classList.toggle("warn", lastMonitorRows.length && online < lastMonitorRows.length);
}
document.getElementById("monitor-updated").textContent =
"UPD " + (data.updated_at || "").replace("T", " ");
renderMonitorGrid(lastMonitorRows);
} catch (e) { } catch (e) {
box.innerHTML = `<div class="err">${esc(e)}</div>`; if (seq !== monitorBoardFetchSeq) return;
if (background && lastMonitorRows.length) {
showToast("监控数据刷新失败,仍显示上次缓存", true);
applyMonitorBoardUi(lastMonitorRows, null, { stale: false });
return;
}
if (box) box.innerHTML = `<div class="err">${esc(e)}</div>`;
} }
} }
@@ -1826,12 +1898,30 @@
initFullscreen(); initFullscreen();
initMobileLayout(); initMobileLayout();
function initShellNav() {
document.querySelectorAll(".top-nav a[href^='/']").forEach((a) => {
a.addEventListener("click", (ev) => {
const href = a.getAttribute("href");
if (!href || ev.ctrlKey || ev.metaKey || ev.shiftKey || ev.altKey) return;
ev.preventDefault();
const path = href.split("?")[0];
if (path === window.location.pathname) {
setActiveNav();
return;
}
history.pushState({}, "", href);
setActiveNav();
});
});
window.addEventListener("popstate", setActiveNav);
}
initAuth().then((ok) => { initAuth().then((ok) => {
if (!ok) return; if (!ok) return;
initShellNav();
setActiveNav(); setActiveNav();
if (currentPage() === "settings") { if (currentPage() === "settings") {
loadSettings().catch(() => {}); loadSettings().catch(() => {});
} }
window.addEventListener("popstate", setActiveNav);
}); });
})(); })();
+1 -1
View File
@@ -245,6 +245,6 @@
<div id="toast"></div> <div id="toast"></div>
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script> <script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
<script src="/assets/chart.js?v=20260528-hub-no-30m"></script> <script src="/assets/chart.js?v=20260528-hub-no-30m"></script>
<script src="/assets/app.js?v=20260528-hub-tpsl-fix"></script> <script src="/assets/app.js?v=20260603-hub-monitor-cache"></script>
</body> </body>
</html> </html>