import React, { useState, useEffect } from 'react'; import { Box, Container, VStack, HStack, Heading, Text, Badge, useToast, Skeleton, IconButton, Flex, useColorModeValue, SimpleGrid, Tooltip, Card, CardBody, Stat, StatLabel, StatNumber, StatHelpText, StatArrow, Alert, AlertIcon, } from '@chakra-ui/react'; import { RepeatIcon, ChevronUpIcon, } from '@chakra-ui/icons'; // 导入拆分的组件 // 注意:在实际使用中,这些组件应该被拆分到独立的文件中 // 这里为了演示,我们假设它们已经被正确导出 // API配置 const API_URL = process.env.NODE_ENV === 'production' ? '/report-api' : 'http://111.198.58.126:5001'; // 导入的组件(实际使用时应该从独立文件导入) // 恢复使用本页自带的轻量日历 import EnhancedCalendar from './components/EnhancedCalendar'; import SectorDetails from './components/SectorDetails'; import { DataAnalysis, StockDetailModal } from './components/DataVisualizationComponents'; import { AdvancedSearch, SearchResultsModal } from './components/SearchComponents'; // 导航栏已由 MainLayout 提供,无需在此导入 // 导入高位股统计组件 import HighPositionStocks from './components/HighPositionStocks'; import { logger } from '../../utils/logger'; import { useLimitAnalyseEvents } from './hooks/useLimitAnalyseEvents'; // 主组件 export default function LimitAnalyse() { const [selectedDate, setSelectedDate] = useState(null); const [dateStr, setDateStr] = useState(''); const [loading, setLoading] = useState(false); const [dailyData, setDailyData] = useState(null); const [availableDates, setAvailableDates] = useState([]); const [wordCloudData, setWordCloudData] = useState([]); const [searchResults, setSearchResults] = useState(null); const [isSearchOpen, setIsSearchOpen] = useState(false); const [selectedStock, setSelectedStock] = useState(null); const [isStockDetailOpen, setIsStockDetailOpen] = useState(false); const toast = useToast(); // 🎯 PostHog 事件追踪 const { trackDateSelected, trackDailyStatsViewed, trackSectorToggled, trackSectorClicked, trackLimitStockClicked, trackSearchInitiated, trackSearchResultClicked, trackHighPositionStocksViewed, trackSectorAnalysisViewed, trackDataRefreshed, trackStockDetailViewed, } = useLimitAnalyseEvents(); const bgColor = useColorModeValue('gray.50', 'gray.900'); const cardBg = useColorModeValue('white', 'gray.800'); const accentColor = useColorModeValue('blue.500', 'blue.300'); // 获取可用日期 useEffect(() => { fetchAvailableDates(); }, []); // 初始进入展示骨架屏,直到选中日期的数据加载完成 useEffect(() => { setLoading(true); }, []); // 根据可用日期加载最近一个有数据的日期 useEffect(() => { if (availableDates && availableDates.length > 0) { // 选择日期字符串最大的那一天(格式为 YYYYMMDD) const latest = availableDates.reduce((max, cur) => (!max || (cur.date && cur.date > max)) ? cur.date : max , null); if (latest) { setDateStr(latest); const year = parseInt(latest.slice(0, 4), 10); const month = parseInt(latest.slice(4, 6), 10) - 1; const day = parseInt(latest.slice(6, 8), 10); setSelectedDate(new Date(year, month, day)); fetchDailyAnalysis(latest); } } else { // 如果暂无可用日期,回退到今日,避免页面长时间空白 const today = new Date(); const dateString = formatDateStr(today); setDateStr(dateString); setSelectedDate(today); fetchDailyAnalysis(dateString); } }, [availableDates]); // API调用函数 const fetchAvailableDates = async () => { try { const response = await fetch(`${API_URL}/api/v1/dates/available`); const data = await response.json(); if (data.success) { setAvailableDates(data.events); logger.debug('LimitAnalyse', '可用日期加载成功', { count: data.events?.length || 0 }); } } catch (error) { logger.error('LimitAnalyse', 'fetchAvailableDates', error); } }; const fetchDailyAnalysis = async (date) => { setLoading(true); try { const response = await fetch(`${API_URL}/api/v1/analysis/daily/${date}`); const data = await response.json(); if (data.success) { setDailyData(data.data); // 🎯 追踪每日统计数据查看 trackDailyStatsViewed(data.data, date); // 获取词云数据 fetchWordCloudData(date); logger.debug('LimitAnalyse', '每日分析数据加载成功', { date, totalStocks: data.data?.total_stocks || 0 }); // ❌ 移除数据加载成功 toast(非关键操作) } } catch (error) { logger.error('LimitAnalyse', 'fetchDailyAnalysis', error, { date }); // ❌ 移除数据加载失败 toast(非关键操作) } finally { setLoading(false); } }; const fetchWordCloudData = async (date) => { try { const response = await fetch(`${API_URL}/api/v1/analysis/wordcloud/${date}`); const data = await response.json(); if (data.success) { setWordCloudData(data.data); logger.debug('LimitAnalyse', '词云数据加载成功', { date, count: data.data?.length || 0 }); } } catch (error) { logger.error('LimitAnalyse', 'fetchWordCloudData', error, { date }); } }; // 格式化日期 const formatDateStr = (date) => { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); return `${year}${month}${day}`; }; // 处理日期选择 const handleDateChange = (date) => { const previousDateStr = dateStr; setSelectedDate(date); const dateString = formatDateStr(date); setDateStr(dateString); // 🎯 追踪日期选择 trackDateSelected(dateString, previousDateStr); fetchDailyAnalysis(dateString); }; // 处理搜索 const handleSearch = async (searchParams) => { // 🎯 追踪搜索开始 trackSearchInitiated( searchParams.query, searchParams.type || 'all', searchParams.mode || 'hybrid' ); setLoading(true); try { const response = await fetch(`${API_URL}/api/v1/stocks/search/hybrid`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(searchParams), }); const data = await response.json(); if (data.success) { setSearchResults(data.data); setIsSearchOpen(true); logger.info('LimitAnalyse', '搜索完成', { resultCount: data.data?.total || 0, searchParams }); toast({ title: '搜索完成', description: `找到 ${data.data.total} 只相关股票`, status: 'success', duration: 3000, }); } } catch (error) { logger.error('LimitAnalyse', 'handleSearch', error, { searchParams }); toast({ title: '搜索失败', description: '请稍后重试', status: 'error', duration: 3000, }); } finally { setLoading(false); } }; // 处理股票点击 const handleStockClick = (stock) => { setSelectedStock(stock); setIsStockDetailOpen(true); // 🎯 追踪股票详情查看 trackStockDetailViewed(stock.scode, stock.sname, 'sector_details'); }; // 关闭股票详情弹窗 const handleCloseStockDetail = () => { setIsStockDetailOpen(false); setSelectedStock(null); }; // 处理板块数据排序 const getSortedSectorData = () => { if (!dailyData?.sector_data) return []; const sectors = Object.entries(dailyData.sector_data); const announcement = sectors.find(([name]) => name === '公告'); const others = sectors.filter(([name]) => name !== '公告' && name !== '其他'); const other = sectors.find(([name]) => name === '其他'); // 按数量排序 others.sort((a, b) => b[1].count - a[1].count); // 组合:公告在最前,其他在最后 let result = []; if (announcement) result.push(announcement); result = result.concat(others); if (other) result.push(other); return result; }; // 渲染统计卡片 const StatsCards = () => ( 今日涨停 {dailyData?.total_stocks || 0} 较昨日 +23% 最热板块 {dailyData?.summary?.top_sector || '-'} {dailyData?.summary?.top_sector_count || 0} 只 公告涨停 {dailyData?.summary?.announcement_stocks || 0} 重大利好 早盘强势 {dailyData?.summary?.zt_time_distribution?.morning || 0} 开盘涨停 ); const formatDisplayDate = (date) => { if (!date) return ''; const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); return `${year}年${month}月${day}日`; }; const getSelectedDateCount = () => { if (!selectedDate || !availableDates?.length) return null; const date = formatDateStr(selectedDate); const found = availableDates.find(d => d.date === date); return found ? found.count : null; }; return ( {/* 导航栏已由 MainLayout 提供 */} {/* 顶部Header */} {/* 左侧:标题置顶,注释与图例贴底 */} AI驱动 实时更新 涨停板块分析平台 以大模型辅助整理海量信息,结合领域知识图谱与分析师复核,呈现涨停板块关键线索 {selectedDate ? `当前选择:${formatDisplayDate(selectedDate)}` : '当前选择:--'} {getSelectedDateCount() != null ? ` - ${getSelectedDateCount()}只涨停` : ''} 涨停数量图例 少量 (≤50只) 中等 (51-80只) 大量 (>80只) {/* 右侧:半屏日历 */} {/* 主内容区 */} {/* 统计卡片 */} {loading ? ( {[...Array(4)].map((_, i) => ( ))} ) : ( )} {/* 高级搜索 */} {/* 板块详情 - 核心内容 */} {loading ? ( ) : ( )} {/* 高位股统计 */} {/* 数据分析 */} {loading ? ( ) : ( )} {/* 弹窗 */} setIsSearchOpen(false)} searchResults={searchResults} onStockClick={() => {}} /> {/* 股票详情弹窗 */} {/* 浮动按钮 */} } colorScheme="blue" size="lg" borderRadius="full" boxShadow="2xl" onClick={() => fetchDailyAnalysis(dateStr)} isLoading={loading} /> } colorScheme="gray" size="lg" borderRadius="full" boxShadow="2xl" onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })} /> ); }