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:
@@ -87,6 +87,7 @@ const VerticalModeLayout = React.memo(({
|
|||||||
flex={isMobile ? '1' : leftFlex}
|
flex={isMobile ? '1' : leftFlex}
|
||||||
minWidth={0}
|
minWidth={0}
|
||||||
w={isMobile ? '100%' : 'auto'}
|
w={isMobile ? '100%' : 'auto'}
|
||||||
|
overflowX="hidden"
|
||||||
overflowY="auto"
|
overflowY="auto"
|
||||||
h="100%"
|
h="100%"
|
||||||
data-event-list-container="true"
|
data-event-list-container="true"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
Box,
|
Box,
|
||||||
Text,
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
useColorModeValue,
|
Badge,
|
||||||
useBreakpointValue,
|
useBreakpointValue,
|
||||||
} from '@chakra-ui/react';
|
} from '@chakra-ui/react';
|
||||||
import { getImportanceConfig } from '@constants/importanceLevels';
|
import { getImportanceConfig } from '@constants/importanceLevels';
|
||||||
@@ -20,14 +20,20 @@ import dayjs from 'dayjs';
|
|||||||
|
|
||||||
// 导入子组件
|
// 导入子组件
|
||||||
import {
|
import {
|
||||||
ImportanceStamp,
|
|
||||||
EventTimeline,
|
EventTimeline,
|
||||||
EventFollowButton,
|
EventFollowButton,
|
||||||
EventEngagement,
|
|
||||||
KeywordsCarousel,
|
|
||||||
} from './atoms';
|
} from './atoms';
|
||||||
import StockChangeIndicators from '@components/StockChangeIndicators';
|
import StockChangeIndicators from '@components/StockChangeIndicators';
|
||||||
import { GLASS_BLUR } from '@/constants/glassConfig';
|
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 showTimeline = useBreakpointValue({ base: false, md: true }); // 移动端隐藏时间轴
|
||||||
const cardPadding = useBreakpointValue({ base: 2, md: 3 }); // 移动端减小内边距
|
const cardPadding = useBreakpointValue({ base: 2, md: 3 }); // 移动端减小内边距
|
||||||
const titleFontSize = useBreakpointValue({ base: 'sm', md: 'md' }); // 移动端减小标题字体
|
const titleFontSize = useBreakpointValue({ base: 'sm', md: 'md' }); // 移动端减小标题字体
|
||||||
const titlePaddingRight = useBreakpointValue({ base: '16px', md: '120px' }); // 桌面端为关键词留空间,移动端不显示关键词
|
|
||||||
const spacing = useBreakpointValue({ base: 1, md: 3 }); // 间距(移动端更紧凑)
|
const spacing = useBreakpointValue({ base: 1, md: 3 }); // 间距(移动端更紧凑)
|
||||||
|
|
||||||
|
// 获取概念标签(优先使用 keywords,fallback 到 industry)
|
||||||
|
const conceptLabel = event.keywords?.[0]?.concept || event.industry;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据平均涨幅计算背景色(专业配色 - 深色主题)
|
* 根据平均涨幅计算背景色(专业配色 - 深色主题)
|
||||||
* @param {number} avgChange - 平均涨跌幅
|
* @param {number} avgChange - 平均涨跌幅
|
||||||
@@ -148,7 +156,6 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
overflow="visible"
|
overflow="visible"
|
||||||
_hover={{
|
_hover={{
|
||||||
boxShadow: '2xl',
|
boxShadow: '2xl',
|
||||||
transform: 'translateY(-3px)',
|
|
||||||
borderColor: isSelected ? 'blue.600' : importance.color,
|
borderColor: isSelected ? 'blue.600' : importance.color,
|
||||||
}}
|
}}
|
||||||
_before={{
|
_before={{
|
||||||
@@ -165,7 +172,7 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
borderTopRightRadius: 'xl',
|
borderTopRightRadius: 'xl',
|
||||||
opacity: 0.8,
|
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"
|
cursor="pointer"
|
||||||
onClick={() => onEventClick?.(event)}
|
onClick={() => onEventClick?.(event)}
|
||||||
>
|
>
|
||||||
@@ -197,31 +204,9 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
</Box>
|
</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梦幻轮播 - 绝对定位在卡片右侧空白处(移动端隐藏) */}
|
<VStack align="stretch" spacing={2}>
|
||||||
{!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 显示完整内容 */}
|
|
||||||
<Tooltip
|
<Tooltip
|
||||||
label={event.title}
|
label={event.title}
|
||||||
placement="top"
|
placement="top"
|
||||||
@@ -237,15 +222,36 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
cursor="pointer"
|
cursor="pointer"
|
||||||
onClick={(e) => onTitleClick?.(e, event)}
|
onClick={(e) => onTitleClick?.(e, event)}
|
||||||
mt={1}
|
mt={1}
|
||||||
paddingRight={titlePaddingRight}
|
|
||||||
paddingLeft={isMobile ? '70px' : undefined}
|
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
|
<Text
|
||||||
|
as="span"
|
||||||
fontSize={titleFontSize}
|
fontSize={titleFontSize}
|
||||||
fontWeight="semibold"
|
fontWeight="semibold"
|
||||||
color={linkColor}
|
color={linkColor}
|
||||||
lineHeight="1.4"
|
|
||||||
noOfLines={2}
|
|
||||||
_hover={{ textDecoration: 'underline' }}
|
_hover={{ textDecoration: 'underline' }}
|
||||||
>
|
>
|
||||||
{event.title}
|
{event.title}
|
||||||
@@ -253,33 +259,74 @@ const HorizontalDynamicNewsEventCard = React.memo(({
|
|||||||
</Box>
|
</Box>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
{/* 第二行:涨跌幅数据(左) + 互动指标(右) */}
|
{/* 底部:左侧涨跌幅 + 右侧工具栏 */}
|
||||||
<HStack justify="space-between" align="center" w="full">
|
<HStack justify="space-between" align="center" w="full" pt={1}>
|
||||||
{/* 左侧:涨跌幅数据,没有时用空盒子占位 */}
|
{/* 左侧:涨跌幅指标(保持默认样式) */}
|
||||||
<Box flex="1">
|
<StockChangeIndicators
|
||||||
<StockChangeIndicators
|
avgChange={event.related_avg_chg}
|
||||||
maxChange={event.related_max_chg}
|
maxChange={event.related_max_chg}
|
||||||
avgChange={event.related_avg_chg}
|
expectationScore={event.expectation_score || event.expectation_surprise_score}
|
||||||
expectationScore={event.expectation_surprise_score}
|
size="default"
|
||||||
size={indicatorSize}
|
/>
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
|
|
||||||
{/* 右侧:互动指标,始终靠右 */}
|
{/* 右侧:工具栏 */}
|
||||||
{showEngagement && (
|
<HStack spacing={1} align="center" onClick={(e) => e.stopPropagation()}>
|
||||||
<Box flexShrink={0}>
|
{/* 浏览量 - 上下结构 */}
|
||||||
<EventEngagement
|
<VStack spacing={0} align="center">
|
||||||
eventId={event.id}
|
<Text fontSize="xs" color="gray.400">👁</Text>
|
||||||
viewCount={event.view_count}
|
<Text fontSize="2xs" color="gray.400">{formatCompactNumber(event.view_count)}</Text>
|
||||||
followerCount={event.follower_count}
|
</VStack>
|
||||||
bullishCount={event.bullish_count}
|
|
||||||
bearishCount={event.bearish_count}
|
{/* 收藏按钮 - 上下结构 */}
|
||||||
userVote={event.user_vote}
|
<EventFollowButton
|
||||||
size="md"
|
isFollowing={isFollowing}
|
||||||
onVoteChange={onVoteChange}
|
followerCount={followerCount}
|
||||||
/>
|
onToggle={() => onToggleFollow?.(event.id)}
|
||||||
</Box>
|
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>
|
</HStack>
|
||||||
</VStack>
|
</VStack>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|||||||
Reference in New Issue
Block a user