diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 2b2336d..4eff66a 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -4488,68 +4488,121 @@ body.hub-page-ai #page-ai { overflow: hidden; } .funds-section-title { - margin: 0 0 10px; + margin: 0 0 4px; font-size: 0.95rem; font-weight: 600; } -.funds-accounts { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); - gap: 12px; +.funds-section-hint { + margin: 0 0 14px; + font-size: 0.75rem; + color: var(--muted); } -.funds-ac-card { - background: var(--panel); - border: 1px solid var(--border-soft); - border-radius: var(--radius); - padding: 12px 14px; +.funds-accounts { + display: flex; + flex-wrap: wrap; + gap: 18px 22px; + justify-content: flex-start; + padding: 4px 0 8px; +} +.funds-ac-item { display: flex; flex-direction: column; - gap: 8px; -} -.funds-ac-card.is-off { - opacity: 0.72; -} -.funds-ac-head { - display: flex; align-items: center; - justify-content: space-between; gap: 8px; + width: 118px; + flex: 0 0 auto; } -.funds-ac-head h3 { - margin: 0; - font-size: 0.92rem; +.funds-ac-circle { + position: relative; + width: 112px; + height: 112px; + padding: 0; + border: 2px solid var(--border-soft); + border-radius: 50%; + background: var(--panel); + cursor: pointer; + overflow: hidden; + transition: border-color 0.15s, box-shadow 0.15s, transform 0.12s; } -.funds-ac-badge { - font-size: 0.68rem; - padding: 2px 8px; - border-radius: 999px; - border: 1px solid var(--border-soft); - color: var(--muted); +.funds-ac-circle:hover:not(:disabled) { + border-color: var(--accent, #3b82f6); + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.12); + transform: translateY(-1px); } -.funds-ac-stats { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 6px 10px; - font-size: 0.78rem; +.funds-ac-circle:focus-visible { + outline: 2px solid var(--accent, #3b82f6); + outline-offset: 3px; } -.funds-ac-stats .k { - color: var(--muted); - margin-right: 6px; +.funds-ac-circle.is-off { + opacity: 0.62; + cursor: default; } -.funds-ac-stats .v { - font-variant-numeric: tabular-nums; +.funds-ac-circle.is-off:hover { + transform: none; + box-shadow: none; + border-color: var(--border-soft); } -.funds-ac-chart { - height: 72px; - min-height: 72px; - border-radius: 6px; +.funds-ac-circle-chart { + position: absolute; + inset: 6px; + border-radius: 50%; + overflow: hidden; background: var(--inset-surface); - font-size: 0.72rem; + font-size: 0.68rem; color: var(--muted); display: flex; align-items: center; justify-content: center; + text-align: center; + line-height: 1.25; + padding: 4px; +} +.funds-ac-circle-overlay { + position: absolute; + inset: 0; + border-radius: 50%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 2px; + padding: 10px; + background: radial-gradient(circle at 50% 55%, rgba(11, 14, 24, 0.55) 0%, rgba(11, 14, 24, 0.82) 68%); + pointer-events: none; +} +html[data-theme="light"] .funds-ac-circle-overlay { + background: radial-gradient(circle at 50% 55%, rgba(240, 244, 249, 0.45) 0%, rgba(240, 244, 249, 0.88) 68%); +} +.funds-ac-circle-name { + font-size: 0.72rem; + font-weight: 600; + letter-spacing: 0.02em; + max-width: 88px; overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.funds-ac-circle-amt { + font-size: 0.78rem; + font-weight: 600; + font-variant-numeric: tabular-nums; + color: var(--text); +} +.funds-ac-circle-badge { + font-size: 0.66rem; + padding: 2px 8px; + border-radius: 999px; + border: 1px solid var(--border-soft); + color: var(--muted); + text-align: center; + max-width: 100%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} +.funds-ac-circle-badge.is-ok { + color: var(--green); + border-color: rgba(34, 197, 94, 0.35); } .funds-empty { color: var(--muted); @@ -4557,6 +4610,100 @@ body.hub-page-ai #page-ai { padding: 12px 0; } +.funds-fullscreen { + position: fixed; + inset: 0; + z-index: 160; + background: var(--fs-scrim); + backdrop-filter: blur(6px); + overflow: auto; + padding: 16px 20px 24px; +} +.funds-fullscreen.hidden { + display: none !important; +} +.funds-fs-backdrop { + position: fixed; + inset: 0; + z-index: 0; + border: none; + padding: 0; + margin: 0; + background: transparent; + cursor: pointer; +} +.funds-fs-panel { + position: relative; + z-index: 1; + max-width: min(1200px, 96vw); + margin: 0 auto; + background: var(--panel); + border: 1px solid var(--border-soft); + border-radius: var(--radius); + padding: 16px 18px 20px; + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28); +} +.funds-fs-head { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; + margin-bottom: 14px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-soft); +} +.funds-fs-title { + margin: 0; + font-size: 1.15rem; + font-weight: 600; +} +.funds-fs-sub { + margin: 4px 0 0; + font-size: 0.76rem; + color: var(--muted); +} +.funds-fs-summary { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); + gap: 10px; + margin-bottom: 14px; +} +.funds-fs-stat { + background: var(--inset-surface); + border: 1px solid var(--border-soft); + border-radius: 8px; + padding: 10px 12px; + display: flex; + flex-direction: column; + gap: 4px; +} +.funds-fs-stat .k { + font-size: 0.72rem; + color: var(--muted); +} +.funds-fs-stat .v { + font-size: 1rem; + font-weight: 600; + font-variant-numeric: tabular-nums; +} +.funds-fs-stat .v.pos { + color: var(--green); +} +.funds-fs-stat .v.neg { + color: var(--red); +} +.funds-fs-chart-host { + height: min(52vh, 420px); + min-height: 260px; + border: 1px solid var(--border-soft); + border-radius: var(--radius); + background: var(--chart-surface, var(--panel)); + overflow: hidden; +} +body.funds-fullscreen-open { + overflow: hidden; +} + /* —— 币种档案 —— */ .archive-toolbar { flex-wrap: wrap; diff --git a/manual_trading_hub/static/funds.js b/manual_trading_hub/static/funds.js index 5766263..adb372a 100644 --- a/manual_trading_hub/static/funds.js +++ b/manual_trading_hub/static/funds.js @@ -15,10 +15,26 @@ const elAccounts = document.getElementById("funds-accounts"); const elBtnRefresh = document.getElementById("funds-btn-refresh"); + const elFs = document.getElementById("funds-fullscreen"); + const elFsBackdrop = document.getElementById("funds-fs-backdrop"); + const elFsClose = document.getElementById("funds-fs-close"); + const elFsTitle = document.getElementById("funds-fs-title"); + const elFsSub = document.getElementById("funds-fs-sub"); + const elFsTotal = document.getElementById("funds-fs-total"); + const elFsFunding = document.getElementById("funds-fs-funding"); + const elFsTrading = document.getElementById("funds-fs-trading"); + const elFsDelta = document.getElementById("funds-fs-delta"); + const elFsDd = document.getElementById("funds-fs-dd"); + const elFsChartHost = document.getElementById("funds-fs-chart"); + let chart = null; let lineSeries = null; + let fsChart = null; + let fsLineSeries = null; let inited = false; let loading = false; + let lastOverview = null; + let fsAccountKey = ""; function fmt(n, d) { if (n == null || n === "" || !Number.isFinite(Number(n))) return "—"; @@ -64,6 +80,15 @@ if (elChartHost) elChartHost.innerHTML = ""; } + function destroyFsChart() { + if (fsChart) { + fsChart.remove(); + fsChart = null; + fsLineSeries = null; + } + if (elFsChartHost) elFsChartHost.innerHTML = ""; + } + function chartPalette() { const light = document.documentElement.getAttribute("data-theme") === "light"; return light @@ -71,11 +96,9 @@ : { bg: "#0b0e18", text: "#9aa4b8", border: "#2a3348", line: "#3b82f6" }; } - function ensureChart() { - if (!elChartHost || !window.LightweightCharts) return; - if (chart) return; + function createAreaChart(host) { const p = chartPalette(); - chart = LightweightCharts.createChart(elChartHost, { + const c = LightweightCharts.createChart(host, { layout: { background: { color: p.bg }, textColor: p.text }, grid: { vertLines: { color: p.border, visible: true }, @@ -87,7 +110,7 @@ handleScroll: { mouseWheel: true, pressedMouseMove: true }, handleScale: { axisPressedMouseMove: true, mouseWheel: true, pinch: true }, }); - lineSeries = chart.addAreaSeries({ + const s = c.addAreaSeries({ lineColor: p.line, topColor: p.line + "44", bottomColor: p.line + "08", @@ -95,11 +118,28 @@ priceFormat: { type: "price", precision: 2, minMove: 0.01 }, }); new ResizeObserver(function () { - if (chart && elChartHost) { - chart.applyOptions({ width: elChartHost.clientWidth, height: elChartHost.clientHeight }); + if (c && host) { + c.applyOptions({ width: host.clientWidth, height: host.clientHeight }); } - }).observe(elChartHost); - chart.applyOptions({ width: elChartHost.clientWidth, height: elChartHost.clientHeight }); + }).observe(host); + c.applyOptions({ width: host.clientWidth, height: host.clientHeight }); + return { chart: c, series: s }; + } + + function ensureChart() { + if (!elChartHost || !window.LightweightCharts) return; + if (chart) return; + const built = createAreaChart(elChartHost); + chart = built.chart; + lineSeries = built.series; + } + + function ensureFsChart() { + if (!elFsChartHost || !window.LightweightCharts) return; + if (fsChart) return; + const built = createAreaChart(elFsChartHost); + fsChart = built.chart; + fsLineSeries = built.series; } function renderMiniChart(host, series) { @@ -120,7 +160,12 @@ handleScroll: false, handleScale: false, }); - const s = mini.addLineSeries({ color: p.line, lineWidth: 1.5 }); + const s = mini.addAreaSeries({ + lineColor: p.line, + topColor: p.line + "55", + bottomColor: p.line + "05", + lineWidth: 1.5, + }); s.setData(data); mini.timeScale().fitContent(); const w = host.clientWidth; @@ -128,6 +173,21 @@ if (w > 0 && h > 0) mini.applyOptions({ width: w, height: h }); } + function accountStatus(ac) { + if (!ac || !ac.monitored) return { text: "未监控", cls: "" }; + if (ac.data_ok) return { text: "已监控", cls: "is-ok" }; + return { text: "余额未齐", cls: "" }; + } + + function monitoredLabel(ac) { + return ac && ac.monitored ? "暂无曲线" : "未参与"; + } + + function shortAmt(ac) { + if (!ac || !ac.monitored || !ac.data_ok) return "—"; + return fmt(ac.total_usdt, 0) + " U"; + } + function renderAccounts(accounts) { if (!elAccounts) return; if (!accounts || !accounts.length) { @@ -137,66 +197,139 @@ elAccounts.innerHTML = accounts .map(function (ac) { const monitored = !!ac.monitored; - const cls = monitored ? "" : " is-off"; - const total = monitored && ac.data_ok ? fmt(ac.total_usdt, 2) + " U" : "—"; - const funding = monitored && ac.funding_usdt != null ? fmt(ac.funding_usdt, 2) : "—"; - const trading = monitored && ac.trading_usdt != null ? fmt(ac.trading_usdt, 2) : "—"; - const dd = ac.drawdown || {}; - const ddU = dd.max_drawdown_u != null ? fmt(dd.max_drawdown_u, 2) + " U" : "—"; - const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—"; - const status = monitored ? (ac.data_ok ? "已监控" : "余额未齐") : "未监控"; + const offCls = monitored ? "" : " is-off"; + const st = accountStatus(ac); + const clickable = monitored ? "" : ' disabled aria-disabled="true"'; return ( - '
' + + '
" + "" + + '' + + st.text + + "" + + "" ); }) .join(""); - elAccounts.querySelectorAll(".funds-ac-card").forEach(function (card, idx) { + elAccounts.querySelectorAll(".funds-ac-circle").forEach(function (btn, idx) { const ac = accounts[idx]; - const host = card.querySelector(".funds-ac-chart"); + const host = btn.querySelector(".funds-ac-circle-chart"); if (ac && ac.monitored && host) { renderMiniChart(host, ac.series || []); } else if (host) { host.textContent = monitoredLabel(ac); } + if (ac && ac.monitored) { + btn.addEventListener("click", function () { + openAccountFullscreen(ac.key); + }); + } }); } - function monitoredLabel(ac) { - return ac && ac.monitored ? "暂无曲线" : "未参与合计"; + function findAccount(key) { + const accounts = (lastOverview && lastOverview.accounts) || []; + return accounts.find(function (ac) { + return String(ac.key || "") === String(key || ""); + }); + } + + function closeAccountFullscreen() { + fsAccountKey = ""; + destroyFsChart(); + if (elFs) { + elFs.classList.add("hidden"); + elFs.setAttribute("aria-hidden", "true"); + } + document.body.classList.remove("funds-fullscreen-open"); + } + + function openAccountFullscreen(key) { + const ac = findAccount(key); + if (!ac || !ac.monitored) return; + fsAccountKey = String(key || ""); + const dd = ac.drawdown || {}; + const meta = lastOverview || {}; + if (elFsTitle) elFsTitle.textContent = ac.name || ac.key || "—"; + if (elFsSub) { + const parts = [ + "资金户 + 交易户(不含浮盈)", + "交易日 " + (meta.trading_day || "—"), + "自 " + (meta.history_start_day || "2026-06-09") + " 起", + ]; + elFsSub.textContent = parts.join(" · "); + } + if (elFsTotal) { + elFsTotal.textContent = + ac.data_ok && ac.total_usdt != null ? fmt(ac.total_usdt, 2) + " U" : "—"; + } + if (elFsFunding) { + elFsFunding.textContent = + ac.funding_usdt != null ? fmt(ac.funding_usdt, 2) + " U" : "—"; + } + if (elFsTrading) { + elFsTrading.textContent = + ac.trading_usdt != null ? fmt(ac.trading_usdt, 2) + " U" : "—"; + } + if (elFsDelta) { + elFsDelta.textContent = fmtDelta(ac.day_delta_usdt); + elFsDelta.className = "v " + deltaClass(ac.day_delta_usdt); + } + if (elFsDd) { + const ddU = dd.max_drawdown_u != null ? fmt(dd.max_drawdown_u, 2) + " U" : "—"; + const ddPct = dd.max_drawdown_pct != null ? fmt(dd.max_drawdown_pct, 2) + "%" : "—"; + elFsDd.textContent = ddU + " / " + ddPct; + } + if (elFs) { + elFs.classList.remove("hidden"); + elFs.setAttribute("aria-hidden", "false"); + document.body.classList.add("funds-fullscreen-open"); + } + destroyFsChart(); + const pts = seriesToChartData(ac.series || []); + if (pts.length) { + ensureFsChart(); + if (fsLineSeries) { + fsLineSeries.setData(pts); + fsChart.timeScale().fitContent(); + } + requestAnimationFrame(function () { + if (fsChart && elFsChartHost) { + fsChart.applyOptions({ + width: elFsChartHost.clientWidth, + height: elFsChartHost.clientHeight, + }); + fsChart.timeScale().fitContent(); + } + }); + } else if (elFsChartHost) { + elFsChartHost.innerHTML = + '

暂无历史曲线,请保持监控板运行以积累快照

'; + } } function renderOverview(data) { + lastOverview = data; const totals = data.totals || {}; const dd = totals.drawdown || {}; if (elTotal) { @@ -235,6 +368,11 @@ } } renderAccounts(data.accounts || []); + if (fsAccountKey) { + const ac = findAccount(fsAccountKey); + if (ac && ac.monitored) openAccountFullscreen(fsAccountKey); + else closeAccountFullscreen(); + } } async function load() { @@ -259,8 +397,14 @@ function bind() { if (elBtnRefresh) elBtnRefresh.addEventListener("click", load); + if (elFsBackdrop) elFsBackdrop.addEventListener("click", closeAccountFullscreen); + if (elFsClose) elFsClose.addEventListener("click", closeAccountFullscreen); + document.addEventListener("keydown", function (ev) { + if (ev.key === "Escape" && fsAccountKey) closeAccountFullscreen(); + }); document.addEventListener("hub-theme-change", function () { destroyChart(); + destroyFsChart(); load(); }); } @@ -275,6 +419,7 @@ } function destroy() { + closeAccountFullscreen(); destroyChart(); } diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 4b2b623..e4f2963 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -357,9 +357,31 @@

分户资金

+

圆形为各交易所快照,点击查看资金曲线与回撤

+ +