import { createFileRoute } from "@tanstack/react-router"; import { useState, useEffect } from "react"; import { sharesApi } from "@/lib/api-client"; import { fetchStockQuotes, fetchStockQuote, getStockBoard, type StockQuote } from "@/lib/stock-api"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { TrendingUp, TrendingDown, Calendar, Share2 } from "lucide-react"; import { Link } from "@tanstack/react-router"; import { toast } from "sonner"; export const Route = createFileRoute("/share/$code")({ component: SharePage, }); interface CollectionInfo { id: number; name: string; description: string | null; } interface StockRecord { id: number; stock_code: string; stock_name: string; added_at: string; added_price: number | null; } function SharePage() { const { code } = Route.useParams(); const [collection, setCollection] = useState(null); const [stocks, setStocks] = useState<(StockRecord & { quote?: StockQuote })[]>([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { loadSharedData(); }, [code]); const loadSharedData = async () => { setLoading(true); setError(null); try { // sharesApi.get 返回集合信息(含股票列表) const data = await sharesApi.get(code); if (!data) { setError("分享链接无效"); setLoading(false); return; } setCollection({ id: data.id, name: data.name, description: data.description, }); const stocksData = data.stocks || []; const stockRecords: StockRecord[] = stocksData || []; // 批量获取实时行情 if (stockRecords.length > 0) { const codes = stockRecords.map((s) => s.stock_code); const quoteMap = await fetchStockQuotesFromCodes(codes); const merged = stockRecords.map((record) => ({ ...record, quote: quoteMap.get(record.stock_code), })); setStocks(merged); } else { setStocks([]); } } catch (err) { console.error("加载失败:", err); const msg = err instanceof Error ? err.message : "加载数据失败"; setError(msg); } finally { setLoading(false); } }; const fetchStockQuotesFromCodes = async (codes: string[]): Promise> => { const map = new Map(); await Promise.all( codes.map(async (code) => { try { const quote = await fetchStockQuote(code); if (quote) map.set(code, quote); } catch { // 忽略单个股票获取失败 } }) ); return map; }; const copyShareLink = async () => { const link = window.location.href; try { await navigator.clipboard.writeText(link); toast.success("链接已复制到剪贴板"); } catch (err) { toast.error("复制失败"); } }; if (loading) { return (

加载中...

); } if (error) { return (

加载失败

{error}

); } if (!collection) { return (

分享链接无效

该链接可能已过期或不存在

); } return (
{/* Header */}

{collection.name}

{collection.description && (

{collection.description}

)}
{/* Stocks List */} {stocks.length === 0 ? (

该集合暂无股票

) : (
{stocks.map((stock) => { const quote = stock.quote; const currentPrice = quote?.currentPrice; const addedPrice = stock.added_price; let changePercent = 0; let hasChange = false; let isPositive = true; if (currentPrice && addedPrice && addedPrice > 0) { changePercent = ((currentPrice - addedPrice) / addedPrice) * 100; hasChange = true; isPositive = changePercent >= 0; } return (
{stock.stock_name} {(() => { const board = getStockBoard(stock.stock_code); if (!board.label) return null; return ( {board.label} ); })()}
{stock.stock_code}
{/* Current Price */}

{currentPrice ? `¥${currentPrice.toFixed(2)}` : "—"}

{/* Added Info and Change */}
添加于 {new Date(stock.added_at).toLocaleDateString("zh-CN")}
{addedPrice && (

添加价: ¥{addedPrice.toFixed(2)}

)} {hasChange && (
{isPositive ? ( ) : ( )} {isPositive ? "+" : ""}{changePercent.toFixed(2)}%
)}
); })}
)}
); }