import { createFileRoute, Link } from "@tanstack/react-router"; import { useMemo, useState } from "react"; import { useQuery } from "@tanstack/react-query"; import { fetchThemes, type ThemeItem, type ThemeSortField } from "@/lib/theme-api"; import { getStockBoard } from "@/lib/stock-api"; import { formatMoney } from "@/lib/utils"; import { Card, CardContent } from "@/components/ui/card"; import { ArrowLeft, RefreshCw, ArrowDown, ArrowUp, TrendingUp, TrendingDown, Flame, Network, } from "lucide-react"; export const Route = createFileRoute("/themes")({ component: ThemesPage, }); /* ============================================================ 排序维度(sortField 与后端/东方财富对齐) ============================================================ */ const SORTS: { key: ThemeSortField; label: string }[] = [ { key: 1, label: "涨幅" }, { key: 3, label: "强度" }, { key: 4, label: "热度" }, { key: 5, label: "成交额" }, ]; function ThemesPage() { const [sortField, setSortField] = useState(1); const [asc, setAsc] = useState(false); // 默认降序 const { data: themes, isLoading, isFetching, isError, refetch } = useQuery({ queryKey: ["themes", sortField, asc], queryFn: () => fetchThemes(sortField, asc), staleTime: 30_000, retry: false, }); const data = themes ?? []; const toggleSort = (key: ThemeSortField) => { if (key === sortField) { setAsc((v) => !v); } else { setSortField(key); setAsc(false); // 切新维度默认降序 } }; return (
{/* ── 顶栏 ── */}

题材热点

热点穿透 热点股
{/* ── 排序切换 + 统计 ── */}

共 {data.length} 个题材 {isFetching && ( · 刷新中… )}

{SORTS.map((s) => ( ))}
{/* ── 内容区 ── */}
{isLoading ? (
{Array.from({ length: 18 }).map((_, i) => (
))}
) : isError ? (

数据加载失败

) : data.length === 0 ? (
暂无题材数据
) : (
{data.map((item) => ( ))}
)}
); } /* ============================================================ 数值格式化 ============================================================ */ function fmt(val: number | null | undefined, digits = 2): string { if (val == null) return "--"; return val.toFixed(digits); } /* ============================================================ 题材卡片 ============================================================ */ function ThemeCard({ item }: { item: ThemeItem }) { const change = item.bf3; const hotPct = item.hotValueUpLimit > 0 ? Math.min((item.hotValue / item.hotValueUpLimit) * 100, 100) : 0; const stockBoard = getStockBoard(item.securityCode); return ( {/* 题材名 + 领涨标签 */}

{item.themeName}

{item.label && ( {item.label} )}
{/* 热度进度条 */}
{item.hotValue}/{item.hotValueUpLimit}
{/* 涨幅 + 成交额 */}
{change != null ? ( = 0 ? "text-red-500" : "text-green-500" }`} > {change >= 0 ? : } {change >= 0 ? "+" : ""} {fmt(change)}% ) : ( -- )} {item.fex5 != null ? formatMoney(item.fex5) : "--"}
{/* 分割线 */}
{/* 领涨股 + 强度 */}
领涨 {item.securityName || "--"} {stockBoard.label && ( {stockBoard.label} )}
{item.f3 != null && ( = 0 ? "text-red-500" : "text-green-500" }`} > {item.f3 >= 0 ? "+" : ""} {fmt(item.f3)}% )} 强度 {item.strengthValue ?? "--"}
); }