Initial commit
This commit is contained in:
303
src/views/Dashboard/components/MyFutureEvents.js
Normal file
303
src/views/Dashboard/components/MyFutureEvents.js
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user