diff --git a/src/views/Company/index.js b/src/views/Company/index.js index 333f3a3a..eb6fd571 100644 --- a/src/views/Company/index.js +++ b/src/views/Company/index.js @@ -3,63 +3,77 @@ import { useSearchParams } from 'react-router-dom'; import { AutoComplete, Spin } from 'antd'; import { useStockSearch } from '@hooks/useStockSearch'; import { - Container, - Heading, - Card, - CardBody, - Tabs, - TabList, - TabPanels, - Tab, - TabPanel, + Box, + Flex, HStack, VStack, - Button, Text, - Badge, - Divider, + Button, Icon, - useColorModeValue, - useColorMode, - IconButton, + Badge, useToast, + Skeleton, } from '@chakra-ui/react'; -import { SearchIcon, MoonIcon, SunIcon, StarIcon } from '@chakra-ui/icons'; -import { FaChartLine, FaMoneyBillWave, FaChartBar, FaInfoCircle } from 'react-icons/fa'; +import { Search, Star, Building2, TrendingUp, Wallet, FileBarChart } from 'lucide-react'; import { useAuth } from '../../contexts/AuthContext'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; + +// Tab 内容组件 import FinancialPanorama from './components/FinancialPanorama'; import ForecastReport from './ForecastReport'; import MarketDataView from './MarketDataView'; import CompanyOverview from './components/CompanyOverview'; -// 导入 PostHog 追踪 Hook + +// PostHog 追踪 import { useCompanyEvents } from './hooks/useCompanyEvents'; -// 页面组件 -import CompanyHeader from './components/CompanyHeader'; -import StockQuoteCard from './components/StockQuoteCard'; -import CompanyTabs from './components/CompanyTabs'; +// 通用组件 +import SubTabContainer from '@components/SubTabContainer'; + +// ============================================ +// 黑金主题配置 +// ============================================ +const THEME = { + bg: '#1A1A2E', // 深蓝黑背景 + cardBg: '#16213E', // 卡片背景 + gold: '#D4AF37', // 金色 + goldLight: '#F0D78C', // 浅金色 + goldDark: '#B8960C', // 深金色 + textPrimary: '#FFFFFF', + textSecondary: 'rgba(255, 255, 255, 0.7)', + textMuted: 'rgba(255, 255, 255, 0.5)', + border: 'rgba(212, 175, 55, 0.2)', + borderHover: 'rgba(212, 175, 55, 0.4)', +}; + +// ============================================ +// Tab 配置 +// ============================================ +const TAB_CONFIG = [ + { key: 'overview', name: '公司概览', icon: Building2, component: CompanyOverview }, + { key: 'market', name: '股票行情', icon: TrendingUp, component: MarketDataView }, + { key: 'financial', name: '财务全景', icon: Wallet, component: FinancialPanorama }, + { key: 'forecast', name: '盈利预测', icon: FileBarChart, component: ForecastReport }, +]; /** * 公司详情页面 * - * 功能: - * - 股票搜索与代码管理 - * - 自选股添加/移除 - * - 多维度数据展示(概览、行情、财务、预测) - * - PostHog 事件追踪 + * 使用黑金主题,紧凑布局 */ const CompanyIndex = () => { const [searchParams, setSearchParams] = useSearchParams(); const [stockCode, setStockCode] = useState(searchParams.get('scode') || '000001'); const [inputCode, setInputCode] = useState(stockCode); + const [stockName, setStockName] = useState(''); + const [stockPrice, setStockPrice] = useState(null); + const [priceChange, setPriceChange] = useState(null); const prevStockCodeRef = useRef(stockCode); - const { colorMode, toggleColorMode } = useColorMode(); const toast = useToast(); const { isAuthenticated } = useAuth(); - // 🎯 PostHog 事件追踪 + // PostHog 事件追踪 const { trackStockSearched, trackTabChanged, @@ -67,7 +81,7 @@ const CompanyIndex = () => { trackWatchlistRemoved, } = useCompanyEvents({ stockCode }); - // 🔍 股票搜索 Hook(支持代码、名称、拼音缩写) + // 股票搜索 Hook const { searchResults, isSearching, @@ -84,28 +98,26 @@ const CompanyIndex = () => { return searchResults.map((stock) => ({ value: stock.stock_code, label: ( - - {stock.stock_code} {stock.stock_name} + + + {stock.stock_code} + {stock.stock_name} + {stock.pinyin_abbr && ( - - ({stock.pinyin_abbr.toUpperCase()}) - + + {stock.pinyin_abbr.toUpperCase()} + )} - + ), })); }, [searchResults]); - // 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(); @@ -125,24 +137,44 @@ const CompanyIndex = () => { setIsInWatchlist(false); } }, [stockCode]); - - // 当URL参数变化时更新股票代码 + + // 加载股票基本信息 + const loadStockInfo = useCallback(async () => { + try { + const base = getApiBase(); + const resp = await fetch(base + `/api/financial/stock-info/${stockCode}`, { + credentials: 'include', + }); + if (resp.ok) { + const data = await resp.json(); + if (data.success && data.data) { + setStockName(data.data.stock_name || ''); + setStockPrice(data.data.close_price); + setPriceChange(data.data.change_pct); + } + } + } catch (e) { + logger.error('CompanyIndex', 'loadStockInfo', e); + } + }, [stockCode]); + + // URL 参数变化时更新股票代码 useEffect(() => { if (stockCode !== prevStockCodeRef.current) { trackStockSearched(stockCode, prevStockCodeRef.current); prevStockCodeRef.current = stockCode; } - }, [searchParams, stockCode]); - + }, [searchParams, stockCode, trackStockSearched]); + useEffect(() => { loadWatchlistStatus(); - }, [loadWatchlistStatus]); - + loadStockInfo(); + }, [loadWatchlistStatus, loadStockInfo]); + + // 搜索处理 const handleSearch = () => { if (inputCode && inputCode !== stockCode) { - // 🎯 追踪股票搜索 trackStockSearched(inputCode, stockCode); - setStockCode(inputCode); setSearchParams({ scode: inputCode }); } @@ -159,14 +191,13 @@ const CompanyIndex = () => { } }; + // 自选股切换 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; } @@ -174,219 +205,206 @@ const CompanyIndex = () => { 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, { + const resp = await fetch(base + `/api/account/watchlist/${stockCode}`, { 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, { + const resp = await fetch(base + '/api/account/watchlist', { method: 'POST', headers: { 'Content-Type': 'application/json' }, credentials: 'include', - body: JSON.stringify(body) + body: JSON.stringify({ stock_code: stockCode }) }); - - 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 }); + logger.error('CompanyIndex', 'handleWatchlistToggle', error); toast({ title: '操作失败,请稍后重试', status: 'error', duration: 2000 }); } finally { setIsWatchlistLoading(false); } }; + // Tab 变更处理 + const handleTabChange = useCallback((index, tabKey) => { + const tabNames = TAB_CONFIG.map(t => t.name); + trackTabChanged(index, tabNames[index], index); + }, [trackTabChanged]); + return ( - - {/* 页面标题和股票搜索 */} - - - - - - 个股详情 - - 查看股票实时行情、财务数据和盈利预测 + + {/* 顶部搜索栏 */} + + + {/* 左侧:股票信息 */} + + {/* 股票代码和名称 */} + + + + {stockCode} - - - - setInputCode(value)} - placeholder="输入代码、名称或拼音缩写" - style={{ width: 280 }} - size="large" - notFoundContent={isSearching ? : null} - onKeyDown={(e) => { - if (e.key === 'Enter') { - handleSearch(); - } - }} - /> - - - : } - onClick={toggleColorMode} - variant="outline" - colorScheme={colorMode === 'light' ? 'blue' : 'yellow'} - size="lg" - aria-label="Toggle color mode" - /> + {stockName && ( + + {stockName} + + )} - - - {/* 当前股票信息 */} - - - 股票代码: {stockCode} - - - 更新时间: {new Date().toLocaleString()} - - - - - - {/* 数据展示区域 */} - - - { - const tabNames = ['公司概览', '股票行情', '财务全景', '盈利预测']; - // 🎯 追踪 Tab 切换 - trackTabChanged(index, tabNames[index], currentTabIndex); - setCurrentTabIndex(index); + {stockPrice !== null && ( + + + ¥{stockPrice?.toFixed(2)} + + {priceChange !== null && ( + = 0 ? 'rgba(239, 68, 68, 0.2)' : 'rgba(34, 197, 94, 0.2)'} + color={priceChange >= 0 ? '#EF4444' : '#22C55E'} + fontSize="sm" + fontWeight="bold" + > + {priceChange >= 0 ? '+' : ''}{priceChange?.toFixed(2)}% + + )} + + )} + + + + {/* 右侧:搜索和操作 */} + + {/* 搜索框 */} + - - - - - 公司概览 - - - - - - 股票行情 - - - - - - 财务全景 - - - - - - 盈利预测 - - - - - - - - - - - - - - - - - - - - - - - - - + setInputCode(value)} + placeholder="输入代码、名称或拼音" + style={{ width: 220 }} + notFoundContent={isSearching ? : null} + onKeyDown={(e) => { + if (e.key === 'Enter') handleSearch(); + }} + /> + + + {/* 搜索按钮 */} + + + {/* 自选按钮 */} + + + + + + {/* 主内容区 - Tab 切换 */} + + + + + + ); };