Files
vf_react/src/views/Community/components/EventCard/HorizontalDynamicNewsEventCard.js
zzlgreat d5858d5d14 fix: 恢复原有涨跌幅样式,将周涨幅改为超预期得分
- 恢复HorizontalDynamicNewsEventCard使用StockChangeIndicators组件
- 修改StockChangeIndicators:周涨幅→超预期得分,平均涨幅→平均超额,最大涨幅→最大超额
- 超预期得分显示为分数形式(如60分),根据分数显示不同颜色
2025-12-03 08:38:17 +08:00

264 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/views/Community/components/EventCard/HorizontalDynamicNewsEventCard.js
// 横向布局的动态新闻事件卡片组件(时间在左,卡片在右)
import React from 'react';
import {
HStack,
Card,
CardBody,
VStack,
Box,
Text,
Tooltip,
useColorModeValue,
useBreakpointValue,
} from '@chakra-ui/react';
import { getImportanceConfig } from '../../../../constants/importanceLevels';
import { PROFESSIONAL_COLORS } from '../../../../constants/professionalTheme';
import { useDevice } from '@hooks/useDevice';
import dayjs from 'dayjs';
// 导入子组件
import ImportanceStamp from './ImportanceStamp';
import EventTimeline from './EventTimeline';
import EventFollowButton from './EventFollowButton';
import StockChangeIndicators from '../../../../components/StockChangeIndicators';
import KeywordsCarousel from './KeywordsCarousel';
/**
* 横向布局的动态新闻事件卡片组件
* @param {Object} props
* @param {Object} props.event - 事件对象
* @param {number} props.index - 事件索引
* @param {boolean} props.isFollowing - 是否已关注
* @param {number} props.followerCount - 关注数
* @param {boolean} props.isSelected - 是否被选中
* @param {Function} props.onEventClick - 卡片点击事件
* @param {Function} props.onTitleClick - 标题点击事件
* @param {Function} props.onToggleFollow - 切换关注事件
* @param {Object} props.timelineStyle - 时间轴样式配置
* @param {string} props.borderColor - 边框颜色
* @param {string} props.indicatorSize - 涨幅指标尺寸 ('default' | 'comfortable' | 'large')
* @param {string} props.layout - 布局模式 ('vertical' | 'four-row'),影响时间轴竖线高度
*/
const HorizontalDynamicNewsEventCard = React.memo(({
event,
index,
isFollowing,
followerCount,
isSelected = false,
onEventClick,
onTitleClick,
onToggleFollow,
timelineStyle,
borderColor: timelineBorderColor,
indicatorSize = 'comfortable',
layout = 'vertical',
}) => {
const importance = getImportanceConfig(event.importance);
const { isMobile } = useDevice();
// 专业配色 - 黑色、灰色、金色主题
const cardBg = PROFESSIONAL_COLORS.background.card;
const cardBgHover = PROFESSIONAL_COLORS.background.cardHover;
const selectedBg = 'rgba(255, 195, 0, 0.1)'; // 金色半透明
const selectedBorderColor = PROFESSIONAL_COLORS.gold[500];
const linkColor = PROFESSIONAL_COLORS.text.primary;
const borderColor = PROFESSIONAL_COLORS.border.default;
// 响应式布局
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 }); // 间距(移动端更紧凑)
/**
* 根据平均涨幅计算背景色(专业配色 - 深色主题)
* @param {number} avgChange - 平均涨跌幅
* @returns {string} CSS 颜色值
*/
const getChangeBasedBgColor = (avgChange) => {
const numChange = Number(avgChange);
// 如果没有涨跌幅数据,使用默认卡片背景
if (avgChange == null || isNaN(numChange)) {
return PROFESSIONAL_COLORS.background.card;
}
// 根据涨跌幅分级返回深色主题背景色
const absChange = Math.abs(numChange);
if (numChange > 0) {
// 涨:深红色系
if (absChange >= 9) return 'rgba(239, 68, 68, 0.15)';
if (absChange >= 7) return 'rgba(239, 68, 68, 0.12)';
if (absChange >= 5) return 'rgba(239, 68, 68, 0.1)';
if (absChange >= 3) return 'rgba(239, 68, 68, 0.08)';
return 'rgba(239, 68, 68, 0.05)';
} else if (numChange < 0) {
// 跌:深绿色系
if (absChange >= 9) return 'rgba(16, 185, 129, 0.15)';
if (absChange >= 7) return 'rgba(16, 185, 129, 0.12)';
if (absChange >= 5) return 'rgba(16, 185, 129, 0.1)';
if (absChange >= 3) return 'rgba(16, 185, 129, 0.08)';
return 'rgba(16, 185, 129, 0.05)';
}
return PROFESSIONAL_COLORS.background.card;
};
return (
<HStack align="stretch" spacing={spacing} w="full">
{/* 左侧时间轴 - 移动端隐藏 */}
{showTimeline && (
<EventTimeline
createdAt={event.created_at}
timelineStyle={timelineStyle}
borderColor={timelineBorderColor}
minHeight={layout === 'four-row' ? '60px' : 0}
importance={importance}
/>
)}
{/* 右侧事件卡片容器 */}
<Box flex="1" position="relative">
{/* 事件卡片 - 增强美化效果 */}
<Card
position="relative"
bg={isSelected
? selectedBg
: getChangeBasedBgColor(event.related_avg_chg)
}
backdropFilter="blur(10px)" // 毛玻璃效果
borderWidth={isSelected ? "2px" : "1px"}
borderColor={isSelected
? selectedBorderColor
: borderColor
}
borderRadius="xl"
boxShadow={isSelected ? "xl" : "md"}
overflow="visible"
_hover={{
boxShadow: '2xl',
transform: 'translateY(-3px)',
borderColor: isSelected ? 'blue.600' : importance.color,
}}
_before={{
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: '3px',
background: isSelected
? PROFESSIONAL_COLORS.gradients.gold
: `linear-gradient(90deg, ${PROFESSIONAL_COLORS.importance[importance.level]?.border || PROFESSIONAL_COLORS.gold[500]} 0%, transparent 100%)`,
borderTopLeftRadius: 'xl',
borderTopRightRadius: 'xl',
opacity: 0.8,
}}
transition="all 0.3s cubic-bezier(0.4, 0, 0.2, 1)"
cursor="pointer"
onClick={() => onEventClick?.(event)}
>
<CardBody p={cardPadding} pb={2}>
{/* 左上角:移动端时间显示 */}
{isMobile && (
<Box
position="absolute"
top={1}
left={1}
zIndex={2}
{...(timelineStyle.bgGradient ? { bgGradient: timelineStyle.bgGradient } : { bg: timelineStyle.bg })}
borderWidth={timelineStyle.borderWidth}
borderColor={timelineStyle.borderColor}
borderRadius="md"
px={1.5}
py={1}
textAlign="center"
boxShadow={timelineStyle.boxShadow}
>
<Text
fontSize="9px"
fontWeight="bold"
color={timelineStyle.textColor}
lineHeight="1.2"
>
{dayjs(event.created_at).format('MM-DD HH:mm')}
</Text>
</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 显示完整内容 */}
<Tooltip
label={event.title}
placement="top"
hasArrow
bg="gray.700"
color="white"
fontSize="sm"
p={2}
borderRadius="md"
isDisabled={event.title.length < 40}
>
<Box
cursor="pointer"
onClick={(e) => onTitleClick?.(e, event)}
mt={1}
paddingRight={titlePaddingRight}
paddingLeft={isMobile ? '70px' : undefined}
>
<Text
fontSize={titleFontSize}
fontWeight="semibold"
color={linkColor}
lineHeight="1.4"
noOfLines={2}
_hover={{ textDecoration: 'underline' }}
>
{event.title}
</Text>
</Box>
</Tooltip>
{/* 第二行:涨跌幅数据 */}
<StockChangeIndicators
maxChange={event.related_max_chg}
avgChange={event.related_avg_chg}
expectationScore={event.expectation_surprise_score}
size={indicatorSize}
/>
</VStack>
</CardBody>
</Card>
</Box>
</HStack>
);
});
export default HorizontalDynamicNewsEventCard;