重写板块资金流向前端:行业/概念 Tab 切换与资金流入/涨幅排序

This commit is contained in:
Sakurasan
2026-07-13 20:57:00 +08:00
parent 2db6bb8519
commit 65013dad3e
+62 -53
View File
@@ -1,29 +1,28 @@
import { createFileRoute, Link } from "@tanstack/react-router"; import { createFileRoute, Link } from "@tanstack/react-router";
import { useState, useMemo } from "react"; import { useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query"; import { useQuery } from "@tanstack/react-query";
import { fetchSectors, type SectorItem, type SectorType } from "@/lib/stock-api"; import { fetchSectors, type SectorItem, type SectorType } from "@/lib/stock-api";
import { Card, CardContent } from "@/components/ui/card"; import { Card, CardContent } from "@/components/ui/card";
import { ArrowLeft, TrendingUp, TrendingDown, RefreshCw } from "lucide-react"; import { ArrowLeft, RefreshCw, ArrowDown, ArrowUp, TrendingUp, TrendingDown } from "lucide-react";
export const Route = createFileRoute("/sectors")({ export const Route = createFileRoute("/sectors")({
component: SectorsPage, component: SectorsPage,
}); });
/* ── Tab 定义 ── */ /* ── Tabs:行业 / 概念 ── */
const TABS: { key: SectorType; label: string }[] = [ const TABS: { key: SectorType; label: string }[] = [
{ key: "industry", label: "行业板块" }, { key: "industry", label: "行业" },
{ key: "concept", label: "概念板块" }, { key: "concept", label: "概念" },
]; ];
type SortMode = "mainNetInflow" | "changePercent"; /* ── 排序维度 ── */
type SortKey = "mainNetInflow" | "changePercent";
/* ── 页面组件 ── */
function SectorsPage() { function SectorsPage() {
const [tab, setTab] = useState<SectorType>("industry"); const [tab, setTab] = useState<SectorType>("industry");
const [sort, setSort] = useState<SortMode>("mainNetInflow"); const [sortKey, setSortKey] = useState<SortKey>("mainNetInflow");
const [asc, setAsc] = useState(false);
// 使用 useQueryqueryKey 包含 tab,切换 tab 时 queryKey 变化
// React Query 自动隔离 industry / concept 的数据,不会混用
const { data = [], isLoading, isFetching, refetch } = useQuery({ const { data = [], isLoading, isFetching, refetch } = useQuery({
queryKey: ["sectors", tab], queryKey: ["sectors", tab],
queryFn: ({ signal }) => fetchSectors(tab, signal), queryFn: ({ signal }) => fetchSectors(tab, signal),
@@ -31,14 +30,22 @@ function SectorsPage() {
retry: false, retry: false,
}); });
const sorted = useMemo( const sorted = useMemo(() => {
() => const dir = asc ? 1 : -1;
[...data].sort((a, b) => { return [...data].sort((a, b) => {
if (sort === "mainNetInflow") return b.mainNetInflow - a.mainNetInflow; const av = sortKey === "mainNetInflow" ? a.mainNetInflow : a.changePercent ?? -Infinity;
return (b.changePercent ?? 0) - (a.changePercent ?? 0); const bv = sortKey === "mainNetInflow" ? b.mainNetInflow : b.changePercent ?? -Infinity;
}), return (bv - av) * dir;
[data, sort], });
); }, [data, sortKey, asc]);
const toggleSort = (key: SortKey) => {
if (key === sortKey) setAsc((v) => !v);
else {
setSortKey(key);
setAsc(false);
}
};
return ( return (
<div className="min-h-screen bg-background"> <div className="min-h-screen bg-background">
@@ -60,9 +67,9 @@ function SectorsPage() {
</div> </div>
</header> </header>
{/* Tab + 排序切换 */} {/* Tab 切换:行业 / 概念 */}
<div className="max-w-5xl mx-auto px-4 mt-4 flex items-center gap-2"> <div className="max-w-5xl mx-auto px-4 mt-4">
<div className="flex gap-1 bg-muted rounded-lg p-1 flex-1"> <div className="flex gap-1 bg-muted rounded-lg p-1">
{TABS.map((t) => ( {TABS.map((t) => (
<button <button
key={t.key} key={t.key}
@@ -73,39 +80,21 @@ function SectorsPage() {
: "text-muted-foreground hover:text-foreground" : "text-muted-foreground hover:text-foreground"
}`} }`}
> >
{t.label} {t.label}
</button> </button>
))} ))}
</div> </div>
<div className="flex gap-0.5 text-xs border rounded overflow-hidden shrink-0">
<button
onClick={() => setSort("mainNetInflow")}
className={`px-2 py-1 transition-colors ${
sort === "mainNetInflow"
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
</button>
<button
onClick={() => setSort("changePercent")}
className={`px-2 py-1 transition-colors ${
sort === "changePercent"
? "bg-primary text-primary-foreground"
: "text-muted-foreground hover:text-foreground"
}`}
>
</button>
</div>
</div> </div>
{/* 数据概览 */} {/* 排序切换 */}
<div className="max-w-5xl mx-auto px-4 mt-2"> <div className="max-w-5xl mx-auto px-4 mt-3 flex items-center justify-between">
<p className="text-[10px] text-muted-foreground"> <p className="text-[10px] text-muted-foreground">
{sorted.length} · {sort === "mainNetInflow" ? "主力净流入" : "涨跌幅"} {sorted.length} · {tab === "industry" ? "行业" : "概念"}
</p> </p>
<div className="flex gap-0.5 text-xs border rounded-md overflow-hidden">
<SortButton label="资金流入" active={sortKey === "mainNetInflow"} asc={asc} onClick={() => toggleSort("mainNetInflow")} />
<SortButton label="涨幅%" active={sortKey === "changePercent"} asc={asc} onClick={() => toggleSort("changePercent")} />
</div>
</div> </div>
{/* 卡片列表 */} {/* 卡片列表 */}
@@ -130,7 +119,31 @@ function SectorsPage() {
); );
} }
/* ── 工具函数 ── */ function SortButton({
label,
active,
asc,
onClick,
}: {
label: string;
active: boolean;
asc: boolean;
onClick: () => void;
}) {
return (
<button
onClick={onClick}
className={`px-2.5 py-1 flex items-center gap-0.5 transition-colors ${
active ? "bg-primary text-primary-foreground" : "text-muted-foreground hover:text-foreground"
}`}
>
{label}
{active && (asc ? <ArrowUp className="h-3 w-3" /> : <ArrowDown className="h-3 w-3" />)}
</button>
);
}
/* ── 数值格式化 ── */
function formatInflow(val: number | null | undefined): string { function formatInflow(val: number | null | undefined): string {
if (val == null) return "--"; if (val == null) return "--";
const abs = Math.abs(val); const abs = Math.abs(val);
@@ -202,11 +215,7 @@ function SectorBlock({ item }: { item: SectorItem }) {
change >= 0 ? "text-red-500" : "text-green-500" change >= 0 ? "text-red-500" : "text-green-500"
}`} }`}
> >
{change >= 0 ? ( {change >= 0 ? <TrendingUp className="h-3 w-3" /> : <TrendingDown className="h-3 w-3" />}
<TrendingUp className="h-3 w-3" />
) : (
<TrendingDown className="h-3 w-3" />
)}
{change >= 0 ? "+" : ""} {change >= 0 ? "+" : ""}
{change.toFixed(2)}% {change.toFixed(2)}%
</span> </span>