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:
@@ -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 ""
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user