Add top-nav Quotes blog feed page.
Surface recent review quotes grouped by trading day with expand, day PnL summary, and AI review jump. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -980,6 +980,7 @@ def root_redirect():
|
||||
@app.get("/calculator")
|
||||
@app.get("/market")
|
||||
@app.get("/archive")
|
||||
@app.get("/quotes")
|
||||
@app.get("/dashboard")
|
||||
@app.get("/funds")
|
||||
@app.get("/ai")
|
||||
@@ -1093,6 +1094,7 @@ class SettingsDisplayBody(BaseModel):
|
||||
show_nav_dashboard: bool = True
|
||||
show_nav_plan: bool = True
|
||||
show_nav_archive: bool = True
|
||||
show_nav_quotes: bool = True
|
||||
show_nav_ai: bool = True
|
||||
show_nav_calculator: bool = True
|
||||
show_nav_strategy: bool = True
|
||||
|
||||
@@ -26,6 +26,7 @@ DEFAULT_DISPLAY = {
|
||||
"show_nav_dashboard": True,
|
||||
"show_nav_plan": True,
|
||||
"show_nav_archive": True,
|
||||
"show_nav_quotes": True,
|
||||
"show_nav_ai": True,
|
||||
"show_nav_calculator": True,
|
||||
"show_nav_strategy": True,
|
||||
|
||||
@@ -8087,6 +8087,95 @@ body.funds-fullscreen-open {
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* —— 语录博客流 —— */
|
||||
#page-quotes .quotes-toolbar {
|
||||
margin-bottom: 14px;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
}
|
||||
#page-quotes .quotes-link-archive {
|
||||
text-decoration: none;
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-soft);
|
||||
}
|
||||
.quotes-feed {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18px;
|
||||
max-width: 820px;
|
||||
}
|
||||
.quotes-empty {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.88rem;
|
||||
}
|
||||
.quotes-day-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.quotes-day-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
justify-content: space-between;
|
||||
gap: 8px 14px;
|
||||
padding-bottom: 6px;
|
||||
border-bottom: 1px solid var(--border-soft);
|
||||
}
|
||||
.quotes-day-title {
|
||||
margin: 0;
|
||||
font-size: 1.05rem;
|
||||
font-weight: 650;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.quotes-day-summary {
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
.quotes-day-cards {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.quotes-card {
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--border-soft);
|
||||
border-radius: var(--radius);
|
||||
padding: 14px 16px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
.quotes-card-body {
|
||||
margin: 0;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.65;
|
||||
color: var(--text);
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
.quotes-card-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
align-items: center;
|
||||
}
|
||||
.quotes-ai-btn {
|
||||
color: var(--accent);
|
||||
border-color: color-mix(in srgb, var(--accent) 35%, var(--border-soft));
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
.quotes-feed {
|
||||
max-width: none;
|
||||
}
|
||||
.quotes-day-head {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
.archive-quote-block {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -29,6 +29,10 @@
|
||||
return displayPref("show_nav_archive", true);
|
||||
}
|
||||
|
||||
function showNavQuotesPref() {
|
||||
return displayPref("show_nav_quotes", true);
|
||||
}
|
||||
|
||||
function showNavAiPref() {
|
||||
return displayPref("show_nav_ai", true);
|
||||
}
|
||||
@@ -56,6 +60,7 @@
|
||||
["nav-dashboard", "m-nav-dashboard", d.show_nav_dashboard === false],
|
||||
["nav-plan", "m-nav-plan", d.show_nav_plan === false],
|
||||
["nav-archive", "m-nav-archive", d.show_nav_archive === false],
|
||||
["nav-quotes", "m-nav-quotes", d.show_nav_quotes === false],
|
||||
["nav-ai", "m-tab-ai", d.show_nav_ai === false],
|
||||
["nav-calculator", "m-tab-calculator", d.show_nav_calculator === false],
|
||||
["nav-strategy", "m-nav-strategy", d.show_nav_strategy === false],
|
||||
@@ -127,6 +132,7 @@
|
||||
if (page === "dashboard") return showNavDashboardPref();
|
||||
if (page === "plan") return showNavPlanPref();
|
||||
if (page === "archive") return showNavArchivePref();
|
||||
if (page === "quotes") return showNavQuotesPref();
|
||||
if (page === "ai") return showNavAiPref();
|
||||
if (page === "calculator") return showNavCalculatorPref();
|
||||
if (page === "strategy") return showNavStrategyPref();
|
||||
@@ -142,6 +148,7 @@
|
||||
const dashCb = document.getElementById("pref-show-nav-dashboard");
|
||||
const planCb = document.getElementById("pref-show-nav-plan");
|
||||
const archiveCb = document.getElementById("pref-show-nav-archive");
|
||||
const quotesCb = document.getElementById("pref-show-nav-quotes");
|
||||
const aiCb = document.getElementById("pref-show-nav-ai");
|
||||
const calcCb = document.getElementById("pref-show-nav-calculator");
|
||||
const strategyCb = document.getElementById("pref-show-nav-strategy");
|
||||
@@ -152,6 +159,7 @@
|
||||
if (dashCb) dashCb.checked = d.show_nav_dashboard !== false;
|
||||
if (planCb) planCb.checked = d.show_nav_plan !== false;
|
||||
if (archiveCb) archiveCb.checked = d.show_nav_archive !== false;
|
||||
if (quotesCb) quotesCb.checked = d.show_nav_quotes !== false;
|
||||
if (aiCb) aiCb.checked = d.show_nav_ai !== false;
|
||||
if (calcCb) calcCb.checked = d.show_nav_calculator !== false;
|
||||
if (strategyCb) strategyCb.checked = d.show_nav_strategy !== false;
|
||||
@@ -1264,6 +1272,7 @@
|
||||
const p = window.location.pathname.replace(/\/$/, "") || "/monitor";
|
||||
if (p.includes("settings")) return "settings";
|
||||
if (p.includes("archive")) return "archive";
|
||||
if (p.includes("quotes")) return "quotes";
|
||||
if (p.includes("dashboard")) return "dashboard";
|
||||
if (p.includes("funds")) return "funds";
|
||||
if (p.includes("plan")) return "plan";
|
||||
@@ -1279,6 +1288,7 @@
|
||||
function pageElementId(page) {
|
||||
if (page === "settings") return "page-settings";
|
||||
if (page === "archive") return "page-archive";
|
||||
if (page === "quotes") return "page-quotes";
|
||||
if (page === "dashboard") return "page-dashboard";
|
||||
if (page === "funds") return "page-funds";
|
||||
if (page === "plan") return "page-plan";
|
||||
@@ -1316,6 +1326,7 @@
|
||||
document.body.classList.toggle("hub-page-calculator", page === "calculator");
|
||||
document.body.classList.toggle("hub-page-settings", page === "settings");
|
||||
document.body.classList.toggle("hub-page-archive", page === "archive");
|
||||
document.body.classList.toggle("hub-page-quotes", page === "quotes");
|
||||
document.body.classList.toggle("hub-page-plan", page === "plan");
|
||||
document.body.classList.toggle("hub-page-strategy", page === "strategy");
|
||||
document.body.classList.toggle("hub-page-logs", page === "logs");
|
||||
@@ -1339,6 +1350,11 @@
|
||||
} else if (window.hubArchivePage && window.hubArchivePage.destroy) {
|
||||
window.hubArchivePage.destroy();
|
||||
}
|
||||
if (page === "quotes" && window.hubQuotesPage) {
|
||||
window.hubQuotesPage.init();
|
||||
} else if (window.hubQuotesPage && window.hubQuotesPage.destroy) {
|
||||
window.hubQuotesPage.destroy();
|
||||
}
|
||||
if (page === "plan" && window.hubPlanPage) {
|
||||
window.hubPlanPage.init();
|
||||
} else if (window.hubPlanPage && window.hubPlanPage.destroy) {
|
||||
@@ -5020,6 +5036,7 @@
|
||||
const dashCb = document.getElementById("pref-show-nav-dashboard");
|
||||
const planCb = document.getElementById("pref-show-nav-plan");
|
||||
const archiveCb = document.getElementById("pref-show-nav-archive");
|
||||
const quotesCb = document.getElementById("pref-show-nav-quotes");
|
||||
const aiCb = document.getElementById("pref-show-nav-ai");
|
||||
const calcCb = document.getElementById("pref-show-nav-calculator");
|
||||
const strategyCb = document.getElementById("pref-show-nav-strategy");
|
||||
@@ -5042,6 +5059,7 @@
|
||||
show_nav_dashboard: dashCb ? !!dashCb.checked : true,
|
||||
show_nav_plan: planCb ? !!planCb.checked : true,
|
||||
show_nav_archive: archiveCb ? !!archiveCb.checked : true,
|
||||
show_nav_quotes: quotesCb ? !!quotesCb.checked : true,
|
||||
show_nav_ai: aiCb ? !!aiCb.checked : true,
|
||||
show_nav_calculator: calcCb ? !!calcCb.checked : true,
|
||||
show_nav_strategy: strategyCb ? !!strategyCb.checked : true,
|
||||
|
||||
@@ -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-tabs" />
|
||||
<link rel="stylesheet" href="/assets/app.css?v=20260717-quotes-feed" />
|
||||
<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>
|
||||
@@ -56,6 +56,7 @@
|
||||
<a href="/market" id="nav-market">行情区</a>
|
||||
<a href="/calculator" id="nav-calculator">计算器</a>
|
||||
<a href="/archive" id="nav-archive">内照明心</a>
|
||||
<a href="/quotes" id="nav-quotes">语录</a>
|
||||
<a href="/dashboard" id="nav-dashboard">数据看板</a>
|
||||
<a href="/ai" id="nav-ai">AI 教练</a>
|
||||
<a href="/logs" id="nav-logs">系统日志</a>
|
||||
@@ -567,6 +568,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="page-quotes" class="page hidden">
|
||||
<div class="page-head">
|
||||
<h1><span class="head-tag">QT</span> 语录</h1>
|
||||
<p class="page-desc">复盘语录博客流 · 按交易日分组 · 可展开全文</p>
|
||||
</div>
|
||||
<div class="quotes-toolbar toolbar">
|
||||
<button type="button" id="quotes-btn-refresh" class="primary">刷新</button>
|
||||
<a href="/archive" class="ghost quotes-link-archive" id="quotes-link-archive">去内照明心写语录</a>
|
||||
<span id="quotes-status" class="toolbar-meta"></span>
|
||||
</div>
|
||||
<div id="quotes-feed" class="quotes-feed" aria-live="polite"></div>
|
||||
</div>
|
||||
|
||||
<div id="page-dashboard" class="page hidden">
|
||||
<div class="dash-bg-grid" aria-hidden="true"></div>
|
||||
<div class="dash-wrap">
|
||||
@@ -1066,6 +1080,10 @@
|
||||
<input type="checkbox" id="pref-show-nav-archive" checked />
|
||||
顶栏显示「内照明心」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-quotes" checked />
|
||||
顶栏显示「语录」
|
||||
</label>
|
||||
<label class="chk-label settings-display-chk">
|
||||
<input type="checkbox" id="pref-show-nav-ai" checked />
|
||||
顶栏显示「AI 教练」
|
||||
@@ -1269,6 +1287,7 @@
|
||||
<a href="/funds" id="m-nav-funds">资金概况</a>
|
||||
<a href="/plan" id="m-nav-plan">开仓计划</a>
|
||||
<a href="/archive" id="m-nav-archive">内照明心</a>
|
||||
<a href="/quotes" id="m-nav-quotes">语录</a>
|
||||
<a href="/dashboard" id="m-nav-dashboard">数据看板</a>
|
||||
<a href="/strategy" id="m-nav-strategy">策略说明</a>
|
||||
<a href="/help" id="m-nav-help">使用说明</a>
|
||||
@@ -1330,6 +1349,7 @@
|
||||
<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-archive-tabs"></script>
|
||||
<script src="/assets/quotes.js?v=20260717-quotes-feed"></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>
|
||||
@@ -1340,6 +1360,6 @@
|
||||
<script src="/assets/options_expiry_countdown.js?v=1"></script>
|
||||
<script src="/assets/options_position_cards.js?v=1"></script>
|
||||
<script src="/assets/backup.js?v=1"></script>
|
||||
<script src="/assets/app.js?v=20260717-scrollbar-right-all"></script>
|
||||
<script src="/assets/app.js?v=20260717-quotes-feed"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
/**
|
||||
* 语录博客流:按交易日分组 · 截断展开 · 当日盈亏摘要 · AI 复盘跳转.
|
||||
*/
|
||||
(function () {
|
||||
const page = document.getElementById("page-quotes");
|
||||
if (!page) return;
|
||||
|
||||
const elFeed = document.getElementById("quotes-feed");
|
||||
const elStatus = document.getElementById("quotes-status");
|
||||
const elBtnRefresh = document.getElementById("quotes-btn-refresh");
|
||||
const elLinkArchive = document.getElementById("quotes-link-archive");
|
||||
|
||||
const RECENT_LIMIT = 20;
|
||||
const PREVIEW_LEN = 140;
|
||||
const ARCHIVE_QUOTE_AI_KEY = "hub_archive_quote_ai";
|
||||
|
||||
let quotes = [];
|
||||
let dayStats = {};
|
||||
let expanded = {};
|
||||
let inited = false;
|
||||
let loading = false;
|
||||
|
||||
function esc(s) {
|
||||
return String(s == null ? "" : s)
|
||||
.replace(/&/g, "&")
|
||||
.replace(/</g, "<")
|
||||
.replace(/>/g, ">")
|
||||
.replace(/"/g, """);
|
||||
}
|
||||
|
||||
async function apiFetch(url, opts) {
|
||||
return fetch(url, Object.assign({ credentials: "same-origin" }, opts || {}));
|
||||
}
|
||||
|
||||
function setStatus(text) {
|
||||
if (elStatus) elStatus.textContent = text || "";
|
||||
}
|
||||
|
||||
function findQuote(id) {
|
||||
return (
|
||||
quotes.find(function (q) {
|
||||
return String(q.id) === String(id);
|
||||
}) || null
|
||||
);
|
||||
}
|
||||
|
||||
function fmtPnl(v) {
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n)) return "—";
|
||||
return (n >= 0 ? "+" : "") + n.toFixed(2) + "U";
|
||||
}
|
||||
|
||||
function pnlClass(v) {
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n) || n === 0) return "";
|
||||
return n > 0 ? "pnl-pos" : "pnl-neg";
|
||||
}
|
||||
|
||||
function fmtWinRate(v) {
|
||||
const n = Number(v);
|
||||
if (!Number.isFinite(n)) return "—";
|
||||
return n.toFixed(1) + "%";
|
||||
}
|
||||
|
||||
function daySummaryHtml(day, st) {
|
||||
if (!st) {
|
||||
return '<span class="quotes-day-summary muted">当日统计加载中…</span>';
|
||||
}
|
||||
const openN = Number(st.open_count) || 0;
|
||||
const pnl = st.pnl_total;
|
||||
return (
|
||||
'<span class="quotes-day-summary">' +
|
||||
openN +
|
||||
" 笔 · 盈亏 <span class=\"" +
|
||||
pnlClass(pnl) +
|
||||
'">' +
|
||||
esc(fmtPnl(pnl)) +
|
||||
"</span> · 胜率 " +
|
||||
esc(fmtWinRate(st.win_rate)) +
|
||||
"</span>"
|
||||
);
|
||||
}
|
||||
|
||||
function previewText(raw) {
|
||||
const text = String(raw || "").trim();
|
||||
if (text.length <= PREVIEW_LEN) return { text: text, truncated: false };
|
||||
return { text: text.slice(0, PREVIEW_LEN).trim() + "…", truncated: true };
|
||||
}
|
||||
|
||||
function groupByDay(rows) {
|
||||
const map = {};
|
||||
const order = [];
|
||||
rows.forEach(function (q) {
|
||||
const day = String(q.quote_date || "").slice(0, 10) || "—";
|
||||
if (!map[day]) {
|
||||
map[day] = [];
|
||||
order.push(day);
|
||||
}
|
||||
map[day].push(q);
|
||||
});
|
||||
return { map: map, order: order };
|
||||
}
|
||||
|
||||
function renderFeed() {
|
||||
if (!elFeed) return;
|
||||
if (!quotes.length) {
|
||||
elFeed.innerHTML =
|
||||
'<p class="quotes-empty">暂无复盘语录.可在「内照明心 → 复盘语录」中添加.</p>';
|
||||
return;
|
||||
}
|
||||
const grouped = groupByDay(quotes);
|
||||
elFeed.innerHTML = grouped.order
|
||||
.map(function (day) {
|
||||
const list = grouped.map[day] || [];
|
||||
const cards = list
|
||||
.map(function (q) {
|
||||
const id = String(q.id);
|
||||
const full = String(q.content || "").trim();
|
||||
const isOpen = !!expanded[id];
|
||||
const prev = previewText(full);
|
||||
const showExpand = prev.truncated;
|
||||
const body = isOpen || !showExpand ? full : prev.text;
|
||||
return (
|
||||
'<article class="quotes-card' +
|
||||
(isOpen ? " is-expanded" : "") +
|
||||
'" data-id="' +
|
||||
esc(id) +
|
||||
'">' +
|
||||
'<div class="quotes-card-body">' +
|
||||
esc(body) +
|
||||
"</div>" +
|
||||
'<div class="quotes-card-actions">' +
|
||||
(showExpand
|
||||
? '<button type="button" class="ghost quotes-expand-btn" data-id="' +
|
||||
esc(id) +
|
||||
'">' +
|
||||
(isOpen ? "收起" : "展开") +
|
||||
"</button>"
|
||||
: "") +
|
||||
'<button type="button" class="ghost quotes-ai-btn" data-id="' +
|
||||
esc(id) +
|
||||
'">AI 复盘</button>' +
|
||||
"</div></article>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
return (
|
||||
'<section class="quotes-day-group" data-day="' +
|
||||
esc(day) +
|
||||
'">' +
|
||||
'<header class="quotes-day-head">' +
|
||||
'<h2 class="quotes-day-title">' +
|
||||
esc(day) +
|
||||
"</h2>" +
|
||||
daySummaryHtml(day, dayStats[day]) +
|
||||
"</header>" +
|
||||
'<div class="quotes-day-cards">' +
|
||||
cards +
|
||||
"</div></section>"
|
||||
);
|
||||
})
|
||||
.join("");
|
||||
|
||||
elFeed.querySelectorAll(".quotes-expand-btn").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
const id = btn.getAttribute("data-id");
|
||||
expanded[id] = !expanded[id];
|
||||
renderFeed();
|
||||
});
|
||||
});
|
||||
elFeed.querySelectorAll(".quotes-ai-btn").forEach(function (btn) {
|
||||
btn.addEventListener("click", function () {
|
||||
startQuoteAiChat(btn.getAttribute("data-id"));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function startQuoteAiChat(quoteId) {
|
||||
const q = findQuote(quoteId);
|
||||
const content = q && String(q.content || "").trim();
|
||||
if (!q || !content) {
|
||||
setStatus("语录内容为空,无法发起 AI 对话");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
sessionStorage.setItem(
|
||||
ARCHIVE_QUOTE_AI_KEY,
|
||||
JSON.stringify({
|
||||
quote_date: q.quote_date || "",
|
||||
content: content,
|
||||
})
|
||||
);
|
||||
} catch (_) {
|
||||
setStatus("无法保存跳转数据");
|
||||
return;
|
||||
}
|
||||
if (typeof window.hubNavigateTo === "function") {
|
||||
window.hubNavigateTo("/ai");
|
||||
return;
|
||||
}
|
||||
location.href = "/ai";
|
||||
}
|
||||
|
||||
async function loadDayStats(days) {
|
||||
const uniq = [];
|
||||
const seen = {};
|
||||
(days || []).forEach(function (d) {
|
||||
const day = String(d || "").slice(0, 10);
|
||||
if (!day || day === "—" || seen[day]) return;
|
||||
seen[day] = true;
|
||||
uniq.push(day);
|
||||
});
|
||||
await Promise.all(
|
||||
uniq.map(async function (day) {
|
||||
if (dayStats[day]) return;
|
||||
try {
|
||||
const q = new URLSearchParams();
|
||||
q.set("period", "today");
|
||||
q.set("trading_day", day);
|
||||
const r = await apiFetch("/api/archive/daily-trades?" + q.toString());
|
||||
const j = await r.json();
|
||||
if (r.ok) {
|
||||
dayStats[day] = j.stats || { open_count: 0, pnl_total: 0, win_rate: null };
|
||||
} else {
|
||||
dayStats[day] = { open_count: 0, pnl_total: 0, win_rate: null };
|
||||
}
|
||||
} catch (_) {
|
||||
dayStats[day] = { open_count: 0, pnl_total: 0, win_rate: null };
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
async function loadQuotes() {
|
||||
if (loading) return;
|
||||
loading = true;
|
||||
setStatus("加载语录…");
|
||||
try {
|
||||
const r = await apiFetch("/api/archive/quotes");
|
||||
const j = await r.json();
|
||||
if (!r.ok) {
|
||||
setStatus(j.detail || "加载失败");
|
||||
return;
|
||||
}
|
||||
quotes = (j.quotes || []).slice(0, RECENT_LIMIT);
|
||||
const days = quotes.map(function (q) {
|
||||
return q.quote_date;
|
||||
});
|
||||
renderFeed();
|
||||
await loadDayStats(days);
|
||||
renderFeed();
|
||||
setStatus("最近 " + quotes.length + " 条 · " + new Date().toLocaleTimeString());
|
||||
} catch (e) {
|
||||
setStatus(String(e && e.message ? e.message : e) || "加载失败");
|
||||
} finally {
|
||||
loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
function bindEvents() {
|
||||
if (elBtnRefresh) elBtnRefresh.addEventListener("click", loadQuotes);
|
||||
if (elLinkArchive) {
|
||||
elLinkArchive.addEventListener("click", function (ev) {
|
||||
if (typeof window.hubNavigateTo === "function") {
|
||||
ev.preventDefault();
|
||||
window.hubNavigateTo("/archive");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function init() {
|
||||
if (!page || page.classList.contains("hidden")) return;
|
||||
if (!inited) {
|
||||
bindEvents();
|
||||
inited = true;
|
||||
}
|
||||
await loadQuotes();
|
||||
}
|
||||
|
||||
function destroy() {}
|
||||
|
||||
window.hubQuotesPage = { init: init, destroy: destroy };
|
||||
})();
|
||||
Reference in New Issue
Block a user