import type React from 'react'; import { useState, useEffect, useCallback } from 'react'; import { Card, Badge } from '../components/common'; import { marketApi } from '../api/market'; import type { SectorMomentum, StockMomentum, NewHighLowStock, UpDownStats, KLineChartResponse, } from '../types/market'; import KLineChartModal from '../components/charts/KLineChartModal'; function formatPercent(value: number): string { const sign = value >= 0 ? '+' : ''; return `${sign}${value.toFixed(2)}%`; } function formatValue(value: number): string { return value.toFixed(4); } function getChangeColor(value: number): string { if (value > 0) return 'text-emerald-400'; if (value < 0) return 'text-red-400'; return 'text-secondary'; } function getRankChangeColor(change: number): string { if (change > 0) return 'text-emerald-400'; // 排名提升 if (change < 0) return 'text-red-400'; // 排名下降 return 'text-secondary'; } function getRankChangeIcon(change: number): React.ReactNode { if (change > 0) return '↑'; if (change < 0) return '↓'; return '→'; } function recommendationBadge(rec?: string) { if (!rec) return null; switch (rec) { case 'buy': return BUY; case 'watch': return WATCH; case 'hold': return HOLD; default: return {rec}; } } const TrendPage: React.FC = () => { const [sectors, setSectors] = useState([]); const [stocks, setStocks] = useState([]); const [newHigh, setNewHigh] = useState([]); const [newLow, setNewLow] = useState([]); const [updownStats, setUpdownStats] = useState(null); const [isLoading, setIsLoading] = useState(true); const [chartModal, setChartModal] = useState<{ open: boolean; type: 'stock' | 'sector'; code: string; name: string; } | null>(null); const [chartData, setChartData] = useState(null); const [isLoadingChart, setIsLoadingChart] = useState(false); const fetchData = useCallback(async () => { setIsLoading(true); try { const [sectorsRes, stocksRes, highRes, lowRes, statsRes] = await Promise.all([ marketApi.getSectorMomentum('momentumValue', 'desc', 5), marketApi.getStockMomentumRecommendation(15), marketApi.getNewHighStocks(20, 20), marketApi.getNewLowStocks(20, 20), marketApi.getUpDownStats(), ]); setSectors(Array.isArray(sectorsRes?.items) ? sectorsRes.items : []); setStocks(Array.isArray(stocksRes?.items) ? stocksRes.items : []); setNewHigh(Array.isArray(highRes) ? highRes : []); setNewLow(Array.isArray(lowRes) ? lowRes : []); setUpdownStats(statsRes || null); } catch (err) { console.error('Failed to fetch trend data:', err); } finally { setIsLoading(false); } }, []); useEffect(() => { fetchData(); }, [fetchData]); const handleItemClick = async (type: 'stock' | 'sector', code: string, name: string) => { setChartModal({ open: true, type, code, name }); setIsLoadingChart(true); setChartData(null); try { if (type === 'stock') { const data = await marketApi.getStockKline(code, 60); setChartData(data); } else { const data = await marketApi.getSectorKline(code, 60); setChartData(data); } } catch (err) { console.error('Failed to fetch kline data:', err); } finally { setIsLoadingChart(false); } }; const closeChartModal = () => { setChartModal(null); setChartData(null); }; return (

趋势分析

动量板块、个股推荐、新高新低分布

{isLoading ? (

加载中...

) : (
动量板块 TOP5
{sectors.map((sector, idx) => (
handleItemClick('sector', sector.code, sector.name)} >
{sector.rank || idx + 1} {sector.name} {sector.rank_change !== undefined && ( {getRankChangeIcon(sector.rank_change)} {Math.abs(sector.rank_change)} )}
{formatValue(sector.momentum_value || 0)} {sector.momentum_value_change !== undefined && ( {formatValue(sector.momentum_value_change)} )} {formatPercent(sector.change_percent)}
))}
动量个股推荐
{stocks.map((stock, idx) => (
handleItemClick('stock', stock.code, stock.name)} >
{idx + 1}
{stock.name} {stock.code}
{recommendationBadge(stock.recommendation)} {formatPercent(stock.change_percent)}
))}
新高个股 (20日内)
{newHigh.slice(0, 10).map((stock) => (
handleItemClick('stock', stock.code, stock.name)} >
{stock.name} {stock.code}
{stock.price.toFixed(2)} {formatPercent(stock.change_percent)}
))}
新低个股 (20日内)
{newLow.slice(0, 10).map((stock) => (
handleItemClick('stock', stock.code, stock.name)} >
{stock.name} {stock.code}
{stock.price.toFixed(2)} {formatPercent(stock.change_percent)}
))}
{updownStats && (
涨跌分布
{updownStats.up_count}
上涨
{updownStats.down_count}
下跌
{updownStats.flat_count}
平盘
{updownStats.limit_up_count}
涨停
{updownStats.limit_down_count}
跌停
)}
)}
{chartModal?.open && ( )}
); }; export default TrendPage;