From fcae7f5446059716344c34447093a3bec3a6d7ff Mon Sep 17 00:00:00 2001 From: wshy Date: Sat, 11 Jul 2026 16:57:23 +0800 Subject: [PATCH] =?UTF-8?q?feat(dashboard):=20=E7=9C=8B=E6=9D=BF=E9=97=B4?= =?UTF-8?q?=E8=B7=9D=E6=94=B6=E7=B4=A7=E3=80=81=E5=8D=A1=E7=89=87=E7=BE=8E?= =?UTF-8?q?=E5=8C=96=E3=80=81=E6=9D=BF=E5=9D=97=E6=A0=87=E8=AF=86=E4=B8=8E?= =?UTF-8?q?=E8=BF=9E=E6=9D=BF=E4=B8=AA=E8=82=A1=E5=90=8D=E7=A7=B0=20(#101)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 收紧看板全局间距(卡片间 gap/外层 padding 降至 6px),信息密度提升 - 卡片统一加微投影/毛玻璃/hover 浮起效果,标题加渐变指示条 - 四榜与概念/行业热度龙头个股加创/科/北板块标识 - 涨停梯队(后端 tiers 带 stocks)及最高连板卡片:≤3 只时显示个股名称 Co-authored-by: shy3130 --- .../app/services/market_overview_builder.py | 17 ++- frontend/src/lib/api.ts | 2 +- frontend/src/pages/Dashboard.tsx | 124 ++++++++++++------ 3 files changed, 101 insertions(+), 42 deletions(-) diff --git a/backend/app/services/market_overview_builder.py b/backend/app/services/market_overview_builder.py index b5fbc17..a61209a 100644 --- a/backend/app/services/market_overview_builder.py +++ b/backend/app/services/market_overview_builder.py @@ -486,11 +486,26 @@ def build_market_overview( b["up_pct"] = b["up"] / count * 100 tiers_map: dict[int, int] = {} + tiers_stocks: dict[int, list] = {} for r in rows: n = int(_finite(r.get("consecutive_limit_ups")) or 0) if n > 0: tiers_map[n] = tiers_map.get(n, 0) + 1 - tiers = [{"boards": k, "count": v} for k, v in sorted(tiers_map.items(), key=lambda item: -item[0])] + sym = str(r.get("symbol") or "") + if sym: + tiers_stocks.setdefault(n, []).append({ + "symbol": sym, + "name": r.get("name") or "", + "amount": _finite(r.get("amount")) or 0.0, + }) + tiers = [ + { + "boards": k, + "count": v, + "stocks": sorted(tiers_stocks.get(k, []), key=lambda x: x["amount"], reverse=True)[:5], + } + for k, v in sorted(tiers_map.items(), key=lambda item: -item[0]) + ] index_changes = [_finite(r.get("change_pct")) for r in indices] index_changes = [v for v in index_changes if v is not None] diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 9c66c60..9a93a93 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -323,7 +323,7 @@ export interface OverviewMarket { } amount: { total: number; avg: number } boards: { board: string; count: number; up: number; down: number; up_pct: number; amount: number }[] - limit: { limit_up: number; broken: number; failed: number; limit_down: number; max_boards: number; seal_rate?: number; tiers: { boards: number; count: number }[]; sealed_ready?: boolean; fake_up?: number; fake_down?: number } + limit: { limit_up: number; broken: number; failed: number; limit_down: number; max_boards: number; seal_rate?: number; tiers: { boards: number; count: number; stocks?: { symbol: string; name?: string; amount?: number }[] }[]; sealed_ready?: boolean; fake_up?: number; fake_down?: number } distribution: { label: string; count: number; pct: number }[] trend: { above_ma5: number; above_ma20: number; above_ma60: number; above_ma5_pct: number; above_ma20_pct: number; above_ma60_pct: number; new_high: number; new_low: number } activity: { avg_turnover: number; high_turnover: number; high_vol_ratio: number; vol_ratio: number } diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index 8141be7..d2405bd 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -71,6 +71,7 @@ function SectionTitle({ icon: Icon, title, hint }: { icon: typeof Activity; titl return (
+

{title}

@@ -218,7 +219,7 @@ function KpiCell({ label, value, sub, tone = 'neutral' }: { label: ReactNode; va const isPlain = typeof value === 'string' || typeof value === 'number' const color = tone === 'bull' ? 'text-bull' : tone === 'bear' ? 'text-bear' : tone === 'accent' ? 'text-accent' : 'text-foreground' return ( -
+
{label}
{value}
{sub &&
{sub}
} @@ -232,7 +233,7 @@ function IndexTicker({ item }: { item: OverviewMarket['indices'][number] }) { return (
{item.name || item.symbol}
{fmtIndexPct(pct)}
@@ -363,22 +364,37 @@ function LadderMini({ limit }: { limit: OverviewMarket['limit'] }) { {(limit.seal_rate ?? 0).toFixed(0)}%
{tiers.length === 0 &&
暂无 2 板以上
} - {tiers.map(t => ( -
- = 5 ? 'text-bull' : t.boards >= 3 ? 'text-accent' : 'text-secondary'}`}>{t.boards}板 -
-
+ {tiers.map(t => { + const stocks = t.stocks ?? [] + const showStocks = stocks.length > 0 && stocks.length <= 3 + return ( +
+
+ = 5 ? 'text-bull' : t.boards >= 3 ? 'text-accent' : 'text-secondary'}`}>{t.boards}板 +
+
+
+ {t.count} +
+ {showStocks && ( +
+ {stocks.map(s => ( + + {s.name || s.symbol} + + ))} +
+ )}
- {t.count} -
- ))} + ) + })}
) } function MiniMetric({ label, value, cls = 'text-foreground' }: { label: string; value: string; cls?: string }) { return ( -
+
{label}
{value}
@@ -390,8 +406,8 @@ function StockList({ title, rows, mode, onStockClick }: { onStockClick?: (symbol: string, name?: string) => void; }) { return ( -
-
+
+

{title}

TOP {Math.min(rows.length, 8)}
@@ -399,13 +415,23 @@ function StockList({ title, rows, mode, onStockClick }: { {rows.slice(0, 8).map((r, idx) => (
onStockClick?.(r.symbol, r.name ?? undefined)} > {idx + 1}
-
{r.name || r.symbol}
-
{r.symbol}
+
+ {r.name || r.symbol} + {(() => { + const board = boardTag(r.symbol) + return board ? ( + + {board.label} + + ) : null + })()} +
+ {r.symbol}
{mode === 'amount' ? ( @@ -441,18 +467,30 @@ function RankColumn({ title, rows, tone, onStockClick }: {
{title}
{rows.slice(0, 5).map((r, idx) => ( -
+
{idx + 1}
{r.name}
-
- {r.count}只 · {r.leader?.symbol ? ( +
+ {r.count}只 + · + {r.leader?.symbol ? ( - ) : r.leader?.name ?? '—'} + ) : ( + {r.leader?.name ?? '—'} + )} + {r.leader?.symbol && (() => { + const board = boardTag(r.leader!.symbol!) + return board ? ( + + {board.label} + + ) : null + })()}
{fmtStockPct(r.avg_pct)}
@@ -469,7 +507,7 @@ function HotRankCard({ title, rank, configUrl, onStockClick }: { }) { const hasData = (rank?.leading?.length ?? 0) > 0 || (rank?.lagging?.length ?? 0) > 0 return ( -
+
{hasData ? (
@@ -614,7 +652,7 @@ export function Dashboard() { const quoteMode = data.quote_status?.mode as ('none' | 'watchlist' | 'full_market') | undefined return ( -
+
{/* 无本地数据常驻引导卡片 —— 一键触发盘后管道获取数据(无 Key 也可) */} {hasNoData && ( )} -
+
+

市场看板

@@ -681,7 +720,7 @@ export function Dashboard() { {/* Free 档提示: 大盘看板为盘后数据, 仅自选股实时。避免用户误读为全市场实时。 */} {quoteMode === 'watchlist' && ( -
+
当前为「自选实时」模式,看板展示的大盘数据为盘后快照(最新有数据日),并非盘中实时; @@ -691,23 +730,28 @@ export function Dashboard() {
)} -
+
{data.indices.map(item => )}
-
+
{data.breadth.up}/{data.breadth.flat}/{data.breadth.down}} sub={`上涨率 ${data.breadth.up_pct.toFixed(1)}%`} /> {strongUp}/{strongDown}} sub="涨跌 ≥3%" /> 涨停 / 跌停} value={<>{data.limit.limit_up}/{data.limit.limit_down}} sub={`封板率 ${(data.limit.seal_rate ?? 0).toFixed(0)}%`} /> - + { + const top = data.limit.tiers.find(t => t.boards === data.limit.max_boards) + const stocks = top?.stocks ?? [] + if (stocks.length > 0 && stocks.length <= 3) return stocks.map(s => s.name || s.symbol).join(' · ') + return `梯队 ${data.limit.tiers.length}` + })()} tone="accent" />
-
-
-
-
+
+
+
+
@@ -720,14 +764,14 @@ export function Dashboard() {
-
+
@@ -739,7 +783,7 @@ export function Dashboard() { 0 ? Math.round(data.trend.new_high / (data.trend.new_high + data.trend.new_low) * 100) : 50}%`} cls={data.trend.new_high >= data.trend.new_low ? 'text-bull' : 'text-bear'} />
-
+
@@ -753,12 +797,12 @@ export function Dashboard() {
-
+
setPreviewStock({symbol, name})} /> setPreviewStock({symbol, name})} />
-
+
setPreviewStock({symbol, name})} /> setPreviewStock({symbol, name})} /> setPreviewStock({symbol, name})} /> @@ -766,12 +810,12 @@ export function Dashboard() {
-