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 => (
|
{field === 'stock_name' ? '股票名称' :
field === 'stock_code' ? '股票代码' :
field === 'current_price' ? '现价' :
field === 'change_percent' ? '当日涨跌幅' : 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 (
|
{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;
return (
handleConceptClick(concept.concept_id, concept.concept, concept, position)}
bg="white"
borderWidth="1px"
borderColor="gray.200"
overflow="hidden"
_hover={{
transform: 'translateY(-8px)',
boxShadow: 'xl',
borderColor: 'purple.300',
}}
transition="all 0.3s"
position="relative"
>
}
/>
{hasChange && (
5 ? `${pulseAnimation} 2s infinite` : 'none'}
>
0 ? FaArrowUp : changePercent < 0 ? FaArrowDown : null}
boxSize={3}
/>
{formatChangePercent(changePercent)}
)}
{concept.stock_count || 0} 只股票
{concept.concept}
{concept.description || '暂无描述信息'}
{hasChange && concept.price_info?.trade_date && (
交易日期: {new Date(concept.price_info.trade_date).toLocaleDateString('zh-CN')}
{formatChangePercent(changePercent)}
)}
{concept.stocks && concept.stocks.length > 0 && (
handleViewStocks(e, concept)}
_hover={{ bg: 'gray.100' }}
transition="background 0.2s"
>
热门个股
{!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)}
} // 改成时间轴图标
colorScheme="purple" // 改成紫色主题
variant="solid"
onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id)} // 传入concept_id
borderRadius="full"
px={4}
fontWeight="medium"
boxShadow="sm"
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
transition="all 0.2s"
>
历史时间轴 {/* 改按钮文字 */}
);
};
// 概念列表项组件 - 列表视图
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)}
{/* 右侧操作区域 */}
}
colorScheme="blue"
variant="outline"
onClick={(e) => handleViewStocks(e, concept)}
borderRadius="full"
>
查看个股
}
colorScheme="purple"
variant="solid"
onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id)}
borderRadius="full"
>
历史时间轴
{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点更新
大模型辅助的信息整理与呈现平台
以大模型协助汇聚与清洗多源信息,结合自主训练的领域知识图谱,
并由资深分析师进行人工整合与校准,提供结构化参考信息
实时更新
毫秒级数据同步
智能追踪
算法智能追踪
深度分析
多维度数据挖掘
专业可靠
权威数据源保障
}>
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' ? 'purple.500' : 'transparent'}
color={viewMode === 'grid' ? 'white' : 'purple.500'}
borderColor="purple.500"
_hover={{ bg: viewMode === 'grid' ? 'purple.600' : 'purple.50' }}
aria-label="网格视图"
/>
}
onClick={() => {
if (viewMode !== 'list') {
trackViewModeChanged('list', viewMode);
setViewMode('list');
}
}}
bg={viewMode === 'list' ? 'purple.500' : 'transparent'}
color={viewMode === 'list' ? 'white' : 'purple.500'}
borderColor="purple.500"
_hover={{ bg: viewMode === 'list' ? 'purple.600' : 'purple.50' }}
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版订阅才能使用
}
onClick={() => {
setUpgradeFeature('pro');
setUpgradeModalOpen(true);
}}
>
升级到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;