feat(EventList): 重构渲染和UI - 精简/详细模式优化、推送控制、描述展开
**主要变更**: 1. **渲染函数重构**: - 重写 renderCompactEvent:标题2行+标签内联+按钮右侧布局 - 重写 renderDetailedEvent:标题+优先级+统计+价格标签+时间作者 - 添加 getTimelineBoxStyle 函数统一时间轴样式 - renderCompactEvent 支持隔行变色(index % 2) 2. **顶部控制栏全面升级**: - 改为 sticky 定位,全宽白色背景 - 左侧占位,中间嵌入分页器,右侧控制按钮 - 新增桌面推送开关(使用 handlePushToggle) - WebSocket 状态简化为 🟢实时/🔴离线 - 精简模式切换改为 xs 尺寸 3. **描述展开/收起功能**: - 详细模式支持长描述(>120字符)展开/收起 - 使用 expandedDescriptions 状态管理 - noOfLines 动态切换 4. **统一时间格式**: - 所有时间显示统一为 YYYY-MM-DD HH:mm **效果**: - 精简模式更紧凑,信息密度更高 - 详细模式布局更清晰,价格标签更易读 - 顶部控制栏功能集中,操作更便捷 - 推送权限管理可视化 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -413,104 +413,150 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
navigate(`/event-detail/${eventId}`);
|
navigate(`/event-detail/${eventId}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 精简模式的事件渲染
|
// 时间轴样式配置(固定使用轻量卡片样式)
|
||||||
const renderCompactEvent = (event) => {
|
const getTimelineBoxStyle = () => {
|
||||||
|
return {
|
||||||
|
bg: useColorModeValue('gray.50', 'gray.700'),
|
||||||
|
borderColor: useColorModeValue('gray.400', 'gray.500'),
|
||||||
|
borderWidth: '2px',
|
||||||
|
textColor: useColorModeValue('blue.600', 'blue.400'),
|
||||||
|
boxShadow: 'sm',
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 精简模式的事件渲染(优化版:标题2行+标签内联+按钮右侧)
|
||||||
|
const renderCompactEvent = (event, index) => {
|
||||||
const importance = getImportanceConfig(event.importance);
|
const importance = getImportanceConfig(event.importance);
|
||||||
const isFollowing = !!followingMap[event.id];
|
const isFollowing = !!followingMap[event.id];
|
||||||
const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
|
const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
|
||||||
|
const timelineStyle = getTimelineBoxStyle();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack align="stretch" spacing={4} w="full">
|
<HStack align="stretch" spacing={3} w="full">
|
||||||
{/* 时间线和重要性标记 */}
|
{/* 左侧时间轴 - 动态样式 */}
|
||||||
<VStack spacing={0} align="center">
|
<VStack spacing={0} align="center" minW="90px">
|
||||||
<Circle
|
{/* 时间长方形卡片 */}
|
||||||
size="32px"
|
<Box
|
||||||
bg={importance.dotBg}
|
{...(timelineStyle.bgGradient ? { bgGradient: timelineStyle.bgGradient } : { bg: timelineStyle.bg })}
|
||||||
color="white"
|
borderWidth={timelineStyle.borderWidth}
|
||||||
fontWeight="bold"
|
borderColor={timelineStyle.borderColor}
|
||||||
fontSize="sm"
|
borderRadius="md"
|
||||||
boxShadow="sm"
|
px={2}
|
||||||
border="2px solid"
|
py={2}
|
||||||
borderColor={cardBg}
|
minW="85px"
|
||||||
|
textAlign="center"
|
||||||
|
boxShadow={timelineStyle.boxShadow}
|
||||||
|
transition="all 0.3s ease"
|
||||||
>
|
>
|
||||||
{event.importance || 'C'}
|
{/* 日期 YYYY-MM-DD */}
|
||||||
</Circle>
|
<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
|
<Box
|
||||||
w="2px"
|
w="2px"
|
||||||
flex="1"
|
flex="1"
|
||||||
bg={borderColor}
|
bg={borderColor}
|
||||||
minH="60px"
|
minH="40px"
|
||||||
|
mt={1}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
{/* 精简事件卡片 */}
|
{/* 右侧内容卡片 */}
|
||||||
<Card
|
<Card
|
||||||
flex="1"
|
flex="1"
|
||||||
bg={cardBg}
|
bg={index % 2 === 0 ? cardBg : useColorModeValue('gray.50', 'gray.750')}
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
borderRadius="lg"
|
borderRadius="md"
|
||||||
boxShadow="sm"
|
boxShadow="sm"
|
||||||
_hover={{
|
_hover={{
|
||||||
boxShadow: 'md',
|
boxShadow: 'lg',
|
||||||
transform: 'translateY(-1px)',
|
transform: 'translateY(-2px)',
|
||||||
borderColor: importance.color,
|
borderColor: importance.color,
|
||||||
}}
|
}}
|
||||||
transition="all 0.2s"
|
transition="all 0.3s ease"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
onClick={() => onEventClick(event)}
|
onClick={() => onEventClick(event)}
|
||||||
mb={3}
|
mb={2}
|
||||||
>
|
>
|
||||||
<CardBody p={4}>
|
<CardBody p={3}>
|
||||||
<Flex align="center" justify="space-between" wrap="wrap" gap={3}>
|
<VStack align="stretch" spacing={2}>
|
||||||
{/* 左侧:标题和时间 */}
|
{/* 第一行:标题(2行)+ 标签(内联)+ 按钮(右侧) */}
|
||||||
<VStack align="start" spacing={2} flex="1" minW="200px">
|
<Flex align="flex-start" gap={2}>
|
||||||
<Heading
|
{/* 标题区域:标题+标签(内联) */}
|
||||||
size="sm"
|
<Box flex="1" minW="150px">
|
||||||
|
<Text
|
||||||
|
fontSize="md"
|
||||||
|
fontWeight="bold"
|
||||||
color={linkColor}
|
color={linkColor}
|
||||||
_hover={{ textDecoration: 'underline' }}
|
lineHeight="1.4"
|
||||||
|
noOfLines={2}
|
||||||
|
display="inline"
|
||||||
|
_hover={{ textDecoration: 'underline', color: 'blue.500' }}
|
||||||
onClick={(e) => handleTitleClick(e, event)}
|
onClick={(e) => handleTitleClick(e, event)}
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
noOfLines={1}
|
|
||||||
>
|
>
|
||||||
{event.title}
|
{event.title}
|
||||||
</Heading>
|
</Text>
|
||||||
<HStack spacing={2} fontSize="xs" color={mutedColor}>
|
{' '}
|
||||||
<TimeIcon />
|
{/* 重要性标签 - 内联 */}
|
||||||
<Text>{moment(event.created_at).format('MM-DD HH:mm')}</Text>
|
<Badge
|
||||||
<Text>•</Text>
|
colorScheme={importance.color.split('.')[0]}
|
||||||
<Text>{event.creator?.username || 'Anonymous'}</Text>
|
fontSize="xs"
|
||||||
</HStack>
|
px={2}
|
||||||
</VStack>
|
|
||||||
|
|
||||||
{/* 右侧:涨跌幅指标 */}
|
|
||||||
<HStack spacing={3}>
|
|
||||||
<Tooltip label="平均" placement="top">
|
|
||||||
<Box
|
|
||||||
px={3}
|
|
||||||
py={1}
|
py={1}
|
||||||
borderRadius="md"
|
borderRadius="md"
|
||||||
bg={getPriceChangeBg(event.related_avg_chg)}
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor={getPriceChangeBorderColor(event.related_avg_chg)}
|
|
||||||
>
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<PriceArrow value={event.related_avg_chg} />
|
|
||||||
<Text
|
|
||||||
fontSize="sm"
|
|
||||||
fontWeight="bold"
|
fontWeight="bold"
|
||||||
color={getPriceChangeColor(event.related_avg_chg)}
|
display="inline-flex"
|
||||||
|
alignItems="center"
|
||||||
|
verticalAlign="middle"
|
||||||
>
|
>
|
||||||
{event.related_avg_chg != null
|
{event.importance || 'C'}级
|
||||||
? `${event.related_avg_chg > 0 ? '+' : ''}${event.related_avg_chg.toFixed(2)}%`
|
</Badge>
|
||||||
: '--'}
|
{' '}
|
||||||
</Text>
|
{/* 涨跌幅标签 - 内联 */}
|
||||||
</HStack>
|
{event.related_avg_chg != null && (
|
||||||
</Box>
|
<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>
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 操作按钮 - 固定右侧 */}
|
||||||
|
<HStack spacing={2} flexShrink={0}>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="xs"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
onClick={(e) => handleViewDetailClick(e, event.id)}
|
onClick={(e) => handleViewDetailClick(e, event.id)}
|
||||||
@@ -518,19 +564,57 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
详情
|
详情
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="xs"
|
||||||
variant={isFollowing ? 'solid' : 'outline'}
|
variant={isFollowing ? 'solid' : 'outline'}
|
||||||
colorScheme="yellow"
|
colorScheme="yellow"
|
||||||
leftIcon={<StarIcon />}
|
leftIcon={<StarIcon boxSize="10px" />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleFollow(event.id);
|
toggleFollow(event.id);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isFollowing ? '已关注' : '关注'} {followerCount ? `(${followerCount})` : ''}
|
{isFollowing ? '已关注' : '关注'}
|
||||||
|
{followerCount > 0 && `(${followerCount})`}
|
||||||
</Button>
|
</Button>
|
||||||
</HStack>
|
</HStack>
|
||||||
</Flex>
|
</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>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
</HStack>
|
</HStack>
|
||||||
@@ -542,28 +626,52 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
const importance = getImportanceConfig(event.importance);
|
const importance = getImportanceConfig(event.importance);
|
||||||
const isFollowing = !!followingMap[event.id];
|
const isFollowing = !!followingMap[event.id];
|
||||||
const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
|
const followerCount = followCountMap[event.id] ?? (event.follower_count || 0);
|
||||||
|
const timelineStyle = getTimelineBoxStyle();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack align="stretch" spacing={4} w="full">
|
<HStack align="stretch" spacing={3} w="full">
|
||||||
{/* 时间线和重要性标记 */}
|
{/* 左侧时间轴 - 动态样式 */}
|
||||||
<VStack spacing={0} align="center">
|
<VStack spacing={0} align="center" minW="90px">
|
||||||
<Circle
|
{/* 时间长方形卡片 */}
|
||||||
size="40px"
|
<Box
|
||||||
bg={importance.dotBg}
|
{...(timelineStyle.bgGradient ? { bgGradient: timelineStyle.bgGradient } : { bg: timelineStyle.bg })}
|
||||||
color="white"
|
borderWidth={timelineStyle.borderWidth}
|
||||||
fontWeight="bold"
|
borderColor={timelineStyle.borderColor}
|
||||||
fontSize="lg"
|
borderRadius="md"
|
||||||
boxShadow="md"
|
px={2}
|
||||||
border="3px solid"
|
py={2}
|
||||||
borderColor={cardBg}
|
minW="85px"
|
||||||
|
textAlign="center"
|
||||||
|
boxShadow={timelineStyle.boxShadow}
|
||||||
|
transition="all 0.3s ease"
|
||||||
>
|
>
|
||||||
{event.importance || 'C'}
|
{/* 日期 YYYY-MM-DD */}
|
||||||
</Circle>
|
<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
|
<Box
|
||||||
w="2px"
|
w="2px"
|
||||||
flex="1"
|
flex="1"
|
||||||
bg={borderColor}
|
bg={borderColor}
|
||||||
minH="100px"
|
minH="80px"
|
||||||
|
mt={1}
|
||||||
/>
|
/>
|
||||||
</VStack>
|
</VStack>
|
||||||
|
|
||||||
@@ -573,22 +681,24 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
bg={cardBg}
|
bg={cardBg}
|
||||||
borderWidth="1px"
|
borderWidth="1px"
|
||||||
borderColor={borderColor}
|
borderColor={borderColor}
|
||||||
borderRadius="lg"
|
borderRadius="md"
|
||||||
boxShadow="sm"
|
boxShadow="sm"
|
||||||
_hover={{
|
_hover={{
|
||||||
boxShadow: 'md',
|
boxShadow: 'xl',
|
||||||
transform: 'translateY(-2px)',
|
transform: 'translateY(-3px)',
|
||||||
borderColor: importance.color,
|
borderColor: importance.color,
|
||||||
}}
|
}}
|
||||||
transition="all 0.2s"
|
transition="all 0.3s ease"
|
||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
onClick={() => onEventClick(event)}
|
onClick={() => onEventClick(event)}
|
||||||
mb={4}
|
mb={3}
|
||||||
>
|
>
|
||||||
<CardBody p={5}>
|
<CardBody p={4}>
|
||||||
<VStack align="stretch" spacing={3}>
|
<VStack align="stretch" spacing={2.5}>
|
||||||
{/* 标题和重要性标签 */}
|
{/* 第一行:标题+优先级 | 统计+关注 */}
|
||||||
<Flex align="center" justify="space-between">
|
<Flex align="center" justify="space-between" gap={3}>
|
||||||
|
{/* 左侧:标题 + 优先级标签 */}
|
||||||
|
<HStack spacing={2} flex="1" align="center">
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label="点击查看事件详情"
|
label="点击查看事件详情"
|
||||||
placement="top"
|
placement="top"
|
||||||
@@ -605,6 +715,7 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
{event.title}
|
{event.title}
|
||||||
</Heading>
|
</Heading>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={
|
label={
|
||||||
<VStack align="start" spacing={1} maxW="320px">
|
<VStack align="start" spacing={1} maxW="320px">
|
||||||
@@ -627,7 +738,7 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
))}
|
))}
|
||||||
</VStack>
|
</VStack>
|
||||||
}
|
}
|
||||||
placement="left"
|
placement="top"
|
||||||
hasArrow
|
hasArrow
|
||||||
bg="white"
|
bg="white"
|
||||||
color="gray.800"
|
color="gray.800"
|
||||||
@@ -640,154 +751,26 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
>
|
>
|
||||||
<Badge
|
<Badge
|
||||||
colorScheme={importance.color.split('.')[0]}
|
colorScheme={importance.color.split('.')[0]}
|
||||||
px={3}
|
px={1.5}
|
||||||
py={1}
|
py={0.5}
|
||||||
borderRadius="full"
|
borderRadius="md"
|
||||||
fontSize="sm"
|
fontSize="xs"
|
||||||
cursor="help"
|
cursor="help"
|
||||||
display="flex"
|
display="flex"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
gap={1}
|
gap={1}
|
||||||
|
flexShrink={0}
|
||||||
>
|
>
|
||||||
<InfoIcon boxSize={3} />
|
<InfoIcon boxSize={2.5} />
|
||||||
{importance.label}优先级
|
{event.importance || 'C'}级
|
||||||
</Badge>
|
</Badge>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</Flex>
|
|
||||||
|
|
||||||
{/* 元信息 */}
|
|
||||||
<HStack spacing={4} fontSize="sm">
|
|
||||||
<HStack
|
|
||||||
bg="blue.50"
|
|
||||||
px={3}
|
|
||||||
py={1}
|
|
||||||
borderRadius="full"
|
|
||||||
color="blue.700"
|
|
||||||
fontWeight="medium"
|
|
||||||
>
|
|
||||||
<TimeIcon />
|
|
||||||
<Text>{moment(event.created_at).format('YYYY-MM-DD HH:mm')}</Text>
|
|
||||||
</HStack>
|
|
||||||
<Text color={mutedColor}>•</Text>
|
|
||||||
<Text color={mutedColor}>{event.creator?.username || 'Anonymous'}</Text>
|
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
{/* 描述 */}
|
{/* 右侧:统计数据 + 关注按钮 */}
|
||||||
<Text color={textColor} fontSize="sm" lineHeight="tall" noOfLines={3}>
|
<HStack spacing={4} flexShrink={0}>
|
||||||
{event.description}
|
{/* 统计数据 */}
|
||||||
</Text>
|
<HStack spacing={4}>
|
||||||
|
|
||||||
{/* 价格变化指标 */}
|
|
||||||
<Box
|
|
||||||
bg={useColorModeValue('gradient.subtle', 'gray.700')}
|
|
||||||
bgGradient="linear(to-r, gray.50, white)"
|
|
||||||
p={4}
|
|
||||||
borderRadius="lg"
|
|
||||||
borderWidth="1px"
|
|
||||||
borderColor={borderColor}
|
|
||||||
boxShadow="sm"
|
|
||||||
>
|
|
||||||
<SimpleGrid columns={{ base: 1, md: 3 }} spacing={3}>
|
|
||||||
<Tooltip label="点击查看相关股票" placement="top" hasArrow>
|
|
||||||
<Box
|
|
||||||
cursor="pointer"
|
|
||||||
p={2}
|
|
||||||
borderRadius="md"
|
|
||||||
bg={getPriceChangeBg(event.related_avg_chg)}
|
|
||||||
borderWidth="2px"
|
|
||||||
borderColor={getPriceChangeBorderColor(event.related_avg_chg)}
|
|
||||||
_hover={{ transform: 'scale(1.02)', boxShadow: 'md' }}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<Stat size="sm">
|
|
||||||
<StatHelpText mb={1} fontWeight="semibold" color="gray.600" fontSize="xs">
|
|
||||||
平均
|
|
||||||
</StatHelpText>
|
|
||||||
<StatNumber fontSize="xl" color={getPriceChangeColor(event.related_avg_chg)}>
|
|
||||||
{event.related_avg_chg != null ? (
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<PriceArrow value={event.related_avg_chg} />
|
|
||||||
<Text fontWeight="bold">
|
|
||||||
{event.related_avg_chg > 0 ? '+' : ''}{event.related_avg_chg.toFixed(2)}%
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
) : (
|
|
||||||
<Text color="gray.400">--</Text>
|
|
||||||
)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip label="点击查看相关股票" placement="top" hasArrow>
|
|
||||||
<Box
|
|
||||||
cursor="pointer"
|
|
||||||
p={2}
|
|
||||||
borderRadius="md"
|
|
||||||
bg={getPriceChangeBg(event.related_max_chg)}
|
|
||||||
borderWidth="2px"
|
|
||||||
borderColor={getPriceChangeBorderColor(event.related_max_chg)}
|
|
||||||
_hover={{ transform: 'scale(1.02)', boxShadow: 'md' }}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<Stat size="sm">
|
|
||||||
<StatHelpText mb={1} fontWeight="semibold" color="gray.600" fontSize="xs">
|
|
||||||
最大
|
|
||||||
</StatHelpText>
|
|
||||||
<StatNumber fontSize="xl" color={getPriceChangeColor(event.related_max_chg)}>
|
|
||||||
{event.related_max_chg != null ? (
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<PriceArrow value={event.related_max_chg} />
|
|
||||||
<Text fontWeight="bold">
|
|
||||||
{event.related_max_chg > 0 ? '+' : ''}{event.related_max_chg.toFixed(2)}%
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
) : (
|
|
||||||
<Text color="gray.400">--</Text>
|
|
||||||
)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
|
|
||||||
<Tooltip label="点击查看相关股票" placement="top" hasArrow>
|
|
||||||
<Box
|
|
||||||
cursor="pointer"
|
|
||||||
p={2}
|
|
||||||
borderRadius="md"
|
|
||||||
bg={getPriceChangeBg(event.related_week_chg)}
|
|
||||||
borderWidth="2px"
|
|
||||||
borderColor={getPriceChangeBorderColor(event.related_week_chg)}
|
|
||||||
_hover={{ transform: 'scale(1.02)', boxShadow: 'md' }}
|
|
||||||
transition="all 0.2s"
|
|
||||||
>
|
|
||||||
<Stat size="sm">
|
|
||||||
<StatHelpText mb={1} fontWeight="semibold" color="gray.600" fontSize="xs">
|
|
||||||
周
|
|
||||||
</StatHelpText>
|
|
||||||
<StatNumber fontSize="xl" color={getPriceChangeColor(event.related_week_chg)}>
|
|
||||||
{event.related_week_chg != null ? (
|
|
||||||
<HStack spacing={1}>
|
|
||||||
<PriceArrow value={event.related_week_chg} />
|
|
||||||
<Text fontWeight="bold">
|
|
||||||
{event.related_week_chg > 0 ? '+' : ''}{event.related_week_chg.toFixed(2)}%
|
|
||||||
</Text>
|
|
||||||
</HStack>
|
|
||||||
) : (
|
|
||||||
<Text color="gray.400">--</Text>
|
|
||||||
)}
|
|
||||||
</StatNumber>
|
|
||||||
</Stat>
|
|
||||||
</Box>
|
|
||||||
</Tooltip>
|
|
||||||
</SimpleGrid>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
<Divider />
|
|
||||||
|
|
||||||
{/* 统计信息和操作按钮 */}
|
|
||||||
<Flex justify="space-between" align="center" wrap="wrap" gap={3}>
|
|
||||||
<HStack spacing={6}>
|
|
||||||
<Tooltip label="浏览量" placement="top">
|
<Tooltip label="浏览量" placement="top">
|
||||||
<HStack spacing={1} color={mutedColor}>
|
<HStack spacing={1} color={mutedColor}>
|
||||||
<ViewIcon />
|
<ViewIcon />
|
||||||
@@ -808,29 +791,12 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</HStack>
|
</HStack>
|
||||||
|
|
||||||
<ButtonGroup size="sm" spacing={2}>
|
{/* 关注按钮 */}
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
colorScheme="gray"
|
|
||||||
leftIcon={<ViewIcon />}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.stopPropagation();
|
|
||||||
onEventClick(event);
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
快速查看
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
colorScheme="blue"
|
|
||||||
leftIcon={<ExternalLinkIcon />}
|
|
||||||
onClick={(e) => handleViewDetailClick(e, event.id)}
|
|
||||||
>
|
|
||||||
详细信息
|
|
||||||
</Button>
|
|
||||||
<Button
|
<Button
|
||||||
|
size="sm"
|
||||||
colorScheme="yellow"
|
colorScheme="yellow"
|
||||||
variant={isFollowing ? 'solid' : 'outline'}
|
variant={isFollowing ? 'solid' : 'outline'}
|
||||||
leftIcon={<StarIcon />}
|
leftIcon={<StarIcon boxSize="12px" />}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleFollow(event.id);
|
toggleFollow(event.id);
|
||||||
@@ -838,8 +804,124 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
>
|
>
|
||||||
{isFollowing ? '已关注' : '关注'}
|
{isFollowing ? '已关注' : '关注'}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</HStack>
|
||||||
</Flex>
|
</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>
|
</VStack>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
@@ -946,47 +1028,117 @@ const EventList = ({ events, pagination, onPageChange, onEventClick, onViewDetai
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Box bg={bgColor} minH="100vh" pb={8}>
|
<Box bg={bgColor} minH="100vh" pb={8}>
|
||||||
|
{/* 顶部控制栏:左空白 + 中间分页器 + 右侧控制(固定sticky) - 铺满全宽 */}
|
||||||
|
<Box
|
||||||
|
position="sticky"
|
||||||
|
top={0}
|
||||||
|
zIndex={10}
|
||||||
|
bg={useColorModeValue('rgba(255, 255, 255, 0.9)', 'rgba(26, 32, 44, 0.9)')}
|
||||||
|
backdropFilter="blur(10px)"
|
||||||
|
boxShadow="sm"
|
||||||
|
mb={4}
|
||||||
|
py={2}
|
||||||
|
w="100%"
|
||||||
|
>
|
||||||
<Container maxW="container.xl">
|
<Container maxW="container.xl">
|
||||||
{/* 顶部控制栏:连接状态 + 视图切换 */}
|
<Flex justify="space-between" align="center">
|
||||||
<Flex justify="space-between" align="center" mb={6}>
|
{/* 左侧占位 */}
|
||||||
{/* WebSocket 连接状态指示器 */}
|
<Box flex="1" />
|
||||||
<HStack spacing={2}>
|
|
||||||
|
{/* 中间:分页器 */}
|
||||||
|
{pagination.total > 0 && localEvents.length > 0 ? (
|
||||||
|
<Flex align="center" gap={2}>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onPageChange(pagination.current - 1)}
|
||||||
|
isDisabled={pagination.current === 1}
|
||||||
|
>
|
||||||
|
上一页
|
||||||
|
</Button>
|
||||||
|
<Text fontSize="xs" color={mutedColor} px={2} whiteSpace="nowrap">
|
||||||
|
第 {pagination.current} / {Math.ceil(pagination.total / pagination.pageSize)} 页
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
size="xs"
|
||||||
|
variant="outline"
|
||||||
|
onClick={() => onPageChange(pagination.current + 1)}
|
||||||
|
isDisabled={pagination.current === Math.ceil(pagination.total / pagination.pageSize)}
|
||||||
|
>
|
||||||
|
下一页
|
||||||
|
</Button>
|
||||||
|
<Text fontSize="xs" color={mutedColor} ml={2} whiteSpace="nowrap">
|
||||||
|
共 {pagination.total} 条
|
||||||
|
</Text>
|
||||||
|
</Flex>
|
||||||
|
) : (
|
||||||
|
<Box flex="1" />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 右侧:控制按钮 */}
|
||||||
|
<Flex align="center" gap={3} flex="1" justify="flex-end">
|
||||||
|
{/* WebSocket 连接状态 */}
|
||||||
<Badge
|
<Badge
|
||||||
colorScheme={isConnected ? 'green' : 'red'}
|
colorScheme={isConnected ? 'green' : 'red'}
|
||||||
fontSize="sm"
|
fontSize="xs"
|
||||||
px={3}
|
px={2}
|
||||||
py={1}
|
py={1}
|
||||||
borderRadius="full"
|
borderRadius="full"
|
||||||
>
|
>
|
||||||
{isConnected ? '🟢 实时推送已开启' : '🔴 实时推送未连接'}
|
{isConnected ? '🟢 实时' : '🔴 离线'}
|
||||||
</Badge>
|
</Badge>
|
||||||
{isConnected && (
|
|
||||||
<Text fontSize="xs" color={mutedColor}>
|
{/* 桌面推送开关 */}
|
||||||
新事件将自动推送
|
<FormControl display="flex" alignItems="center" w="auto">
|
||||||
</Text>
|
<FormLabel htmlFor="push-notification" mb="0" fontSize="xs" color={textColor} mr={2}>
|
||||||
)}
|
推送
|
||||||
</HStack>
|
</FormLabel>
|
||||||
|
<Tooltip
|
||||||
|
label={
|
||||||
|
browserPermission === 'granted'
|
||||||
|
? '桌面推送已开启'
|
||||||
|
: browserPermission === 'denied'
|
||||||
|
? '推送权限被拒绝,请在浏览器设置中允许通知权限'
|
||||||
|
: '点击开启桌面推送通知'
|
||||||
|
}
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<Switch
|
||||||
|
id="push-notification"
|
||||||
|
size="sm"
|
||||||
|
isChecked={browserPermission === 'granted'}
|
||||||
|
onChange={handlePushToggle}
|
||||||
|
colorScheme="green"
|
||||||
|
/>
|
||||||
|
</Tooltip>
|
||||||
|
</FormControl>
|
||||||
|
|
||||||
{/* 视图切换控制 */}
|
{/* 视图切换控制 */}
|
||||||
<FormControl display="flex" alignItems="center" w="auto">
|
<FormControl display="flex" alignItems="center" w="auto">
|
||||||
<FormLabel htmlFor="compact-mode" mb="0" fontSize="sm" color={textColor}>
|
<FormLabel htmlFor="compact-mode" mb="0" fontSize="xs" color={textColor} mr={2}>
|
||||||
精简模式
|
精简
|
||||||
</FormLabel>
|
</FormLabel>
|
||||||
<Switch
|
<Switch
|
||||||
id="compact-mode"
|
id="compact-mode"
|
||||||
|
size="sm"
|
||||||
isChecked={isCompactMode}
|
isChecked={isCompactMode}
|
||||||
onChange={(e) => setIsCompactMode(e.target.checked)}
|
onChange={(e) => setIsCompactMode(e.target.checked)}
|
||||||
colorScheme="blue"
|
colorScheme="blue"
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
</Flex>
|
||||||
|
</Container>
|
||||||
|
</Box>
|
||||||
|
|
||||||
|
{/* 事件列表内容 */}
|
||||||
|
<Container maxW="container.xl">
|
||||||
{localEvents.length > 0 ? (
|
{localEvents.length > 0 ? (
|
||||||
<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
|
{isCompactMode
|
||||||
? renderCompactEvent(event)
|
? renderCompactEvent(event, index)
|
||||||
: renderDetailedEvent(event)
|
: renderDetailedEvent(event)
|
||||||
}
|
}
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user