From 1da4f8f47901c07bc67d5ebf18579de9c4fd4645 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Mon, 13 Jul 2026 20:24:25 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E5=86=99=E6=9D=BF=E5=9D=97=E8=B5=84?= =?UTF-8?q?=E9=87=91=E6=B5=81=E5=90=91=E5=89=8D=E7=AB=AF=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=EF=BC=8C=E4=BD=BF=E7=94=A8=20React=20Query=20=E6=9B=BF?= =?UTF-8?q?=E4=BB=A3=E6=89=8B=E5=8A=A8=E7=8A=B6=E6=80=81=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=95=B0=E6=8D=AE=E5=90=88=E5=B9=B6=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用 @tanstack/react-query 的 useQuery 替代 useState + useEffect + AbortController - queryKey 按 tab 类型 (industry/concept) 隔离数据,根除快速切换导致的数据混用 - 增加空数据提示状态 - 保持原有 UI 结构和交互一致性 --- src/routes/sectors.tsx | 122 ++++++++++++++++++++--------------------- 1 file changed, 60 insertions(+), 62 deletions(-) diff --git a/src/routes/sectors.tsx b/src/routes/sectors.tsx index 2f7e0da..9d3db24 100644 --- a/src/routes/sectors.tsx +++ b/src/routes/sectors.tsx @@ -1,14 +1,15 @@ -import { createFileRoute } from "@tanstack/react-router"; -import { useState, useEffect, useMemo, useRef } from "react"; +import { createFileRoute, Link } from "@tanstack/react-router"; +import { useState, useMemo } from "react"; +import { useQuery } from "@tanstack/react-query"; import { fetchSectors, type SectorItem, type SectorType } from "@/lib/stock-api"; import { Card, CardContent } from "@/components/ui/card"; import { ArrowLeft, TrendingUp, TrendingDown, RefreshCw } from "lucide-react"; -import { Link } from "@tanstack/react-router"; export const Route = createFileRoute("/sectors")({ component: SectorsPage, }); +/* ── Tab 定义 ── */ const TABS: { key: SectorType; label: string }[] = [ { key: "industry", label: "行业板块" }, { key: "concept", label: "概念板块" }, @@ -16,38 +17,28 @@ const TABS: { key: SectorType; label: string }[] = [ type SortMode = "mainNetInflow" | "changePercent"; +/* ── 页面组件 ── */ function SectorsPage() { const [tab, setTab] = useState("industry"); - const [data, setData] = useState([]); - const [loading, setLoading] = useState(true); const [sort, setSort] = useState("mainNetInflow"); - const abortRef = useRef(null); - const loadData = (t: SectorType) => { - // 取消上次未完成的请求 - abortRef.current?.abort(); - const controller = new AbortController(); - abortRef.current = controller; - setLoading(true); - fetchSectors(t, controller.signal).then((items) => { - if (!controller.signal.aborted) { - setData(items); - setLoading(false); - } - }); - }; + // 使用 useQuery:queryKey 包含 tab,切换 tab 时 queryKey 变化 + // React Query 自动隔离 industry / concept 的数据,不会混用 + const { data = [], isLoading, isFetching, refetch } = useQuery({ + queryKey: ["sectors", tab], + queryFn: ({ signal }) => fetchSectors(tab, signal), + staleTime: 30_000, + retry: false, + }); - useEffect(() => { - loadData(tab); - return () => abortRef.current?.abort(); - }, [tab]); - - const sorted = useMemo(() => { - return [...data].sort((a, b) => { - if (sort === "mainNetInflow") return b.mainNetInflow - a.mainNetInflow; - return (b.changePercent ?? 0) - (a.changePercent ?? 0); - }); - }, [data, sort]); + const sorted = useMemo( + () => + [...data].sort((a, b) => { + if (sort === "mainNetInflow") return b.mainNetInflow - a.mainNetInflow; + return (b.changePercent ?? 0) - (a.changePercent ?? 0); + }), + [data, sort], + ); return (
@@ -61,27 +52,21 @@ function SectorsPage() {

板块资金流向

- {/* Tab + Sorting 切换 */} + {/* Tab + 排序切换 */}
{TABS.map((t) => (
- - + + > + 涨幅 +
- {/* 数据信息 */} + {/* 数据概览 */}

- 含 {sorted.length} 个板块 · 按{sort === "mainNetInflow" ? "主力净流入" : "涨跌幅"}降序 + 共 {sorted.length} 个板块 · 按{sort === "mainNetInflow" ? "主力净流入" : "涨跌幅"}降序

- {/* 卡片网格 */} + {/* 卡片列表 */}
- {loading ? ( + {isLoading ? (
{Array.from({ length: 20 }).map((_, i) => (
))}
+ ) : sorted.length === 0 ? ( +
暂无数据
) : (
{sorted.map((item) => ( @@ -137,6 +130,7 @@ function SectorsPage() { ); } +/* ── 工具函数 ── */ function formatInflow(val: number | null | undefined): string { if (val == null) return "--"; const abs = Math.abs(val); @@ -145,6 +139,7 @@ function formatInflow(val: number | null | undefined): string { return val.toFixed(0); } +/* ── 资金流向横条 ── */ function FundFlowBar({ value, maxAbs }: { value: number | null | undefined; maxAbs: number }) { if (value == null) return null; const pct = maxAbs > 0 ? (value / maxAbs) * 100 : 0; @@ -159,15 +154,18 @@ function FundFlowBar({ value, maxAbs }: { value: number | null | undefined; maxA style={{ width: `${Math.min(Math.abs(pct), 100)}%`, marginLeft: isPos ? "50%" : undefined }} />
- + {formatInflow(value)}
); } +/* ── 单个板块卡片 ── */ function SectorBlock({ item }: { item: SectorItem }) { const inflow = item.mainNetInflow; const isPositive = inflow >= 0; @@ -178,7 +176,7 @@ function SectorBlock({ item }: { item: SectorItem }) { Math.abs(item.largeInflow ?? 0), Math.abs(item.mediumInflow ?? 0), Math.abs(item.smallInflow ?? 0), - 1 + 1, ); return ( @@ -196,7 +194,7 @@ function SectorBlock({ item }: { item: SectorItem }) { )}
- {/* 涨跌幅 */} + {/* 涨跌幅 + 成交额 */}
{change != null ? ( -- )} - - {formatInflow(item.turnover)} - + {formatInflow(item.turnover)}
- {/* 主力净流入 */} + {/* 主力净流入 + 明细 */}
主力净流入 - - {isPositive ? "+" : ""}{formatInflow(inflow)} + + {isPositive ? "+" : ""} + {formatInflow(inflow)}
- {/* 资金流向明细条 */}