From cf1265763cd243248fcfa6aff7209b33656f134d Mon Sep 17 00:00:00 2001 From: dekun Date: Thu, 11 Jun 2026 20:42:35 +0800 Subject: [PATCH] feat(hub): refactor archive quotes to list-detail with top-form edit Co-authored-by: Cursor --- manual_trading_hub/static/app.css | 75 ++++++------ manual_trading_hub/static/archive.js | 165 +++++++++++++++------------ manual_trading_hub/static/index.html | 13 ++- 3 files changed, 140 insertions(+), 113 deletions(-) diff --git a/manual_trading_hub/static/app.css b/manual_trading_hub/static/app.css index a48a6be..427fa18 100644 --- a/manual_trading_hub/static/app.css +++ b/manual_trading_hub/static/app.css @@ -5532,37 +5532,49 @@ body.funds-fullscreen-open { } .archive-quotes-list { flex: 1 1 auto; - min-height: 420px; + min-height: 160px; overflow: auto; display: flex; flex-direction: column; gap: 8px; } -.archive-quote-card { +.archive-quote-item { + display: grid; + grid-template-columns: auto 1fr; + gap: 8px; + align-items: center; + width: 100%; + padding: 8px 10px; border: 1px solid var(--border-soft); border-radius: 8px; background: var(--inset-surface); - overflow: hidden; -} -.archive-quote-summary { - display: grid; - grid-template-columns: auto 1fr auto; - gap: 8px; - align-items: center; - padding: 8px 10px; + color: inherit; + font: inherit; + text-align: left; cursor: pointer; - list-style: none; } -.archive-quote-open-hint { - font-size: 0.7rem; - color: var(--accent); - white-space: nowrap; +.archive-quote-item:hover { + border-color: color-mix(in srgb, var(--accent) 40%, var(--border-soft)); } -.archive-quote-card[open] .archive-quote-open-hint { - color: var(--muted); +.archive-quote-item.is-selected { + border-color: var(--accent); + background: color-mix(in srgb, var(--accent) 12%, var(--inset-surface)); } -.archive-quote-summary::-webkit-details-marker { - display: none; +.archive-quote-detail { + flex: 0 0 auto; + display: flex; + flex-direction: column; + gap: 8px; + padding-top: 10px; + border-top: 1px solid var(--border-soft); + min-height: 140px; + max-height: 42vh; +} +.archive-quote-detail .archive-quote-full { + flex: 1 1 auto; + min-height: 96px; + max-height: 34vh; + overflow: auto; } .archive-quote-date { font-weight: 600; @@ -5577,12 +5589,6 @@ body.funds-fullscreen-open { text-overflow: ellipsis; white-space: nowrap; } -.archive-quote-body { - padding: 0 10px 10px; - display: flex; - flex-direction: column; - gap: 8px; -} .archive-quote-full { padding: 10px 12px; border-radius: 8px; @@ -5595,20 +5601,6 @@ body.funds-fullscreen-open { word-break: break-word; max-height: none; } -.archive-quote-edit { - width: 100%; - min-height: 160px; - padding: 8px 10px; - border-radius: 8px; - border: 1px solid var(--border-soft); - background: var(--panel); - color: var(--text); - font-family: var(--font); - font-size: 0.8rem; - resize: vertical; - line-height: 1.55; - white-space: pre-wrap; -} .archive-quote-actions { display: flex; gap: 8px; @@ -5943,7 +5935,10 @@ body.funds-fullscreen-open { max-height: none; } .archive-quotes-list { - min-height: 220px; + min-height: 120px; + } + .archive-quote-detail { + max-height: 36vh; } } diff --git a/manual_trading_hub/static/archive.js b/manual_trading_hub/static/archive.js index 3dd709e..20e1b65 100644 --- a/manual_trading_hub/static/archive.js +++ b/manual_trading_hub/static/archive.js @@ -25,6 +25,11 @@ const elQuoteForm = document.getElementById("archive-quote-form"); const elQuoteDate = document.getElementById("archive-quote-date"); const elQuoteContent = document.getElementById("archive-quote-content"); + const elQuoteSubmit = document.getElementById("archive-quote-submit"); + const elQuoteDetail = document.getElementById("archive-quote-detail"); + const elQuoteDetailFull = document.getElementById("archive-quote-detail-full"); + const elQuoteEditBtn = document.getElementById("archive-quote-edit-btn"); + const elQuoteDelBtn = document.getElementById("archive-quote-del-btn"); const elChartSection = document.getElementById("archive-chart-section"); const elChartTitle = document.getElementById("archive-chart-title"); const elTfTabs = document.getElementById("archive-tf-tabs"); @@ -49,6 +54,8 @@ let meta = null; let quotes = []; + let selectedQuoteId = null; + let editingQuoteId = null; let dailyTrades = []; let dailyStats = { open_count: 0, by_exchange: {} }; let periodMode = "today"; @@ -480,20 +487,57 @@ return s.length > 36 ? s.slice(0, 36) + "…" : s; } - function quoteEditRows(text) { - const t = String(text || ""); - const lines = t.split(/\n/).length; - const wrapLines = Math.ceil(t.length / 26); - return Math.min(32, Math.max(8, lines, wrapLines)); + function findQuote(id) { + if (id == null || id === "") return null; + return ( + quotes.find(function (q) { + return String(q.id) === String(id); + }) || null + ); } - function resizeQuoteTextarea(ta) { - if (!ta) return; - ta.style.height = "auto"; - const lines = String(ta.value || "").split("\n").length; - const wrapLines = Math.ceil(String(ta.value || "").length / 26); - ta.rows = Math.min(32, Math.max(8, lines, wrapLines)); - ta.style.height = Math.max(ta.scrollHeight, 160) + "px"; + function updateQuoteSubmitBtn() { + if (!elQuoteSubmit) return; + elQuoteSubmit.textContent = editingQuoteId ? "修改保存" : "添加语录"; + } + + function resetQuoteForm() { + editingQuoteId = null; + if (elQuoteContent) elQuoteContent.value = ""; + updateQuoteSubmitBtn(); + } + + function startEditQuote() { + const q = findQuote(selectedQuoteId); + if (!q) return; + editingQuoteId = q.id; + if (elQuoteDate) elQuoteDate.value = q.quote_date || ""; + if (elQuoteContent) { + elQuoteContent.value = q.content || ""; + elQuoteContent.focus(); + } + updateQuoteSubmitBtn(); + } + + function selectQuote(id) { + if (editingQuoteId != null && String(id) !== String(editingQuoteId)) { + resetQuoteForm(); + } + selectedQuoteId = id; + renderQuotes(); + renderQuoteDetail(); + } + + function renderQuoteDetail() { + if (!elQuoteDetail) return; + const q = findQuote(selectedQuoteId); + if (!q) { + elQuoteDetail.hidden = true; + if (elQuoteDetailFull) elQuoteDetailFull.textContent = ""; + return; + } + elQuoteDetail.hidden = false; + if (elQuoteDetailFull) elQuoteDetailFull.textContent = q.content || "(空)"; } function renderQuotes() { @@ -503,79 +547,58 @@ } if (!quotes.length) { elQuotesList.innerHTML = '

暂无复盘语录,可在上方添加。

'; + if (elQuoteDetail) elQuoteDetail.hidden = true; return; } elQuotesList.innerHTML = quotes .map(function (q) { + const selected = String(q.id) === String(selectedQuoteId); return ( - '
' + - '' + + '" + - '
' + - '
' + - esc(q.content || "(空)") + - "
" + - '" + - '
' + - '' + - '' + - "
" + "" ); }) .join(""); - elQuotesList.querySelectorAll(".archive-quote-card").forEach(function (card) { - card.addEventListener("toggle", function () { - if (!card.open) return; - resizeQuoteTextarea(card.querySelector(".archive-quote-edit")); - }); - }); - elQuotesList.querySelectorAll(".archive-quote-save").forEach(function (btn) { + elQuotesList.querySelectorAll(".archive-quote-item").forEach(function (btn) { btn.addEventListener("click", function () { - const id = btn.getAttribute("data-id"); - const card = btn.closest(".archive-quote-card"); - const ta = card && card.querySelector(".archive-quote-edit"); - const dateEl = card && card.querySelector(".archive-quote-date"); - if (!id || !ta) return; - void saveQuote(id, dateEl ? dateEl.textContent : "", ta.value, card); - }); - }); - elQuotesList.querySelectorAll(".archive-quote-del").forEach(function (btn) { - btn.addEventListener("click", function () { - void deleteQuote(btn.getAttribute("data-id")); + selectQuote(btn.getAttribute("data-id")); }); }); + renderQuoteDetail(); } async function loadQuotes() { const r = await apiFetch("/api/archive/quotes"); const j = await r.json(); quotes = j.quotes || []; + if (!quotes.length) { + selectedQuoteId = null; + } else if (!findQuote(selectedQuoteId)) { + selectedQuoteId = quotes[0].id; + } renderQuotes(); } - async function addQuote(ev) { + async function submitQuoteForm(ev) { if (ev) ev.preventDefault(); const date = elQuoteDate && elQuoteDate.value; const content = elQuoteContent && elQuoteContent.value.trim(); if (!date || !content) return; + if (editingQuoteId) { + await saveQuote(editingQuoteId, date, content); + return; + } const r = await apiFetch("/api/archive/quotes", { method: "POST", headers: { "Content-Type": "application/json" }, @@ -586,34 +609,27 @@ setStatus(j.detail || "添加失败"); return; } - if (elQuoteContent) elQuoteContent.value = ""; + resetQuoteForm(); + selectedQuoteId = + j.quote && j.quote.id != null ? j.quote.id : selectedQuoteId; await loadQuotes(); setStatus("语录已添加"); } - async function saveQuote(id, quoteDate, content, cardEl) { - let card = cardEl; - if (!card && elQuotesList) { - const ta = elQuotesList.querySelector('.archive-quote-edit[data-id="' + id + '"]'); - card = ta ? ta.closest(".archive-quote-card") : null; - } - const date = - quoteDate || - (card && - card.querySelector(".archive-quote-date") && - card.querySelector(".archive-quote-date").textContent) || - ""; + async function saveQuote(id, quoteDate, content) { const r = await apiFetch("/api/archive/quotes/" + id, { method: "PATCH", headers: { "Content-Type": "application/json" }, - body: JSON.stringify({ quote_date: date.trim(), content: content }), + body: JSON.stringify({ quote_date: String(quoteDate || "").trim(), content: content }), }); const j = await r.json(); if (!r.ok) { setStatus(j.detail || "保存失败"); return; } - if (card) card.open = false; + const savedId = id; + resetQuoteForm(); + selectedQuoteId = savedId; await loadQuotes(); setStatus("语录已保存"); } @@ -628,6 +644,8 @@ setStatus(j.detail || "删除失败"); return; } + if (String(id) === String(editingQuoteId)) resetQuoteForm(); + if (String(id) === String(selectedQuoteId)) selectedQuoteId = null; await loadQuotes(); setStatus("语录已删除"); } @@ -1363,7 +1381,14 @@ } }); } - if (elQuoteForm) elQuoteForm.addEventListener("submit", addQuote); + if (elQuoteForm) elQuoteForm.addEventListener("submit", submitQuoteForm); + if (elQuoteEditBtn) elQuoteEditBtn.addEventListener("click", startEditQuote); + if (elQuoteDelBtn) { + elQuoteDelBtn.addEventListener("click", function () { + if (!selectedQuoteId) return; + void deleteQuote(selectedQuoteId); + }); + } if (elTfTabs) { elTfTabs.addEventListener("click", function (ev) { const btn = ev.target.closest(".archive-tf-btn"); diff --git a/manual_trading_hub/static/index.html b/manual_trading_hub/static/index.html index ede1b80..a9c6464 100644 --- a/manual_trading_hub/static/index.html +++ b/manual_trading_hub/static/index.html @@ -15,7 +15,7 @@ - + @@ -303,9 +303,16 @@
- +
+
@@ -584,7 +591,7 @@ - +