import React, { useState, useEffect, useCallback } from 'react'; import { useSearchParams, useNavigate } from 'react-router-dom'; import { logger } from '../../utils/logger'; import defaultEventImage from '../../assets/img/default-event.jpg'; import { Box, Container, Heading, Text, Input, InputGroup, InputLeftElement, Button, SimpleGrid, Card, CardBody, Image, Badge, Stack, HStack, VStack, Flex, Spacer, Select, Tag, TagLabel, Wrap, WrapItem, useToast, Spinner, Center, Icon, useColorModeValue, IconButton, ButtonGroup, Skeleton, SkeletonText, Modal, ModalOverlay, ModalContent, ModalHeader, ModalFooter, ModalBody, ModalCloseButton, Table, Thead, Tbody, Tr, Th, Td, TableContainer, Tooltip, Stat, StatLabel, StatNumber, StatHelpText, StatArrow, Divider, Popover, PopoverTrigger, PopoverContent, PopoverBody, PopoverArrow, PopoverCloseButton, Tabs, TabList, TabPanels, Tab, TabPanel, Accordion, AccordionItem, AccordionButton, AccordionPanel, AccordionIcon, useDisclosure, Menu, MenuButton, MenuList, MenuItem, Collapse, } from '@chakra-ui/react'; import { SearchIcon, ViewIcon, CalendarIcon, ExternalLinkIcon, StarIcon, ChevronDownIcon, InfoIcon, CloseIcon, ChevronRightIcon } from '@chakra-ui/icons'; import { FaThLarge, FaList, FaTags, FaChartLine, FaRobot, FaTable, FaHistory, FaBrain, FaLightbulb, FaRocket, FaShieldAlt, FaCalendarAlt, FaArrowUp, FaArrowDown, FaNewspaper, FaFileAlt, FaExpand, FaCompress, FaClock, FaLock } from 'react-icons/fa'; import { BsGraphUp, BsLightningFill } from 'react-icons/bs'; import { keyframes } from '@emotion/react'; import ConceptTimelineModal from './ConceptTimelineModal'; import ConceptStatsPanel from './components/ConceptStatsPanel'; // 导航栏已由 MainLayout 提供,无需在此导入 // 导入订阅权限管理 import { useSubscription } from '../../hooks/useSubscription'; import SubscriptionUpgradeModal from '../../components/SubscriptionUpgradeModal'; // 导入市场服务 import { marketService } from '../../services/marketService'; // 导入 PostHog 追踪 Hook import { useConceptEvents } from './hooks/useConceptEvents'; const API_BASE_URL = process.env.NODE_ENV === 'production' ? '/concept-api' : 'http://111.198.58.126:16801'; // 新闻和研报API配置 const NEWS_API_URL = process.env.NODE_ENV === 'production' ? '/news-api' : 'http://111.198.58.126:21891'; const REPORT_API_URL = process.env.NODE_ENV === 'production' ? '/report-api' : 'http://111.198.58.126:8811'; // 渐变动画 const gradientAnimation = keyframes` 0% { background-position: 0% 50%; } 50% { background-position: 100% 50%; } 100% { background-position: 0% 50%; } `; // 浮动动画 const floatAnimation = keyframes` 0%, 100% { transform: translateY(0px); } 50% { transform: translateY(-20px); } `; // 涨跌幅脉冲动画 const pulseAnimation = keyframes` 0% { transform: scale(1); } 50% { transform: scale(1.05); } 100% { transform: scale(1); } `; const ConceptCenter = () => { const [searchParams, setSearchParams] = useSearchParams(); const navigate = useNavigate(); const toast = useToast(); // 🎯 PostHog 事件追踪 const { trackConceptSearched, trackFilterApplied, trackConceptClicked, trackConceptStocksViewed, trackConceptStockClicked, trackConceptTimelineViewed, trackPageChange, trackViewModeChanged, } = useConceptEvents({ navigate }); // 订阅权限管理 const { hasFeatureAccess, getUpgradeRecommendation } = useSubscription(); const [upgradeModalOpen, setUpgradeModalOpen] = useState(false); const [upgradeFeature, setUpgradeFeature] = useState('pro'); // 状态管理 const [concepts, setConcepts] = useState([]); const [loading, setLoading] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [sortBy, setSortBy] = useState('change_pct'); const [pageSize] = useState(12); const [currentPage, setCurrentPage] = useState(1); const [totalConcepts, setTotalConcepts] = useState(0); const [totalPages, setTotalPages] = useState(1); const [viewMode, setViewMode] = useState('grid'); // 日期相关状态 const [selectedDate, setSelectedDate] = useState(null); const [latestTradeDate, setLatestTradeDate] = useState(null); const [isDatePickerOpen, setIsDatePickerOpen] = useState(false); const [selectedConceptForContent, setSelectedConceptForContent] = useState(''); // 股票详情Modal const [isStockModalOpen, setIsStockModalOpen] = useState(false); const [selectedConceptStocks, setSelectedConceptStocks] = useState(null); const [selectedConceptName, setSelectedConceptName] = useState(''); const [isTimelineModalOpen, setIsTimelineModalOpen] = useState(false); const [selectedConceptId, setSelectedConceptId] = useState(''); // 股票行情数据状态 const [stockMarketData, setStockMarketData] = useState({}); const [loadingStockData, setLoadingStockData] = useState(false); // 默认图片路径 const defaultImage = defaultEventImage; // 获取最新交易日期 const fetchLatestTradeDate = useCallback(async () => { try { const response = await fetch(`${API_BASE_URL}/price/latest`); if (response.ok) { const data = await response.json(); if (data.latest_trade_date) { const date = new Date(data.latest_trade_date); setLatestTradeDate(date); setSelectedDate(date); return date; } } } catch (error) { logger.error('ConceptCenter', 'fetchLatestTradeDate', error); } return null; }, []); // 打开内容模态框(新闻和研报)- 需要Max版权限 const handleViewContent = (e, conceptName, conceptId) => { e.stopPropagation(); // 检查历史时间轴权限 if (!hasFeatureAccess('concept_timeline')) { const recommendation = getUpgradeRecommendation('concept_timeline'); setUpgradeFeature(recommendation?.required || 'max'); setUpgradeModalOpen(true); return; } // 🎯 追踪历史时间轴查看 trackConceptTimelineViewed(conceptName, conceptId); setSelectedConceptForContent(conceptName); setSelectedConceptId(conceptId); setIsTimelineModalOpen(true); }; // 格式化日期 const formatDate = (dateStr) => { if (!dateStr) return '未知日期'; try { const date = new Date(dateStr); return date.toLocaleDateString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' }); } catch { return dateStr; } }; // 截取文本 const truncateText = (text, maxLength = 200) => { if (!text) return ''; if (text.length <= maxLength) return text; return text.substring(0, maxLength) + '...'; }; // 从URL获取参数 const getFiltersFromUrl = useCallback(() => { const q = searchParams.get('q') || ''; let defaultSort = q ? '_score' : 'change_pct'; return { q: q, sort: searchParams.get('sort') || defaultSort, page: parseInt(searchParams.get('page') || '1', 10), date: searchParams.get('date') || null, size: 12 }; }, [searchParams]); // 更新URL参数 const updateUrlParams = useCallback((params) => { const newParams = new URLSearchParams(searchParams); Object.entries(params).forEach(([key, value]) => { if (value) { newParams.set(key, value); } else { newParams.delete(key); } }); setSearchParams(newParams); }, [searchParams, setSearchParams]); // 获取概念数据 const fetchConcepts = useCallback(async (query = '', page = 1, date = selectedDate, customSortBy = null) => { setLoading(true); try { const sortToUse = customSortBy !== null ? customSortBy : sortBy; const requestBody = { query: query, size: pageSize, page: page, sort_by: sortToUse }; if (date) { requestBody.trade_date = date.toISOString().split('T')[0]; } const response = await fetch(`${API_BASE_URL}/search`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(requestBody), }); if (!response.ok) throw new Error('搜索失败'); const data = await response.json(); setConcepts(data.results || []); setTotalConcepts(data.total || 0); setTotalPages(data.total_pages || 1); setCurrentPage(data.page || 1); if (data.price_date) { setSelectedDate(new Date(data.price_date)); } } catch (error) { logger.error('ConceptCenter', 'fetchConcepts', error, { query, page, date: date?.toISOString(), sortToUse }); // ❌ 移除获取数据失败toast // toast({ title: '获取数据失败', description: error.message, status: 'error', duration: 3000, isClosable: true }); } finally { setLoading(false); } }, [pageSize, sortBy]); // 清除搜索 const handleClearSearch = () => { setSearchQuery(''); setSortBy('change_pct'); setCurrentPage(1); updateUrlParams({ q: '', page: 1, sort: 'change_pct' }); fetchConcepts('', 1, selectedDate, 'change_pct'); }; // 处理搜索 const handleSearch = () => { setCurrentPage(1); let newSortBy = sortBy; if (searchQuery && searchQuery.trim() !== '') { newSortBy = '_score'; setSortBy('_score'); } else if (!searchQuery || searchQuery.trim() === '') { newSortBy = 'change_pct'; setSortBy('change_pct'); } // 🎯 追踪搜索查询(在fetchConcepts后追踪结果数量) updateUrlParams({ q: searchQuery, page: 1, sort: newSortBy }); fetchConcepts(searchQuery, 1, selectedDate, newSortBy).then(() => { if (searchQuery && searchQuery.trim() !== '') { // 使用当前 concepts.length 作为结果数量 setTimeout(() => trackConceptSearched(searchQuery, concepts.length), 100); } }); }; // 处理Enter键搜索 const handleKeyPress = (e) => { if (e.key === 'Enter') { handleSearch(); } }; // 处理排序变化 const handleSortChange = (value) => { const previousSort = sortBy; // 🎯 追踪排序变化 trackFilterApplied('sort', value, previousSort); setSortBy(value); setCurrentPage(1); updateUrlParams({ sort: value, page: 1 }); fetchConcepts(searchQuery, 1, selectedDate, value); }; // 处理日期变化 const handleDateChange = (e) => { const date = new Date(e.target.value); const previousDate = selectedDate ? selectedDate.toISOString().split('T')[0] : null; // 🎯 追踪日期变化 trackFilterApplied('date', e.target.value, previousDate); setSelectedDate(date); setCurrentPage(1); updateUrlParams({ date: e.target.value, page: 1 }); fetchConcepts(searchQuery, 1, date, sortBy); }; // 快速选择日期 const handleQuickDateSelect = (days) => { const date = new Date(); date.setDate(date.getDate() - days); setSelectedDate(date); setCurrentPage(1); const dateStr = date.toISOString().split('T')[0]; updateUrlParams({ date: dateStr, page: 1 }); fetchConcepts(searchQuery, 1, date, sortBy); }; // 处理页码变化 const handlePageChange = (page) => { // 🎯 追踪翻页 trackPageChange(page, { sort: sortBy, q: searchQuery, date: selectedDate?.toISOString().split('T')[0] }); setCurrentPage(page); updateUrlParams({ page }); fetchConcepts(searchQuery, page, selectedDate, sortBy); window.scrollTo(0, 0); }; // 处理概念点击 const handleConceptClick = (conceptId, conceptName, concept = null, position = 0) => { // 🎯 追踪概念点击 if (concept) { trackConceptClicked(concept, position); } const htmlPath = `https://valuefrontier.cn/htmls/${encodeURIComponent(conceptName)}.html`; window.open(htmlPath, '_blank'); }; // 获取股票行情数据 const fetchStockMarketData = async (stocks) => { if (!stocks || stocks.length === 0) return; setLoadingStockData(true); const newMarketData = {}; try { // 批量获取股票数据,每次处理5个股票以避免并发过多 const batchSize = 5; for (let i = 0; i < stocks.length; i += batchSize) { const batch = stocks.slice(i, i + batchSize); const promises = batch.map(async (stock) => { if (!stock.stock_code) return null; // 提取6位股票代码(去掉交易所后缀) const seccode = stock.stock_code.substring(0, 6); try { const response = await marketService.getTradeData(seccode, 1); if (response.success && response.data && response.data.length > 0) { const latestData = response.data[response.data.length - 1]; return { stock_code: stock.stock_code, ...latestData }; } } catch (error) { logger.warn('ConceptCenter', `获取股票行情数据失败`, { stockCode: seccode, error: error.message }); } return null; }); const batchResults = await Promise.all(promises); batchResults.forEach(result => { if (result) { newMarketData[result.stock_code] = result; } }); } setStockMarketData(newMarketData); logger.info('ConceptCenter', '股票行情数据批量加载完成', { totalStocks: stocks.length, loadedCount: Object.keys(newMarketData).length }); } catch (error) { logger.error('ConceptCenter', 'fetchStockMarketData', error, { stockCount: stocks?.length }); } finally { setLoadingStockData(false); } }; // 打开股票详情Modal - 需要Pro版权限 const handleViewStocks = (e, concept) => { e.stopPropagation(); // 检查热门个股权限 if (!hasFeatureAccess('hot_stocks')) { const recommendation = getUpgradeRecommendation('hot_stocks'); setUpgradeFeature(recommendation?.required || 'pro'); setUpgradeModalOpen(true); return; } // 🎯 追踪查看个股 trackConceptStocksViewed(concept.concept, concept.stocks?.length || 0); setSelectedConceptStocks(concept.stocks || []); setSelectedConceptName(concept.concept); setStockMarketData({}); // 清空之前的数据 setIsStockModalOpen(true); // 获取股票行情数据 fetchStockMarketData(concept.stocks || []); }; // 格式化涨跌幅显示 const formatChangePercent = (value) => { if (value === null || value === undefined) return null; const formatted = value.toFixed(2); return formatted > 0 ? `+${formatted}%` : `${formatted}%`; }; // 获取涨跌幅颜色 const getChangeColor = (value) => { if (value === null || value === undefined) return 'gray'; return value > 0 ? 'red' : value < 0 ? 'green' : 'gray'; }; // 格式化价格显示 const formatPrice = (value) => { if (value === null || value === undefined) return '-'; return `¥${value.toFixed(2)}`; }; // 格式化涨跌幅显示(股票表格专用) const formatStockChangePercent = (value) => { if (value === null || value === undefined) return '-'; const formatted = value.toFixed(2); return value >= 0 ? `+${formatted}%` : `${formatted}%`; }; // 获取涨跌幅颜色(股票表格专用) const getStockChangeColor = (value) => { if (value === null || value === undefined) return 'gray'; return value > 0 ? 'red' : value < 0 ? 'green' : 'gray'; }; // 生成公司详情链接 const generateCompanyLink = (stockCode) => { if (!stockCode) return '#'; // 提取6位股票代码 const seccode = stockCode.substring(0, 6); return `https://valuefrontier.cn/company?scode=${seccode}`; }; // 渲染动态表格列 const renderStockTable = () => { if (!selectedConceptStocks || selectedConceptStocks.length === 0) { return 暂无相关股票数据; } const allFields = new Set(); selectedConceptStocks.forEach(stock => { Object.keys(stock).forEach(key => allFields.add(key)); }); // 定义固定的列顺序,包含新增的现价和涨跌幅列 const orderedFields = ['stock_name', 'stock_code', 'current_price', 'change_percent']; allFields.forEach(field => { if (!orderedFields.includes(field)) { orderedFields.push(field); } }); return ( {loadingStockData && ( 正在获取行情数据... )} {orderedFields.map(field => ( ))} {selectedConceptStocks.map((stock, idx) => { const marketData = stockMarketData[stock.stock_code]; const companyLink = generateCompanyLink(stock.stock_code); return ( {orderedFields.map(field => { let cellContent = stock[field] || '-'; let cellProps = {}; // 处理特殊字段 if (field === 'current_price') { cellContent = marketData ? formatPrice(marketData.close) : (loadingStockData ? : '-'); } else if (field === 'change_percent') { if (marketData) { cellContent = formatStockChangePercent(marketData.change_percent); cellProps.color = `${getStockChangeColor(marketData.change_percent)}.500`; cellProps.fontWeight = 'bold'; } else { cellContent = loadingStockData ? : '-'; } } else if (field === 'stock_name' || field === 'stock_code') { // 添加超链接 cellContent = ( {stock[field] || '-'} ); } return ( ); })} ); })}
{field === 'stock_name' ? '股票名称' : field === 'stock_code' ? '股票代码' : field === 'current_price' ? '现价' : field === 'change_percent' ? '当日涨跌幅' : field}
{cellContent}
); }; // 格式化日期显示 const formatHappenedTimes = (times) => { if (!times || times.length === 0) return null; if (times.length === 1) { return ( {new Date(times[0]).toLocaleDateString('zh-CN')} ); } const sortedTimes = [...times].sort((a, b) => new Date(b) - new Date(a)); const latestDate = new Date(sortedTimes[0]).toLocaleDateString('zh-CN'); return ( 历史爆发日期: {sortedTimes.map((time, idx) => ( {new Date(time).toLocaleDateString('zh-CN')} ))} } bg="purple.600" color="white" borderRadius="md" p={3} > {latestDate} (共{times.length}次) ); }; // 初始化加载 useEffect(() => { const init = async () => { const latestDate = await fetchLatestTradeDate(); const filters = getFiltersFromUrl(); setSearchQuery(filters.q); setSortBy(filters.sort); setCurrentPage(filters.page); const dateToUse = filters.date ? new Date(filters.date) : latestDate; if (dateToUse) { setSelectedDate(dateToUse); fetchConcepts(filters.q, filters.page, dateToUse, filters.sort); } else { fetchConcepts(filters.q, filters.page, null, filters.sort); } }; init(); }, []); // 概念卡片组件 - 科幻毛玻璃版 const ConceptCard = ({ concept, position = 0 }) => { const changePercent = concept.price_info?.avg_change_pct; const changeColor = getChangeColor(changePercent); const hasChange = changePercent !== null && changePercent !== undefined; // 生成随机涨幅数字背景 const generateNumbersBackground = () => { const numbers = []; for (let i = 0; i < 30; i++) { const isPositive = Math.random() > 0.5; const value = (Math.random() * 15).toFixed(2); const sign = isPositive ? '+' : '-'; numbers.push(`${sign}${value}%`); } return numbers; }; const backgroundNumbers = generateNumbersBackground(); return ( handleConceptClick(concept.concept_id, concept.concept, concept, position)} bg="white" borderWidth="1px" borderColor="transparent" overflow="hidden" _hover={{ transform: 'translateY(-8px)', boxShadow: '0 20px 40px rgba(139, 92, 246, 0.3)', borderColor: 'purple.400', }} transition="all 0.3s" position="relative" boxShadow="0 4px 12px rgba(0, 0, 0, 0.1)" > {/* 毛玻璃涨幅数字背景 */} {/* 渐变背景层 */} 0 ? "linear(135deg, #667eea 0%, #764ba2 100%)" : hasChange && changePercent < 0 ? "linear(135deg, #f093fb 0%, #f5576c 100%)" : "linear(135deg, #4facfe 0%, #00f2fe 100%)" } /> {/* 数字矩阵层 */} {backgroundNumbers.map((num, idx) => ( {num} ))} {/* 毛玻璃层 */} {/* 渐变遮罩 */} {/* 发光边框效果 */} {/* 右上角股票数量徽章 */} {concept.stock_count || 0} 只股票 {/* 概念名称 */} {concept.concept} {/* 描述信息 */} {concept.description || '暂无描述信息'} {/* 涨跌幅信息卡片 */} {hasChange && concept.price_info?.trade_date && ( 0 ? "linear(to-r, rgba(139, 92, 246, 0.1), rgba(168, 85, 247, 0.05))" : "linear(to-r, rgba(236, 72, 153, 0.1), rgba(251, 113, 133, 0.05))" } border="1px solid" borderColor={changePercent > 0 ? "purple.200" : "pink.200"} > 0 ? "purple.500" : "pink.500"} /> {new Date(concept.price_info.trade_date).toLocaleDateString('zh-CN')} 0 ? FaArrowUp : FaArrowDown} boxSize={3} color={changePercent > 0 ? "red.500" : "green.500"} /> 0 ? "red.500" : "green.500"} > {formatChangePercent(changePercent)} )} {concept.stocks && concept.stocks.length > 0 && ( handleViewStocks(e, concept)} _hover={{ bg: 'linear-gradient(135deg, rgba(99, 102, 241, 0.1) 0%, rgba(168, 85, 247, 0.1) 100%)', transform: 'translateX(2px)', }} transition="all 0.2s" border="1px solid" borderColor="purple.100" > 热门个股 {!hasFeatureAccess('hot_stocks') && ( 🔒Pro )} {hasFeatureAccess('hot_stocks') ? ( <> {concept.stocks.slice(0, 2).map((stock, idx) => ( {stock.stock_name} ))} {concept.stocks.length > 2 && ( +{concept.stocks.length - 2} )} ) : ( 升级查看{concept.stocks.length}只个股 )} )} {formatHappenedTimes(concept.happened_times)} {/* 顶部发光条 */} ); }; // 概念列表项组件 - 列表视图 const ConceptListItem = ({ concept, position = 0 }) => { const changePercent = concept.price_info?.avg_change_pct; const changeColor = getChangeColor(changePercent); const hasChange = changePercent !== null && changePercent !== undefined; return ( handleConceptClick(concept.concept_id, concept.concept, concept, position)} bg="white" borderWidth="1px" borderColor="gray.200" overflow="hidden" _hover={{ transform: 'translateX(4px)', boxShadow: 'lg', borderColor: 'purple.300', }} transition="all 0.3s" > {/* 左侧图标区域 */} {hasChange && ( 0 ? FaArrowUp : changePercent < 0 ? FaArrowDown : null} boxSize={2} mr={1} /> {formatChangePercent(changePercent)} )} {/* 中间内容区域 */} {concept.concept} {concept.description || '该概念板块涵盖相关技术、产业链和市场应用等多个维度的投资机会'} {concept.stock_count || 0} 只股票 {hasChange && concept.price_info?.trade_date && ( {new Date(concept.price_info.trade_date).toLocaleDateString('zh-CN')} )} {formatHappenedTimes(concept.happened_times)} {/* 右侧操作区域 */} {concept.stocks && concept.stocks.length > 0 && ( 热门个股 {!hasFeatureAccess('hot_stocks') && ( 🔒需Pro )} {hasFeatureAccess('hot_stocks') ? ( <> {concept.stocks.slice(0, 3).map((stock, idx) => ( {stock.stock_name} ))} {concept.stocks.length > 3 && ( +{concept.stocks.length - 3}更多 )} ) : ( 升级查看{concept.stocks.length}只 )} )} ); }; // 骨架屏组件 const SkeletonCard = () => ( ); // 日期选择组件 - 科幻风格 const DateSelector = () => ( 交易日期: {latestTradeDate && ( 最新: {latestTradeDate.toLocaleDateString('zh-CN')} )} ); return ( {/* 导航栏已由 MainLayout 提供 */} {/* Hero Section - 精简版 */} {/* 科幻网格背景 */} {/* 发光球体 */} {/* 标题区域 */} 概念中心 数据约下午4点更新 AI驱动的概念板块分析平台 · 实时追踪市场热点 · 智能挖掘投资机会 {/* 核心数据展示 */} } bg="whiteAlpha.100" backdropFilter="blur(10px)" px={8} py={3} borderRadius="full" border="1px solid" borderColor="whiteAlpha.300" boxShadow="0 8px 32px rgba(0, 0, 0, 0.3)" > 500+ 概念板块 5000+ 相关个股 24/7 实时监控 {/* 搜索框 */} setSearchQuery(e.target.value)} onKeyPress={handleKeyPress} pr={searchQuery ? "40px" : "16px"} /> {searchQuery && ( } variant="ghost" color="gray.500" _hover={{ color: 'gray.700', bg: 'gray.100' }} onClick={handleClearSearch} zIndex={1} /> )} {searchQuery && sortBy === '_score' && ( 正在搜索 "{searchQuery}",已自动切换到相关度排序 )} {/* 主内容区域 */} {/* 双栏布局:左侧概念卡片,右侧统计面板 */} {/* 左侧概念卡片区域 */} 排序方式: {searchQuery && sortBy === '_score' && ( 智能排序 )} } onClick={() => { if (viewMode !== 'grid') { trackViewModeChanged('grid', viewMode); setViewMode('grid'); } }} bg={viewMode === 'grid' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'} color={viewMode === 'grid' ? 'white' : 'purple.500'} borderColor="purple.500" _hover={{ bg: viewMode === 'grid' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'purple.50', boxShadow: viewMode === 'grid' ? '0 0 10px rgba(139, 92, 246, 0.3)' : 'none', }} aria-label="网格视图" /> } onClick={() => { if (viewMode !== 'list') { trackViewModeChanged('list', viewMode); setViewMode('list'); } }} bg={viewMode === 'list' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'transparent'} color={viewMode === 'list' ? 'white' : 'purple.500'} borderColor="purple.500" _hover={{ bg: viewMode === 'list' ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'purple.50', boxShadow: viewMode === 'list' ? '0 0 10px rgba(139, 92, 246, 0.3)' : 'none', }} aria-label="列表视图" /> {selectedDate && ( 当前显示 {selectedDate.toLocaleDateString('zh-CN')} 的概念涨跌幅数据 {searchQuery && ,搜索词:"{searchQuery}"} )} {loading ? ( {[...Array(12)].map((_, i) => ( ))} ) : concepts.length > 0 ? ( <> {viewMode === 'grid' ? ( {concepts.map((concept, index) => ( ))} ) : ( {concepts.map((concept, index) => ( ))} )}
{[...Array(Math.min(5, totalPages))].map((_, i) => { const pageNum = currentPage <= 3 ? i + 1 : currentPage >= totalPages - 2 ? totalPages - 4 + i : currentPage - 2 + i; if (pageNum < 1 || pageNum > totalPages) return null; return ( ); })}
) : (
暂无概念数据 请尝试其他搜索关键词或选择其他日期
)}
{/* 右侧统计面板 */} {hasFeatureAccess('concept_stats_panel') ? ( ) : ( 概念统计中心 此功能需要Pro版订阅才能使用 )}
{/* 股票详情Modal */} setIsStockModalOpen(false)} size="6xl" scrollBehavior="inside" > {selectedConceptName} - 相关个股 {renderStockTable()} {/* 时间轴Modal */} setIsTimelineModalOpen(false)} conceptName={selectedConceptForContent} conceptId={selectedConceptId} /> {/* 订阅升级Modal */} setUpgradeModalOpen(false)} requiredLevel={upgradeFeature} featureName={ upgradeFeature === 'pro' ? '概念统计中心和热门个股' : '概念历史时间轴' } />
); }; export default ConceptCenter;