feat(hub): show cached monitor board when returning from market area
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -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;
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user