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