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:
@@ -5569,6 +5569,7 @@ body.funds-fullscreen-open {
|
|||||||
gap: 10px;
|
gap: 10px;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
|
min-height: 0;
|
||||||
}
|
}
|
||||||
.archive-period-bar {
|
.archive-period-bar {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -5606,20 +5607,43 @@ body.funds-fullscreen-open {
|
|||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
}
|
}
|
||||||
.archive-stats-bar {
|
.archive-stats-bar {
|
||||||
padding: 10px 12px;
|
padding: 0;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
border: 1px solid var(--border-soft);
|
border: 1px solid var(--border-soft);
|
||||||
background: var(--inset-surface);
|
background: var(--inset-surface);
|
||||||
font-size: 0.82rem;
|
font-size: 0.82rem;
|
||||||
color: var(--text);
|
color: var(--text);
|
||||||
line-height: 1.45;
|
line-height: 1.45;
|
||||||
display: flex;
|
overflow: auto;
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
}
|
||||||
.archive-stats-sub {
|
.archive-stats-table {
|
||||||
font-size: 0.78rem;
|
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);
|
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 {
|
.archive-acc-section {
|
||||||
border: 1px solid var(--border-soft);
|
border: 1px solid var(--border-soft);
|
||||||
@@ -5648,10 +5672,24 @@ body.funds-fullscreen-open {
|
|||||||
.archive-chart-section > :not(summary) {
|
.archive-chart-section > :not(summary) {
|
||||||
padding: 0 10px 10px;
|
padding: 0 10px 10px;
|
||||||
}
|
}
|
||||||
|
.archive-trades-section {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-height: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
.archive-trades-section > .archive-trades {
|
.archive-trades-section > .archive-trades {
|
||||||
border: none;
|
border: none;
|
||||||
border-radius: 0;
|
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 {
|
.archive-chart-toolbar {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -5712,7 +5750,6 @@ body.funds-fullscreen-open {
|
|||||||
}
|
}
|
||||||
.archive-trades {
|
.archive-trades {
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
max-height: 380px;
|
|
||||||
border: 1px solid var(--border-soft);
|
border: 1px solid var(--border-soft);
|
||||||
border-radius: var(--radius);
|
border-radius: var(--radius);
|
||||||
background: var(--panel);
|
background: var(--panel);
|
||||||
|
|||||||
@@ -35,7 +35,9 @@
|
|||||||
const elChartHost = document.getElementById("archive-chart");
|
const elChartHost = document.getElementById("archive-chart");
|
||||||
const elMarkAuto = document.getElementById("archive-mark-auto");
|
const elMarkAuto = document.getElementById("archive-mark-auto");
|
||||||
const elTrades = document.getElementById("archive-trades");
|
const elTrades = document.getElementById("archive-trades");
|
||||||
|
const elTradesSection = document.getElementById("archive-trades-section");
|
||||||
const ARCHIVE_MARK_AUTO_KEY = "hubArchiveMarkAuto";
|
const ARCHIVE_MARK_AUTO_KEY = "hubArchiveMarkAuto";
|
||||||
|
const TRADES_VISIBLE_ROWS_CHART_OPEN = 10;
|
||||||
|
|
||||||
const TF_MS = {
|
const TF_MS = {
|
||||||
"5m": 5 * 60_000,
|
"5m": 5 * 60_000,
|
||||||
@@ -269,12 +271,30 @@
|
|||||||
return !!(elChartSection && elChartSection.open);
|
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) {
|
function setChartOpen(on) {
|
||||||
if (!elChartSection) return;
|
if (!elChartSection) return;
|
||||||
elChartSection.open = !!on;
|
elChartSection.open = !!on;
|
||||||
if (elBtnChartToggle) {
|
if (elBtnChartToggle) {
|
||||||
elBtnChartToggle.classList.toggle("is-active", !!on);
|
elBtnChartToggle.classList.toggle("is-active", !!on);
|
||||||
}
|
}
|
||||||
|
syncTradesLayout();
|
||||||
if (!on) {
|
if (!on) {
|
||||||
destroyChart();
|
destroyChart();
|
||||||
return;
|
return;
|
||||||
@@ -359,50 +379,59 @@
|
|||||||
if (cur) elExchange.value = cur;
|
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() {
|
function renderStats() {
|
||||||
if (!elStats) return;
|
if (!elStats) return;
|
||||||
const st = dailyStats || { open_count: 0, by_exchange: {} };
|
const st = dailyStats || { open_count: 0, by_exchange: {} };
|
||||||
const label = periodLabel || "本日";
|
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 byEx = st.by_exchange || {};
|
||||||
const exKeys = Object.keys(byEx).sort();
|
const exKeys = Object.keys(byEx).sort();
|
||||||
if (exKeys.length) {
|
let rows =
|
||||||
const exParts = exKeys.map(function (ex) {
|
renderStatsRow(
|
||||||
const e = byEx[ex] || {};
|
label,
|
||||||
const sickN = e.sick_count || 0;
|
{
|
||||||
const openN = e.open_count || 0;
|
open_count: st.open_count,
|
||||||
const sickShare = openN ? Math.round((sickN / openN) * 1000) / 10 : 0;
|
sick_count: st.sick_count,
|
||||||
return (
|
sick_pct: st.sick_pct,
|
||||||
esc(exchangeLabel(ex)) +
|
pnl_total: st.pnl_total,
|
||||||
" " +
|
pnl_ex_sick: st.pnl_ex_sick,
|
||||||
openN +
|
},
|
||||||
"次/犯病" +
|
true
|
||||||
sickN +
|
) +
|
||||||
"(" +
|
exKeys
|
||||||
sickShare +
|
.map(function (ex) {
|
||||||
"%)/盈亏" +
|
return renderStatsRow(exchangeLabel(ex), byEx[ex] || {}, false);
|
||||||
fmtPnlStat(e.pnl_total) +
|
})
|
||||||
"/剔犯" +
|
.join("");
|
||||||
fmtPnlStat(e.pnl_ex_sick)
|
elStats.innerHTML =
|
||||||
);
|
'<table class="archive-stats-table"><thead><tr>' +
|
||||||
});
|
"<th>范围</th><th>开仓</th><th>犯病</th><th>犯病占比</th><th>盈亏</th><th>剔除犯病盈亏</th>" +
|
||||||
html += '<div class="archive-stats-sub">' + exParts.join(" · ") + "</div>";
|
"</tr></thead><tbody>" +
|
||||||
}
|
rows +
|
||||||
elStats.innerHTML = html;
|
"</tbody></table>";
|
||||||
}
|
}
|
||||||
|
|
||||||
function quotePreview(text) {
|
function quotePreview(text) {
|
||||||
@@ -1014,6 +1043,7 @@
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
requestAnimationFrame(syncTradesLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteTrade(tradeId, exchangeKey) {
|
async function deleteTrade(tradeId, exchangeKey) {
|
||||||
@@ -1177,6 +1207,7 @@
|
|||||||
if (elChartSection) {
|
if (elChartSection) {
|
||||||
elChartSection.addEventListener("toggle", async function () {
|
elChartSection.addEventListener("toggle", async function () {
|
||||||
if (elBtnChartToggle) elBtnChartToggle.classList.toggle("is-active", elChartSection.open);
|
if (elBtnChartToggle) elBtnChartToggle.classList.toggle("is-active", elChartSection.open);
|
||||||
|
syncTradesLayout();
|
||||||
if (elChartSection.open) {
|
if (elChartSection.open) {
|
||||||
await ensureChartSelection();
|
await ensureChartSelection();
|
||||||
void loadChart();
|
void loadChart();
|
||||||
@@ -1216,6 +1247,7 @@
|
|||||||
loadMarkAutoPref();
|
loadMarkAutoPref();
|
||||||
setChartOpen(false);
|
setChartOpen(false);
|
||||||
syncPeriodUI();
|
syncPeriodUI();
|
||||||
|
syncTradesLayout();
|
||||||
bindEvents();
|
bindEvents();
|
||||||
inited = true;
|
inited = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -584,7 +584,7 @@
|
|||||||
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
<script src="https://unpkg.com/lightweight-charts@4.2.0/dist/lightweight-charts.standalone.production.js"></script>
|
||||||
<script src="/assets/chart_draw.js?v=20260609-market-day-split"></script>
|
<script src="/assets/chart_draw.js?v=20260609-market-day-split"></script>
|
||||||
<script src="/assets/chart.js?v=20260609-market-day-split"></script>
|
<script src="/assets/chart.js?v=20260609-market-day-split"></script>
|
||||||
<script src="/assets/archive.js?v=20260612-period-stats"></script>
|
<script src="/assets/archive.js?v=20260612-stats-table-layout"></script>
|
||||||
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
<script src="/assets/funds.js?v=20260609-hub-funds-fold"></script>
|
||||||
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
<script src="/assets/dashboard.js?v=20260612-dash-monitor-count"></script>
|
||||||
<script src="/assets/ai_review_render.js?v=3"></script>
|
<script src="/assets/ai_review_render.js?v=3"></script>
|
||||||
|
|||||||
Reference in New Issue
Block a user