diff --git a/serve/frontend/src/pages/Dashboard.tsx b/serve/frontend/src/pages/Dashboard.tsx index 1a7aa9f..f4269c3 100644 --- a/serve/frontend/src/pages/Dashboard.tsx +++ b/serve/frontend/src/pages/Dashboard.tsx @@ -2,19 +2,16 @@ import { useState, useEffect, useRef, type ReactNode } from 'react' import { Link } from 'react-router-dom' import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query' import { motion, AnimatePresence } from 'framer-motion' -import { Activity, ArrowDownRight, ArrowUpRight, BarChart3, BellRing, Database, Flame, Gauge, LineChart, Loader2, Play, RefreshCw, Sparkles, Target, Timer } from 'lucide-react' +import { Activity, ArrowDownRight, ArrowUpRight, BarChart3, Database, Flame, Gauge, LineChart, Loader2, Play, RefreshCw, Sparkles, Target, Timer } from 'lucide-react' import { DatePicker } from '@/components/DatePicker' -import { api, type MarketSnapshotRow, type OverviewDimensionRankItem, type OverviewMarket, type AlertEvent } from '@/lib/api' +import { api, type MarketSnapshotRow, type OverviewDimensionRankItem, type OverviewMarket } from '@/lib/api' import { QK } from '@/lib/queryKeys' -import { fmtBigNum, fmtPct } from '@/lib/format' +import { fmtBigNum } from '@/lib/format' import { useDataStatus, useCapabilities, useSettings } from '@/lib/useSharedQueries' import { SealedBadge } from '@/components/SealedBadge' -import { StockPreviewDialog } from '@/components/StockPreviewDialog' import { SettingsModal } from '@/components/data/SettingsModal' import { STAGE_LABELS } from '@/components/data/ActiveJobCard' -import { cn } from '@/lib/cn' -import { cnSignal } from '@/lib/signals' -import { boardTag } from '@/components/stock-table/primitives' + function n(v: number | null | undefined) { return typeof v === 'number' && Number.isFinite(v) ? v : null @@ -79,140 +76,6 @@ function SectionTitle({ icon: Icon, title, hint }: { icon: typeof Activity; titl ) } -// 看板监控中心小组件 — 显示前 10 条触发记录 + 更多按钮 -const _SOURCE_BADGE: Record = { - strategy: 'bg-amber-400/10 text-amber-400', - signal: 'bg-accent/10 text-accent', - price: 'bg-emerald-400/10 text-emerald-400', - market: 'bg-purple-500/10 text-purple-400', -} -const _SOURCE_LABEL: Record = { - strategy: '策略', signal: '信号', price: '价格', market: '异动', -} -const _SEVERITY_BAR: Record = { - info: 'bg-accent/40', warn: 'bg-warning', critical: 'bg-danger', -} - -function MonitorWidget() { - const [previewEv, setPreviewEv] = useState(null) - const alerts = useQuery({ - queryKey: ['alerts', ''], - queryFn: () => api.alertsList({ days: 7, limit: 10 }), - refetchInterval: 10000, - refetchIntervalInBackground: true, - }) - const events: AlertEvent[] = alerts.data?.alerts ?? [] - - if (events.length === 0) { - return ( -
暂无触发记录
- ) - } - - return ( - <> -
- {events - .filter((ev: AlertEvent) => !(ev.source === 'strategy' && !ev.symbol)) - .map((ev, i) => { - const sev = _SEVERITY_BAR[ev.severity ?? 'info'] ?? _SEVERITY_BAR.info - const pct = ev.change_pct ?? 0 - const isStrategy = ev.source === 'strategy' - const sm = isStrategy ? ev.message?.match(/策略「([^」]+)」/) : null - const sname = sm ? sm[1] : '' - const isNew = ev.type === 'new_entry' - return ( - -
- {/* 第一行: 代码 + 名称 + 价格 + 涨跌幅 (点击代码/名称弹日K) */} -
- - - {ev.price != null && ( - {fmtPrice(ev.price)} - )} - {ev.change_pct != null && ( - = 0 ? 'text-danger' : 'text-bear')}> - {fmtPct(pct)} - - )} -
- {/* 第二行: 策略类型走新格式, 其他走旧格式 */} - {isStrategy ? ( -
- - {isNew ? '进入' : '移出'} - - 策略 - 「{sname}」 - - - {ev.ts ? new Date(ev.ts).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : ''} - -
- ) : ( - <> -
- - {_SOURCE_LABEL[ev.source] ?? ev.source} - - {ev.message && ( - {ev.message} - )} - - {ev.ts ? new Date(ev.ts).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }) : ''} - -
- {ev.signals && ev.signals.length > 0 && ( -
- {ev.signals.map((s, j) => ( - {cnSignal(s)} - ))} -
- )} - - )} - - ) - })} -
- - setPreviewEv(null)} - /> - - ) -} function KpiCell({ label, value, sub, tone = 'neutral' }: { label: ReactNode; value: ReactNode; sub?: string; tone?: 'bull' | 'bear' | 'accent' | 'neutral' }) { const isPlain = typeof value === 'string' || typeof value === 'number' @@ -375,6 +238,30 @@ function LadderMini({ limit }: { limit: OverviewMarket['limit'] }) { ) } +function DownLadderMini() { + const { data, isLoading } = useQuery({ + queryKey: ['limit-ladder-down'], + queryFn: () => api.limitLadder(undefined, undefined, 'down'), + refetchInterval: 30_000, + }) + const tiers = data?.tiers?.filter(t => t.boards >= 2).slice(0, 6) ?? [] + return ( +
+ {isLoading &&
加载中…
} + {!isLoading && tiers.length === 0 &&
暂无 2 板以上
} + {tiers.map(t => ( +
+ = 5 ? 'text-bear' : t.boards >= 3 ? 'text-warning' : 'text-secondary'}`}>{t.boards}板 +
+
+
+ {t.count} +
+ ))} +
+ ) +} + function MiniMetric({ label, value, cls = 'text-foreground' }: { label: string; value: string; cls?: string }) { return (
@@ -728,17 +615,8 @@ export function Dashboard() {
-
-
- -

跌停梯队

- 封板强度 -
- - - -
- + {data.limit.limit_down} 只} /> +