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("/calculator")
|
||||||
@app.get("/market")
|
@app.get("/market")
|
||||||
@app.get("/archive")
|
@app.get("/archive")
|
||||||
|
@app.get("/quotes")
|
||||||
@app.get("/dashboard")
|
@app.get("/dashboard")
|
||||||
@app.get("/funds")
|
@app.get("/funds")
|
||||||
@app.get("/ai")
|
@app.get("/ai")
|
||||||
@@ -1093,6 +1094,7 @@ class SettingsDisplayBody(BaseModel):
|
|||||||
show_nav_dashboard: bool = True
|
show_nav_dashboard: bool = True
|
||||||
show_nav_plan: bool = True
|
show_nav_plan: bool = True
|
||||||
show_nav_archive: bool = True
|
show_nav_archive: bool = True
|
||||||
|
show_nav_quotes: bool = True
|
||||||
show_nav_ai: bool = True
|
show_nav_ai: bool = True
|
||||||
show_nav_calculator: bool = True
|
show_nav_calculator: bool = True
|
||||||
show_nav_strategy: bool = True
|
show_nav_strategy: bool = True
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ DEFAULT_DISPLAY = {
|
|||||||
"show_nav_dashboard": True,
|
"show_nav_dashboard": True,
|
||||||
"show_nav_plan": True,
|
"show_nav_plan": True,
|
||||||
"show_nav_archive": True,
|
"show_nav_archive": True,
|
||||||
|
"show_nav_quotes": True,
|
||||||
"show_nav_ai": True,
|
"show_nav_ai": True,
|
||||||
"show_nav_calculator": True,
|
"show_nav_calculator": True,
|
||||||
"show_nav_strategy": True,
|
"show_nav_strategy": True,
|
||||||
|
|||||||
@@ -8087,6 +8087,95 @@ body.funds-fullscreen-open {
|
|||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 8px;
|
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 {
|
.archive-quote-block {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
@@ -29,6 +29,10 @@
|
|||||||
return displayPref("show_nav_archive", true);
|
return displayPref("show_nav_archive", true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function showNavQuotesPref() {
|
||||||
|
return displayPref("show_nav_quotes", true);
|
||||||
|
}
|
||||||
|
|
||||||
function showNavAiPref() {
|
function showNavAiPref() {
|
||||||
return displayPref("show_nav_ai", true);
|
return displayPref("show_nav_ai", true);
|
||||||
}
|
}
|
||||||
@@ -56,6 +60,7 @@
|
|||||||
["nav-dashboard", "m-nav-dashboard", d.show_nav_dashboard === false],
|
["nav-dashboard", "m-nav-dashboard", d.show_nav_dashboard === false],
|
||||||
["nav-plan", "m-nav-plan", d.show_nav_plan === false],
|
["nav-plan", "m-nav-plan", d.show_nav_plan === false],
|
||||||
["nav-archive", "m-nav-archive", d.show_nav_archive === 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-ai", "m-tab-ai", d.show_nav_ai === false],
|
||||||
["nav-calculator", "m-tab-calculator", d.show_nav_calculator === false],
|
["nav-calculator", "m-tab-calculator", d.show_nav_calculator === false],
|
||||||
["nav-strategy", "m-nav-strategy", d.show_nav_strategy === false],
|
["nav-strategy", "m-nav-strategy", d.show_nav_strategy === false],
|
||||||
@@ -127,6 +132,7 @@
|
|||||||
if (page === "dashboard") return showNavDashboardPref();
|
if (page === "dashboard") return showNavDashboardPref();
|
||||||
if (page === "plan") return showNavPlanPref();
|
if (page === "plan") return showNavPlanPref();
|
||||||
if (page === "archive") return showNavArchivePref();
|
if (page === "archive") return showNavArchivePref();
|
||||||
|
if (page === "quotes") return showNavQuotesPref();
|
||||||
if (page === "ai") return showNavAiPref();
|
if (page === "ai") return showNavAiPref();
|
||||||
if (page === "calculator") return showNavCalculatorPref();
|
if (page === "calculator") return showNavCalculatorPref();
|
||||||
if (page === "strategy") return showNavStrategyPref();
|
if (page === "strategy") return showNavStrategyPref();
|
||||||
@@ -142,6 +148,7 @@
|
|||||||
const dashCb = document.getElementById("pref-show-nav-dashboard");
|
const dashCb = document.getElementById("pref-show-nav-dashboard");
|
||||||
const planCb = document.getElementById("pref-show-nav-plan");
|
const planCb = document.getElementById("pref-show-nav-plan");
|
||||||
const archiveCb = document.getElementById("pref-show-nav-archive");
|
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 aiCb = document.getElementById("pref-show-nav-ai");
|
||||||
const calcCb = document.getElementById("pref-show-nav-calculator");
|
const calcCb = document.getElementById("pref-show-nav-calculator");
|
||||||
const strategyCb = document.getElementById("pref-show-nav-strategy");
|
const strategyCb = document.getElementById("pref-show-nav-strategy");
|
||||||
@@ -152,6 +159,7 @@
|
|||||||
if (dashCb) dashCb.checked = d.show_nav_dashboard !== false;
|
if (dashCb) dashCb.checked = d.show_nav_dashboard !== false;
|
||||||
if (planCb) planCb.checked = d.show_nav_plan !== false;
|
if (planCb) planCb.checked = d.show_nav_plan !== false;
|
||||||
if (archiveCb) archiveCb.checked = d.show_nav_archive !== 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 (aiCb) aiCb.checked = d.show_nav_ai !== false;
|
||||||
if (calcCb) calcCb.checked = d.show_nav_calculator !== false;
|
if (calcCb) calcCb.checked = d.show_nav_calculator !== false;
|
||||||
if (strategyCb) strategyCb.checked = d.show_nav_strategy !== false;
|
if (strategyCb) strategyCb.checked = d.show_nav_strategy !== false;
|
||||||
@@ -1264,6 +1272,7 @@
|
|||||||
const p = window.location.pathname.replace(/\/$/, "") || "/monitor";
|
const p = window.location.pathname.replace(/\/$/, "") || "/monitor";
|
||||||
if (p.includes("settings")) return "settings";
|
if (p.includes("settings")) return "settings";
|
||||||
if (p.includes("archive")) return "archive";
|
if (p.includes("archive")) return "archive";
|
||||||
|
if (p.includes("quotes")) return "quotes";
|
||||||
if (p.includes("dashboard")) return "dashboard";
|
if (p.includes("dashboard")) return "dashboard";
|
||||||
if (p.includes("funds")) return "funds";
|
if (p.includes("funds")) return "funds";
|
||||||
if (p.includes("plan")) return "plan";
|
if (p.includes("plan")) return "plan";
|
||||||
@@ -1279,6 +1288,7 @@
|
|||||||
function pageElementId(page) {
|
function pageElementId(page) {
|
||||||
if (page === "settings") return "page-settings";
|
if (page === "settings") return "page-settings";
|
||||||
if (page === "archive") return "page-archive";
|
if (page === "archive") return "page-archive";
|
||||||
|
if (page === "quotes") return "page-quotes";
|
||||||
if (page === "dashboard") return "page-dashboard";
|
if (page === "dashboard") return "page-dashboard";
|
||||||
if (page === "funds") return "page-funds";
|
if (page === "funds") return "page-funds";
|
||||||
if (page === "plan") return "page-plan";
|
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-calculator", page === "calculator");
|
||||||
document.body.classList.toggle("hub-page-settings", page === "settings");
|
document.body.classList.toggle("hub-page-settings", page === "settings");
|
||||||
document.body.classList.toggle("hub-page-archive", page === "archive");
|
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-plan", page === "plan");
|
||||||
document.body.classList.toggle("hub-page-strategy", page === "strategy");
|
document.body.classList.toggle("hub-page-strategy", page === "strategy");
|
||||||
document.body.classList.toggle("hub-page-logs", page === "logs");
|
document.body.classList.toggle("hub-page-logs", page === "logs");
|
||||||
@@ -1339,6 +1350,11 @@
|
|||||||
} else if (window.hubArchivePage && window.hubArchivePage.destroy) {
|
} else if (window.hubArchivePage && window.hubArchivePage.destroy) {
|
||||||
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) {
|
if (page === "plan" && window.hubPlanPage) {
|
||||||
window.hubPlanPage.init();
|
window.hubPlanPage.init();
|
||||||
} else if (window.hubPlanPage && window.hubPlanPage.destroy) {
|
} else if (window.hubPlanPage && window.hubPlanPage.destroy) {
|
||||||
@@ -5020,6 +5036,7 @@
|
|||||||
const dashCb = document.getElementById("pref-show-nav-dashboard");
|
const dashCb = document.getElementById("pref-show-nav-dashboard");
|
||||||
const planCb = document.getElementById("pref-show-nav-plan");
|
const planCb = document.getElementById("pref-show-nav-plan");
|
||||||
const archiveCb = document.getElementById("pref-show-nav-archive");
|
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 aiCb = document.getElementById("pref-show-nav-ai");
|
||||||
const calcCb = document.getElementById("pref-show-nav-calculator");
|
const calcCb = document.getElementById("pref-show-nav-calculator");
|
||||||
const strategyCb = document.getElementById("pref-show-nav-strategy");
|
const strategyCb = document.getElementById("pref-show-nav-strategy");
|
||||||
@@ -5042,6 +5059,7 @@
|
|||||||
show_nav_dashboard: dashCb ? !!dashCb.checked : true,
|
show_nav_dashboard: dashCb ? !!dashCb.checked : true,
|
||||||
show_nav_plan: planCb ? !!planCb.checked : true,
|
show_nav_plan: planCb ? !!planCb.checked : true,
|
||||||
show_nav_archive: archiveCb ? !!archiveCb.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_ai: aiCb ? !!aiCb.checked : true,
|
||||||
show_nav_calculator: calcCb ? !!calcCb.checked : true,
|
show_nav_calculator: calcCb ? !!calcCb.checked : true,
|
||||||
show_nav_strategy: strategyCb ? !!strategyCb.checked : true,
|
show_nav_strategy: strategyCb ? !!strategyCb.checked : true,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
<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'" />
|
<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>
|
<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/trade_stats_calendar.css?v=4" />
|
||||||
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
<link rel="stylesheet" href="/assets/account_risk_badge.css?v=4" />
|
||||||
<script src="/assets/account_risk_badge.js?v=4"></script>
|
<script src="/assets/account_risk_badge.js?v=4"></script>
|
||||||
@@ -56,6 +56,7 @@
|
|||||||
<a href="/market" id="nav-market">行情区</a>
|
<a href="/market" id="nav-market">行情区</a>
|
||||||
<a href="/calculator" id="nav-calculator">计算器</a>
|
<a href="/calculator" id="nav-calculator">计算器</a>
|
||||||
<a href="/archive" id="nav-archive">内照明心</a>
|
<a href="/archive" id="nav-archive">内照明心</a>
|
||||||
|
<a href="/quotes" id="nav-quotes">语录</a>
|
||||||
<a href="/dashboard" id="nav-dashboard">数据看板</a>
|
<a href="/dashboard" id="nav-dashboard">数据看板</a>
|
||||||
<a href="/ai" id="nav-ai">AI 教练</a>
|
<a href="/ai" id="nav-ai">AI 教练</a>
|
||||||
<a href="/logs" id="nav-logs">系统日志</a>
|
<a href="/logs" id="nav-logs">系统日志</a>
|
||||||
@@ -567,6 +568,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</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 id="page-dashboard" class="page hidden">
|
||||||
<div class="dash-bg-grid" aria-hidden="true"></div>
|
<div class="dash-bg-grid" aria-hidden="true"></div>
|
||||||
<div class="dash-wrap">
|
<div class="dash-wrap">
|
||||||
@@ -1066,6 +1080,10 @@
|
|||||||
<input type="checkbox" id="pref-show-nav-archive" checked />
|
<input type="checkbox" id="pref-show-nav-archive" checked />
|
||||||
顶栏显示「内照明心」
|
顶栏显示「内照明心」
|
||||||
</label>
|
</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">
|
<label class="chk-label settings-display-chk">
|
||||||
<input type="checkbox" id="pref-show-nav-ai" checked />
|
<input type="checkbox" id="pref-show-nav-ai" checked />
|
||||||
顶栏显示「AI 教练」
|
顶栏显示「AI 教练」
|
||||||
@@ -1269,6 +1287,7 @@
|
|||||||
<a href="/funds" id="m-nav-funds">资金概况</a>
|
<a href="/funds" id="m-nav-funds">资金概况</a>
|
||||||
<a href="/plan" id="m-nav-plan">开仓计划</a>
|
<a href="/plan" id="m-nav-plan">开仓计划</a>
|
||||||
<a href="/archive" id="m-nav-archive">内照明心</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="/dashboard" id="m-nav-dashboard">数据看板</a>
|
||||||
<a href="/strategy" id="m-nav-strategy">策略说明</a>
|
<a href="/strategy" id="m-nav-strategy">策略说明</a>
|
||||||
<a href="/help" id="m-nav-help">使用说明</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/calculator.js?v=20260715-calc-tabs"></script>
|
||||||
<script src="/assets/trade_stats_calendar.js?v=3"></script>
|
<script src="/assets/trade_stats_calendar.js?v=3"></script>
|
||||||
<script src="/assets/archive.js?v=20260717-archive-tabs"></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/funds.js?v=20260717-funds-scroll-fix"></script>
|
||||||
<script src="/assets/dashboard.js?v=20260708-options-expiry-cd"></script>
|
<script src="/assets/dashboard.js?v=20260708-options-expiry-cd"></script>
|
||||||
<script src="/assets/strategy.js?v=3"></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_expiry_countdown.js?v=1"></script>
|
||||||
<script src="/assets/options_position_cards.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/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>
|
</body>
|
||||||
</html>
|
</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