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 <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-11 18:18:08 +08:00
parent 7b0b8996fe
commit 879ea5e228
3 changed files with 116 additions and 47 deletions
+70 -38
View File
@@ -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 (
"<tr" +
rowCls +
"><td>" +
(isTotal ? "<strong>" + esc(label) + "</strong>" : esc(label)) +
"</td><td>" +
openN +
"</td><td>" +
sickN +
"</td><td>" +
sickShare +
"%</td><td>" +
fmtPnlStat(e.pnl_total) +
"</td><td>" +
fmtPnlStat(e.pnl_ex_sick) +
"</td></tr>"
);
}
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 =
'<div class="archive-stats-line"><strong>' +
esc(label) +
"</strong> · 开仓 " +
(st.open_count || 0) +
" 次 · 犯病 " +
(st.sick_count || 0) +
" 次(" +
sickPct +
"% · 盈亏 " +
fmtPnlStat(st.pnl_total) +
" · 剔除犯病盈亏 " +
fmtPnlStat(st.pnl_ex_sick) +
"</div>";
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 += '<div class="archive-stats-sub">' + exParts.join(" · ") + "</div>";
}
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 =
'<table class="archive-stats-table"><thead><tr>' +
"<th>范围</th><th>开仓</th><th>犯病</th><th>犯病占比</th><th>盈亏</th><th>剔除犯病盈亏</th>" +
"</tr></thead><tbody>" +
rows +
"</tbody></table>";
}
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;
}