feat: 活跃核心股 10 日涨幅矩阵页面

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Sakurasan
2026-08-10 20:50:39 +08:00
co-authored by Claude
parent 77f6e36c13
commit 5e7f0c4f13
2 changed files with 126 additions and 0 deletions
+21
View File
@@ -12,6 +12,7 @@ import { Route as rootRouteImport } from './routes/__root'
import { Route as ThemesRouteImport } from './routes/themes'
import { Route as SectorsRouteImport } from './routes/sectors'
import { Route as HotMapRouteImport } from './routes/hot-map'
import { Route as CoreStocksRouteImport } from './routes/core-stocks'
import { Route as IndexRouteImport } from './routes/index'
import { Route as ThemeCodeRouteImport } from './routes/theme.$code'
import { Route as StockCodeRouteImport } from './routes/stock.$code'
@@ -32,6 +33,11 @@ const HotMapRoute = HotMapRouteImport.update({
path: '/hot-map',
getParentRoute: () => rootRouteImport,
} as any)
const CoreStocksRoute = CoreStocksRouteImport.update({
id: '/core-stocks',
path: '/core-stocks',
getParentRoute: () => rootRouteImport,
} as any)
const IndexRoute = IndexRouteImport.update({
id: '/',
path: '/',
@@ -55,6 +61,7 @@ const ShareCodeRoute = ShareCodeRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/core-stocks': typeof CoreStocksRoute
'/hot-map': typeof HotMapRoute
'/sectors': typeof SectorsRoute
'/themes': typeof ThemesRoute
@@ -64,6 +71,7 @@ export interface FileRoutesByFullPath {
}
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/core-stocks': typeof CoreStocksRoute
'/hot-map': typeof HotMapRoute
'/sectors': typeof SectorsRoute
'/themes': typeof ThemesRoute
@@ -74,6 +82,7 @@ export interface FileRoutesByTo {
export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/core-stocks': typeof CoreStocksRoute
'/hot-map': typeof HotMapRoute
'/sectors': typeof SectorsRoute
'/themes': typeof ThemesRoute
@@ -85,6 +94,7 @@ export interface FileRouteTypes {
fileRoutesByFullPath: FileRoutesByFullPath
fullPaths:
| '/'
| '/core-stocks'
| '/hot-map'
| '/sectors'
| '/themes'
@@ -94,6 +104,7 @@ export interface FileRouteTypes {
fileRoutesByTo: FileRoutesByTo
to:
| '/'
| '/core-stocks'
| '/hot-map'
| '/sectors'
| '/themes'
@@ -103,6 +114,7 @@ export interface FileRouteTypes {
id:
| '__root__'
| '/'
| '/core-stocks'
| '/hot-map'
| '/sectors'
| '/themes'
@@ -113,6 +125,7 @@ export interface FileRouteTypes {
}
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
CoreStocksRoute: typeof CoreStocksRoute
HotMapRoute: typeof HotMapRoute
SectorsRoute: typeof SectorsRoute
ThemesRoute: typeof ThemesRoute
@@ -144,6 +157,13 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof HotMapRouteImport
parentRoute: typeof rootRouteImport
}
'/core-stocks': {
id: '/core-stocks'
path: '/core-stocks'
fullPath: '/core-stocks'
preLoaderRoute: typeof CoreStocksRouteImport
parentRoute: typeof rootRouteImport
}
'/': {
id: '/'
path: '/'
@@ -177,6 +197,7 @@ declare module '@tanstack/react-router' {
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
CoreStocksRoute: CoreStocksRoute,
HotMapRoute: HotMapRoute,
SectorsRoute: SectorsRoute,
ThemesRoute: ThemesRoute,
+105
View File
@@ -0,0 +1,105 @@
import { createFileRoute, Link } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query";
import { fetchActiveCoreStocks } from "@/lib/core-stock-api";
import { ArrowLeft, RefreshCw, Flame } from "lucide-react";
export const Route = createFileRoute("/core-stocks")({
component: CoreStocksPage,
});
/** 格式化涨幅,红涨绿跌 */
function formatGain(v: number | null | undefined): string {
if (v == null) return "·";
const s = v > 0 ? `+${v.toFixed(2)}%` : `${v.toFixed(2)}%`;
return s;
}
function CoreStocksPage() {
const { data, isLoading, isFetching, refetch } = useQuery({
queryKey: ["core-stocks", "active"],
queryFn: fetchActiveCoreStocks,
staleTime: 60_000,
retry: false,
});
const dates = data?.dates ?? [];
const stocks = data?.stocks ?? [];
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="/hot-map" className="hover:opacity-70 transition-opacity">
<ArrowLeft className="h-5 w-5" />
</Link>
<h1 className="text-base font-semibold"></h1>
</div>
<button
onClick={() => refetch()}
className="text-muted-foreground hover:text-foreground transition-colors"
title="刷新"
>
<RefreshCw className={`h-4 w-4 ${isFetching ? "animate-spin" : ""}`} />
</button>
</div>
</header>
<div className="max-w-5xl mx-auto px-4 mt-3 pb-8">
<p className="text-[10px] text-muted-foreground mb-2">
10 · · {stocks.length}
</p>
{isLoading ? (
<div className="animate-pulse rounded-xl bg-muted h-32" />
) : dates.length === 0 ? (
<div className="text-center text-sm text-muted-foreground py-16">
</div>
) : (
<div className="overflow-x-auto rounded-xl border bg-card">
<table className="w-full text-sm">
<thead>
<tr className="border-b bg-muted/50">
<th className="px-3 py-2 text-left font-medium whitespace-nowrap"></th>
{dates.map((d) => (
<th key={d} className="px-2 py-2 text-right font-medium tabular-nums whitespace-nowrap">
{d.slice(5)}
</th>
))}
<th className="px-2 py-2 text-right font-medium"></th>
</tr>
</thead>
<tbody>
{stocks.map((s) => (
<tr key={s.stockCode} className="border-b last:border-0 hover:bg-muted/30">
<td className="px-3 py-1.5 whitespace-nowrap">
<span className="font-medium">{s.stockName}</span>
<span className="ml-1 text-[10px] text-muted-foreground">{s.stockCode}</span>
</td>
{dates.map((d) => {
const g = s.dailyGains[d];
const cls = g == null ? "text-muted-foreground/40" : g >= 0 ? "text-red-500" : "text-green-500";
return (
<td key={d} className={`px-2 py-1.5 text-right tabular-nums whitespace-nowrap ${cls}`}>
{formatGain(g)}
</td>
);
})}
<td className="px-2 py-1.5 text-right tabular-nums whitespace-nowrap">
<span className="inline-flex items-center gap-0.5 text-orange-500">
<Flame className="h-3 w-3" />
{s.appearCount}
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
);
}