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