- 迁移 klineDataCache.js 到 src/utils/stock/(被 StockChart 使用) - 迁移 InvestmentCalendar 到 src/components/InvestmentCalendar/(被 Navbar、Dashboard 使用) - 迁移 DynamicNewsDetail 到 src/components/EventDetailPanel/(被 EventDetail 使用) - 更新所有相关导入路径,使用路径别名 - 保持 Community 目录其余结构不变 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
192 lines
6.3 KiB
JavaScript
192 lines
6.3 KiB
JavaScript
// src/components/EventDetailPanel/RelatedConceptsSection/DetailedConceptCard.js
|
||
// 详细概念卡片组件
|
||
|
||
import React from 'react';
|
||
import {
|
||
Box,
|
||
HStack,
|
||
VStack,
|
||
Text,
|
||
Badge,
|
||
Card,
|
||
CardBody,
|
||
Divider,
|
||
SimpleGrid,
|
||
useColorModeValue,
|
||
} from '@chakra-ui/react';
|
||
import ConceptStockItem from './ConceptStockItem';
|
||
|
||
/**
|
||
* 详细概念卡片组件
|
||
* @param {Object} props
|
||
* @param {Object} props.concept - 概念对象(兼容 v1/v2 API)
|
||
* - concept: 概念名称
|
||
* - stock_count: 相关股票数量
|
||
* - score: 相关度(0-1)
|
||
* - price_info.avg_change_pct: 平均涨跌幅
|
||
* - description: 概念描述
|
||
* - outbreak_dates / happened_times: 爆发日期数组
|
||
* - stocks: 相关股票数组 [{ name/stock_name, code/stock_code }]
|
||
* @param {Function} props.onClick - 点击回调
|
||
*/
|
||
const DetailedConceptCard = ({ concept, onClick }) => {
|
||
const cardBg = useColorModeValue('white', 'gray.700');
|
||
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
||
const headingColor = useColorModeValue('gray.700', 'gray.200');
|
||
const stockCountColor = useColorModeValue('gray.500', 'gray.400');
|
||
|
||
// 计算相关度百分比
|
||
const relevanceScore = Math.round((concept.score || 0) * 100);
|
||
|
||
// 计算涨跌幅颜色
|
||
const changePct = parseFloat(concept.price_info?.avg_change_pct);
|
||
const changeColor = changePct > 0 ? 'red' : changePct < 0 ? 'green' : 'gray';
|
||
const changeSymbol = changePct > 0 ? '+' : '';
|
||
|
||
return (
|
||
<Card
|
||
bg={cardBg}
|
||
borderColor={borderColor}
|
||
borderWidth="2px"
|
||
cursor="pointer"
|
||
transition="all 0.3s"
|
||
_hover={{
|
||
transform: 'translateY(-2px)',
|
||
boxShadow: 'xl',
|
||
borderColor: 'blue.400'
|
||
}}
|
||
onClick={() => onClick(concept)}
|
||
>
|
||
<CardBody p={4}>
|
||
<VStack spacing={3} align="stretch">
|
||
{/* 头部信息 */}
|
||
<HStack justify="space-between" align="flex-start">
|
||
{/* 左侧:概念名称 + Badge */}
|
||
<VStack align="start" spacing={2} flex={1}>
|
||
<Text fontSize="md" fontWeight="bold" color="blue.600">
|
||
{concept.concept}
|
||
</Text>
|
||
<HStack spacing={2} flexWrap="wrap">
|
||
<Badge colorScheme="purple" fontSize="xs">
|
||
相关度: {relevanceScore}%
|
||
</Badge>
|
||
<Badge colorScheme="orange" fontSize="xs">
|
||
{concept.stock_count} 只股票
|
||
</Badge>
|
||
</HStack>
|
||
</VStack>
|
||
|
||
{/* 右侧:涨跌幅 */}
|
||
{concept.price_info?.avg_change_pct && (
|
||
<Box textAlign="right">
|
||
<Text fontSize="xs" color={stockCountColor} mb={1}>
|
||
平均涨跌幅
|
||
</Text>
|
||
<Badge
|
||
size="lg"
|
||
colorScheme={changeColor}
|
||
fontSize="md"
|
||
px={3}
|
||
py={1}
|
||
>
|
||
{changeSymbol}{changePct.toFixed(2)}%
|
||
</Badge>
|
||
</Box>
|
||
)}
|
||
</HStack>
|
||
|
||
<Divider />
|
||
|
||
{/* 概念描述 */}
|
||
{concept.description && (
|
||
<Text
|
||
fontSize="sm"
|
||
color={stockCountColor}
|
||
lineHeight="1.6"
|
||
noOfLines={3}
|
||
>
|
||
{concept.description}
|
||
</Text>
|
||
)}
|
||
|
||
{/* 爆发日期(兼容 happened_times 和 outbreak_dates) */}
|
||
{((concept.outbreak_dates && concept.outbreak_dates.length > 0) ||
|
||
(concept.happened_times && concept.happened_times.length > 0)) && (
|
||
<Box>
|
||
<Text fontSize="xs" fontWeight="semibold" mb={2} color={stockCountColor}>
|
||
爆发日期:
|
||
</Text>
|
||
<HStack spacing={2} flexWrap="wrap">
|
||
{(concept.outbreak_dates || concept.happened_times).map((date, idx) => (
|
||
<Badge key={idx} variant="subtle" colorScheme="orange" fontSize="xs">
|
||
{date}
|
||
</Badge>
|
||
))}
|
||
</HStack>
|
||
</Box>
|
||
)}
|
||
|
||
{/* 核心相关股票 */}
|
||
{concept.stocks && concept.stocks.length > 0 && (
|
||
<Box>
|
||
<HStack justify="space-between" mb={2}>
|
||
<Text fontSize="sm" fontWeight="semibold" color={headingColor}>
|
||
核心相关股票
|
||
</Text>
|
||
<Text fontSize="xs" color={stockCountColor}>
|
||
共 {concept.stock_count} 只
|
||
</Text>
|
||
</HStack>
|
||
|
||
{/* 可滚动容器 - 默认显示4条,可滚动查看全部 */}
|
||
<Box
|
||
maxH="300px"
|
||
overflowY="auto"
|
||
pr={2}
|
||
onWheel={(e) => {
|
||
const element = e.currentTarget;
|
||
const scrollTop = element.scrollTop;
|
||
const scrollHeight = element.scrollHeight;
|
||
const clientHeight = element.clientHeight;
|
||
|
||
// 如果在滚动范围内,阻止事件冒泡到父容器
|
||
if (
|
||
(e.deltaY < 0 && scrollTop > 0) || // 向上滚动且未到顶部
|
||
(e.deltaY > 0 && scrollTop + clientHeight < scrollHeight) // 向下滚动且未到底部
|
||
) {
|
||
e.stopPropagation();
|
||
}
|
||
}}
|
||
css={{
|
||
overscrollBehavior: 'contain', // 防止滚动链
|
||
'&::-webkit-scrollbar': {
|
||
width: '6px',
|
||
},
|
||
'&::-webkit-scrollbar-track': {
|
||
background: '#f1f1f1',
|
||
},
|
||
'&::-webkit-scrollbar-thumb': {
|
||
background: '#888',
|
||
borderRadius: '3px',
|
||
},
|
||
'&::-webkit-scrollbar-thumb:hover': {
|
||
background: '#555',
|
||
},
|
||
}}
|
||
>
|
||
<SimpleGrid columns={{ base: 1 }} spacing={2}>
|
||
{concept.stocks.map((stock, idx) => (
|
||
<ConceptStockItem key={idx} stock={stock} />
|
||
))}
|
||
</SimpleGrid>
|
||
</Box>
|
||
</Box>
|
||
)}
|
||
</VStack>
|
||
</CardBody>
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
export default DetailedConceptCard;
|