From 879ea5e228d35518cfc03d136105929f8675881b Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 11 Jun 2026 18:18:08 +0800 Subject: [PATCH] feat(hub): stats table and adaptive trades list height on archive page Show period stats in a table; limit trades to 10 rows when chart is open and fill viewport when collapsed. Co-authored-by: Cursor --- manual_trading_hub/static/app.css | 53 +++++++++++-- manual_trading_hub/static/archive.js | 108 +++++++++++++++++---------- manual_trading_hub/static/index.html | 2 +- 3 files changed, 116 insertions(+), 47 deletions(-) diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index 8a3f04b..8aca927 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -5569,6 +5569,7 @@ body.funds-fullscreen-open { gap: 10px; padding: 12px; min-height: 100%; + min-height: 0; } .archive-period-bar { flex-wrap: wrap; @@ -5606,20 +5607,43 @@ body.funds-fullscreen-open { font-size: 0.82rem; } .archive-stats-bar { - padding: 10px 12px; + padding: 0; border-radius: 8px; border: 1px solid var(--border-soft); background: var(--inset-surface); font-size: 0.82rem; color: var(--text); line-height: 1.45; - display: flex; - flex-direction: column; - gap: 4px; + overflow: auto; } -.archive-stats-sub { - font-size: 0.78rem; +.archive-stats-table { + width: 100%; + border-collapse: collapse; + font-size: 0.8rem; +} +.archive-stats-table th, +.archive-stats-table td { + padding: 7px 10px; + border-bottom: 1px solid var(--border-soft); + text-align: left; + white-space: nowrap; +} +.archive-stats-table th { color: var(--muted); + font-weight: 500; + background: var(--inset-surface); +} +.archive-stats-table tr:last-child td { + border-bottom: none; +} +.archive-stats-table tr.archive-stats-total td { + background: color-mix(in srgb, var(--accent) 6%, transparent); +} +.archive-stats-table .pnl-pos { + color: #22c55e; +} +.archive-stats-table .pnl-neg { + color: #ef4444; } .archive-acc-section { border: 1px solid var(--border-soft); @@ -5648,10 +5672,24 @@ body.funds-fullscreen-open { .archive-chart-section > :not(summary) { padding: 0 10px 10px; } +.archive-trades-section { + flex: 1 1 auto; + min-height: 0; + display: flex; + flex-direction: column; +} .archive-trades-section > .archive-trades { border: none; border-radius: 0; - max-height: 380px; + flex: 1 1 auto; + min-height: 0; + max-height: none; +} +#page-archive.is-chart-open .archive-trades-section > .archive-trades { + flex: 0 0 auto; +} +#page-archive:not(.is-chart-open) .archive-trades-section > .archive-trades { + min-height: calc(100vh - 420px); } .archive-chart-toolbar { flex-wrap: wrap; @@ -5712,7 +5750,6 @@ body.funds-fullscreen-open { } .archive-trades { overflow: auto; - max-height: 380px; border: 1px solid var(--border-soft); border-radius: var(--radius); background: var(--panel); diff --git a/manual_trading_hub/static/archive.js b/manual_trading_hub/static/archive.js index 1ea47e3..ed3effd 100644 --- a/manual_trading_hub/static/archive.js +++ b/manual_trading_hub/static/archive.js @@ -35,7 +35,9 @@ const elChartHost = document.getElementById("archive-chart"); const elMarkAuto = document.getElementById("archive-mark-auto"); const elTrades = document.getElementById("archive-trades"); + const elTradesSection = document.getElementById("archive-trades-section"); const ARCHIVE_MARK_AUTO_KEY = "hubArchiveMarkAuto"; + const TRADES_VISIBLE_ROWS_CHART_OPEN = 10; const TF_MS = { "5m": 5 * 60_000, @@ -269,12 +271,30 @@ return !!(elChartSection && elChartSection.open); } + function syncTradesLayout() { + const open = isChartOpen(); + if (page) page.classList.toggle("is-chart-open", open); + if (elTradesSection) elTradesSection.classList.toggle("is-chart-open", open); + if (!elTrades) return; + if (open) { + const head = elTrades.querySelector("thead tr"); + const row = elTrades.querySelector("tbody tr"); + if (head && row) { + const h = head.offsetHeight + row.offsetHeight * TRADES_VISIBLE_ROWS_CHART_OPEN; + elTrades.style.maxHeight = h + "px"; + } + } else { + elTrades.style.maxHeight = ""; + } + } + function setChartOpen(on) { if (!elChartSection) return; elChartSection.open = !!on; if (elBtnChartToggle) { elBtnChartToggle.classList.toggle("is-active", !!on); } + syncTradesLayout(); if (!on) { destroyChart(); return; @@ -359,50 +379,59 @@ if (cur) elExchange.value = cur; } + function renderStatsRow(label, e, isTotal) { + const openN = e.open_count || 0; + const sickN = e.sick_count || 0; + const sickShare = e.sick_pct != null ? e.sick_pct : openN ? Math.round((sickN / openN) * 1000) / 10 : 0; + const rowCls = isTotal ? ' class="archive-stats-total"' : ""; + return ( + "" + + (isTotal ? "" + esc(label) + "" : esc(label)) + + "" + + openN + + "" + + sickN + + "" + + sickShare + + "%" + + fmtPnlStat(e.pnl_total) + + "" + + fmtPnlStat(e.pnl_ex_sick) + + "" + ); + } + function renderStats() { if (!elStats) return; const st = dailyStats || { open_count: 0, by_exchange: {} }; const label = periodLabel || "本日"; - const sickPct = st.sick_pct != null ? st.sick_pct : 0; - let html = - '
' + - esc(label) + - " · 开仓 " + - (st.open_count || 0) + - " 次 · 犯病 " + - (st.sick_count || 0) + - " 次(" + - sickPct + - "%) · 盈亏 " + - fmtPnlStat(st.pnl_total) + - " · 剔除犯病盈亏 " + - fmtPnlStat(st.pnl_ex_sick) + - "
"; const byEx = st.by_exchange || {}; const exKeys = Object.keys(byEx).sort(); - if (exKeys.length) { - const exParts = exKeys.map(function (ex) { - const e = byEx[ex] || {}; - const sickN = e.sick_count || 0; - const openN = e.open_count || 0; - const sickShare = openN ? Math.round((sickN / openN) * 1000) / 10 : 0; - return ( - esc(exchangeLabel(ex)) + - " " + - openN + - "次/犯病" + - sickN + - "(" + - sickShare + - "%)/盈亏" + - fmtPnlStat(e.pnl_total) + - "/剔犯" + - fmtPnlStat(e.pnl_ex_sick) - ); - }); - html += '
' + exParts.join(" · ") + "
"; - } - elStats.innerHTML = html; + let rows = + renderStatsRow( + label, + { + open_count: st.open_count, + sick_count: st.sick_count, + sick_pct: st.sick_pct, + pnl_total: st.pnl_total, + pnl_ex_sick: st.pnl_ex_sick, + }, + true + ) + + exKeys + .map(function (ex) { + return renderStatsRow(exchangeLabel(ex), byEx[ex] || {}, false); + }) + .join(""); + elStats.innerHTML = + '' + + "" + + "" + + rows + + "
范围开仓犯病犯病占比盈亏剔除犯病盈亏
"; } function quotePreview(text) { @@ -1014,6 +1043,7 @@ ); }); }); + requestAnimationFrame(syncTradesLayout); } async function deleteTrade(tradeId, exchangeKey) { @@ -1177,6 +1207,7 @@ if (elChartSection) { elChartSection.addEventListener("toggle", async function () { if (elBtnChartToggle) elBtnChartToggle.classList.toggle("is-active", elChartSection.open); + syncTradesLayout(); if (elChartSection.open) { await ensureChartSelection(); void loadChart(); @@ -1216,6 +1247,7 @@ loadMarkAutoPref(); setChartOpen(false); syncPeriodUI(); + syncTradesLayout(); bindEvents(); inited = true; } diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 0fb9f3e..8e8818c 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -584,7 +584,7 @@ - +