feat: 重构主组件
│ │ │ │ - ❌ 移除 renderPriceChange 函数(60行) │ │ │ │ - ❌ 移除 renderCompactEvent 函数(200行) │ │ │ │ - ❌ 移除 renderDetailedEvent 函数(300行) │ │ │ │ - ❌ 移除 expandedDescriptions state │ │ │ │ - ❌ 精简 Chakra UI 导入 │ │ │ │ - ✅ 使用 EventCard 组件统一渲染 │ │ │ │ - ✅ 保留所有业务逻辑(WebSocket、通知、关注)
This commit is contained in:
@@ -7,68 +7,28 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
Button,
|
Button,
|
||||||
Badge,
|
Badge,
|
||||||
Tag,
|
|
||||||
TagLabel,
|
|
||||||
TagLeftIcon,
|
|
||||||
Flex,
|
Flex,
|
||||||
Avatar,
|
|
||||||
Tooltip,
|
|
||||||
IconButton,
|
|
||||||
Divider,
|
|
||||||
Container,
|
Container,
|
||||||
useColorModeValue,
|
useColorModeValue,
|
||||||
Circle,
|
|
||||||
Stat,
|
|
||||||
StatNumber,
|
|
||||||
StatHelpText,
|
|
||||||
StatArrow,
|
|
||||||
ButtonGroup,
|
|
||||||
Heading,
|
|
||||||
SimpleGrid,
|
|
||||||
Card,
|
|
||||||
CardBody,
|
|
||||||
Center,
|
|
||||||
Link,
|
|
||||||
Spacer,
|
|
||||||
Switch,
|
Switch,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
useToast,
|
useToast,
|
||||||
|
Center,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import {
|
import { InfoIcon } from '@chakra-ui/icons';
|
||||||
ViewIcon,
|
|
||||||
ChatIcon,
|
|
||||||
StarIcon,
|
|
||||||
TimeIcon,
|
|
||||||
InfoIcon,
|
|
||||||
WarningIcon,
|
|
||||||
WarningTwoIcon,
|
|
||||||
CheckCircleIcon,
|
|
||||||
ArrowForwardIcon,
|
|
||||||
ExternalLinkIcon,
|
|
||||||
ViewOffIcon,
|
|
||||||
} from '@chakra-ui/icons';
|
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import moment from 'moment';
|
|
||||||
|
|
||||||
// 导入工具函数和常量
|
// 导入工具函数和常量
|
||||||
import { logger } from '../../../utils/logger';
|
import { logger } from '../../../utils/logger';
|
||||||
import { getApiBase } from '../../../utils/apiConfig';
|
import { getApiBase } from '../../../utils/apiConfig';
|
||||||
import { useEventNotifications } from '../../../hooks/useEventNotifications';
|
import { useEventNotifications } from '../../../hooks/useEventNotifications';
|
||||||
import { getImportanceConfig, getAllImportanceLevels } from '../../../constants/importanceLevels';
|
|
||||||
import { browserNotificationService } from '../../../services/browserNotificationService';
|
import { browserNotificationService } from '../../../services/browserNotificationService';
|
||||||
import { useNotification } from '../../../contexts/NotificationContext';
|
import { useNotification } from '../../../contexts/NotificationContext';
|
||||||
|
import { getImportanceConfig } from '../../../constants/importanceLevels';
|
||||||
|
|
||||||
// 导入价格相关工具函数
|
// 导入子组件
|
||||||
import {
|
import EventCard from './EventCard';
|
||||||
getPriceChangeColor,
|
|
||||||
getPriceChangeBg,
|
|
||||||
getPriceChangeBorderColor,
|
|
||||||
PriceArrow,
|
|
||||||
} from '../../../utils/priceFormatters';
|
|
||||||
|
|
||||||
// 导入动画定义
|
|
||||||
import { pulseAnimation } from '../../../constants/animations';
|
|
||||||
|
|
||||||
// ========== 主组件 ==========
|
// ========== 主组件 ==========
|
||||||
const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetail }) => {
|
const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetail }) => {
|
||||||
@@ -78,7 +38,6 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
const [followingMap, setFollowingMap] = useState({});
|
const [followingMap, setFollowingMap] = useState({});
|
||||||
const [followCountMap, setFollowCountMap] = useState({});
|
const [followCountMap, setFollowCountMap] = useState({});
|
||||||
const [localEvents, setLocalEvents] = useState(events); // 用于实时更新的本地事件列表
|
const [localEvents, setLocalEvents] = useState(events); // 用于实时更新的本地事件列表
|
||||||
const [expandedDescriptions, setExpandedDescriptions] = useState({}); // 描述展开状态映射
|
|
||||||
|
|
||||||
// 从 NotificationContext 获取推送权限相关状态和方法
|
// 从 NotificationContext 获取推送权限相关状态和方法
|
||||||
const { browserPermission, requestBrowserPermission } = useNotification();
|
const { browserPermission, requestBrowserPermission } = useNotification();
|
||||||
@@ -265,67 +224,6 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
const linkColor = useColorModeValue('blue.600', 'blue.400');
|
const linkColor = useColorModeValue('blue.600', 'blue.400');
|
||||||
const hoverBg = useColorModeValue('gray.50', 'gray.700');
|
const hoverBg = useColorModeValue('gray.50', 'gray.700');
|
||||||
|
|
||||||
const renderPriceChange = (value, label) => {
|
|
||||||
if (value === null || value === undefined) {
|
|
||||||
return (
|
|
||||||
<Tag size="lg" colorScheme="gray" borderRadius="full" variant="subtle">
|
|
||||||
<TagLabel fontSize="sm" fontWeight="medium">{label}: --</TagLabel>
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const absValue = Math.abs(value);
|
|
||||||
const isPositive = value > 0;
|
|
||||||
|
|
||||||
// 根据涨跌幅大小选择不同的颜色深浅
|
|
||||||
let colorScheme = 'gray';
|
|
||||||
let variant = 'solid';
|
|
||||||
|
|
||||||
if (isPositive) {
|
|
||||||
// 上涨用红色系
|
|
||||||
if (absValue >= 3) {
|
|
||||||
colorScheme = 'red';
|
|
||||||
variant = 'solid'; // 深色
|
|
||||||
} else if (absValue >= 1) {
|
|
||||||
colorScheme = 'red';
|
|
||||||
variant = 'subtle'; // 中等
|
|
||||||
} else {
|
|
||||||
colorScheme = 'red';
|
|
||||||
variant = 'outline'; // 浅色
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 下跌用绿色系
|
|
||||||
if (absValue >= 3) {
|
|
||||||
colorScheme = 'green';
|
|
||||||
variant = 'solid'; // 深色
|
|
||||||
} else if (absValue >= 1) {
|
|
||||||
colorScheme = 'green';
|
|
||||||
variant = 'subtle'; // 中等
|
|
||||||
} else {
|
|
||||||
colorScheme = 'green';
|
|
||||||
variant = 'outline'; // 浅色
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const Icon = isPositive ? TriangleUpIcon : TriangleDownIcon;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Tag
|
|
||||||
size="lg"
|
|
||||||
colorScheme={colorScheme}
|
|
||||||
borderRadius="full"
|
|
||||||
variant={variant}
|
|
||||||
boxShadow="sm"
|
|
||||||
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<TagLeftIcon as={Icon} boxSize="16px" />
|
|
||||||
<TagLabel fontSize="sm" fontWeight="bold">
|
|
||||||
{label}: {isPositive ? '+' : ''}{value.toFixed(2)}%
|
|
||||||
</TagLabel>
|
|
||||||
</Tag>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleTitleClick = (e, event) => {
|
const handleTitleClick = (e, event) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -349,511 +247,6 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// 精简模式的事件渲染(优化版:标题2行+标签内联+按钮右侧)
|
|
||||||
const renderCompactEvent = (event, index) => {
|
|
||||||
const importance = getImportanceConfig(event.importance);
|
|
||||||
const isFollowing = !!followingMap[event.id];
|
|
||||||
const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
|
|
||||||
const timelineStyle = getTimelineBoxStyle();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HStack align="stretch" spacing={3} w="full">
|
|
||||||
{/* 左侧时间轴 - 动态样式 */}
|
|
||||||
<VStack spacing={0} align="center" minW="90px">
|
|
||||||
{/* 时间长方形卡片 */}
|
|
||||||
<Box
|
|
||||||
{...(timelineStyle.bgGradient ? { bgGradient: timelineStyle.bgGradient } : { bg: timelineStyle.bg })}
|
|
||||||
borderWidth={timelineStyle.borderWidth}
|
|
||||||
borderColor={timelineStyle.borderColor}
|
|
||||||
borderRadius="md"
|
|
||||||
px={2}
|
|
||||||
py={2}
|
|
||||||
minW="85px"
|
|
||||||
textAlign="center"
|
|
||||||
boxShadow={timelineStyle.boxShadow}
|
|
||||||
transition="all 0.3s ease"
|
|
||||||
>
|
|
||||||
{/* 日期 YYYY-MM-DD */}
|
|
||||||
<Text
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={timelineStyle.textColor}
|
|
||||||
lineHeight="1.3"
|
|
||||||
>
|
|
||||||
{moment(event.created_at).format('YYYY-MM-DD')}
|
|
||||||
</Text>
|
|
||||||
{/* 时间 HH:mm */}
|
|
||||||
<Text
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={timelineStyle.textColor}
|
|
||||||
lineHeight="1.3"
|
|
||||||
mt={0.5}
|
|
||||||
>
|
|
||||||
{moment(event.created_at).format('HH:mm')}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
{/* 时间轴竖线 */}
|
|
||||||
<Box
|
|
||||||
w="2px"
|
|
||||||
flex="1"
|
|
||||||
bg={borderColor}
|
|
||||||
minH="40px"
|
|
||||||
mt={1}
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
|
|
||||||
{/* 右侧内容卡片 */}
|
|
||||||
<Card
|
|
||||||
flex="1"
|
|
||||||
bg={index % 2 === 0 ? cardBg : useColorModeValue('gray.50', 'gray.750')}
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor={borderColor}
|
|
||||||
borderRadius="md"
|
|
||||||
boxShadow="sm"
|
|
||||||
_hover={{
|
|
||||||
boxShadow: 'lg',
|
|
||||||
transform: 'translateY(-2px)',
|
|
||||||
borderColor: importance.color,
|
|
||||||
}}
|
|
||||||
transition="all 0.3s ease"
|
|
||||||
cursor="pointer"
|
|
||||||
onClick={() => onEventClick(event)}
|
|
||||||
mb={2}
|
|
||||||
>
|
|
||||||
<CardBody p={3}>
|
|
||||||
<VStack align="stretch" spacing={2}>
|
|
||||||
{/* 第一行:标题(2行)+ 标签(内联)+ 按钮(右侧) */}
|
|
||||||
<Flex align="flex-start" gap={2}>
|
|
||||||
{/* 标题区域:标题+标签(内联) */}
|
|
||||||
<Box flex="1" minW="150px">
|
|
||||||
<Text
|
|
||||||
fontSize="md"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={linkColor}
|
|
||||||
lineHeight="1.4"
|
|
||||||
noOfLines={2}
|
|
||||||
display="inline"
|
|
||||||
_hover={{ textDecoration: 'underline', color: 'blue.500' }}
|
|
||||||
onClick={(e) => handleTitleClick(e, event)}
|
|
||||||
cursor="pointer"
|
|
||||||
>
|
|
||||||
{event.title}
|
|
||||||
</Text>
|
|
||||||
{' '}
|
|
||||||
{/* 重要性标签 - 内联 */}
|
|
||||||
<Badge
|
|
||||||
colorScheme={importance.color.split('.')[0]}
|
|
||||||
fontSize="xs"
|
|
||||||
px={2}
|
|
||||||
py={1}
|
|
||||||
borderRadius="md"
|
|
||||||
fontWeight="bold"
|
|
||||||
display="inline-flex"
|
|
||||||
alignItems="center"
|
|
||||||
verticalAlign="middle"
|
|
||||||
>
|
|
||||||
{event.importance || 'C'}级
|
|
||||||
</Badge>
|
|
||||||
{' '}
|
|
||||||
{/* 涨跌幅标签 - 内联 */}
|
|
||||||
{event.related_avg_chg != null && (
|
|
||||||
<Tooltip label="平均" placement="top">
|
|
||||||
<Badge
|
|
||||||
colorScheme={event.related_avg_chg > 0 ? 'red' : 'green'}
|
|
||||||
fontSize="xs"
|
|
||||||
px={2}
|
|
||||||
py={1}
|
|
||||||
borderRadius="md"
|
|
||||||
fontWeight="bold"
|
|
||||||
display="inline-flex"
|
|
||||||
alignItems="center"
|
|
||||||
gap={1}
|
|
||||||
verticalAlign="middle"
|
|
||||||
>
|
|
||||||
<PriceArrow value={event.related_avg_chg} />
|
|
||||||
{event.related_avg_chg > 0 ? '+' : ''}{event.related_avg_chg.toFixed(2)}%
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 操作按钮 - 固定右侧 */}
|
|
||||||
<HStack spacing={2} flexShrink={0}>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant="ghost"
|
|
||||||
colorScheme="blue"
|
|
||||||
onClick={(e) => handleViewDetailClick(e, event.id)}
|
|
||||||
>
|
|
||||||
详情
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
size="xs"
|
|
||||||
variant={isFollowing ? 'solid' : 'outline'}
|
|
||||||
colorScheme="yellow"
|
|
||||||
leftIcon={<StarIcon boxSize="10px" />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
toggleFollow(event.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isFollowing ? '已关注' : '关注'}
|
|
||||||
{followerCount > 0 && `(${followerCount})`}
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* 第二行:统计数据(左) + 作者时间(右) */}
|
|
||||||
<Flex justify="space-between" align="center" fontSize="xs" color={mutedColor}>
|
|
||||||
{/* 左侧:统计数据 */}
|
|
||||||
<HStack spacing={3} display={{ base: 'none', md: 'flex' }}>
|
|
||||||
<Tooltip label="浏览量" placement="top">
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<ViewIcon boxSize="12px" />
|
|
||||||
<Text>{event.view_count || 0}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip label="帖子数" placement="top">
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<ChatIcon boxSize="12px" />
|
|
||||||
<Text>{event.post_count || 0}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip label="关注数" placement="top">
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<StarIcon boxSize="12px" />
|
|
||||||
<Text>{followerCount}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 右侧:作者 + 时间(统一格式 YYYY-MM-DD HH:mm) */}
|
|
||||||
<HStack spacing={2}>
|
|
||||||
<Text>@{event.creator?.username || 'Anonymous'}</Text>
|
|
||||||
<Text>•</Text>
|
|
||||||
<Text fontWeight="bold" color={linkColor}>
|
|
||||||
{moment(event.created_at).format('YYYY-MM-DD HH:mm')}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
</VStack>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 详细模式的事件渲染(原有的渲染方式,但修复了箭头颜色)
|
|
||||||
const renderDetailedEvent = (event) => {
|
|
||||||
const importance = getImportanceConfig(event.importance);
|
|
||||||
const isFollowing = !!followingMap[event.id];
|
|
||||||
const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
|
|
||||||
const timelineStyle = getTimelineBoxStyle();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<HStack align="stretch" spacing={3} w="full">
|
|
||||||
{/* 左侧时间轴 - 动态样式 */}
|
|
||||||
<VStack spacing={0} align="center" minW="90px">
|
|
||||||
{/* 时间长方形卡片 */}
|
|
||||||
<Box
|
|
||||||
{...(timelineStyle.bgGradient ? { bgGradient: timelineStyle.bgGradient } : { bg: timelineStyle.bg })}
|
|
||||||
borderWidth={timelineStyle.borderWidth}
|
|
||||||
borderColor={timelineStyle.borderColor}
|
|
||||||
borderRadius="md"
|
|
||||||
px={2}
|
|
||||||
py={2}
|
|
||||||
minW="85px"
|
|
||||||
textAlign="center"
|
|
||||||
boxShadow={timelineStyle.boxShadow}
|
|
||||||
transition="all 0.3s ease"
|
|
||||||
>
|
|
||||||
{/* 日期 YYYY-MM-DD */}
|
|
||||||
<Text
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={timelineStyle.textColor}
|
|
||||||
lineHeight="1.3"
|
|
||||||
>
|
|
||||||
{moment(event.created_at).format('YYYY-MM-DD')}
|
|
||||||
</Text>
|
|
||||||
{/* 时间 HH:mm */}
|
|
||||||
<Text
|
|
||||||
fontSize="xs"
|
|
||||||
fontWeight="bold"
|
|
||||||
color={timelineStyle.textColor}
|
|
||||||
lineHeight="1.3"
|
|
||||||
mt={0.5}
|
|
||||||
>
|
|
||||||
{moment(event.created_at).format('HH:mm')}
|
|
||||||
</Text>
|
|
||||||
</Box>
|
|
||||||
{/* 时间轴竖线 */}
|
|
||||||
<Box
|
|
||||||
w="2px"
|
|
||||||
flex="1"
|
|
||||||
bg={borderColor}
|
|
||||||
minH="80px"
|
|
||||||
mt={1}
|
|
||||||
/>
|
|
||||||
</VStack>
|
|
||||||
|
|
||||||
{/* 事件卡片 */}
|
|
||||||
<Card
|
|
||||||
flex="1"
|
|
||||||
bg={cardBg}
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor={borderColor}
|
|
||||||
borderRadius="md"
|
|
||||||
boxShadow="sm"
|
|
||||||
_hover={{
|
|
||||||
boxShadow: 'xl',
|
|
||||||
transform: 'translateY(-3px)',
|
|
||||||
borderColor: importance.color,
|
|
||||||
}}
|
|
||||||
transition="all 0.3s ease"
|
|
||||||
cursor="pointer"
|
|
||||||
onClick={() => onEventClick(event)}
|
|
||||||
mb={3}
|
|
||||||
>
|
|
||||||
<CardBody p={4}>
|
|
||||||
<VStack align="stretch" spacing={2.5}>
|
|
||||||
{/* 第一行:标题+优先级 | 统计+关注 */}
|
|
||||||
<Flex align="center" justify="space-between" gap={3}>
|
|
||||||
{/* 左侧:标题 + 优先级标签 */}
|
|
||||||
<HStack spacing={2} flex="1" align="center">
|
|
||||||
<Tooltip
|
|
||||||
label="点击查看事件详情"
|
|
||||||
placement="top"
|
|
||||||
hasArrow
|
|
||||||
openDelay={500}
|
|
||||||
>
|
|
||||||
<Heading
|
|
||||||
size="md"
|
|
||||||
color={linkColor}
|
|
||||||
_hover={{ textDecoration: 'underline', color: 'blue.500' }}
|
|
||||||
onClick={(e) => handleTitleClick(e, event)}
|
|
||||||
cursor="pointer"
|
|
||||||
>
|
|
||||||
{event.title}
|
|
||||||
</Heading>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip
|
|
||||||
label={
|
|
||||||
<VStack align="start" spacing={1} maxW="320px">
|
|
||||||
<Text fontWeight="bold" fontSize="sm" mb={1}>
|
|
||||||
重要性等级说明
|
|
||||||
</Text>
|
|
||||||
<Divider borderColor="gray.300" />
|
|
||||||
{getAllImportanceLevels().map((level) => (
|
|
||||||
<HStack key={level.level} spacing={2} align="center" w="full" py={0.5}>
|
|
||||||
<Circle
|
|
||||||
size="8px"
|
|
||||||
bg={level.dotBg}
|
|
||||||
flexShrink={0}
|
|
||||||
/>
|
|
||||||
<Text fontSize="xs" color="gray.700" lineHeight="1.5">
|
|
||||||
<Text as="span" fontWeight="bold">{level.level}级</Text>
|
|
||||||
{level.description}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
))}
|
|
||||||
</VStack>
|
|
||||||
}
|
|
||||||
placement="top"
|
|
||||||
hasArrow
|
|
||||||
bg="white"
|
|
||||||
color="gray.800"
|
|
||||||
fontSize="md"
|
|
||||||
p={3}
|
|
||||||
borderRadius="lg"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor="gray.200"
|
|
||||||
boxShadow="lg"
|
|
||||||
>
|
|
||||||
<Badge
|
|
||||||
colorScheme={importance.color.split('.')[0]}
|
|
||||||
px={1.5}
|
|
||||||
py={0.5}
|
|
||||||
borderRadius="md"
|
|
||||||
fontSize="xs"
|
|
||||||
cursor="help"
|
|
||||||
display="flex"
|
|
||||||
alignItems="center"
|
|
||||||
gap={1}
|
|
||||||
flexShrink={0}
|
|
||||||
>
|
|
||||||
<InfoIcon boxSize={2.5} />
|
|
||||||
{event.importance || 'C'}级
|
|
||||||
</Badge>
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 右侧:统计数据 + 关注按钮 */}
|
|
||||||
<HStack spacing={4} flexShrink={0}>
|
|
||||||
{/* 统计数据 */}
|
|
||||||
<HStack spacing={4}>
|
|
||||||
<Tooltip label="浏览量" placement="top">
|
|
||||||
<HStack spacing={1} color={mutedColor}>
|
|
||||||
<ViewIcon />
|
|
||||||
<Text fontSize="sm">{event.view_count || 0}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label="帖子数" placement="top">
|
|
||||||
<HStack spacing={1} color={mutedColor}>
|
|
||||||
<ChatIcon />
|
|
||||||
<Text fontSize="sm">{event.post_count || 0}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Tooltip>
|
|
||||||
<Tooltip label="关注数" placement="top">
|
|
||||||
<HStack spacing={1} color={mutedColor}>
|
|
||||||
<StarIcon />
|
|
||||||
<Text fontSize="sm">{followerCount}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Tooltip>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 关注按钮 */}
|
|
||||||
<Button
|
|
||||||
size="sm"
|
|
||||||
colorScheme="yellow"
|
|
||||||
variant={isFollowing ? 'solid' : 'outline'}
|
|
||||||
leftIcon={<StarIcon boxSize="12px" />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
toggleFollow(event.id);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{isFollowing ? '已关注' : '关注'}
|
|
||||||
</Button>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* 第二行:价格标签 | 时间+作者 */}
|
|
||||||
<Flex justify="space-between" align="center" wrap="wrap" gap={3}>
|
|
||||||
{/* 左侧:价格标签 */}
|
|
||||||
<HStack spacing={2} flexWrap="wrap">
|
|
||||||
{/* 平均涨幅 - 始终显示,无数据时显示 -- */}
|
|
||||||
<Badge
|
|
||||||
colorScheme={event.related_avg_chg != null
|
|
||||||
? (event.related_avg_chg > 0 ? 'red' : event.related_avg_chg < 0 ? 'green' : 'gray')
|
|
||||||
: 'gray'}
|
|
||||||
fontSize="xs"
|
|
||||||
px={2}
|
|
||||||
py={0.5}
|
|
||||||
borderRadius="md"
|
|
||||||
cursor="pointer"
|
|
||||||
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Text fontSize="xs" opacity={0.8}>平均</Text>
|
|
||||||
<Text fontWeight="bold">
|
|
||||||
{event.related_avg_chg != null
|
|
||||||
? `${event.related_avg_chg > 0 ? '+' : ''}${event.related_avg_chg.toFixed(2)}%`
|
|
||||||
: '--'}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Badge>
|
|
||||||
|
|
||||||
{/* 最大涨幅 - 始终显示,无数据时显示 -- */}
|
|
||||||
<Badge
|
|
||||||
colorScheme={event.related_max_chg != null
|
|
||||||
? (event.related_max_chg > 0 ? 'red' : event.related_max_chg < 0 ? 'green' : 'gray')
|
|
||||||
: 'gray'}
|
|
||||||
fontSize="xs"
|
|
||||||
px={2}
|
|
||||||
py={0.5}
|
|
||||||
borderRadius="md"
|
|
||||||
cursor="pointer"
|
|
||||||
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Text fontSize="xs" opacity={0.8}>最大</Text>
|
|
||||||
<Text fontWeight="bold">
|
|
||||||
{event.related_max_chg != null
|
|
||||||
? `${event.related_max_chg > 0 ? '+' : ''}${event.related_max_chg.toFixed(2)}%`
|
|
||||||
: '--'}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Badge>
|
|
||||||
|
|
||||||
{/* 周涨幅 - 始终显示,无数据时显示 -- */}
|
|
||||||
<Badge
|
|
||||||
colorScheme={event.related_week_chg != null
|
|
||||||
? (event.related_week_chg > 0 ? 'red' : event.related_week_chg < 0 ? 'green' : 'gray')
|
|
||||||
: 'gray'}
|
|
||||||
fontSize="xs"
|
|
||||||
px={2}
|
|
||||||
py={0.5}
|
|
||||||
borderRadius="md"
|
|
||||||
cursor="pointer"
|
|
||||||
_hover={{ transform: 'scale(1.05)', boxShadow: 'md' }}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<Text fontSize="xs" opacity={0.8}>周</Text>
|
|
||||||
{event.related_week_chg != null && <PriceArrow value={event.related_week_chg} />}
|
|
||||||
<Text fontWeight="bold">
|
|
||||||
{event.related_week_chg != null
|
|
||||||
? `${event.related_week_chg > 0 ? '+' : ''}${event.related_week_chg.toFixed(2)}%`
|
|
||||||
: '--'}
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
</Badge>
|
|
||||||
</HStack>
|
|
||||||
|
|
||||||
{/* 右侧:时间 + 作者 */}
|
|
||||||
<HStack spacing={2} fontSize="sm" flexShrink={0}>
|
|
||||||
<Text fontWeight="bold" color={linkColor}>
|
|
||||||
{moment(event.created_at).format('YYYY-MM-DD HH:mm')}
|
|
||||||
</Text>
|
|
||||||
<Text color={mutedColor}>•</Text>
|
|
||||||
<Text color={mutedColor}>@{event.creator?.username || 'Anonymous'}</Text>
|
|
||||||
</HStack>
|
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* 第三行:描述文字 + 展开/收起 */}
|
|
||||||
{event.description && (
|
|
||||||
<Box>
|
|
||||||
<Text
|
|
||||||
color={textColor}
|
|
||||||
fontSize="sm"
|
|
||||||
lineHeight="tall"
|
|
||||||
noOfLines={expandedDescriptions[event.id] ? undefined : 3}
|
|
||||||
>
|
|
||||||
{event.description}
|
|
||||||
</Text>
|
|
||||||
{event.description.length > 120 && (
|
|
||||||
<Button
|
|
||||||
variant="link"
|
|
||||||
size="xs"
|
|
||||||
colorScheme="blue"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
setExpandedDescriptions(prev => ({
|
|
||||||
...prev,
|
|
||||||
[event.id]: !prev[event.id]
|
|
||||||
}));
|
|
||||||
}}
|
|
||||||
mt={1}
|
|
||||||
>
|
|
||||||
{expandedDescriptions[event.id] ? '收起' : '...展开'}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</Box>
|
|
||||||
)}
|
|
||||||
</VStack>
|
|
||||||
</CardBody>
|
|
||||||
</Card>
|
|
||||||
</HStack>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// 分页组件
|
// 分页组件
|
||||||
const Pagination = ({ current, total, pageSize, onChange }) => {
|
const Pagination = ({ current, total, pageSize, onChange }) => {
|
||||||
const totalPages = Math.ceil(total / pageSize);
|
const totalPages = Math.ceil(total / pageSize);
|
||||||
@@ -1062,10 +455,19 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
<VStack align="stretch" spacing={0}>
|
<VStack align="stretch" spacing={0}>
|
||||||
{localEvents.map((event, index) => (
|
{localEvents.map((event, index) => (
|
||||||
<Box key={event.id} position="relative">
|
<Box key={event.id} position="relative">
|
||||||
{isCompactMode
|
<EventCard
|
||||||
? renderCompactEvent(event, index)
|
event={event}
|
||||||
: renderDetailedEvent(event)
|
index={index}
|
||||||
}
|
isCompactMode={isCompactMode}
|
||||||
|
isFollowing={!!followingMap[event.id]}
|
||||||
|
followerCount={followCountMap[event.id] ?? (event.follower_count || 0)}
|
||||||
|
onEventClick={onEventClick}
|
||||||
|
onTitleClick={handleTitleClick}
|
||||||
|
onViewDetail={handleViewDetailClick}
|
||||||
|
onToggleFollow={toggleFollow}
|
||||||
|
timelineStyle={getTimelineBoxStyle()}
|
||||||
|
borderColor={borderColor}
|
||||||
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
))}
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|||||||
Reference in New Issue
Block a user