feat(hub): dashboard SSE push, light-theme cards, simplify AI coach
Replace dashboard polling with backend SSE and snapshot refresh. Restyle for light/dark theme with soft card glow instead of neon. Remove Today's Summary from AI page; keep trading and general chat only. Update hub documentation. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,12 +1,13 @@
|
||||
/**
|
||||
* 中控数据看板:总览 / 分户 / 平仓明细,60s 自动刷新。
|
||||
* 中控数据看板:后端 SSE 推送版本号,前端拉快照刷新(无轮询闪烁)。
|
||||
*/
|
||||
(function () {
|
||||
const page = document.getElementById("page-dashboard");
|
||||
if (!page) return;
|
||||
|
||||
const POLL_MS = 60 * 1000;
|
||||
let timer = null;
|
||||
let dashEventSource = null;
|
||||
let dashReconnectTimer = null;
|
||||
let localDashVersion = 0;
|
||||
let inited = false;
|
||||
let loading = false;
|
||||
|
||||
@@ -191,10 +192,11 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchDashboard() {
|
||||
if (loading) return;
|
||||
async function fetchDashboardSnapshot(opts) {
|
||||
const options = opts || {};
|
||||
if (loading && !options.force) return;
|
||||
loading = true;
|
||||
setStatus("同步中…");
|
||||
if (!options.silent) setStatus("同步中…");
|
||||
try {
|
||||
const r = await fetch("/api/dashboard/daily", { credentials: "same-origin" });
|
||||
if (r.status === 401) {
|
||||
@@ -202,9 +204,12 @@
|
||||
return;
|
||||
}
|
||||
const data = await r.json();
|
||||
if (!data.ok) throw new Error(data.detail || data.msg || "加载失败");
|
||||
if (!data.ok) throw new Error(data.detail || data.msg || data.error || "加载失败");
|
||||
const ver = Number(data.dashboard_version) || 0;
|
||||
if (ver) localDashVersion = ver;
|
||||
renderPayload(data);
|
||||
setStatus(`每 ${(data.poll_interval_sec || 60)}s 自动刷新`);
|
||||
const sec = Number(data.poll_interval_sec) || 60;
|
||||
setStatus(options.silent ? `SSE 已连接 · 后台每 ${sec}s 聚合` : `已更新 · 后台每 ${sec}s 聚合`);
|
||||
} catch (e) {
|
||||
setStatus(String(e.message || e), true);
|
||||
} finally {
|
||||
@@ -212,31 +217,78 @@
|
||||
}
|
||||
}
|
||||
|
||||
function startPoll() {
|
||||
stopPoll();
|
||||
void fetchDashboard();
|
||||
timer = setInterval(fetchDashboard, POLL_MS);
|
||||
}
|
||||
|
||||
function stopPoll() {
|
||||
if (timer) {
|
||||
clearInterval(timer);
|
||||
timer = null;
|
||||
function closeDashboardStream() {
|
||||
if (dashEventSource) {
|
||||
dashEventSource.close();
|
||||
dashEventSource = null;
|
||||
}
|
||||
if (dashReconnectTimer) {
|
||||
clearTimeout(dashReconnectTimer);
|
||||
dashReconnectTimer = null;
|
||||
}
|
||||
}
|
||||
|
||||
function connectDashboardStream() {
|
||||
closeDashboardStream();
|
||||
dashEventSource = new EventSource("/api/dashboard/stream");
|
||||
dashEventSource.addEventListener("dashboard", (ev) => {
|
||||
try {
|
||||
const st = JSON.parse(ev.data || "{}");
|
||||
const ver = Number(st.dashboard_version) || 0;
|
||||
if (ver && ver !== localDashVersion) {
|
||||
void fetchDashboardSnapshot({ silent: true });
|
||||
} else if (st.aggregating) {
|
||||
setStatus("后台聚合中…");
|
||||
}
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
});
|
||||
dashEventSource.onerror = () => {
|
||||
closeDashboardStream();
|
||||
setStatus("SSE 断开,8s 后重连…", true);
|
||||
dashReconnectTimer = setTimeout(() => {
|
||||
if (inited) {
|
||||
connectDashboardStream();
|
||||
void fetchDashboardSnapshot({ silent: true });
|
||||
}
|
||||
}, 8000);
|
||||
};
|
||||
}
|
||||
|
||||
async function requestDashboardRefresh() {
|
||||
try {
|
||||
await fetch("/api/dashboard/refresh", { method: "POST", credentials: "same-origin" });
|
||||
} catch (_) {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
|
||||
function startLive() {
|
||||
void fetchDashboardSnapshot();
|
||||
connectDashboardStream();
|
||||
}
|
||||
|
||||
function stopLive() {
|
||||
closeDashboardStream();
|
||||
setStatus("");
|
||||
}
|
||||
|
||||
if (btnRefresh) {
|
||||
btnRefresh.addEventListener("click", () => void fetchDashboard());
|
||||
btnRefresh.addEventListener("click", () => {
|
||||
void requestDashboardRefresh();
|
||||
void fetchDashboardSnapshot({ force: true });
|
||||
});
|
||||
}
|
||||
|
||||
window.hubDashboardPage = {
|
||||
init() {
|
||||
if (!inited) inited = true;
|
||||
startPoll();
|
||||
inited = true;
|
||||
startLive();
|
||||
},
|
||||
destroy() {
|
||||
stopPoll();
|
||||
setStatus("");
|
||||
inited = false;
|
||||
stopLive();
|
||||
},
|
||||
};
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user