fix(hub): inner-light-mind chart, exchange column and layout

Preserve exchange_key from archive cache, fix chart resize in details panel, move filters above quotes, align panel heights, and style sick trades with red/cyan.
This commit is contained in:
dekun
2026-06-11 17:53:25 +08:00
parent bb800b876b
commit 2388ecc882
4 changed files with 138 additions and 45 deletions
+17
View File
@@ -420,6 +420,23 @@ def _trade_row_to_dict(row: sqlite3.Row, overlay: dict | None = None) -> dict[st
except (json.JSONDecodeError, TypeError): except (json.JSONDecodeError, TypeError):
payload = {} payload = {}
out = {**payload, **{k: d[k] for k in d.keys() if k not in payload}} out = {**payload, **{k: d[k] for k in d.keys() if k not in payload}}
for key in (
"exchange_key",
"symbol",
"trade_id",
"direction",
"result",
"pnl_amount",
"opened_at",
"closed_at",
"opened_at_ms",
"closed_at_ms",
"monitor_type",
"entry_reason",
"synced_at",
):
if key in d and d[key] not in (None, ""):
out[key] = d[key]
ov = overlay or {} ov = overlay or {}
out["behavior_tag"] = ov.get("behavior_tag") or "" out["behavior_tag"] = ov.get("behavior_tag") or ""
out["note"] = ov.get("note") or "" out["note"] = ov.get("note") or ""
+27 -10
View File
@@ -5427,12 +5427,15 @@ body.funds-fullscreen-open {
color: var(--text); color: var(--text);
font-family: var(--font); font-family: var(--font);
} }
#page-archive .archive-toolbar {
margin-bottom: 12px;
}
.archive-layout { .archive-layout {
display: grid; display: grid;
grid-template-columns: minmax(240px, 300px) minmax(0, 1fr); grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
gap: 14px; gap: 14px;
min-height: 520px; min-height: calc(100vh - 240px);
align-items: start; align-items: stretch;
} }
.archive-quotes-panel, .archive-quotes-panel,
.archive-main-panel { .archive-main-panel {
@@ -5446,7 +5449,7 @@ body.funds-fullscreen-open {
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
padding: 12px; padding: 12px;
max-height: calc(100vh - 180px); min-height: 100%;
overflow: hidden; overflow: hidden;
} }
.archive-panel-head { .archive-panel-head {
@@ -5546,7 +5549,7 @@ body.funds-fullscreen-open {
flex-direction: column; flex-direction: column;
gap: 10px; gap: 10px;
padding: 12px; padding: 12px;
min-height: 520px; min-height: 100%;
} }
.archive-stats-bar { .archive-stats-bar {
padding: 10px 12px; padding: 10px 12px;
@@ -5587,7 +5590,7 @@ body.funds-fullscreen-open {
.archive-trades-section > .archive-trades { .archive-trades-section > .archive-trades {
border: none; border: none;
border-radius: 0; border-radius: 0;
max-height: min(56vh, 560px); max-height: 380px;
} }
.archive-chart-toolbar { .archive-chart-toolbar {
flex-wrap: wrap; flex-wrap: wrap;
@@ -5648,10 +5651,11 @@ body.funds-fullscreen-open {
} }
.archive-trades { .archive-trades {
overflow: auto; overflow: auto;
max-height: min(56vh, 560px); 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);
overscroll-behavior: contain;
} }
.archive-trades-table { .archive-trades-table {
width: 100%; width: 100%;
@@ -5697,13 +5701,24 @@ body.funds-fullscreen-open {
background: var(--inset-surface); background: var(--inset-surface);
} }
.archive-trade-row.archive-trade-sick { .archive-trade-row.archive-trade-sick {
background: rgba(239, 68, 68, 0.12); background: rgba(220, 38, 38, 0.3);
} }
.archive-trade-row.archive-trade-sick.is-active { .archive-trade-row.archive-trade-sick.is-active {
background: rgba(239, 68, 68, 0.18); background: rgba(220, 38, 38, 0.38);
} }
.archive-trade-row.archive-trade-sick td { .archive-trade-row.archive-trade-sick td {
border-bottom-color: rgba(239, 68, 68, 0.22); color: var(--accent);
border-bottom-color: rgba(220, 38, 38, 0.4);
}
.archive-trade-row.archive-trade-sick .archive-tag-select,
.archive-trade-row.archive-trade-sick .archive-note-input {
color: var(--accent);
background: rgba(0, 0, 0, 0.2);
border-color: color-mix(in srgb, var(--accent) 50%, var(--border-soft));
}
.archive-trade-row.archive-trade-sick td.pos,
.archive-trade-row.archive-trade-sick td.neg {
color: var(--accent);
} }
.archive-actions-cell { .archive-actions-cell {
white-space: nowrap; white-space: nowrap;
@@ -5754,9 +5769,11 @@ body.funds-fullscreen-open {
@media (max-width: 900px) { @media (max-width: 900px) {
.archive-layout { .archive-layout {
grid-template-columns: 1fr; grid-template-columns: 1fr;
min-height: 0;
} }
.archive-quotes-panel { .archive-quotes-panel {
max-height: 280px; min-height: 280px;
max-height: 320px;
} }
} }
+71 -12
View File
@@ -211,12 +211,50 @@
if (elStatus) elStatus.textContent = text || ""; if (elStatus) elStatus.textContent = text || "";
} }
function tradeRowExchange(tr) {
if (!tr) return "—";
const exKey = tr.exchange_key || tr.account_exchange_key || "";
if (exKey) return exchangeLabel(exKey);
const name = tr.account_name || tr.exchange_name || "";
return name || "—";
}
function exchangeLabel(exKey) { function exchangeLabel(exKey) {
const key = String(exKey || "").toLowerCase(); const key = String(exKey || "").toLowerCase();
if (!key) return "—";
const hit = (meta && meta.exchanges || []).find(function (ex) { const hit = (meta && meta.exchanges || []).find(function (ex) {
return String(ex.key || "").toLowerCase() === key; return String(ex.key || "").toLowerCase() === key;
}); });
return hit ? hit.name || hit.key : exKey || "—"; return hit ? hit.name || hit.key : exKey;
}
function scheduleChartResize() {
requestAnimationFrame(function () {
if (chart && elChartHost) {
const w = elChartHost.clientWidth;
const h = elChartHost.clientHeight;
if (w > 0 && h > 0) chart.applyOptions({ width: w, height: h });
}
requestAnimationFrame(function () {
if (chart && elChartHost) {
const w = elChartHost.clientWidth;
const h = elChartHost.clientHeight;
if (w > 0 && h > 0) chart.applyOptions({ width: w, height: h });
}
});
});
}
async function ensureChartSelection() {
if (selected && selected.exchange_key && selected.symbol) return;
if (!dailyTrades.length) return;
const tr = dailyTrades.find(function (t) {
return t.exchange_key && t.symbol;
});
if (!tr) return;
selected = { exchange_key: tr.exchange_key, symbol: tr.symbol };
selectedTradeId = String(tr.trade_id || tr.id);
await loadSymbolTradesForChart(tr.exchange_key, tr.symbol);
} }
function isChartOpen() { function isChartOpen() {
@@ -229,7 +267,11 @@
if (elBtnChartToggle) { if (elBtnChartToggle) {
elBtnChartToggle.classList.toggle("is-active", !!on); elBtnChartToggle.classList.toggle("is-active", !!on);
} }
if (!on) destroyChart(); if (!on) {
destroyChart();
return;
}
scheduleChartResize();
} }
function updateChartTitle() { function updateChartTitle() {
@@ -718,7 +760,11 @@
setStatus(j.detail || "K 线加载失败"); setStatus(j.detail || "K 线加载失败");
return; return;
} }
if (chart) {
destroyChart();
}
ensureChart(); ensureChart();
scheduleChartResize();
const candles = j.candles || []; const candles = j.candles || [];
lastCandles = candles; lastCandles = candles;
candleSeries.setData( candleSeries.setData(
@@ -742,14 +788,18 @@
chart.timeScale().setVisibleLogicalRange({ from: candles.length - 120, to: candles.length + 5 }); chart.timeScale().setVisibleLogicalRange({ from: candles.length - 120, to: candles.length + 5 });
} }
updateChartTitle(); updateChartTitle();
scheduleChartResize();
setStatus("K 线 " + candles.length + " 根 · " + timeframe); setStatus("K 线 " + candles.length + " 根 · " + timeframe);
} }
async function openTradeChart(tr) { async function openTradeChart(tr) {
if (!tr) return; if (!tr) return;
const exKey = tr.exchange_key; const exKey = tr.exchange_key || tr.account_exchange_key || "";
const sym = tr.symbol; const sym = tr.symbol || "";
if (!exKey || !sym) return; if (!exKey || !sym) {
setStatus("该笔交易缺少交易所或合约,无法加载图表");
return;
}
selected = { exchange_key: exKey, symbol: sym }; selected = { exchange_key: exKey, symbol: sym };
selectedTradeId = String(tr.trade_id || tr.id); selectedTradeId = String(tr.trade_id || tr.id);
setChartOpen(true); setChartOpen(true);
@@ -772,7 +822,7 @@
dailyTrades dailyTrades
.map(function (t) { .map(function (t) {
const tid = t.trade_id || t.id; const tid = t.trade_id || t.id;
const exKey = t.exchange_key || ""; const exKey = t.exchange_key || t.account_exchange_key || "";
const tag = t.behavior_tag || ""; const tag = t.behavior_tag || "";
const sick = tag === "sick"; const sick = tag === "sick";
const active = String(tid) === String(selectedTradeId) ? " is-active" : ""; const active = String(tid) === String(selectedTradeId) ? " is-active" : "";
@@ -785,9 +835,11 @@
tid + tid +
'" data-ex="' + '" data-ex="' +
esc(exKey) + esc(exKey) +
'" data-sym="' +
esc(t.symbol || "") +
'">' + '">' +
"<td>" + "<td>" +
esc(exchangeLabel(exKey)) + esc(tradeRowExchange(t)) +
"</td>" + "</td>" +
"<td>" + "<td>" +
(rev ? '<span class="archive-review-mark">' + rev + "</span>" : "") + (rev ? '<span class="archive-review-mark">' + rev + "</span>" : "") +
@@ -1015,17 +1067,24 @@
}); });
} }
if (elBtnChartToggle) { if (elBtnChartToggle) {
elBtnChartToggle.addEventListener("click", function () { elBtnChartToggle.addEventListener("click", async function () {
const next = !isChartOpen(); const next = !isChartOpen();
setChartOpen(next); setChartOpen(next);
if (next && selected) void loadChart(); if (next) {
await ensureChartSelection();
void loadChart();
}
}); });
} }
if (elChartSection) { if (elChartSection) {
elChartSection.addEventListener("toggle", function () { elChartSection.addEventListener("toggle", async function () {
if (elBtnChartToggle) elBtnChartToggle.classList.toggle("is-active", elChartSection.open); if (elBtnChartToggle) elBtnChartToggle.classList.toggle("is-active", elChartSection.open);
if (elChartSection.open && selected) void loadChart(); if (elChartSection.open) {
else if (!elChartSection.open) destroyChart(); await ensureChartSelection();
void loadChart();
} else {
destroyChart();
}
}); });
} }
if (elQuoteForm) elQuoteForm.addEventListener("submit", addQuote); if (elQuoteForm) elQuoteForm.addEventListener("submit", addQuote);
+16 -16
View File
@@ -15,7 +15,7 @@
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" /> <link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" media="print" onload="this.media='all'" />
<noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript> <noscript><link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&family=Orbitron:wght@500;600;700&display=swap" rel="stylesheet" /></noscript>
<link rel="stylesheet" href="/assets/app.css?v=20260612-inner-light-mind" /> <link rel="stylesheet" href="/assets/app.css?v=20260612-inner-light-fix" />
<link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" /> <link rel="stylesheet" href="/assets/dashboard.css?v=20260612-dash-monitor-count" />
</head> </head>
<body> <body>
@@ -262,20 +262,6 @@
<h1><span class="head-tag">IN</span> 内照明心</h1> <h1><span class="head-tag">IN</span> 内照明心</h1>
<p class="page-desc">复盘语录 · 当日交易记录 · 按需查看 K 线</p> <p class="page-desc">复盘语录 · 当日交易记录 · 按需查看 K 线</p>
</div> </div>
<div class="archive-layout">
<aside class="archive-quotes-panel">
<div class="archive-panel-head">
<h2>复盘语录</h2>
<span id="archive-quotes-count" class="archive-panel-meta"></span>
</div>
<form id="archive-quote-form" class="archive-quote-form">
<input id="archive-quote-date" type="date" required />
<textarea id="archive-quote-content" rows="3" placeholder="今日复盘心得…" required></textarea>
<button type="submit" class="primary">添加语录</button>
</form>
<div id="archive-quotes-list" class="archive-quotes-list"></div>
</aside>
<main class="archive-main-panel">
<div class="archive-toolbar toolbar"> <div class="archive-toolbar toolbar">
<label class="chk-label"><input type="checkbox" id="archive-filter-profit" /> 盈利单</label> <label class="chk-label"><input type="checkbox" id="archive-filter-profit" /> 盈利单</label>
<label class="chk-label"><input type="checkbox" id="archive-filter-loss" /> 亏损单</label> <label class="chk-label"><input type="checkbox" id="archive-filter-loss" /> 亏损单</label>
@@ -297,6 +283,20 @@
<button type="button" id="archive-btn-sync" class="ghost">同步</button> <button type="button" id="archive-btn-sync" class="ghost">同步</button>
<span id="archive-status" class="toolbar-meta"></span> <span id="archive-status" class="toolbar-meta"></span>
</div> </div>
<div class="archive-layout">
<aside class="archive-quotes-panel">
<div class="archive-panel-head">
<h2>复盘语录</h2>
<span id="archive-quotes-count" class="archive-panel-meta"></span>
</div>
<form id="archive-quote-form" class="archive-quote-form">
<input id="archive-quote-date" type="date" required />
<textarea id="archive-quote-content" rows="3" placeholder="今日复盘心得…" required></textarea>
<button type="submit" class="primary">添加语录</button>
</form>
<div id="archive-quotes-list" class="archive-quotes-list"></div>
</aside>
<main class="archive-main-panel">
<div id="archive-stats" class="archive-stats-bar"></div> <div id="archive-stats" class="archive-stats-bar"></div>
<details id="archive-chart-section" class="archive-acc-section archive-chart-section"> <details id="archive-chart-section" class="archive-acc-section archive-chart-section">
<summary class="archive-acc-summary">K 线图表 <span id="archive-chart-title" class="archive-acc-sub"></span></summary> <summary class="archive-acc-summary">K 线图表 <span id="archive-chart-title" class="archive-acc-sub"></span></summary>
@@ -573,7 +573,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-inner-light-mind"></script> <script src="/assets/archive.js?v=20260612-inner-light-fix"></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=2"></script> <script src="/assets/ai_review_render.js?v=2"></script>