From 2388ecc882a5f20c58fa63c15d467f1b4a12d4d2 Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 11 Jun 2026 17:53:25 +0800 Subject: [PATCH] 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. --- hub_symbol_archive_lib.py | 17 ++++++ manual_trading_hub/static/app.css | 37 +++++++++---- manual_trading_hub/static/archive.js | 83 ++++++++++++++++++++++++---- manual_trading_hub/static/index.html | 46 +++++++-------- 4 files changed, 138 insertions(+), 45 deletions(-) diff --git a/hub_symbol_archive_lib.py b/hub_symbol_archive_lib.py index bf87432..2646d03 100644 --- a/hub_symbol_archive_lib.py +++ b/hub_symbol_archive_lib.py @@ -420,6 +420,23 @@ def _trade_row_to_dict(row: sqlite3.Row, overlay: dict | None = None) -> dict[st except (json.JSONDecodeError, TypeError): 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 {} out["behavior_tag"] = ov.get("behavior_tag") or "" out["note"] = ov.get("note") or "" diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index df8ae3b..8dd21e2 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -5427,12 +5427,15 @@ body.funds-fullscreen-open { color: var(--text); font-family: var(--font); } +#page-archive .archive-toolbar { + margin-bottom: 12px; +} .archive-layout { display: grid; grid-template-columns: minmax(240px, 300px) minmax(0, 1fr); gap: 14px; - min-height: 520px; - align-items: start; + min-height: calc(100vh - 240px); + align-items: stretch; } .archive-quotes-panel, .archive-main-panel { @@ -5446,7 +5449,7 @@ body.funds-fullscreen-open { flex-direction: column; gap: 10px; padding: 12px; - max-height: calc(100vh - 180px); + min-height: 100%; overflow: hidden; } .archive-panel-head { @@ -5546,7 +5549,7 @@ body.funds-fullscreen-open { flex-direction: column; gap: 10px; padding: 12px; - min-height: 520px; + min-height: 100%; } .archive-stats-bar { padding: 10px 12px; @@ -5587,7 +5590,7 @@ body.funds-fullscreen-open { .archive-trades-section > .archive-trades { border: none; border-radius: 0; - max-height: min(56vh, 560px); + max-height: 380px; } .archive-chart-toolbar { flex-wrap: wrap; @@ -5648,10 +5651,11 @@ body.funds-fullscreen-open { } .archive-trades { overflow: auto; - max-height: min(56vh, 560px); + max-height: 380px; border: 1px solid var(--border-soft); border-radius: var(--radius); background: var(--panel); + overscroll-behavior: contain; } .archive-trades-table { width: 100%; @@ -5697,13 +5701,24 @@ body.funds-fullscreen-open { background: var(--inset-surface); } .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 { - background: rgba(239, 68, 68, 0.18); + background: rgba(220, 38, 38, 0.38); } .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 { white-space: nowrap; @@ -5754,9 +5769,11 @@ body.funds-fullscreen-open { @media (max-width: 900px) { .archive-layout { grid-template-columns: 1fr; + min-height: 0; } .archive-quotes-panel { - max-height: 280px; + min-height: 280px; + max-height: 320px; } } diff --git a/manual_trading_hub/static/archive.js b/manual_trading_hub/static/archive.js index 0b0ca16..036e9a7 100644 --- a/manual_trading_hub/static/archive.js +++ b/manual_trading_hub/static/archive.js @@ -211,12 +211,50 @@ 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) { const key = String(exKey || "").toLowerCase(); + if (!key) return "—"; const hit = (meta && meta.exchanges || []).find(function (ex) { 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() { @@ -229,7 +267,11 @@ if (elBtnChartToggle) { elBtnChartToggle.classList.toggle("is-active", !!on); } - if (!on) destroyChart(); + if (!on) { + destroyChart(); + return; + } + scheduleChartResize(); } function updateChartTitle() { @@ -718,7 +760,11 @@ setStatus(j.detail || "K 线加载失败"); return; } + if (chart) { + destroyChart(); + } ensureChart(); + scheduleChartResize(); const candles = j.candles || []; lastCandles = candles; candleSeries.setData( @@ -742,14 +788,18 @@ chart.timeScale().setVisibleLogicalRange({ from: candles.length - 120, to: candles.length + 5 }); } updateChartTitle(); + scheduleChartResize(); setStatus("K 线 " + candles.length + " 根 · " + timeframe); } async function openTradeChart(tr) { if (!tr) return; - const exKey = tr.exchange_key; - const sym = tr.symbol; - if (!exKey || !sym) return; + const exKey = tr.exchange_key || tr.account_exchange_key || ""; + const sym = tr.symbol || ""; + if (!exKey || !sym) { + setStatus("该笔交易缺少交易所或合约,无法加载图表"); + return; + } selected = { exchange_key: exKey, symbol: sym }; selectedTradeId = String(tr.trade_id || tr.id); setChartOpen(true); @@ -772,7 +822,7 @@ dailyTrades .map(function (t) { 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 sick = tag === "sick"; const active = String(tid) === String(selectedTradeId) ? " is-active" : ""; @@ -785,9 +835,11 @@ tid + '" data-ex="' + esc(exKey) + + '" data-sym="' + + esc(t.symbol || "") + '">' + "" + - esc(exchangeLabel(exKey)) + + esc(tradeRowExchange(t)) + "" + "" + (rev ? '' + rev + "" : "") + @@ -1015,17 +1067,24 @@ }); } if (elBtnChartToggle) { - elBtnChartToggle.addEventListener("click", function () { + elBtnChartToggle.addEventListener("click", async function () { const next = !isChartOpen(); setChartOpen(next); - if (next && selected) void loadChart(); + if (next) { + await ensureChartSelection(); + void loadChart(); + } }); } if (elChartSection) { - elChartSection.addEventListener("toggle", function () { + elChartSection.addEventListener("toggle", async function () { if (elBtnChartToggle) elBtnChartToggle.classList.toggle("is-active", elChartSection.open); - if (elChartSection.open && selected) void loadChart(); - else if (!elChartSection.open) destroyChart(); + if (elChartSection.open) { + await ensureChartSelection(); + void loadChart(); + } else { + destroyChart(); + } }); } if (elQuoteForm) elQuoteForm.addEventListener("submit", addQuote); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index 2c7d95f..c4100f6 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -15,7 +15,7 @@ - + @@ -262,6 +262,27 @@

IN 内照明心

复盘语录 · 当日交易记录 · 按需查看 K 线

+
+ + + + + + + + + + +
-
- - - - - - - - - - -
K 线图表 @@ -573,7 +573,7 @@ - +