304 lines
8.5 KiB
JavaScript
304 lines
8.5 KiB
JavaScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import {
|
|
Box,
|
|
VStack,
|
|
HStack,
|
|
Text,
|
|
Badge,
|
|
Button,
|
|
Icon,
|
|
Center,
|
|
Spinner,
|
|
LinkBox,
|
|
LinkOverlay,
|
|
Tooltip,
|
|
Tag,
|
|
TagLabel,
|
|
Wrap,
|
|
WrapItem,
|
|
useColorModeValue,
|
|
useToast,
|
|
} from '@chakra-ui/react';
|
|
import { Link } from 'react-router-dom';
|
|
import {
|
|
FiCalendar,
|
|
FiClock,
|
|
FiStar,
|
|
FiTrendingUp,
|
|
FiAlertCircle,
|
|
} from 'react-icons/fi';
|
|
import { eventService } from '../../../services/eventService';
|
|
import moment from 'moment';
|
|
|
|
export default function MyFutureEvents({ limit = 5 }) {
|
|
const [futureEvents, setFutureEvents] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const toast = useToast();
|
|
|
|
// 颜色主题
|
|
const borderColor = useColorModeValue('gray.200', 'gray.600');
|
|
const hoverBg = useColorModeValue('gray.50', 'gray.700');
|
|
const secondaryText = useColorModeValue('gray.600', 'gray.400');
|
|
const importanceBg = useColorModeValue('yellow.50', 'yellow.900');
|
|
const importanceColor = useColorModeValue('yellow.600', 'yellow.300');
|
|
|
|
// 加载关注的未来事件
|
|
const loadFutureEvents = useCallback(async () => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await eventService.calendar.getFollowingEvents();
|
|
if (response.success) {
|
|
// 按时间排序,最近的在前
|
|
const sortedEvents = (response.data || []).sort((a, b) =>
|
|
moment(a.calendar_time).valueOf() - moment(b.calendar_time).valueOf()
|
|
);
|
|
setFutureEvents(sortedEvents);
|
|
}
|
|
} catch (error) {
|
|
console.error('加载未来事件失败:', error);
|
|
toast({
|
|
title: '加载失败',
|
|
description: '无法加载关注的未来事件',
|
|
status: 'error',
|
|
duration: 3000,
|
|
isClosable: true,
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
}, [toast]);
|
|
|
|
useEffect(() => {
|
|
loadFutureEvents();
|
|
}, [loadFutureEvents]);
|
|
|
|
// 取消关注
|
|
const handleUnfollow = async (eventId) => {
|
|
try {
|
|
const response = await eventService.calendar.toggleFollow(eventId);
|
|
if (response.success) {
|
|
setFutureEvents(prev => prev.filter(event => event.id !== eventId));
|
|
toast({
|
|
title: '取消关注成功',
|
|
status: 'success',
|
|
duration: 2000,
|
|
isClosable: true,
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('取消关注失败:', error);
|
|
toast({
|
|
title: '操作失败',
|
|
description: '取消关注失败,请重试',
|
|
status: 'error',
|
|
duration: 3000,
|
|
isClosable: true,
|
|
});
|
|
}
|
|
};
|
|
|
|
// 格式化时间
|
|
const formatEventTime = (time) => {
|
|
const eventTime = moment(time);
|
|
const now = moment();
|
|
const daysDiff = eventTime.diff(now, 'days');
|
|
|
|
if (daysDiff === 0) {
|
|
return {
|
|
date: '今天',
|
|
time: eventTime.format('HH:mm'),
|
|
color: 'red',
|
|
urgent: true
|
|
};
|
|
} else if (daysDiff === 1) {
|
|
return {
|
|
date: '明天',
|
|
time: eventTime.format('HH:mm'),
|
|
color: 'orange',
|
|
urgent: true
|
|
};
|
|
} else if (daysDiff <= 7) {
|
|
return {
|
|
date: `${daysDiff}天后`,
|
|
time: eventTime.format('MM/DD HH:mm'),
|
|
color: 'yellow',
|
|
urgent: false
|
|
};
|
|
} else {
|
|
return {
|
|
date: eventTime.format('MM月DD日'),
|
|
time: eventTime.format('HH:mm'),
|
|
color: 'gray',
|
|
urgent: false
|
|
};
|
|
}
|
|
};
|
|
|
|
// 获取重要性颜色
|
|
const getImportanceColor = (star) => {
|
|
if (star >= 5) return 'red';
|
|
if (star >= 4) return 'orange';
|
|
if (star >= 3) return 'yellow';
|
|
return 'blue';
|
|
};
|
|
|
|
if (loading) {
|
|
return (
|
|
<Center py={4}>
|
|
<Spinner size="sm" />
|
|
</Center>
|
|
);
|
|
}
|
|
|
|
if (futureEvents.length === 0) {
|
|
return (
|
|
<Center py={8}>
|
|
<VStack spacing={3}>
|
|
<Icon as={FiCalendar} boxSize={12} color="gray.300" />
|
|
<Text color={secondaryText} fontSize="sm">
|
|
暂无关注的未来事件
|
|
</Text>
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
colorScheme="blue"
|
|
as={Link}
|
|
to="/community"
|
|
>
|
|
探索投资日历
|
|
</Button>
|
|
</VStack>
|
|
</Center>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<VStack align="stretch" spacing={3}>
|
|
{futureEvents.slice(0, limit).map((event) => {
|
|
const timeInfo = formatEventTime(event.calendar_time);
|
|
|
|
return (
|
|
<LinkBox
|
|
key={event.id}
|
|
p={4}
|
|
borderRadius="lg"
|
|
border="1px"
|
|
borderColor={borderColor}
|
|
bg={timeInfo.urgent ? importanceBg : 'transparent'}
|
|
_hover={{ shadow: 'md', transform: 'translateY(-2px)' }}
|
|
transition="all 0.2s"
|
|
position="relative"
|
|
>
|
|
{/* 紧急标记 */}
|
|
{timeInfo.urgent && (
|
|
<Box
|
|
position="absolute"
|
|
top={2}
|
|
right={2}
|
|
>
|
|
<Badge colorScheme={timeInfo.color} variant="solid" fontSize="xs">
|
|
即将发生
|
|
</Badge>
|
|
</Box>
|
|
)}
|
|
|
|
<VStack align="stretch" spacing={3}>
|
|
{/* 标题 */}
|
|
<LinkOverlay as={Link} to="/community">
|
|
<Text fontWeight="medium" fontSize="md" noOfLines={2}>
|
|
{event.title}
|
|
</Text>
|
|
</LinkOverlay>
|
|
|
|
{/* 时间和重要性 */}
|
|
<HStack justify="space-between">
|
|
<HStack spacing={3}>
|
|
<Badge colorScheme={timeInfo.color} variant="subtle">
|
|
<HStack spacing={1}>
|
|
<Icon as={FiClock} boxSize={3} />
|
|
<Text>{timeInfo.date}</Text>
|
|
</HStack>
|
|
</Badge>
|
|
<Text fontSize="sm" color={secondaryText}>
|
|
{timeInfo.time}
|
|
</Text>
|
|
</HStack>
|
|
|
|
{/* 重要性星级 */}
|
|
<HStack spacing={0}>
|
|
{[...Array(5)].map((_, i) => (
|
|
<Icon
|
|
key={i}
|
|
as={FiStar}
|
|
boxSize={3}
|
|
color={i < event.star ? importanceColor : 'gray.300'}
|
|
fill={i < event.star ? 'currentColor' : 'none'}
|
|
/>
|
|
))}
|
|
</HStack>
|
|
</HStack>
|
|
|
|
{/* 标签和相关信息 */}
|
|
<HStack justify="space-between" flexWrap="wrap">
|
|
<HStack spacing={2}>
|
|
{event.type && (
|
|
<Tag size="sm" variant="subtle" colorScheme={event.type === 'event' ? 'blue' : 'green'}>
|
|
<TagLabel>{event.type === 'event' ? '事件' : '数据'}</TagLabel>
|
|
</Tag>
|
|
)}
|
|
{event.related_stocks && event.related_stocks.length > 0 && (
|
|
<Tag size="sm" variant="subtle" colorScheme="purple">
|
|
<Icon as={FiTrendingUp} boxSize={3} mr={1} />
|
|
<TagLabel>{event.related_stocks.length}只相关股票</TagLabel>
|
|
</Tag>
|
|
)}
|
|
</HStack>
|
|
|
|
{/* 操作按钮 */}
|
|
<Button
|
|
size="xs"
|
|
variant="ghost"
|
|
colorScheme="red"
|
|
onClick={(e) => {
|
|
e.preventDefault();
|
|
handleUnfollow(event.id);
|
|
}}
|
|
>
|
|
取消关注
|
|
</Button>
|
|
</HStack>
|
|
|
|
{/* 预测信息 */}
|
|
{event.forecast && (
|
|
<Box>
|
|
<HStack spacing={1} mb={1}>
|
|
<Icon as={FiAlertCircle} boxSize={3} color={secondaryText} />
|
|
<Text fontSize="xs" color={secondaryText}>
|
|
预测
|
|
</Text>
|
|
</HStack>
|
|
<Text fontSize="sm" color={secondaryText} noOfLines={2}>
|
|
{event.forecast}
|
|
</Text>
|
|
</Box>
|
|
)}
|
|
</VStack>
|
|
</LinkBox>
|
|
);
|
|
})}
|
|
|
|
{/* 查看更多 */}
|
|
{futureEvents.length > limit && (
|
|
<Button
|
|
size="sm"
|
|
variant="ghost"
|
|
as={Link}
|
|
to="/community"
|
|
>
|
|
查看全部 ({futureEvents.length})
|
|
</Button>
|
|
)}
|
|
</VStack>
|
|
);
|
|
}
|