Initial commit

This commit is contained in:
2025-10-11 11:55:25 +08:00
parent 467dad8449
commit 8107dee8d3
2879 changed files with 610575 additions and 0 deletions

View File

@@ -0,0 +1,303 @@
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>
);
}