Add archive tabs for trades vs review quotes.

Switch hub 内照明心 to a single-column tab layout and auto-show that day's closed trades under the quote form for AI review context.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-07-17 13:41:13 +08:00
parent afb771fef1
commit 7a0d015416
4 changed files with 282 additions and 39 deletions
+2 -2
View File
@@ -165,7 +165,7 @@ def build_chat_user_prompt(
ARCHIVE_QUOTE_REVIEW_INSTRUCTION = """
【任务】用户从内照明心提交了一条复盘语录,并附上该交易日的档案交易记录(仅供你分析,用户界面不展示明细).
【任务】用户从内照明心提交了一条复盘语录,并附上该交易日的档案交易记录(界面「复盘语录」下方也会展示当日已平仓明细).
请结合语录与交易记录:
1) 帮他核对自述与操作事实是否一致;
2) 指出心态,纪律,执行上的偏差点(若有);
@@ -236,7 +236,7 @@ def build_archive_quote_review_prompt(
parts = [
f"【复盘交易日】{quote_date}",
ARCHIVE_QUOTE_REVIEW_INSTRUCTION,
"【该日交易记录(内照明心档案,用户不可见此段)】",
"【该日交易记录(内照明心档案,与界面「当日已平仓」一致)】",
(archive_trades_text or "(该日无交易记录)").strip(),
"【用户复盘语录(对话框已展示,请优先回应)】",
user_message.strip(),
+80 -21
View File
@@ -7956,10 +7956,30 @@ body.funds-fullscreen-open {
margin-bottom: 12px;
}
.archive-layout {
display: grid;
grid-template-columns: minmax(240px, 300px) minmax(0, 1fr);
gap: 14px;
align-items: start;
display: flex;
flex-direction: column;
gap: 12px;
align-items: stretch;
}
.archive-content-tabs {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.archive-content-tab {
border: 1px solid var(--border-soft);
background: transparent;
color: inherit;
padding: 7px 14px;
border-radius: 8px;
cursor: pointer;
font-family: var(--font);
font-size: 0.84rem;
}
.archive-content-tab.is-active {
background: rgba(59, 130, 246, 0.22);
border-color: rgba(59, 130, 246, 0.55);
color: var(--text);
}
.archive-quotes-panel,
.archive-main-panel {
@@ -7968,13 +7988,23 @@ body.funds-fullscreen-open {
border-radius: var(--radius);
min-width: 0;
}
.archive-quotes-panel {
.archive-tab-panel {
display: none;
}
.archive-tab-panel.is-active {
display: flex;
flex-direction: column;
}
.archive-quotes-panel.is-active {
gap: 10px;
padding: 12px;
overflow: visible;
}
.archive-main-panel.is-active {
gap: 10px;
padding: 12px;
min-height: 0;
}
.archive-panel-head {
display: flex;
align-items: center;
@@ -8009,6 +8039,46 @@ body.funds-fullscreen-open {
.archive-quote-form textarea {
min-height: 110px;
}
.archive-quote-day-trades {
border: 1px solid var(--border-soft);
border-radius: 8px;
background: var(--inset-surface);
padding: 10px;
display: flex;
flex-direction: column;
gap: 8px;
}
.archive-quote-day-trades-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.archive-quote-day-trades-head h3 {
margin: 0;
font-size: 0.84rem;
font-weight: 600;
}
.archive-quote-day-trades-body {
overflow: auto;
max-height: min(280px, 40vh);
}
.archive-quote-day-trades-table {
width: 100%;
border-collapse: collapse;
font-size: 0.78rem;
}
.archive-quote-day-trades-table th,
.archive-quote-day-trades-table td {
padding: 6px 8px;
border-bottom: 1px solid var(--border-soft);
text-align: left;
white-space: nowrap;
}
.archive-quote-day-trades-table th {
color: var(--muted);
font-weight: 500;
}
.archive-quotes-list {
flex: 0 1 auto;
max-height: min(420px, 50vh);
@@ -8101,14 +8171,6 @@ body.funds-fullscreen-open {
color: var(--accent);
border-color: color-mix(in srgb, var(--accent) 35%, var(--border-soft));
}
.archive-main-panel {
display: flex;
flex-direction: column;
gap: 10px;
padding: 12px;
min-height: 100%;
min-height: 0;
}
.archive-period-bar {
flex-wrap: wrap;
}
@@ -8841,19 +8903,13 @@ body.funds-fullscreen-open {
gap: 12px;
min-height: 0;
}
#page-archive .archive-quotes-panel {
order: 1;
#page-archive .archive-quotes-panel,
#page-archive .archive-main-panel {
flex: 0 0 auto;
min-height: 0;
max-height: none;
overflow: visible;
}
#page-archive .archive-main-panel {
order: 2;
flex: 0 0 auto;
min-height: 0;
gap: 10px;
}
#page-archive .archive-stats-card {
margin-bottom: 10px;
}
@@ -8861,6 +8917,9 @@ body.funds-fullscreen-open {
min-height: 120px;
max-height: 42vh;
}
#page-archive .archive-quote-day-trades-body {
max-height: 32vh;
}
#page-archive .archive-stats-table th,
#page-archive .archive-stats-table td {
padding: 6px 8px;
+172 -1
View File
@@ -34,6 +34,11 @@
const elQuoteDate = document.getElementById("archive-quote-date");
const elQuoteContent = document.getElementById("archive-quote-content");
const elQuoteSubmit = document.getElementById("archive-quote-submit");
const elContentTabs = document.getElementById("archive-content-tabs");
const elPanelTrades = document.getElementById("archive-panel-trades");
const elPanelQuotes = document.getElementById("archive-panel-quotes");
const elQuoteDayTradesMeta = document.getElementById("archive-quote-day-trades-meta");
const elQuoteDayTradesBody = document.getElementById("archive-quote-day-trades-body");
const elChartSection = document.getElementById("archive-chart-section");
const elChartTitle = document.getElementById("archive-chart-title");
const elTfTabs = document.getElementById("archive-tf-tabs");
@@ -65,6 +70,10 @@
let quotes = [];
let selectedQuoteId = null;
let editingQuoteId = null;
let archiveContentTab = "trades";
let quoteDayTrades = [];
let quoteDayTradesDay = "";
let quoteDayTradesReq = 0;
let dailyTrades = [];
let tradesPage = 0;
let dailyStats = { open_count: 0, by_exchange: {} };
@@ -547,9 +556,11 @@
selectedCalendarDay = day;
setPeriodMode("today");
if (elTradingDay) elTradingDay.value = day;
if (elQuoteDate) elQuoteDate.value = day;
if (elFilterSick) elFilterSick.checked = false;
syncPeriodUI();
void loadDailyTrades();
if (archiveContentTab === "quotes") void loadQuoteDayTrades();
},
});
calendarWidget.ensureMonth(calendarRefDate());
@@ -943,6 +954,146 @@
elQuoteSubmit.textContent = editingQuoteId ? "修改保存" : "添加语录";
}
function syncQuoteDateFromTradingDay(force) {
if (!elQuoteDate) return;
const day =
(elTradingDay && elTradingDay.value) ||
selectedCalendarDay ||
tradingDay ||
"";
if (!day) return;
if (force || !elQuoteDate.value) {
elQuoteDate.value = day;
}
}
function setArchiveContentTab(tab) {
const next = tab === "quotes" ? "quotes" : "trades";
archiveContentTab = next;
const showQuotes = next === "quotes";
if (elContentTabs) {
elContentTabs.querySelectorAll(".archive-content-tab").forEach(function (btn) {
const on = btn.getAttribute("data-archive-tab") === next;
btn.classList.toggle("is-active", on);
btn.setAttribute("aria-selected", on ? "true" : "false");
});
}
if (elPanelTrades) {
elPanelTrades.classList.toggle("is-active", !showQuotes);
elPanelTrades.hidden = showQuotes;
}
if (elPanelQuotes) {
elPanelQuotes.classList.toggle("is-active", showQuotes);
elPanelQuotes.hidden = !showQuotes;
}
if (showQuotes) {
syncQuoteDateFromTradingDay(false);
void loadQuoteDayTrades();
} else {
requestAnimationFrame(syncTradesLayout);
}
}
function quoteTagLabel(t) {
if (!t) return "—";
if (t.behavior_tag_from_journal || t.behavior_tag === "sick") return "犯病";
if (t.behavior_tag === "emotion") return "情绪";
return "—";
}
function renderQuoteDayTrades() {
if (!elQuoteDayTradesBody) return;
const day = quoteDayTradesDay || (elQuoteDate && elQuoteDate.value) || "";
if (elQuoteDayTradesMeta) {
elQuoteDayTradesMeta.textContent = day
? day + " · " + quoteDayTrades.length + " 笔"
: "";
}
if (!day) {
elQuoteDayTradesBody.innerHTML =
'<p class="archive-empty">选择日期后自动带上该日已平仓记录.</p>';
return;
}
if (!quoteDayTrades.length) {
elQuoteDayTradesBody.innerHTML =
'<p class="archive-empty">该日暂无已平仓记录.</p>';
return;
}
elQuoteDayTradesBody.innerHTML =
'<table class="archive-quote-day-trades-table"><thead><tr>' +
"<th>交易所</th><th>合约</th><th>方向</th><th>开仓</th><th>平仓</th><th>盈亏</th><th>标签</th>" +
"</tr></thead><tbody>" +
quoteDayTrades
.map(function (t) {
return (
"<tr>" +
"<td>" +
esc(tradeRowExchange(t)) +
"</td>" +
"<td>" +
esc(t.symbol || "—") +
"</td>" +
"<td>" +
esc(t.direction || "—") +
"</td>" +
'<td class="archive-dt">' +
fmtDt(t.opened_at) +
"</td>" +
'<td class="archive-dt">' +
fmtDt(t.closed_at) +
"</td>" +
'<td class="' +
pnlClass(t.pnl_amount) +
'">' +
fmtPnl(t.pnl_amount) +
"</td>" +
"<td>" +
esc(quoteTagLabel(t)) +
"</td></tr>"
);
})
.join("") +
"</tbody></table>";
}
async function loadQuoteDayTrades() {
if (!elQuoteDayTradesBody) return;
const day = elQuoteDate && String(elQuoteDate.value || "").trim();
if (!day) {
quoteDayTrades = [];
quoteDayTradesDay = "";
renderQuoteDayTrades();
return;
}
const req = ++quoteDayTradesReq;
const q = new URLSearchParams();
q.set("period", "today");
q.set("trading_day", day);
elQuoteDayTradesBody.innerHTML = '<p class="archive-empty">加载当日已平仓…</p>';
if (elQuoteDayTradesMeta) elQuoteDayTradesMeta.textContent = day;
try {
const r = await apiFetch("/api/archive/daily-trades?" + q.toString());
const j = await r.json();
if (req !== quoteDayTradesReq) return;
if (!r.ok) {
quoteDayTrades = [];
quoteDayTradesDay = day;
elQuoteDayTradesBody.innerHTML =
'<p class="archive-empty">' + esc(j.detail || "加载失败") + "</p>";
return;
}
quoteDayTrades = j.trades || [];
quoteDayTradesDay = day;
renderQuoteDayTrades();
} catch (_) {
if (req !== quoteDayTradesReq) return;
quoteDayTrades = [];
quoteDayTradesDay = day;
elQuoteDayTradesBody.innerHTML =
'<p class="archive-empty">加载失败</p>';
}
}
function resetQuoteForm() {
editingQuoteId = null;
if (elQuoteContent) elQuoteContent.value = "";
@@ -959,6 +1110,7 @@
elQuoteContent.focus();
}
updateQuoteSubmitBtn();
void loadQuoteDayTrades();
}
function selectQuote(id) {
@@ -1839,6 +1991,7 @@
renderStats();
renderTrades();
void loadCalendar();
if (archiveContentTab === "quotes") void loadQuoteDayTrades();
setStatus(
(periodLabel || tradingDay || "当日") +
" · 列表 " +
@@ -1932,7 +2085,12 @@
loadDailyTrades();
});
}
if (elTradingDay) elTradingDay.addEventListener("change", loadDailyTrades);
if (elTradingDay) {
elTradingDay.addEventListener("change", function () {
if (elQuoteDate && elTradingDay.value) elQuoteDate.value = elTradingDay.value;
void loadDailyTrades();
});
}
if (elDateFrom) elDateFrom.addEventListener("change", loadDailyTrades);
if (elDateTo) elDateTo.addEventListener("change", loadDailyTrades);
[elFilterProfit, elFilterLoss, elFilterSick].forEach(function (el) {
@@ -1967,6 +2125,18 @@
});
}
if (elQuoteForm) elQuoteForm.addEventListener("submit", submitQuoteForm);
if (elQuoteDate) {
elQuoteDate.addEventListener("change", function () {
void loadQuoteDayTrades();
});
}
if (elContentTabs) {
elContentTabs.addEventListener("click", function (ev) {
const btn = ev.target.closest(".archive-content-tab");
if (!btn) return;
setArchiveContentTab(btn.getAttribute("data-archive-tab") || "trades");
});
}
if (elTfTabs) {
elTfTabs.addEventListener("click", function (ev) {
const btn = ev.target.closest(".archive-tf-btn");
@@ -1999,6 +2169,7 @@
syncPeriodUI();
syncTradesLayout();
bindEvents();
setArchiveContentTab("trades");
inited = true;
}
await loadMeta();
+28 -15
View File
@@ -15,7 +15,7 @@
<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'" />
<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=20260717-archive-compact" />
<link rel="stylesheet" href="/assets/app.css?v=20260717-archive-tabs" />
<link rel="stylesheet" href="/assets/trade_stats_calendar.css?v=4" />
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
<script src="/assets/account_risk_badge.js?v=4"></script>
@@ -489,19 +489,11 @@
</details>
</section>
<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="5" placeholder="今日复盘心得…" required></textarea>
<button type="submit" id="archive-quote-submit" class="primary">添加语录</button>
</form>
<div id="archive-quotes-list" class="archive-quotes-list"></div>
</aside>
<main class="archive-main-panel">
<div class="archive-content-tabs" id="archive-content-tabs" role="tablist" aria-label="内照明心分区">
<button type="button" class="archive-content-tab is-active" role="tab" aria-selected="true" data-archive-tab="trades" id="archive-tab-trades">交易记录</button>
<button type="button" class="archive-content-tab" role="tab" aria-selected="false" data-archive-tab="quotes" id="archive-tab-quotes">复盘语录</button>
</div>
<main class="archive-main-panel archive-tab-panel is-active" id="archive-panel-trades" role="tabpanel" aria-labelledby="archive-tab-trades">
<details class="archive-acc-section archive-calendar-section" id="archive-calendar-section" open>
<summary class="archive-acc-summary">交易日历 <span id="archive-cal-summary-sub" class="archive-acc-sub"></span></summary>
<div id="archive-calendar-wrap" class="trade-cal-wrap">
@@ -551,6 +543,27 @@
</div>
</details>
</main>
<aside class="archive-quotes-panel archive-tab-panel" id="archive-panel-quotes" role="tabpanel" aria-labelledby="archive-tab-quotes" hidden>
<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="5" placeholder="今日复盘心得…" required></textarea>
<button type="submit" id="archive-quote-submit" class="primary">添加语录</button>
</form>
<div class="archive-quote-day-trades" id="archive-quote-day-trades">
<div class="archive-quote-day-trades-head">
<h3>当日已平仓</h3>
<span id="archive-quote-day-trades-meta" class="archive-panel-meta"></span>
</div>
<div id="archive-quote-day-trades-body" class="archive-quote-day-trades-body">
<p class="archive-empty">选择日期后自动带上该日已平仓记录.</p>
</div>
</div>
<div id="archive-quotes-list" class="archive-quotes-list"></div>
</aside>
</div>
</div>
@@ -1316,7 +1329,7 @@
<script src="/assets/plan.js?v=20260614-plan-refresh"></script>
<script src="/assets/calculator.js?v=20260715-calc-tabs"></script>
<script src="/assets/trade_stats_calendar.js?v=3"></script>
<script src="/assets/archive.js?v=20260717-trades-pager"></script>
<script src="/assets/archive.js?v=20260717-archive-tabs"></script>
<script src="/assets/funds.js?v=20260717-funds-scroll-fix"></script>
<script src="/assets/dashboard.js?v=20260708-options-expiry-cd"></script>
<script src="/assets/strategy.js?v=3"></script>