233 lines
9.9 KiB
JavaScript
233 lines
9.9 KiB
JavaScript
// 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 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 = ({
|
||
event,
|
||
index,
|
||
isFollowing,
|
||
followerCount,
|
||
isSelected = false,
|
||
onEventClick,
|
||
onTitleClick,
|
||
onToggleFollow,
|
||
timelineStyle,
|
||
borderColor: timelineBorderColor,
|
||
indicatorSize = 'comfortable',
|
||
layout = 'vertical',
|
||
}) => {
|
||
const importance = getImportanceConfig(event.importance);
|
||
|
||
// 专业配色 - 黑色、灰色、金色主题
|
||
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: '80px', md: '120px' }); // 为关键词留空间
|
||
const spacing = useBreakpointValue({ base: 2, 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}>
|
||
{/* 右上角:关注按钮 */}
|
||
<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梦幻轮播 - 绝对定位在卡片右侧空白处 */}
|
||
{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}
|
||
>
|
||
<Text
|
||
fontSize={titleFontSize}
|
||
fontWeight="semibold"
|
||
color={linkColor}
|
||
lineHeight="1.4"
|
||
noOfLines={2}
|
||
_hover={{ textDecoration: 'underline' }}
|
||
>
|
||
{event.title}
|
||
</Text>
|
||
</Box>
|
||
</Tooltip>
|
||
|
||
{/* 第二行:涨跌幅数据 */}
|
||
<StockChangeIndicators
|
||
avgChange={event.related_avg_chg}
|
||
maxChange={event.related_max_chg}
|
||
weekChange={event.related_week_chg}
|
||
size={indicatorSize}
|
||
/>
|
||
</VStack>
|
||
</CardBody>
|
||
</Card>
|
||
</Box>
|
||
</HStack>
|
||
);
|
||
};
|
||
|
||
export default HorizontalDynamicNewsEventCard;
|