This commit is contained in:
Sakurasan
2026-07-05 21:29:17 +08:00
commit 013ebdaffe
150 changed files with 15614 additions and 0 deletions
+154
View File
@@ -0,0 +1,154 @@
import { createFileRoute } from "@tanstack/react-router";
import { useState, useEffect } from "react";
import { fetchSectors, type SectorItem, type SectorType } from "@/lib/stock-api";
import { formatMoney } from "@/lib/utils";
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,
});
const TABS: { key: SectorType; label: string }[] = [
{ key: "industry", label: "行业板块" },
{ key: "concept", label: "概念板块" },
];
function SectorsPage() {
const [tab, setTab] = useState<SectorType>("industry");
const [data, setData] = useState<SectorItem[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
setLoading(true);
fetchSectors(tab).then((items) => {
setData(items);
setLoading(false);
});
}, [tab]);
return (
<div className="min-h-screen bg-background">
{/* 顶栏 */}
<header className="sticky top-0 z-10 bg-background/95 backdrop-blur border-b">
<div className="max-w-5xl mx-auto px-4 h-12 flex items-center justify-between">
<div className="flex items-center gap-3">
<Link to="/" className="hover:opacity-70 transition-opacity">
<ArrowLeft className="h-5 w-5" />
</Link>
<h1 className="text-base font-semibold"></h1>
</div>
<button
onClick={() => {
setLoading(true);
fetchSectors(tab).then(setData).finally(() => setLoading(false));
}}
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
</button>
</div>
</header>
{/* Tab 切换 */}
<div className="max-w-5xl mx-auto px-4 mt-4">
<div className="flex gap-1 bg-muted rounded-lg p-1">
{TABS.map((t) => (
<button
key={t.key}
onClick={() => setTab(t.key)}
className={`flex-1 py-1.5 text-sm font-medium rounded-md transition-colors ${
tab === t.key
? "bg-background text-foreground shadow-sm"
: "text-muted-foreground hover:text-foreground"
}`}
>
{t.label}
</button>
))}
</div>
</div>
{/* 数据更新时间 */}
<div className="max-w-5xl mx-auto px-4 mt-2">
<p className="text-[10px] text-muted-foreground">
· · {data.length}
</p>
</div>
{/* 方块图 */}
<div className="max-w-5xl mx-auto px-4 mt-3 pb-8">
{loading ? (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
{Array.from({ length: 20 }).map((_, i) => (
<div key={i} className="animate-pulse rounded-xl bg-muted h-24" />
))}
</div>
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
{data.map((item) => (
<SectorBlock key={item.code} item={item} />
))}
</div>
)}
</div>
</div>
);
}
function SectorBlock({ item }: { item: SectorItem }) {
const inflow = item.mainNetInflow;
const isPositive = inflow >= 0;
const change = item.changePercent;
return (
<Card className="rounded-xl hover:shadow-md transition-shadow">
<CardContent className="p-3 space-y-2">
{/* 板块名称 + 编号 */}
<div className="flex items-center justify-between gap-1">
<p className="text-sm font-medium truncate" title={item.name}>
{item.name}
</p>
{item.code && (
<span className="shrink-0 text-[9px] text-muted-foreground/60 font-mono">
{item.code}
</span>
)}
</div>
{/* 涨跌幅 */}
<div className="flex items-center gap-1">
{change != null && (
<span
className={`inline-flex items-center gap-0.5 text-xs font-semibold ${
change >= 0 ? "text-red-500" : "text-green-500"
}`}
>
{change >= 0 ? (
<TrendingUp className="h-3 w-3" />
) : (
<TrendingDown className="h-3 w-3" />
)}
{change >= 0 ? "+" : ""}
{change.toFixed(2)}%
</span>
)}
</div>
{/* 资金流向 */}
<div className="pt-1 border-t border-border/50">
<p className="text-[10px] text-muted-foreground"></p>
<p
className={`text-xs font-bold ${
isPositive ? "text-red-500" : "text-green-500"
}`}
>
{isPositive ? "+" : ""}
{formatMoney(inflow)}
</p>
</div>
</CardContent>
</Card>
);
}