refactor(EventCard): 工具栏 UI 重构为上下结构

- 投票按钮改用 TrendingUp/TrendingDown 折线图标
- 投票按钮改为上图标下数字的垂直布局
- 浏览量、收藏按钮改为上下结构
- 工具栏间距从 2 减为 1
- 传递超预期得分到 StockChangeIndicators

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
zdl
2026-01-09 16:11:36 +08:00
parent 483b9ad298
commit 846c44c1ec
2 changed files with 108 additions and 60 deletions

View File

@@ -87,6 +87,7 @@ const VerticalModeLayout = React.memo(({
flex={isMobile ? '1' : leftFlex}
minWidth={0}
w={isMobile ? '100%' : 'auto'}
overflowX="hidden"
overflowY="auto"
h="100%"
data-event-list-container="true"

View File

@@ -10,7 +10,7 @@ import {
Box,
Text,
Tooltip,
useColorModeValue,
Badge,
useBreakpointValue,
} from '@chakra-ui/react';
import { getImportanceConfig } from '@constants/importanceLevels';
@@ -20,14 +20,20 @@ import dayjs from 'dayjs';
// 导入子组件
import {
ImportanceStamp,
EventTimeline,
EventFollowButton,
EventEngagement,
KeywordsCarousel,
} from './atoms';
import StockChangeIndicators from '@components/StockChangeIndicators';
import { GLASS_BLUR } from '@/constants/glassConfig';
import { TrendingUp, TrendingDown } from 'lucide-react';
// 数字格式化8600 → 8.6k
const formatCompactNumber = (num) => {
if (num == null) return '0';
if (num >= 10000) return (num / 10000).toFixed(1) + 'w';
if (num >= 1000) return (num / 1000).toFixed(1) + 'k';
return String(num);
};
/**
* 横向布局的动态新闻事件卡片组件
@@ -78,9 +84,11 @@ const HorizontalDynamicNewsEventCard = React.memo(({
const showTimeline = useBreakpointValue({ base: false, md: true }); // 移动端隐藏时间轴
const cardPadding = useBreakpointValue({ base: 2, md: 3 }); // 移动端减小内边距
const titleFontSize = useBreakpointValue({ base: 'sm', md: 'md' }); // 移动端减小标题字体
const titlePaddingRight = useBreakpointValue({ base: '16px', md: '120px' }); // 桌面端为关键词留空间,移动端不显示关键词
const spacing = useBreakpointValue({ base: 1, md: 3 }); // 间距(移动端更紧凑)
// 获取概念标签(优先使用 keywordsfallback 到 industry
const conceptLabel = event.keywords?.[0]?.concept || event.industry;
/**
* 根据平均涨幅计算背景色(专业配色 - 深色主题)
* @param {number} avgChange - 平均涨跌幅
@@ -148,7 +156,6 @@ const HorizontalDynamicNewsEventCard = React.memo(({
overflow="visible"
_hover={{
boxShadow: '2xl',
transform: 'translateY(-3px)',
borderColor: isSelected ? 'blue.600' : importance.color,
}}
_before={{
@@ -165,7 +172,7 @@ const HorizontalDynamicNewsEventCard = React.memo(({
borderTopRightRadius: 'xl',
opacity: 0.8,
}}
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
transition="box-shadow 0.3s, border-color 0.3s"
cursor="pointer"
onClick={() => onEventClick?.(event)}
>
@@ -197,31 +204,9 @@ const HorizontalDynamicNewsEventCard = React.memo(({
</Box>
)}
{/* 右上角:关注按钮 */}
<Box position="absolute" top={{ base: 1, md: 2 }} right={{ base: 1, md: 2 }} zIndex={2}>
<EventFollowButton
isFollowing={isFollowing}
followerCount={followerCount}
onToggle={() => onToggleFollow?.(event.id)}
size="xs"
showCount={false}
/>
</Box>
{/* Keywords梦幻轮播 - 绝对定位在卡片右侧空白处(移动端隐藏) */}
{!isMobile && event.keywords && event.keywords.length > 0 && (
<KeywordsCarousel
keywords={event.keywords}
interval={4000}
onKeywordClick={(keyword) => {
console.log('Keyword clicked:', keyword);
// TODO: 实现关键词筛选功能
}}
/>
)}
<VStack align="stretch" spacing={1.5}>
{/* 标题 - 最多两行hover 显示完整内容 */}
<VStack align="stretch" spacing={2}>
{/* 第一行:概念标签 + 标题(内联布局) */}
<Tooltip
label={event.title}
placement="top"
@@ -237,15 +222,36 @@ const HorizontalDynamicNewsEventCard = React.memo(({
cursor="pointer"
onClick={(e) => onTitleClick?.(e, event)}
mt={1}
paddingRight={titlePaddingRight}
paddingLeft={isMobile ? '70px' : undefined}
lineHeight="1.6"
css={{
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden',
}}
>
{/* 概念标签(内联) */}
{conceptLabel && (
<Badge
as="span"
colorScheme="purple"
fontSize="xs"
borderRadius="md"
px={1.5}
py={0.5}
mr={1.5}
verticalAlign="middle"
>
{conceptLabel}
</Badge>
)}
{/* 标题(内联) */}
<Text
as="span"
fontSize={titleFontSize}
fontWeight="semibold"
color={linkColor}
lineHeight="1.4"
noOfLines={2}
_hover={{ textDecoration: 'underline' }}
>
{event.title}
@@ -253,33 +259,74 @@ const HorizontalDynamicNewsEventCard = React.memo(({
</Box>
</Tooltip>
{/* 第二行:涨跌幅数据(左) + 互动指标(右) */}
<HStack justify="space-between" align="center" w="full">
{/* 左侧:涨跌幅数据,没有时用空盒子占位 */}
<Box flex="1">
<StockChangeIndicators
maxChange={event.related_max_chg}
avgChange={event.related_avg_chg}
expectationScore={event.expectation_surprise_score}
size={indicatorSize}
/>
</Box>
{/* 底部:左侧涨跌幅 + 右侧工具栏 */}
<HStack justify="space-between" align="center" w="full" pt={1}>
{/* 左侧:涨跌幅指标(保持默认样式) */}
<StockChangeIndicators
avgChange={event.related_avg_chg}
maxChange={event.related_max_chg}
expectationScore={event.expectation_score || event.expectation_surprise_score}
size="default"
/>
{/* 右侧:互动指标,始终靠右 */}
{showEngagement && (
<Box flexShrink={0}>
<EventEngagement
eventId={event.id}
viewCount={event.view_count}
followerCount={event.follower_count}
bullishCount={event.bullish_count}
bearishCount={event.bearish_count}
userVote={event.user_vote}
size="md"
onVoteChange={onVoteChange}
/>
</Box>
)}
{/* 右侧:工具栏 */}
<HStack spacing={1} align="center" onClick={(e) => e.stopPropagation()}>
{/* 浏览量 - 上下结构 */}
<VStack spacing={0} align="center">
<Text fontSize="xs" color="gray.400">👁</Text>
<Text fontSize="2xs" color="gray.400">{formatCompactNumber(event.view_count)}</Text>
</VStack>
{/* 收藏按钮 - 上下结构 */}
<EventFollowButton
isFollowing={isFollowing}
followerCount={followerCount}
onToggle={() => onToggleFollow?.(event.id)}
size="sm"
showCount={true}
variant="minimal"
layout="vertical"
/>
{/* 合并投票按钮 - 左红右绿,上图标下文字 */}
<HStack spacing={0} borderRadius="md" overflow="hidden">
{/* 看涨 - 红色左半边 */}
<Tooltip label="看涨" placement="top" hasArrow>
<VStack
as="button"
bg="#C53030"
color="white"
px={2}
py={1}
spacing={0}
_hover={{ bg: '#9B2C2C' }}
transition="all 0.2s"
onClick={() => onVoteChange?.({ eventId: event.id, voteType: 'bullish' })}
>
<TrendingUp size={14} />
<Text fontSize="2xs" fontWeight="semibold">{formatCompactNumber(event.bullish_count)}</Text>
</VStack>
</Tooltip>
{/* 看跌 - 绿色右半边 */}
<Tooltip label="看跌" placement="top" hasArrow>
<VStack
as="button"
bg="#38A169"
color="white"
px={2}
py={1}
spacing={0}
_hover={{ bg: '#2F855A' }}
transition="all 0.2s"
onClick={() => onVoteChange?.({ eventId: event.id, voteType: 'bearish' })}
>
<TrendingDown size={14} />
<Text fontSize="2xs" fontWeight="semibold">{formatCompactNumber(event.bearish_count)}</Text>
</VStack>
</Tooltip>
</HStack>
</HStack>
</HStack>
</VStack>
</CardBody>