Files
vf_react/src/views/Dashboard/components/EventDetailCard.tsx

146 lines
4.0 KiB
TypeScript

/**
* EventDetailCard - 事件详情卡片组件
* 用于日历视图中展示单个事件的详细信息
*/
import React, { useState, useRef, useEffect } from 'react';
import {
Box,
Badge,
Flex,
HStack,
Text,
Tag,
TagLabel,
TagLeftIcon,
Button,
useColorModeValue,
} from '@chakra-ui/react';
import {
FiTrendingUp,
FiChevronDown,
FiChevronUp,
} from 'react-icons/fi';
import type { InvestmentEvent } from '@/types';
/**
* EventDetailCard Props
*/
export interface EventDetailCardProps {
/** 事件数据 */
event: InvestmentEvent;
/** 边框颜色 */
borderColor?: string;
/** 次要文字颜色 */
secondaryText?: string;
}
/**
* 最大显示行数
*/
const MAX_LINES = 3;
/**
* EventDetailCard 组件
*/
export const EventDetailCard: React.FC<EventDetailCardProps> = ({
event,
borderColor: borderColorProp,
secondaryText: secondaryTextProp,
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const [isOverflow, setIsOverflow] = useState(false);
const descriptionRef = useRef<HTMLParagraphElement>(null);
// 默认颜色
const defaultBorderColor = useColorModeValue('gray.200', 'gray.600');
const defaultSecondaryText = useColorModeValue('gray.600', 'gray.400');
const borderColor = borderColorProp || defaultBorderColor;
const secondaryText = secondaryTextProp || defaultSecondaryText;
// 检测内容是否溢出
useEffect(() => {
const el = descriptionRef.current;
if (el) {
// 计算行高和最大高度
const lineHeight = parseInt(getComputedStyle(el).lineHeight) || 20;
const maxHeight = lineHeight * MAX_LINES;
setIsOverflow(el.scrollHeight > maxHeight + 5); // 5px 容差
}
}, [event.description]);
// 获取事件类型标签
const getEventBadge = () => {
if (event.source === 'future') {
return <Badge colorScheme="blue" variant="subtle"></Badge>;
} else if (event.type === 'plan') {
return <Badge colorScheme="purple" variant="subtle"></Badge>;
} else if (event.type === 'review') {
return <Badge colorScheme="green" variant="subtle"></Badge>;
}
return null;
};
return (
<Box
p={{ base: 3, md: 4 }}
borderRadius="md"
border="1px"
borderColor={borderColor}
>
{/* 标题和标签 */}
<Flex justify="space-between" align="start" mb={{ base: 1, md: 2 }} gap={{ base: 1, md: 2 }}>
<HStack flexWrap="wrap" flex={1} spacing={{ base: 1, md: 2 }} gap={1}>
<Text fontWeight="bold" fontSize={{ base: 'md', md: 'lg' }}>
{event.title}
</Text>
{getEventBadge()}
</HStack>
</Flex>
{/* 描述内容 - 支持展开/收起 */}
{event.description && (
<Box mb={{ base: 1, md: 2 }}>
<Text
ref={descriptionRef}
fontSize={{ base: 'xs', md: 'sm' }}
color={secondaryText}
noOfLines={isExpanded ? undefined : MAX_LINES}
whiteSpace="pre-wrap"
>
{event.description}
</Text>
{isOverflow && (
<Button
size="xs"
variant="link"
colorScheme="blue"
mt={1}
onClick={() => setIsExpanded(!isExpanded)}
rightIcon={isExpanded ? <FiChevronUp /> : <FiChevronDown />}
>
{isExpanded ? '收起' : '展开'}
</Button>
)}
</Box>
)}
{/* 相关股票 */}
{event.stocks && event.stocks.length > 0 && (
<HStack spacing={{ base: 1, md: 2 }} flexWrap="wrap" gap={1}>
<Text fontSize={{ base: 'xs', md: 'sm' }} color={secondaryText}>:</Text>
{event.stocks.map((stock, i) => (
<Tag key={i} size="sm" colorScheme="blue" mb={1}>
<TagLeftIcon as={FiTrendingUp} />
<TagLabel>{stock}</TagLabel>
</Tag>
))}
</HStack>
)}
</Box>
);
};
export default EventDetailCard;