feat: 新增实时要闻·动态追踪与市场复盘功能,优化导航体验

新增功能:
- 实时要闻·动态追踪横向滚动卡片(DynamicNewsCard)
- 动态新闻事件卡片组件(DynamicNewsEventCard)
- 市场复盘卡片组件(MarketReviewCard)
- 股票涨跌幅指标组件(StockChangeIndicators)
- 交易时间工具函数(tradingTimeUtils)
- Mock API 支持动态新闻数据生成

UI 优化:
- EventFollowButton 改用 react-icons 星星图标,实现真正的空心/实心效果
- 关注按钮添加半透明白色背景(whiteAlpha.500),悬停效果更明显
- 事件卡片标题添加右侧留白,防止关注按钮遮挡文字

性能优化:
- 禁用 Router v7_startTransition 特性,解决路由切换延迟 2 秒问题
- 调整导航菜单点击顺序(先跳转后关闭),提升响应速度

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2025-10-31 14:11:03 +08:00
parent 5d8ad5e442
commit c372832f1f
11 changed files with 1211 additions and 23 deletions

View File

@@ -0,0 +1,239 @@
// src/views/Community/components/DynamicNewsCard.js
// 横向滚动事件卡片组件(实时要闻·动态追踪)
import React, { forwardRef, useRef, useState } from 'react';
import {
Card,
CardHeader,
CardBody,
Box,
Flex,
VStack,
HStack,
Heading,
Text,
Badge,
IconButton,
Center,
Spinner,
useColorModeValue
} from '@chakra-ui/react';
import { ChevronLeftIcon, ChevronRightIcon, TimeIcon } from '@chakra-ui/icons';
import DynamicNewsEventCard from './EventCard/DynamicNewsEventCard';
/**
* 实时要闻·动态追踪 - 横向滚动卡片组件
* @param {Array} events - 事件列表
* @param {boolean} loading - 加载状态
* @param {Date} lastUpdateTime - 最后更新时间
* @param {Function} onEventClick - 事件点击回调
* @param {Function} onViewDetail - 查看详情回调
* @param {Object} ref - 用于滚动的ref
*/
const DynamicNewsCard = forwardRef(({
events,
loading,
lastUpdateTime,
onEventClick,
onViewDetail,
...rest
}, ref) => {
const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const scrollContainerRef = useRef(null);
const [showLeftArrow, setShowLeftArrow] = useState(false);
const [showRightArrow, setShowRightArrow] = useState(true);
// 滚动到左侧
const scrollLeft = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: -400,
behavior: 'smooth'
});
}
};
// 滚动到右侧
const scrollRight = () => {
if (scrollContainerRef.current) {
scrollContainerRef.current.scrollBy({
left: 400,
behavior: 'smooth'
});
}
};
// 监听滚动位置,更新箭头显示状态
const handleScroll = (e) => {
const container = e.target;
const scrollLeft = container.scrollLeft;
const scrollWidth = container.scrollWidth;
const clientWidth = container.clientWidth;
setShowLeftArrow(scrollLeft > 0);
setShowRightArrow(scrollLeft < scrollWidth - clientWidth - 10);
};
// 时间轴样式配置
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',
};
};
return (
<Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}>
{/* 标题部分 */}
<CardHeader>
<Flex justify="space-between" align="center">
<VStack align="start" spacing={1}>
<Heading size="md">
<HStack>
<TimeIcon />
<Text>实时要闻·动态追踪</Text>
</HStack>
</Heading>
<HStack fontSize="sm" color="gray.500">
<Badge colorScheme="red">实时</Badge>
<Badge colorScheme="green">盘中</Badge>
<Badge colorScheme="blue">快讯</Badge>
</HStack>
</VStack>
<Text fontSize="xs" color="gray.500">
最后更新: {lastUpdateTime?.toLocaleTimeString() || '未知'}
</Text>
</Flex>
</CardHeader>
{/* 主体内容 */}
<CardBody position="relative">
{/* Loading 状态 */}
{loading && (
<Center py={10}>
<VStack>
<Spinner size="xl" color="blue.500" thickness="4px" />
<Text color="gray.500">正在加载最新事件...</Text>
</VStack>
</Center>
)}
{/* Empty 状态 */}
{!loading && (!events || events.length === 0) && (
<Center py={10}>
<VStack>
<Text fontSize="lg" color="gray.500">暂无事件数据</Text>
</VStack>
</Center>
)}
{/* 横向滚动事件列表 */}
{!loading && events && events.length > 0 && (
<Box position="relative">
{/* 左侧滚动按钮 */}
{showLeftArrow && (
<IconButton
icon={<ChevronLeftIcon boxSize={6} />}
position="absolute"
left="-4"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={scrollLeft}
colorScheme="blue"
variant="solid"
size="md"
borderRadius="full"
shadow="md"
aria-label="向左滚动"
/>
)}
{/* 右侧滚动按钮 */}
{showRightArrow && (
<IconButton
icon={<ChevronRightIcon boxSize={6} />}
position="absolute"
right="-4"
top="50%"
transform="translateY(-50%)"
zIndex={2}
onClick={scrollRight}
colorScheme="blue"
variant="solid"
size="md"
borderRadius="full"
shadow="md"
aria-label="向右滚动"
/>
)}
{/* 横向滚动容器 */}
<Flex
ref={scrollContainerRef}
overflowX="auto"
overflowY="hidden"
gap={4}
py={4}
px={2}
onScroll={handleScroll}
css={{
'&::-webkit-scrollbar': {
height: '8px',
},
'&::-webkit-scrollbar-track': {
background: useColorModeValue('#f1f1f1', '#2D3748'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb': {
background: useColorModeValue('#888', '#4A5568'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb:hover': {
background: useColorModeValue('#555', '#718096'),
},
// 平滑滚动
scrollBehavior: 'smooth',
// 触摸设备优化
WebkitOverflowScrolling: 'touch',
}}
>
{events.map((event, index) => (
<Box
key={event.id}
minW="calc((100% - 64px) / 5)"
maxW="calc((100% - 64px) / 5)"
flexShrink={0}
>
<DynamicNewsEventCard
event={event}
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
onEventClick={onEventClick}
onTitleClick={(e) => {
e.preventDefault();
e.stopPropagation();
onEventClick(event);
}}
onToggleFollow={() => {}}
timelineStyle={getTimelineBoxStyle()}
borderColor={borderColor}
/>
</Box>
))}
</Flex>
</Box>
)}
</CardBody>
</Card>
);
});
DynamicNewsCard.displayName = 'DynamicNewsCard';
export default DynamicNewsCard;

View File

@@ -0,0 +1,141 @@
// src/views/Community/components/EventCard/DynamicNewsEventCard.js
// 动态新闻事件卡片组件(纵向布局,时间在上)
import React from 'react';
import {
VStack,
Card,
CardBody,
Box,
Text,
useColorModeValue,
} from '@chakra-ui/react';
import moment from 'moment';
import { getImportanceConfig } from '../../../../constants/importanceLevels';
// 导入子组件
import EventFollowButton from './EventFollowButton';
import StockChangeIndicators from '../../../../components/StockChangeIndicators';
/**
* 动态新闻事件卡片组件(极简版)
* @param {Object} props
* @param {Object} props.event - 事件对象
* @param {number} props.index - 事件索引
* @param {boolean} props.isFollowing - 是否已关注
* @param {number} props.followerCount - 关注数
* @param {Function} props.onEventClick - 卡片点击事件
* @param {Function} props.onTitleClick - 标题点击事件
* @param {Function} props.onToggleFollow - 切换关注事件
* @param {Object} props.timelineStyle - 时间轴样式配置
* @param {string} props.borderColor - 边框颜色
*/
const DynamicNewsEventCard = ({
event,
index,
isFollowing,
followerCount,
onEventClick,
onTitleClick,
onToggleFollow,
timelineStyle,
borderColor,
}) => {
const importance = getImportanceConfig(event.importance);
const cardBg = useColorModeValue('white', 'gray.800');
const linkColor = useColorModeValue('blue.600', 'blue.400');
return (
<VStack align="stretch" spacing={2} w="full">
{/* 时间标签 - 在卡片上方 */}
<Box
{...(timelineStyle.bgGradient ? { bgGradient: timelineStyle.bgGradient } : { bg: timelineStyle.bg })}
borderWidth={timelineStyle.borderWidth}
borderColor={timelineStyle.borderColor}
borderRadius="md"
px={3}
py={1.5}
textAlign="center"
boxShadow={timelineStyle.boxShadow}
transition="all 0.3s ease"
>
<Text
fontSize="xs"
fontWeight="bold"
color={timelineStyle.textColor}
lineHeight="1.3"
>
{moment(event.created_at).format('YYYY-MM-DD HH:mm')}
</Text>
</Box>
{/* 事件卡片 */}
<Card
position="relative"
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)}
>
<CardBody p={3}>
{/* 关注按钮 - 绝对定位在右上角 */}
<Box position="absolute" top={2} right={2} zIndex={1}>
<EventFollowButton
isFollowing={isFollowing}
followerCount={followerCount}
onToggle={() => onToggleFollow?.(event.id)}
size="xs"
showCount={false}
/>
</Box>
<VStack align="stretch" spacing={2.5}>
{/* 第一行:标题 + 重要性(行内文字) */}
<Box
cursor="pointer"
onClick={(e) => onTitleClick?.(e, event)}
paddingRight="10px"
>
<Text
fontSize="md"
fontWeight="semibold"
color={linkColor}
lineHeight="1.4"
_hover={{ textDecoration: 'underline' }}
>
{event.title}
<Text
as="span"
fontSize="sm"
fontWeight="bold"
color={importance.color}
ml={2}
>
[{importance.level}]
</Text>
</Text>
</Box>
{/* 第二行:涨跌幅数据 */}
<StockChangeIndicators
avgChange={event.related_avg_chg}
maxChange={event.related_max_chg}
weekChange={event.related_week_chg}
/>
</VStack>
</CardBody>
</Card>
</VStack>
);
};
export default DynamicNewsEventCard;

View File

@@ -1,7 +1,7 @@
// src/views/Community/components/EventCard/EventFollowButton.js
import React from 'react';
import { Button } from '@chakra-ui/react';
import { StarIcon } from '@chakra-ui/icons';
import { IconButton, Box } from '@chakra-ui/react';
import { AiFillStar, AiOutlineStar } from 'react-icons/ai';
/**
* 事件关注按钮组件
@@ -19,7 +19,7 @@ const EventFollowButton = ({
size = 'sm',
showCount = true
}) => {
const iconSize = size === 'xs' ? '10px' : '12px';
const iconSize = size === 'xs' ? '16px' : size === 'sm' ? '18px' : '22px';
const handleClick = (e) => {
e.stopPropagation();
@@ -27,16 +27,38 @@ const EventFollowButton = ({
};
return (
<Button
size={size}
colorScheme="yellow"
variant={isFollowing ? 'solid' : 'outline'}
leftIcon={<StarIcon boxSize={iconSize} />}
onClick={handleClick}
>
{isFollowing ? '已关注' : '关注'}
{showCount && followerCount > 0 && `(${followerCount})`}
</Button>
<Box display="inline-flex" alignItems="center" gap={1}>
<IconButton
size={size}
colorScheme="yellow"
variant="ghost"
bg="whiteAlpha.500"
boxShadow="sm"
_hover={{
bg: 'whiteAlpha.800',
boxShadow: 'md'
}}
icon={
isFollowing ? (
<AiFillStar
size={iconSize}
color="gold"
/>
) : (
<AiOutlineStar
size={iconSize}
color="#718096"
strokeWidth="1"
/>
)
}
onClick={handleClick}
aria-label={isFollowing ? '取消关注' : '关注'}
/>
{/* <Box fontSize="xs" color="gray.500">
{followerCount || 0}
</Box> */}
</Box>
);
};

View File

@@ -0,0 +1,300 @@
// src/views/Community/components/MarketReviewCard.js
// 市场复盘组件(左右布局:事件列表 | 事件详情)
import React, { forwardRef, useState } from 'react';
import {
Card,
CardHeader,
CardBody,
Box,
Flex,
VStack,
HStack,
Heading,
Text,
Badge,
Center,
Spinner,
useColorModeValue,
Grid,
GridItem,
} from '@chakra-ui/react';
import { TimeIcon, InfoIcon } from '@chakra-ui/icons';
import moment from 'moment';
import CompactEventCard from './EventCard/CompactEventCard';
import EventHeader from './EventCard/EventHeader';
import EventStats from './EventCard/EventStats';
import EventFollowButton from './EventCard/EventFollowButton';
import EventPriceDisplay from './EventCard/EventPriceDisplay';
import EventDescription from './EventCard/EventDescription';
import { getImportanceConfig } from '../../../constants/importanceLevels';
/**
* 市场复盘 - 左右布局卡片组件
* @param {Array} events - 事件列表
* @param {boolean} loading - 加载状态
* @param {Date} lastUpdateTime - 最后更新时间
* @param {Function} onEventClick - 事件点击回调
* @param {Function} onViewDetail - 查看详情回调
* @param {Function} onToggleFollow - 切换关注回调
* @param {Object} ref - 用于滚动的ref
*/
const MarketReviewCard = forwardRef(({
events,
loading,
lastUpdateTime,
onEventClick,
onViewDetail,
onToggleFollow,
...rest
}, ref) => {
const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const linkColor = useColorModeValue('blue.600', 'blue.400');
const mutedColor = useColorModeValue('gray.500', 'gray.400');
const textColor = useColorModeValue('gray.700', 'gray.200');
const selectedBg = useColorModeValue('blue.50', 'blue.900');
// 选中的事件
const [selectedEvent, setSelectedEvent] = useState(null);
// 时间轴样式配置
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',
};
};
// 处理事件点击
const handleEventClick = (event) => {
setSelectedEvent(event);
if (onEventClick) {
onEventClick(event);
}
};
// 渲染右侧事件详情
const renderEventDetail = () => {
if (!selectedEvent) {
return (
<Center h="full" minH="400px">
<VStack spacing={4}>
<InfoIcon boxSize={12} color={mutedColor} />
<Text color={mutedColor} fontSize="lg">
请从左侧选择事件查看详情
</Text>
</VStack>
</Center>
);
}
const importance = getImportanceConfig(selectedEvent.importance);
return (
<Card
bg={cardBg}
borderWidth="1px"
borderColor={borderColor}
borderRadius="md"
boxShadow="md"
h="full"
>
<CardBody p={6}>
<VStack align="stretch" spacing={4}>
{/* 第一行:标题+优先级 | 统计+关注 */}
<Flex align="center" justify="space-between" gap={3}>
{/* 左侧:标题 + 优先级标签 */}
<EventHeader
title={selectedEvent.title}
importance={selectedEvent.importance}
onTitleClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (onViewDetail) {
onViewDetail(e, selectedEvent.id);
}
}}
linkColor={linkColor}
compact={false}
size="lg"
/>
{/* 右侧:统计数据 + 关注按钮 */}
<HStack spacing={4} flexShrink={0}>
{/* 统计数据 */}
<EventStats
viewCount={selectedEvent.view_count}
postCount={selectedEvent.post_count}
followerCount={selectedEvent.follower_count}
size="md"
spacing={4}
display="flex"
mutedColor={mutedColor}
/>
{/* 关注按钮 */}
<EventFollowButton
isFollowing={false}
followerCount={selectedEvent.follower_count}
onToggle={() => onToggleFollow && onToggleFollow(selectedEvent.id)}
size="sm"
showCount={false}
/>
</HStack>
</Flex>
{/* 第二行:价格标签 | 时间+作者 */}
<Flex justify="space-between" align="center" wrap="wrap" gap={3}>
{/* 左侧:价格标签 */}
<EventPriceDisplay
avgChange={selectedEvent.related_avg_chg}
maxChange={selectedEvent.related_max_chg}
weekChange={selectedEvent.related_week_chg}
compact={false}
/>
{/* 右侧:时间 + 作者 */}
<HStack spacing={2} fontSize="sm" flexShrink={0}>
<Text fontWeight="bold" color={linkColor}>
{moment(selectedEvent.created_at).format('YYYY-MM-DD HH:mm')}
</Text>
<Text color={mutedColor}></Text>
<Text color={mutedColor}>@{selectedEvent.creator?.username || 'Anonymous'}</Text>
</HStack>
</Flex>
{/* 第三行:描述文字 */}
<EventDescription
description={selectedEvent.description}
textColor={textColor}
minLength={200}
noOfLines={10}
/>
</VStack>
</CardBody>
</Card>
);
};
return (
<Card ref={ref} {...rest} bg={cardBg} borderColor={borderColor} mb={4}>
{/* 标题部分 */}
<CardHeader>
<Flex justify="space-between" align="center">
<VStack align="start" spacing={1}>
<Heading size="md">
<HStack>
<TimeIcon />
<Text>市场复盘</Text>
</HStack>
</Heading>
<HStack fontSize="sm" color="gray.500">
<Badge colorScheme="orange">复盘</Badge>
<Badge colorScheme="purple">总结</Badge>
<Badge colorScheme="gray">完整</Badge>
</HStack>
</VStack>
<Text fontSize="xs" color="gray.500">
最后更新: {lastUpdateTime?.toLocaleTimeString() || '未知'}
</Text>
</Flex>
</CardHeader>
{/* 主体内容 */}
<CardBody>
{/* Loading 状态 */}
{loading && (
<Center py={10}>
<VStack>
<Spinner size="xl" color="blue.500" thickness="4px" />
<Text color="gray.500">正在加载复盘数据...</Text>
</VStack>
</Center>
)}
{/* Empty 状态 */}
{!loading && (!events || events.length === 0) && (
<Center py={10}>
<VStack>
<Text fontSize="lg" color="gray.500">暂无复盘数据</Text>
</VStack>
</Center>
)}
{/* 左右布局:事件列表 | 事件详情 */}
{!loading && events && events.length > 0 && (
<Grid templateColumns="1fr 2fr" gap={6} minH="500px">
{/* 左侧:事件列表 (33.3%) */}
<GridItem>
<Box
overflowY="auto"
maxH="600px"
pr={2}
css={{
'&::-webkit-scrollbar': {
width: '6px',
},
'&::-webkit-scrollbar-track': {
background: useColorModeValue('#f1f1f1', '#2D3748'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb': {
background: useColorModeValue('#888', '#4A5568'),
borderRadius: '10px',
},
'&::-webkit-scrollbar-thumb:hover': {
background: useColorModeValue('#555', '#718096'),
},
}}
>
<VStack align="stretch" spacing={2}>
{events.map((event, index) => (
<Box
key={event.id}
onClick={() => handleEventClick(event)}
cursor="pointer"
bg={selectedEvent?.id === event.id ? selectedBg : 'transparent'}
borderRadius="md"
transition="all 0.2s"
_hover={{ bg: selectedBg }}
>
<CompactEventCard
event={event}
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
onEventClick={() => handleEventClick(event)}
onTitleClick={(e) => {
e.preventDefault();
e.stopPropagation();
handleEventClick(event);
}}
onViewDetail={onViewDetail}
onToggleFollow={() => {}}
timelineStyle={getTimelineBoxStyle()}
borderColor={borderColor}
/>
</Box>
))}
</VStack>
</Box>
</GridItem>
{/* 右侧:事件详情 (66.7%) */}
<GridItem>
{renderEventDetail()}
</GridItem>
</Grid>
)}
</CardBody>
</Card>
);
});
MarketReviewCard.displayName = 'MarketReviewCard';
export default MarketReviewCard;

View File

@@ -1,5 +1,5 @@
// src/views/Community/index.js
import React, { useState, useEffect, useRef, useCallback } from 'react';
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';
import { fetchPopularKeywords, fetchHotEvents } from '../../store/slices/communityDataSlice';
@@ -11,6 +11,8 @@ import {
// 导入组件
import EventTimelineCard from './components/EventTimelineCard';
import DynamicNewsCard from './components/DynamicNewsCard';
import MarketReviewCard from './components/MarketReviewCard';
import HotEventsSection from './components/HotEventsSection';
import EventModals from './components/EventModals';
@@ -19,6 +21,13 @@ import { useEventData } from './hooks/useEventData';
import { useEventFilters } from './hooks/useEventFilters';
import { useCommunityEvents } from './hooks/useCommunityEvents';
// 导入时间工具函数
import {
getCurrentTradingTimeRange,
getMarketReviewTimeRange,
filterEventsByTimeRange
} from '../../utils/tradingTimeUtils';
import { logger } from '../../utils/logger';
import { useNotification } from '../../contexts/NotificationContext';
import { usePostHogTrack } from '../../hooks/usePostHogRedux';
@@ -48,6 +57,10 @@ const Community = () => {
const [selectedEvent, setSelectedEvent] = useState(null);
const [selectedEventForStock, setSelectedEventForStock] = useState(null);
// 动态新闻数据状态
const [dynamicNewsEvents, setDynamicNewsEvents] = useState([]);
const [dynamicNewsLoading, setDynamicNewsLoading] = useState(true);
// 🎯 初始化Community埋点Hook
const communityEvents = useCommunityEvents({ navigate });
@@ -60,12 +73,64 @@ const Community = () => {
const { events, pagination, loading, lastUpdateTime } = useEventData(filters);
// 计算市场复盘的时间范围和过滤后的事件
const marketReviewData = useMemo(() => {
const timeRange = getMarketReviewTimeRange();
const filteredEvents = filterEventsByTimeRange(events, timeRange.startTime, timeRange.endTime);
logger.debug('Community', '市场复盘时间范围', {
description: timeRange.description,
rangeType: timeRange.rangeType,
eventCount: filteredEvents.length
});
return {
events: filteredEvents,
timeRange
};
}, [events]);
// 加载热门关键词和热点事件使用Redux内部有缓存判断
useEffect(() => {
dispatch(fetchPopularKeywords());
dispatch(fetchHotEvents());
}, [dispatch]);
// 加载动态新闻数据
useEffect(() => {
const fetchDynamicNews = async () => {
setDynamicNewsLoading(true);
try {
const timeRange = getCurrentTradingTimeRange();
const response = await fetch(
`/api/events/dynamic-news?start_time=${timeRange.startTime.toISOString()}&end_time=${timeRange.endTime.toISOString()}&count=30`,
{ credentials: 'include' }
);
const data = await response.json();
if (data.success && data.data) {
setDynamicNewsEvents(data.data);
logger.info('Community', '动态新闻加载成功', {
count: data.data.length,
timeRange: timeRange.description
});
} else {
logger.warn('Community', '动态新闻加载失败', data);
setDynamicNewsEvents([]);
}
} catch (error) {
logger.error('Community', '动态新闻加载异常', error);
setDynamicNewsEvents([]);
} finally {
setDynamicNewsLoading(false);
}
};
fetchDynamicNews();
// 每5分钟刷新一次动态新闻
const interval = setInterval(fetchDynamicNews, 5 * 60 * 1000);
return () => clearInterval(interval);
}, []);
// 🎯 PostHog 追踪:页面浏览
// useEffect(() => {
// track(RETENTION_EVENTS.COMMUNITY_PAGE_VIEWED, {
@@ -120,7 +185,28 @@ const Community = () => {
{/* 热点事件区域 */}
<HotEventsSection events={hotEvents} />
{/* 实时事件 */}
{/* 实时要闻·动态追踪 - 横向滚动 */}
<DynamicNewsCard
mt={6}
events={dynamicNewsEvents}
loading={dynamicNewsLoading}
lastUpdateTime={lastUpdateTime}
onEventClick={handleEventClick}
onViewDetail={handleViewDetail}
/>
{/* 市场复盘 - 左右布局 */}
<MarketReviewCard
mt={6}
events={marketReviewData.events}
loading={loading}
lastUpdateTime={lastUpdateTime}
onEventClick={handleEventClick}
onViewDetail={handleViewDetail}
onToggleFollow={() => {}}
/>
{/* 实时事件 - 原纵向列表 */}
<EventTimelineCard
ref={eventTimelineRef}
mt={6}