import React, { useState, useEffect, useCallback } from 'react'; import { useSearchParams } from 'react-router-dom'; import { Container, Heading, Card, CardBody, Tabs, TabList, TabPanels, Tab, TabPanel, HStack, VStack, Input, Button, InputGroup, InputLeftElement, Text, Badge, Divider, Icon, useColorModeValue, useColorMode, IconButton, useToast, } from '@chakra-ui/react'; import { SearchIcon, MoonIcon, SunIcon, StarIcon } from '@chakra-ui/icons'; import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-icons/fa'; import { useAuth } from '../../contexts/AuthContext'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; import FinancialPanorama from './FinancialPanorama'; import ForecastReport from './ForecastReport'; import MarketDataView from './MarketDataView'; import CompanyOverview from './CompanyOverview'; // 导入 PostHog 追踪 Hook import { useCompanyEvents } from './hooks/useCompanyEvents'; const CompanyIndex = () => { const [searchParams, setSearchParams] = useSearchParams(); const [stockCode, setStockCode] = useState(searchParams.get('scode') || '000001'); const [inputCode, setInputCode] = useState(stockCode); const { colorMode, toggleColorMode } = useColorMode(); const toast = useToast(); const { isAuthenticated } = useAuth(); // 🎯 PostHog 事件追踪 const { trackStockSearched, trackTabChanged, trackWatchlistAdded, trackWatchlistRemoved, } = useCompanyEvents({ stockCode }); // Tab 索引状态(用于追踪 Tab 切换) const [currentTabIndex, setCurrentTabIndex] = useState(0); const bgColor = useColorModeValue('white', 'gray.800'); const tabBg = useColorModeValue('gray.50', 'gray.700'); const activeBg = useColorModeValue('blue.500', 'blue.400'); const [isInWatchlist, setIsInWatchlist] = useState(false); const [isWatchlistLoading, setIsWatchlistLoading] = useState(false); const loadWatchlistStatus = useCallback(async () => { try { const base = getApiBase(); const resp = await fetch(base + '/api/account/watchlist', { credentials: 'include', cache: 'no-store', headers: { 'Cache-Control': 'no-cache' } }); if (!resp.ok) { setIsInWatchlist(false); return; } const data = await resp.json(); const list = Array.isArray(data?.data) ? data.data : []; const codes = new Set(list.map((item) => item.stock_code)); setIsInWatchlist(codes.has(stockCode)); } catch (e) { setIsInWatchlist(false); } }, [stockCode]); // 当URL参数变化时更新股票代码 useEffect(() => { const scode = searchParams.get('scode'); if (scode && scode !== stockCode) { setStockCode(scode); setInputCode(scode); } }, [searchParams]); useEffect(() => { loadWatchlistStatus(); }, [loadWatchlistStatus]); const handleSearch = () => { if (inputCode && inputCode !== stockCode) { // 🎯 追踪股票搜索 trackStockSearched(inputCode, stockCode); setStockCode(inputCode); setSearchParams({ scode: inputCode }); } }; const handleKeyPress = (e) => { if (e.key === 'Enter') { handleSearch(); } }; const handleWatchlistToggle = async () => { if (!stockCode) { logger.warn('CompanyIndex', 'handleWatchlistToggle', '无效的股票代码', { stockCode }); toast({ title: '无效的股票代码', status: 'error', duration: 2000 }); return; } if (!isAuthenticated) { logger.warn('CompanyIndex', 'handleWatchlistToggle', '用户未登录', { stockCode }); toast({ title: '请先登录后再加入自选', status: 'warning', duration: 2000 }); return; } try { setIsWatchlistLoading(true); const base = getApiBase(); if (isInWatchlist) { logger.debug('CompanyIndex', '准备从自选移除', { stockCode }); const url = base + `/api/account/watchlist/${stockCode}`; logger.api.request('DELETE', url, { stockCode }); const resp = await fetch(url, { method: 'DELETE', credentials: 'include' }); logger.api.response('DELETE', url, resp.status); if (!resp.ok) throw new Error('删除失败'); // 🎯 追踪移除自选 trackWatchlistRemoved(stockCode); setIsInWatchlist(false); toast({ title: '已从自选移除', status: 'info', duration: 1500 }); } else { logger.debug('CompanyIndex', '准备添加到自选', { stockCode }); const url = base + '/api/account/watchlist'; const body = { stock_code: stockCode }; logger.api.request('POST', url, body); const resp = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', body: JSON.stringify(body) }); logger.api.response('POST', url, resp.status); if (!resp.ok) throw new Error('添加失败'); // 🎯 追踪加入自选 trackWatchlistAdded(stockCode); setIsInWatchlist(true); toast({ title: '已加入自选', status: 'success', duration: 1500 }); } } catch (error) { logger.error('CompanyIndex', 'handleWatchlistToggle', error, { stockCode, isInWatchlist }); toast({ title: '操作失败,请稍后重试', status: 'error', duration: 2000 }); } finally { setIsWatchlistLoading(false); } }; return ( {/* 页面标题和股票搜索 */} 个股信息分析 查看股票实时行情、财务数据和盈利预测 setInputCode(e.target.value)} onKeyPress={handleKeyPress} borderRadius="md" _focus={{ borderColor: 'blue.500', boxShadow: '0 0 0 1px #3182ce' }} /> : } onClick={toggleColorMode} variant="outline" colorScheme={colorMode === 'light' ? 'blue' : 'yellow'} size="lg" aria-label="Toggle color mode" /> {/* 当前股票信息 */} 股票代码: {stockCode} 更新时间: {new Date().toLocaleString()} {/* 数据展示区域 */} { const tabNames = ['公司概览', '股票行情', '财务全景', '盈利预测']; // 🎯 追踪 Tab 切换 trackTabChanged(index, tabNames[index], currentTabIndex); setCurrentTabIndex(index); }} > 公司概览 股票行情 财务全景 盈利预测 ); }; export default CompanyIndex;