/** * 账户流水:资金/交易 Tab · 分页 10 · SSE 自动刷新 · 时间窗跟随顶栏预设. */ (function (global) { const PAGE_SIZE = 10; let account = "funding"; let page = 1; let pages = 0; let localVersion = 0; let es = null; let reconnectTimer = null; let loading = false; let booted = false; function root() { const active = document.querySelector('.embed-tab-pane.is-active-pane [data-account-ledger="1"]'); if (active) return active; return document.getElementById("account-ledger-root"); } function $(id) { const r = root(); return (r && r.querySelector("#" + id)) || document.getElementById(id); } function escapeHtml(s) { return String(s == null ? "" : s) .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function listWindowQs() { if (typeof global.listWindowQueryString === "function") { const q = global.listWindowQueryString(); return q ? (q.charAt(0) === "?" ? q.slice(1) : q) : ""; } try { return new URLSearchParams(location.search).toString(); } catch (_) { return ""; } } function fmtBj(ms) { const n = Number(ms); if (!Number.isFinite(n) || n <= 0) return "—"; try { const d = new Date(n); const parts = new Intl.DateTimeFormat("zh-CN", { timeZone: "Asia/Shanghai", year: "numeric", month: "2-digit", day: "2-digit", hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false, }).formatToParts(d); const get = (t) => (parts.find((p) => p.type === t) || {}).value || ""; return ( get("year") + "-" + get("month") + "-" + get("day") + " " + get("hour") + ":" + get("minute") + ":" + get("second") ); } catch (_) { return "—"; } } function fmtAmt(v) { const n = Number(v); if (!Number.isFinite(n)) return "—"; const cls = n > 0 ? "account-ledger-amt-pos" : n < 0 ? "account-ledger-amt-neg" : ""; const sign = n > 0 ? "+" : ""; return '' + sign + n.toFixed(6).replace(/\.?0+$/, "") + ""; } function fmtBal(v) { if (v == null || v === "") return "—"; const n = Number(v); if (!Number.isFinite(n)) return "—"; return n.toFixed(6).replace(/\.?0+$/, ""); } function setStatus(msg, isErr) { const el = $("account-ledger-status"); if (!el) return; el.textContent = msg || ""; el.style.color = isErr ? "#f07178" : ""; } function setSyncLabel(data) { const el = $("account-ledger-sync"); if (!el) return; const ts = data && data.last_sync_at; if (!ts) { el.textContent = "尚未同步"; return; } el.textContent = "同步 " + fmtBj(Number(ts) * 1000); } function renderRows(items) { const tbody = $("account-ledger-tbody"); if (!tbody) return; if (!items || !items.length) { tbody.innerHTML = '当前时间窗暂无流水'; return; } tbody.innerHTML = items .map(function (it) { const note = [it.symbol, it.note, it.raw_type].filter(Boolean).join(" · "); return ( "" + "" + escapeHtml(fmtBj(it.ts_ms)) + "" + "" + escapeHtml(it.ccy || "") + "" + "" + escapeHtml(it.kind_label || it.kind || "") + "" + "" + fmtAmt(it.amount) + "" + "" + escapeHtml(fmtBal(it.balance_after)) + "" + "" + escapeHtml(note || "—") + "" + "" ); }) .join(""); } function renderPager(data) { pages = Number(data.pages || 0); page = Number(data.page || 1); const info = $("account-ledger-page-info"); const prev = $("account-ledger-prev"); const next = $("account-ledger-next"); if (info) { info.textContent = "第 " + page + " / " + (pages || 1) + " 页 · 共 " + (data.total || 0) + " 条 · 每页 " + PAGE_SIZE; } if (prev) prev.disabled = page <= 1; if (next) next.disabled = !pages || page >= pages; } async function loadList(opts) { const r = root(); if (!r) return; if (loading) return; loading = true; const force = opts && opts.force; try { if (!force) setStatus("加载中…"); const qs = new URLSearchParams(listWindowQs()); qs.set("account", account); qs.set("page", String(page)); const res = await fetch("/api/account_ledger?" + qs.toString(), { credentials: "same-origin", }); const data = await res.json().catch(function () { return {}; }); if (!res.ok || data.ok === false) { throw new Error(data.msg || res.statusText || "加载失败"); } if (data.ledger_version != null) localVersion = Number(data.ledger_version) || localVersion; renderRows(data.items || []); renderPager(data); setSyncLabel(data); const winLabel = (data.window && data.window.label) || ""; const err = data.last_error ? " · 同步提示: " + data.last_error : ""; setStatus( (winLabel ? "时间窗 " + winLabel + " · " : "") + (account === "trading" ? "交易账户" : "资金账户") + err, !!data.last_error ); } catch (e) { setStatus(e.message || String(e), true); } finally { loading = false; } } async function refreshNow() { setStatus("正在从交易所同步…"); try { const res = await fetch("/api/account_ledger/refresh", { method: "POST", credentials: "same-origin", headers: { "Content-Type": "application/json" }, body: "{}", }); const data = await res.json().catch(function () { return {}; }); if (!res.ok || data.ok === false) { throw new Error(data.msg || "同步失败"); } await loadList({ force: true }); } catch (e) { setStatus(e.message || String(e), true); } } function bindUi() { const r = root(); if (!r || r.getAttribute("data-ledger-bound") === "1") return; r.setAttribute("data-ledger-bound", "1"); r.querySelectorAll(".account-ledger-tab").forEach(function (btn) { btn.addEventListener("click", function () { const acc = btn.getAttribute("data-ledger-account") || "funding"; if (acc === account) return; account = acc; page = 1; r.querySelectorAll(".account-ledger-tab").forEach(function (b) { const on = b.getAttribute("data-ledger-account") === account; b.classList.toggle("active", on); b.setAttribute("aria-selected", on ? "true" : "false"); }); loadList(); }); }); const prev = $("account-ledger-prev"); const next = $("account-ledger-next"); const ref = $("account-ledger-refresh"); if (prev) prev.addEventListener("click", function () { if (page > 1) { page -= 1; loadList(); } }); if (next) next.addEventListener("click", function () { if (!pages || page < pages) { page += 1; loadList(); } }); if (ref) ref.addEventListener("click", refreshNow); } function connectSse() { if (es) { try { es.close(); } catch (_) {} es = null; } if (typeof EventSource === "undefined") return; try { es = new EventSource("/api/account_ledger/stream"); es.addEventListener("ledger", function (ev) { let data = {}; try { data = JSON.parse(ev.data || "{}"); } catch (_) {} const ver = Number(data.ledger_version || 0); if (ver && ver !== localVersion) { localVersion = ver; loadList({ force: true }); } }); es.onerror = function () { try { es.close(); } catch (_) {} es = null; if (reconnectTimer) clearTimeout(reconnectTimer); reconnectTimer = setTimeout(connectSse, 5000); }; } catch (_) {} } function boot() { const r = root(); if (!r) return; bindUi(); if (!booted) { booted = true; connectSse(); } loadList(); } function onTabActivated(tab) { if (tab !== "account_ledger") return; boot(); } global.AccountLedgerPage = { boot: boot, onTabActivated: onTabActivated, reload: function () { page = 1; loadList(); }, }; document.addEventListener("DOMContentLoaded", function () { const page = (document.body && document.body.getAttribute("data-page")) || (document.body && document.body.getAttribute("data-initial-tab")) || ""; if (page === "account_ledger" || root()) { // embed 延后到 tab 激活;独立页直接 boot if (!document.body || document.body.getAttribute("data-embed-shell") !== "1") { boot(); } else if (page === "account_ledger") { boot(); } } }); document.addEventListener("instance-embed-tab-activated", function (ev) { const tab = ev && ev.detail && ev.detail.tab; onTabActivated(tab); }); })(window);