update pay ui

This commit is contained in:
2025-12-05 19:40:11 +08:00
parent 89f581aeed
commit 4c7a761324
4 changed files with 214 additions and 104 deletions

96
app.py
View File

@@ -5508,6 +5508,102 @@ def delete_related_stock(stock_id):
return jsonify({'success': False, 'error': str(e)}), 500 return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/events/by-stocks', methods=['POST'])
def get_events_by_stocks():
"""
通过股票代码列表获取关联的事件(新闻)
用于概念中心时间轴:聚合概念下所有股票的相关新闻
请求体:
{
"stock_codes": ["000001.SZ", "600000.SH", ...], # 股票代码列表
"start_date": "2024-01-01", # 可选,开始日期
"end_date": "2024-12-31", # 可选,结束日期
"limit": 100 # 可选限制返回数量默认100
}
"""
try:
data = request.get_json()
stock_codes = data.get('stock_codes', [])
start_date_str = data.get('start_date')
end_date_str = data.get('end_date')
limit = data.get('limit', 100)
if not stock_codes:
return jsonify({'success': False, 'error': '缺少股票代码列表'}), 400
# 构建查询:通过 RelatedStock 表找到关联的事件
query = db.session.query(Event).join(
RelatedStock, Event.id == RelatedStock.event_id
).filter(
RelatedStock.stock_code.in_(stock_codes)
)
# 日期过滤
if start_date_str:
try:
start_date = datetime.strptime(start_date_str, '%Y-%m-%d')
query = query.filter(Event.event_date >= start_date)
except ValueError:
pass
if end_date_str:
try:
end_date = datetime.strptime(end_date_str, '%Y-%m-%d')
# 设置为当天结束
end_date = end_date.replace(hour=23, minute=59, second=59)
query = query.filter(Event.event_date <= end_date)
except ValueError:
pass
# 去重并排序
query = query.distinct().order_by(Event.event_date.desc())
# 限制数量
if limit:
query = query.limit(limit)
events = query.all()
# 构建返回数据
events_data = []
for event in events:
# 获取该事件关联的股票信息(在请求的股票列表中的)
related_stocks_in_list = [
{
'stock_code': rs.stock_code,
'stock_name': rs.stock_name,
'sector': rs.sector
}
for rs in event.related_stocks
if rs.stock_code in stock_codes
]
events_data.append({
'id': event.id,
'title': event.title,
'content': event.content,
'event_date': event.event_date.isoformat() if event.event_date else None,
'published_time': event.event_date.strftime('%Y-%m-%d %H:%M:%S') if event.event_date else None,
'source': 'event', # 标记来源为事件系统
'importance': event.importance,
'view_count': event.view_count,
'like_count': event.like_count,
'related_stocks': related_stocks_in_list,
'cover_image': event.cover_image,
'created_at': event.created_at.isoformat() if event.created_at else None
})
return jsonify({
'success': True,
'data': events_data,
'total': len(events_data)
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
@app.route('/api/events/<int:event_id>/concepts', methods=['GET']) @app.route('/api/events/<int:event_id>/concepts', methods=['GET'])
def get_related_concepts(event_id): def get_related_concepts(event_id):
"""获取相关概念列表""" """获取相关概念列表"""

View File

@@ -78,11 +78,17 @@ const REPORT_API_URL = process.env.NODE_ENV === 'production'
? '/report-api' ? '/report-api'
: 'http://111.198.58.126:8811'; : 'http://111.198.58.126:8811';
// 主应用后端 API 基础 URL用于获取股票关联的事件/新闻)
const MAIN_API_URL = process.env.NODE_ENV === 'production'
? ''
: 'http://111.198.58.126:5001';
const ConceptTimelineModal = ({ const ConceptTimelineModal = ({
isOpen, isOpen,
onClose, onClose,
conceptName, conceptName,
conceptId conceptId,
stocks = []
}) => { }) => {
const toast = useToast(); const toast = useToast();
@@ -334,61 +340,66 @@ const ConceptTimelineModal = ({
}) })
); );
// 获取新闻(精确匹配最近100天最多100条 // 获取新闻(通过股票代码聚合关联事件
// 🔄 添加回退逻辑如果结果不足30条去掉 exact_match 参数重新搜索 // 新逻辑:每个概念有关联的股票,通过 related_stock 表聚合所有股票的关联新闻/事件
const fetchNews = async () => { const fetchNews = async () => {
try { try {
// 第一次尝试:使用精确匹配 // 提取股票代码列表
const newsParams = new URLSearchParams({ const stockCodes = (stocks || [])
query: conceptName, .map(s => s.code || s.stock_code)
exact_match: 1, .filter(Boolean);
start_date: startDateStr,
end_date: endDateStr,
top_k: 100
});
const newsUrl = `${NEWS_API_URL}/search_china_news?${newsParams}`; if (stockCodes.length === 0) {
const res = await fetch(newsUrl); logger.warn('ConceptTimelineModal', '概念没有关联股票,无法获取新闻', { conceptName });
if (!res.ok) {
const text = await res.text();
logger.error('ConceptTimelineModal', 'fetchTimelineData - News API (exact_match=1)', new Error(`HTTP ${res.status}`), { conceptName, status: res.status, response: text.substring(0, 200) });
return []; return [];
} }
const newsResult = await res.json(); logger.info('ConceptTimelineModal', `通过 ${stockCodes.length} 只股票获取关联新闻`, { conceptName, stockCodes: stockCodes.slice(0, 5) });
const newsArray = Array.isArray(newsResult) ? newsResult : [];
// 检查结果数量如果不足30条则进行回退搜索 // 调用后端新 API 获取股票关联的事件
if (newsArray.length < 30) { const res = await fetch(`${MAIN_API_URL}/api/events/by-stocks`, {
logger.info('ConceptTimelineModal', `新闻精确搜索结果不足30条 (${newsArray.length}),尝试模糊搜索`, { conceptName }); method: 'POST',
headers: {
// 第二次尝试:去掉精确匹配参数 'Content-Type': 'application/json',
const fallbackParams = new URLSearchParams({ },
query: conceptName, body: JSON.stringify({
stock_codes: stockCodes,
start_date: startDateStr, start_date: startDateStr,
end_date: endDateStr, end_date: endDateStr,
top_k: 100 limit: 200
}),
credentials: 'include'
}); });
const fallbackUrl = `${NEWS_API_URL}/search_china_news?${fallbackParams}`; if (!res.ok) {
const fallbackRes = await fetch(fallbackUrl); const text = await res.text();
logger.error('ConceptTimelineModal', 'fetchTimelineData - Events by Stocks API', new Error(`HTTP ${res.status}`), { conceptName, status: res.status, response: text.substring(0, 200) });
if (!fallbackRes.ok) { return [];
logger.warn('ConceptTimelineModal', '新闻模糊搜索失败,使用精确搜索结果', { conceptName });
return newsArray;
} }
const fallbackResult = await fallbackRes.json(); const result = await res.json();
const fallbackArray = Array.isArray(fallbackResult) ? fallbackResult : [];
logger.info('ConceptTimelineModal', `新闻模糊搜索成功,获取 ${fallbackArray.length} 条结果`, { conceptName }); if (!result.success) {
return fallbackArray; logger.warn('ConceptTimelineModal', '获取股票关联事件失败', { conceptName, error: result.error });
return [];
} }
// 转换为新闻格式
const events = result.data || [];
const newsArray = events.map(event => ({
title: event.title,
detail: event.description || event.summary || '',
description: event.description || event.summary || '',
published_time: event.event_date || event.created_at,
source: 'event', // 标记为事件来源
url: null, // 事件没有外链
related_stocks: event.related_stocks || [] // 保留关联股票信息
}));
logger.info('ConceptTimelineModal', `获取到 ${newsArray.length} 条股票关联事件`, { conceptName });
return newsArray; return newsArray;
} catch (err) { } catch (err) {
logger.error('ConceptTimelineModal', 'fetchTimelineData - News API', err, { conceptName }); logger.error('ConceptTimelineModal', 'fetchTimelineData - Events by Stocks API', err, { conceptName });
return []; return [];
} }
}; };
@@ -1376,10 +1387,10 @@ const ConceptTimelineModal = ({
<HStack spacing={3} fontSize="sm"> <HStack spacing={3} fontSize="sm">
{selectedNews?.source && ( {selectedNews?.source && (
<Badge <Badge
bg={selectedNews.source === 'zsxq' ? 'purple.500' : 'blue.500'} bg={selectedNews.source === 'zsxq' ? 'purple.500' : selectedNews.source === 'event' ? 'cyan.500' : 'blue.500'}
color="white" color="white"
> >
{selectedNews.source === 'zsxq' ? '知识星球' : selectedNews.source} {selectedNews.source === 'zsxq' ? '知识星球' : selectedNews.source === 'event' ? '关联事件' : selectedNews.source}
</Badge> </Badge>
)} )}
{selectedNews?.time && ( {selectedNews?.time && (
@@ -1411,8 +1422,8 @@ const ConceptTimelineModal = ({
<ModalFooter borderTop="1px solid" borderColor="whiteAlpha.100" bg="rgba(15, 23, 42, 0.98)"> <ModalFooter borderTop="1px solid" borderColor="whiteAlpha.100" bg="rgba(15, 23, 42, 0.98)">
<HStack spacing={3}> <HStack spacing={3}>
{/* zsxq来源不显示查看原文按钮 */} {/* zsxq和event来源不显示查看原文按钮 */}
{selectedNews?.url && selectedNews?.source !== 'zsxq' && ( {selectedNews?.url && selectedNews?.source !== 'zsxq' && selectedNews?.source !== 'event' && (
<Button <Button
size="sm" size="sm"
bg="whiteAlpha.100" bg="whiteAlpha.100"

View File

@@ -71,27 +71,27 @@ const glowPulse = keyframes`
50% { opacity: 0.6; transform: scale(1.05); } 50% { opacity: 0.6; transform: scale(1.05); }
`; `;
// 一级分类颜色映射(基础色 - 半透明玻璃态 - 黑金主题 // 一级分类颜色映射(基础色 - 半透明玻璃态)
const LV1_COLORS = { const LV1_COLORS = {
'人工智能': '#EAB308', // 金色 '人工智能': '#8B5CF6',
'半导体': '#F59E0B', // 琥珀色 '半导体': '#3B82F6',
'机器人': '#CA8A04', // 深金色 '机器人': '#10B981',
'消费电子': '#FACC15', // 亮金色 '消费电子': '#F59E0B',
'智能驾驶与汽车': '#D97706', // 橙金色 '智能驾驶与汽车': '#EF4444',
'新能源与电力': '#FDE047', // 浅金色 '新能源与电力': '#06B6D4',
'空天经济': '#A16207', // 古铜金 '空天经济': '#6366F1',
'国防军工': '#B45309', // 铜色 '国防军工': '#EC4899',
'政策与主题': '#EAB308', // 金色 '政策与主题': '#14B8A6',
'周期与材料': '#F59E0B', // 琥珀色 '周期与材料': '#F97316',
'大消费': '#FACC15', // 亮金色 '大消费': '#A855F7',
'数字经济与金融科技': '#CA8A04', // 深金色 '数字经济与金融科技': '#22D3EE',
'全球宏观与贸易': '#D97706', // 橙金色 '全球宏观与贸易': '#84CC16',
'医药健康': '#FDE047', // 浅金色 '医药健康': '#E879F9',
'前沿科技': '#EAB308', // 金色 '前沿科技': '#38BDF8',
}; };
// 根据涨跌幅获取颜色(涨红跌绿 - 玻璃态半透明) // 根据涨跌幅获取颜色(涨红跌绿 - 玻璃态半透明)
const getChangeColor = (value, baseColor = '#A16207') => { const getChangeColor = (value, baseColor = '#64748B') => {
if (value === null || value === undefined) return baseColor; if (value === null || value === undefined) return baseColor;
// 涨 - 红色系(调整透明度使其更柔和) // 涨 - 红色系(调整透明度使其更柔和)
@@ -681,7 +681,7 @@ const ForceGraphView = ({
gapWidth: 4, gapWidth: 4,
borderRadius: 16, borderRadius: 16,
shadowBlur: 20, shadowBlur: 20,
shadowColor: 'rgba(234, 179, 8, 0.3)', shadowColor: 'rgba(139, 92, 246, 0.3)',
}, },
upperLabel: { upperLabel: {
show: true, show: true,
@@ -717,7 +717,7 @@ const ForceGraphView = ({
gapWidth: 3, gapWidth: 3,
borderRadius: 12, borderRadius: 12,
shadowBlur: 10, shadowBlur: 10,
shadowColor: 'rgba(234, 179, 8, 0.2)', shadowColor: 'rgba(139, 92, 246, 0.2)',
}, },
upperLabel: { upperLabel: {
show: true, show: true,
@@ -810,12 +810,12 @@ const ForceGraphView = ({
backgroundColor: 'transparent', backgroundColor: 'transparent',
tooltip: { tooltip: {
trigger: 'item', trigger: 'item',
backgroundColor: 'rgba(12, 10, 9, 0.95)', backgroundColor: 'rgba(15, 23, 42, 0.9)',
borderColor: 'rgba(234, 179, 8, 0.4)', borderColor: 'rgba(139, 92, 246, 0.4)',
borderWidth: 1, borderWidth: 1,
borderRadius: 16, borderRadius: 16,
padding: [14, 18], padding: [14, 18],
extraCssText: 'backdrop-filter: blur(20px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);', extraCssText: 'backdrop-filter: blur(20px); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);',
textStyle: { textStyle: {
color: '#E2E8F0', color: '#E2E8F0',
fontSize: 13, fontSize: 13,
@@ -871,11 +871,11 @@ const ForceGraphView = ({
${data.conceptCount ? `<span>📁 ${data.conceptCount} 个概念</span>` : ''} ${data.conceptCount ? `<span>📁 ${data.conceptCount} 个概念</span>` : ''}
</div> </div>
${data.level === 'concept' ? ` ${data.level === 'concept' ? `
<div style="color: #FACC15; font-size: 11px; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.1);"> <div style="color: #A78BFA; font-size: 11px; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.1);">
🔗 点击查看概念详情 🔗 点击查看概念详情
</div> </div>
` : data.level !== 'concept' ? ` ` : data.level !== 'concept' ? `
<div style="color: #A1A1AA; font-size: 11px; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.1);"> <div style="color: #64748B; font-size: 11px; margin-top: 10px; padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.1);">
📂 点击进入查看子分类 📂 点击进入查看子分类
</div> </div>
` : ''} ` : ''}
@@ -911,7 +911,7 @@ const ForceGraphView = ({
emphasis: { emphasis: {
itemStyle: { itemStyle: {
shadowBlur: 30, shadowBlur: 30,
shadowColor: 'rgba(234, 179, 8, 0.5)', shadowColor: 'rgba(139, 92, 246, 0.5)',
borderColor: 'rgba(255, 255, 255, 0.3)', borderColor: 'rgba(255, 255, 255, 0.3)',
}, },
}, },
@@ -1014,7 +1014,7 @@ const ForceGraphView = ({
return ( return (
<Center <Center
h="500px" h="500px"
bg="rgba(12, 10, 9, 0.6)" bg="rgba(15, 23, 42, 0.6)"
borderRadius="3xl" borderRadius="3xl"
backdropFilter="blur(20px)" backdropFilter="blur(20px)"
border="1px solid" border="1px solid"
@@ -1027,13 +1027,13 @@ const ForceGraphView = ({
<Box <Box
position="absolute" position="absolute"
inset={-4} inset={-4}
bg="yellow.500" bg="purple.500"
borderRadius="full" borderRadius="full"
filter="blur(20px)" filter="blur(20px)"
opacity={0.4} opacity={0.4}
animation={`${glowPulse} 2s ease-in-out infinite`} animation={`${glowPulse} 2s ease-in-out infinite`}
/> />
<Spinner size="xl" color="yellow.400" thickness="4px" /> <Spinner size="xl" color="purple.400" thickness="4px" />
</Box> </Box>
<Text color="gray.400" fontSize="sm">正在构建矩形树图...</Text> <Text color="gray.400" fontSize="sm">正在构建矩形树图...</Text>
</VStack> </VStack>
@@ -1045,7 +1045,7 @@ const ForceGraphView = ({
return ( return (
<Center <Center
h="500px" h="500px"
bg="rgba(12, 10, 9, 0.6)" bg="rgba(15, 23, 42, 0.6)"
borderRadius="3xl" borderRadius="3xl"
backdropFilter="blur(20px)" backdropFilter="blur(20px)"
border="1px solid" border="1px solid"
@@ -1055,7 +1055,7 @@ const ForceGraphView = ({
<Icon as={FaLayerGroup} boxSize={16} color="gray.600" /> <Icon as={FaLayerGroup} boxSize={16} color="gray.600" />
<Text color="gray.400">加载失败{error}</Text> <Text color="gray.400">加载失败{error}</Text>
<Button <Button
colorScheme="yellow" colorScheme="purple"
size="sm" size="sm"
onClick={fetchHierarchy} onClick={fetchHierarchy}
borderRadius="full" borderRadius="full"
@@ -1084,26 +1084,26 @@ const ForceGraphView = ({
h={containerHeight} h={containerHeight}
bg="transparent" bg="transparent"
> >
{/* 极光背景层 - 黑金主题 */} {/* 极光背景层 */}
<Box <Box
position="absolute" position="absolute"
top={0} top={0}
left={0} left={0}
right={0} right={0}
bottom={0} bottom={0}
bg="linear-gradient(135deg, #0C0A09 0%, #1C1917 25%, #292524 50%, #1C1917 75%, #0C0A09 100%)" bg="linear-gradient(135deg, #0F172A 0%, #1E1B4B 25%, #312E81 50%, #1E1B4B 75%, #0F172A 100%)"
backgroundSize="400% 400%" backgroundSize="400% 400%"
animation={`${auroraAnimation} 15s ease infinite`} animation={`${auroraAnimation} 15s ease infinite`}
/> />
{/* 弥散光晕层 - 黑金主题 */} {/* 弥散光晕层 */}
<Box <Box
position="absolute" position="absolute"
top="20%" top="20%"
left="10%" left="10%"
w="300px" w="300px"
h="300px" h="300px"
bg="radial-gradient(circle, rgba(234, 179, 8, 0.25) 0%, transparent 70%)" bg="radial-gradient(circle, rgba(139, 92, 246, 0.3) 0%, transparent 70%)"
filter="blur(60px)" filter="blur(60px)"
pointerEvents="none" pointerEvents="none"
animation={`${glowPulse} 4s ease-in-out infinite`} animation={`${glowPulse} 4s ease-in-out infinite`}
@@ -1114,7 +1114,7 @@ const ForceGraphView = ({
right="15%" right="15%"
w="250px" w="250px"
h="250px" h="250px"
bg="radial-gradient(circle, rgba(245, 158, 11, 0.2) 0%, transparent 70%)" bg="radial-gradient(circle, rgba(59, 130, 246, 0.25) 0%, transparent 70%)"
filter="blur(50px)" filter="blur(50px)"
pointerEvents="none" pointerEvents="none"
animation={`${glowPulse} 5s ease-in-out infinite 1s`} animation={`${glowPulse} 5s ease-in-out infinite 1s`}
@@ -1125,7 +1125,7 @@ const ForceGraphView = ({
right="30%" right="30%"
w="200px" w="200px"
h="200px" h="200px"
bg="radial-gradient(circle, rgba(217, 119, 6, 0.15) 0%, transparent 70%)" bg="radial-gradient(circle, rgba(236, 72, 153, 0.2) 0%, transparent 70%)"
filter="blur(40px)" filter="blur(40px)"
pointerEvents="none" pointerEvents="none"
animation={`${glowPulse} 6s ease-in-out infinite 2s`} animation={`${glowPulse} 6s ease-in-out infinite 2s`}
@@ -1159,8 +1159,8 @@ const ForceGraphView = ({
borderColor="whiteAlpha.200" borderColor="whiteAlpha.200"
borderRadius="full" borderRadius="full"
_hover={{ _hover={{
bg: 'rgba(234, 179, 8, 0.4)', bg: 'rgba(139, 92, 246, 0.4)',
borderColor: 'yellow.400', borderColor: 'purple.400',
transform: 'scale(1.05)', transform: 'scale(1.05)',
}} }}
transition="all 0.2s" transition="all 0.2s"
@@ -1179,7 +1179,7 @@ const ForceGraphView = ({
borderColor="whiteAlpha.150" borderColor="whiteAlpha.150"
boxShadow="0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1)" boxShadow="0 8px 32px rgba(0, 0, 0, 0.3), inset 0 1px 0 rgba(255, 255, 255, 0.1)"
> >
<Icon as={FaTh} color="yellow.400" /> <Icon as={FaTh} color="purple.300" />
<Text color="white" fontWeight="bold" fontSize="sm"> <Text color="white" fontWeight="bold" fontSize="sm">
概念矩形树图 概念矩形树图
</Text> </Text>
@@ -1202,7 +1202,7 @@ const ForceGraphView = ({
<Icon as={FaChevronRight} color="whiteAlpha.400" boxSize={3} /> <Icon as={FaChevronRight} color="whiteAlpha.400" boxSize={3} />
)} )}
<Text <Text
color={index === breadcrumbItems.length - 1 ? 'yellow.400' : 'whiteAlpha.700'} color={index === breadcrumbItems.length - 1 ? 'purple.300' : 'whiteAlpha.700'}
fontSize="sm" fontSize="sm"
fontWeight={index === breadcrumbItems.length - 1 ? 'bold' : 'normal'} fontWeight={index === breadcrumbItems.length - 1 ? 'bold' : 'normal'}
cursor={index < breadcrumbItems.length - 1 ? 'pointer' : 'default'} cursor={index < breadcrumbItems.length - 1 ? 'pointer' : 'default'}
@@ -1224,7 +1224,7 @@ const ForceGraphView = ({
{/* 右侧控制按钮 */} {/* 右侧控制按钮 */}
<HStack spacing={2} pointerEvents="auto"> <HStack spacing={2} pointerEvents="auto">
{priceLoading && <Spinner size="sm" color="yellow.400" />} {priceLoading && <Spinner size="sm" color="purple.300" />}
{drillPath && ( {drillPath && (
<Tooltip label="返回全部" placement="left"> <Tooltip label="返回全部" placement="left">
@@ -1239,8 +1239,8 @@ const ForceGraphView = ({
borderColor="whiteAlpha.200" borderColor="whiteAlpha.200"
borderRadius="full" borderRadius="full"
_hover={{ _hover={{
bg: 'rgba(234, 179, 8, 0.4)', bg: 'rgba(139, 92, 246, 0.4)',
borderColor: 'yellow.400', borderColor: 'purple.400',
transform: 'scale(1.05)', transform: 'scale(1.05)',
}} }}
transition="all 0.2s" transition="all 0.2s"

View File

@@ -342,6 +342,7 @@ const ConceptCenter = () => {
const [selectedConceptName, setSelectedConceptName] = useState(''); const [selectedConceptName, setSelectedConceptName] = useState('');
const [isTimelineModalOpen, setIsTimelineModalOpen] = useState(false); const [isTimelineModalOpen, setIsTimelineModalOpen] = useState(false);
const [selectedConceptId, setSelectedConceptId] = useState(''); const [selectedConceptId, setSelectedConceptId] = useState('');
const [selectedConceptStocksForTimeline, setSelectedConceptStocksForTimeline] = useState([]);
// 股票行情数据状态 // 股票行情数据状态
const [stockMarketData, setStockMarketData] = useState({}); const [stockMarketData, setStockMarketData] = useState({});
const [loadingStockData, setLoadingStockData] = useState(false); const [loadingStockData, setLoadingStockData] = useState(false);
@@ -367,7 +368,7 @@ const ConceptCenter = () => {
return null; return null;
}, []); }, []);
// 打开内容模态框(新闻和研报)- 需要Max版权限 // 打开内容模态框(新闻和研报)- 需要Max版权限
const handleViewContent = (e, conceptName, conceptId) => { const handleViewContent = (e, conceptName, conceptId, stocks = []) => {
e.stopPropagation(); e.stopPropagation();
// 检查历史时间轴权限 // 检查历史时间轴权限
@@ -383,6 +384,7 @@ const ConceptCenter = () => {
setSelectedConceptForContent(conceptName); setSelectedConceptForContent(conceptName);
setSelectedConceptId(conceptId); setSelectedConceptId(conceptId);
setSelectedConceptStocksForTimeline(stocks || []);
setIsTimelineModalOpen(true); setIsTimelineModalOpen(true);
}; };
@@ -1110,7 +1112,7 @@ const ConceptCenter = () => {
bg="purple.500" bg="purple.500"
color="white" color="white"
variant="solid" variant="solid"
onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id)} onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id, concept.stocks)}
borderRadius="full" borderRadius="full"
px={{ base: 2, md: 4 }} px={{ base: 2, md: 4 }}
fontWeight="medium" fontWeight="medium"
@@ -1296,7 +1298,7 @@ const ConceptCenter = () => {
leftIcon={<FaChartLine />} leftIcon={<FaChartLine />}
bg="purple.500" bg="purple.500"
color="white" color="white"
onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id)} onClick={(e) => handleViewContent(e, concept.concept, concept.concept_id, concept.stocks)}
borderRadius="full" borderRadius="full"
boxShadow="0 4px 12px rgba(139, 92, 246, 0.4)" boxShadow="0 4px 12px rgba(139, 92, 246, 0.4)"
_hover={{ bg: 'purple.400', boxShadow: '0 6px 16px rgba(139, 92, 246, 0.5)' }} _hover={{ bg: 'purple.400', boxShadow: '0 6px 16px rgba(139, 92, 246, 0.5)' }}
@@ -1769,12 +1771,12 @@ const ConceptCenter = () => {
setViewMode('force3d'); setViewMode('force3d');
} }
}} }}
bg={viewMode === 'force3d' ? 'yellow.500' : 'transparent'} bg={viewMode === 'force3d' ? 'purple.500' : 'transparent'}
color={viewMode === 'force3d' ? 'gray.900' : 'whiteAlpha.700'} color={viewMode === 'force3d' ? 'white' : 'whiteAlpha.700'}
borderColor="whiteAlpha.300" borderColor="whiteAlpha.300"
_hover={{ _hover={{
bg: viewMode === 'force3d' ? 'yellow.400' : 'whiteAlpha.100', bg: viewMode === 'force3d' ? 'purple.400' : 'whiteAlpha.100',
boxShadow: viewMode === 'force3d' ? '0 0 10px rgba(234, 179, 8, 0.4)' : 'none', boxShadow: viewMode === 'force3d' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
}} }}
aria-label="概念矩形树图" aria-label="概念矩形树图"
/> />
@@ -1788,12 +1790,12 @@ const ConceptCenter = () => {
setViewMode('hierarchy'); setViewMode('hierarchy');
} }
}} }}
bg={viewMode === 'hierarchy' ? 'yellow.500' : 'transparent'} bg={viewMode === 'hierarchy' ? 'purple.500' : 'transparent'}
color={viewMode === 'hierarchy' ? 'gray.900' : 'whiteAlpha.700'} color={viewMode === 'hierarchy' ? 'white' : 'whiteAlpha.700'}
borderColor="whiteAlpha.300" borderColor="whiteAlpha.300"
_hover={{ _hover={{
bg: viewMode === 'hierarchy' ? 'yellow.400' : 'whiteAlpha.100', bg: viewMode === 'hierarchy' ? 'purple.400' : 'whiteAlpha.100',
boxShadow: viewMode === 'hierarchy' ? '0 0 10px rgba(234, 179, 8, 0.4)' : 'none', boxShadow: viewMode === 'hierarchy' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
}} }}
aria-label="层级图" aria-label="层级图"
/> />
@@ -1807,12 +1809,12 @@ const ConceptCenter = () => {
setViewMode('list'); setViewMode('list');
} }
}} }}
bg={viewMode === 'list' ? 'yellow.500' : 'transparent'} bg={viewMode === 'list' ? 'purple.500' : 'transparent'}
color={viewMode === 'list' ? 'gray.900' : 'whiteAlpha.700'} color={viewMode === 'list' ? 'white' : 'whiteAlpha.700'}
borderColor="whiteAlpha.300" borderColor="whiteAlpha.300"
_hover={{ _hover={{
bg: viewMode === 'list' ? 'yellow.400' : 'whiteAlpha.100', bg: viewMode === 'list' ? 'purple.400' : 'whiteAlpha.100',
boxShadow: viewMode === 'list' ? '0 0 10px rgba(234, 179, 8, 0.4)' : 'none', boxShadow: viewMode === 'list' ? '0 0 10px rgba(139, 92, 246, 0.4)' : 'none',
}} }}
aria-label="列表视图" aria-label="列表视图"
/> />
@@ -2057,6 +2059,7 @@ const ConceptCenter = () => {
onClose={() => setIsTimelineModalOpen(false)} onClose={() => setIsTimelineModalOpen(false)}
conceptName={selectedConceptForContent} conceptName={selectedConceptForContent}
conceptId={selectedConceptId} conceptId={selectedConceptId}
stocks={selectedConceptStocksForTimeline}
/> />
{/* 订阅升级Modal */} {/* 订阅升级Modal */}