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,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>
);
};