使用push2 数据接口,优化代码

This commit is contained in:
Sakurasan
2026-07-06 22:27:45 +08:00
parent 83c88ee945
commit 6390545e61
7 changed files with 750 additions and 276 deletions
+13 -4
View File
@@ -27,6 +27,10 @@ export interface KLineData {
high: number;
low: number;
volume: number;
changePercent?: number;
turnover?: number;
amplitude?: number;
turnoverRate?: number;
}
export interface StockSearchResult {
@@ -430,11 +434,16 @@ export interface SectorItem {
changePercent: number | null;
changeAmount: number | null;
mainNetInflow: number;
mainNetInflowPercent: number;
superLargeInflow: number;
superLargeInflowPercent: number;
mainNetInflowPercent: number | null;
superLargeInflow: number | null;
superLargeInflowPercent: number | null;
largeInflow: number | null;
largeInflowPercent: number | null;
mediumInflow: number | null;
mediumInflowPercent: number | null;
smallInflow: number | null;
smallInflowPercent: number | null;
turnover: number;
smallNetInflow: number;
}
export type SectorType = "industry" | "concept";
+107 -30
View File
@@ -1,7 +1,6 @@
import { createFileRoute } from "@tanstack/react-router";
import { useState, useEffect } from "react";
import { useState, useEffect, useMemo } 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";
@@ -15,19 +14,33 @@ const TABS: { key: SectorType; label: string }[] = [
{ key: "concept", label: "概念板块" },
];
type SortMode = "mainNetInflow" | "changePercent";
function SectorsPage() {
const [tab, setTab] = useState<SectorType>("industry");
const [data, setData] = useState<SectorItem[]>([]);
const [loading, setLoading] = useState(true);
const [sort, setSort] = useState<SortMode>("mainNetInflow");
useEffect(() => {
const loadData = (t: SectorType) => {
setLoading(true);
fetchSectors(tab).then((items) => {
fetchSectors(t).then((items) => {
setData(items);
setLoading(false);
});
};
useEffect(() => {
loadData(tab);
}, [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]);
return (
<div className="min-h-screen bg-background">
{/* 顶栏 */}
@@ -40,10 +53,7 @@ function SectorsPage() {
<h1 className="text-base font-semibold"></h1>
</div>
<button
onClick={() => {
setLoading(true);
fetchSectors(tab).then(setData).finally(() => setLoading(false));
}}
onClick={() => loadData(tab)}
className="text-muted-foreground hover:text-foreground transition-colors"
>
<RefreshCw className={`h-4 w-4 ${loading ? "animate-spin" : ""}`} />
@@ -51,9 +61,9 @@ function SectorsPage() {
</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">
{/* Tab + Sorting 切换 */}
<div className="max-w-5xl mx-auto px-4 mt-4 flex items-center gap-2">
<div className="flex gap-1 bg-muted rounded-lg p-1 flex-1">
{TABS.map((t) => (
<button
key={t.key}
@@ -68,26 +78,42 @@ function SectorsPage() {
</button>
))}
</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 className="max-w-5xl mx-auto px-4 mt-2">
<p className="text-[10px] text-muted-foreground">
· · {data.length}
{sorted.length} · {sort === "mainNetInflow" ? "主力净流入" : "涨跌幅"}
</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 key={i} className="animate-pulse rounded-xl bg-muted h-32" />
))}
</div>
) : (
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 gap-3">
{data.map((item) => (
{sorted.map((item) => (
<SectorBlock key={item.code} item={item} />
))}
</div>
@@ -97,29 +123,68 @@ function SectorsPage() {
);
}
function formatInflow(val: number | null | undefined): string {
if (val == null) return "--";
const abs = Math.abs(val);
if (abs >= 1e8) return (val / 1e8).toFixed(2) + "亿";
if (abs >= 1e4) return (val / 1e4).toFixed(0) + "万";
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;
const isPos = value >= 0;
return (
<div className="flex items-center gap-1">
<div className="flex-1 h-1.5 rounded-full bg-muted overflow-hidden">
<div
className={`h-full rounded-full transition-all ${
isPos ? "bg-red-500/60" : "bg-green-500/60"
}`}
style={{ width: `${Math.min(Math.abs(pct), 100)}%`, marginLeft: isPos ? "50%" : undefined }}
/>
</div>
<span className={`text-[10px] font-medium tabular-nums w-14 text-right ${
isPos ? "text-red-500" : "text-green-500"
}`}>
{formatInflow(value)}
</span>
</div>
);
}
function SectorBlock({ item }: { item: SectorItem }) {
const inflow = item.mainNetInflow;
const isPositive = inflow >= 0;
const change = item.changePercent;
const maxAbs = Math.max(
Math.abs(item.mainNetInflow),
Math.abs(item.superLargeInflow ?? 0),
Math.abs(item.largeInflow ?? 0),
Math.abs(item.mediumInflow ?? 0),
Math.abs(item.smallInflow ?? 0),
1
);
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}
{item.code.replace("BK", "")}
</span>
)}
</div>
{/* 涨跌幅 */}
<div className="flex items-center gap-1">
{change != null && (
<div className="flex items-center justify-between">
{change != null ? (
<span
className={`inline-flex items-center gap-0.5 text-xs font-semibold ${
change >= 0 ? "text-red-500" : "text-green-500"
@@ -133,20 +198,32 @@ function SectorBlock({ item }: { item: SectorItem }) {
{change >= 0 ? "+" : ""}
{change.toFixed(2)}%
</span>
) : (
<span className="text-xs text-muted-foreground">--</span>
)}
<span className="text-[10px] text-muted-foreground">
{formatInflow(item.turnover)}
</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 ${
{/* 主力净流入 */}
<div className="pt-1.5 border-t border-border/40">
<div className="flex items-center justify-between mb-1">
<span className="text-[10px] text-muted-foreground"></span>
<span className={`text-xs font-bold tabular-nums ${
isPositive ? "text-red-500" : "text-green-500"
}`}
>
{isPositive ? "+" : ""}
{formatMoney(inflow)}
</p>
}`}>
{isPositive ? "+" : ""}{formatInflow(inflow)}
</span>
</div>
{/* 资金流向明细条 */}
<div className="space-y-0.5">
<FundFlowBar value={item.superLargeInflow} maxAbs={maxAbs} />
<FundFlowBar value={item.largeInflow} maxAbs={maxAbs} />
<FundFlowBar value={item.mediumInflow} maxAbs={maxAbs} />
<FundFlowBar value={item.smallInflow} maxAbs={maxAbs} />
</div>
</div>
</CardContent>
</Card>
+3 -8
View File
@@ -46,6 +46,7 @@ interface StockData {
low: number;
volume: number;
isAddedDate: boolean;
changePercent: number;
}
interface StockInfo {
@@ -226,6 +227,7 @@ function StockDetail() {
low: kline.low,
volume: kline.volume,
isAddedDate,
changePercent: kline.changePercent ?? 0,
};
});
};
@@ -685,14 +687,7 @@ function StockDetail() {
const displayData = chartData.slice(-dailyTableDays).reverse();
return displayData.map((item, idx) => {
// 计算涨幅:与后一天收盘价比较(因为已倒序,后一天是更早的日期)
let changePercent = 0;
if (idx < displayData.length - 1) {
const nextClose = displayData[idx + 1].close;
if (nextClose > 0) {
changePercent = ((item.close - nextClose) / nextClose) * 100;
}
}
const changePercent = item.changePercent;
const isPositive = changePercent >= 0;
// 查找对应的资金流向数据