up
This commit is contained in:
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user