feat: 添加事件详情面板

This commit is contained in:
zdl
2025-10-31 14:38:43 +08:00
parent c372832f1f
commit 9fd9fcb731
3 changed files with 346 additions and 7 deletions

View File

@@ -1,7 +1,7 @@
// src/views/Community/components/DynamicNewsCard.js
// 横向滚动事件卡片组件(实时要闻·动态追踪)
import React, { forwardRef, useRef, useState } from 'react';
import React, { forwardRef, useRef, useState, useEffect } from 'react';
import {
Card,
CardHeader,
@@ -20,6 +20,7 @@ import {
} from '@chakra-ui/react';
import { ChevronLeftIcon, ChevronRightIcon, TimeIcon } from '@chakra-ui/icons';
import DynamicNewsEventCard from './EventCard/DynamicNewsEventCard';
import DynamicNewsDetailPanel from './DynamicNewsDetailPanel';
/**
* 实时要闻·动态追踪 - 横向滚动卡片组件
@@ -43,6 +44,14 @@ const DynamicNewsCard = forwardRef(({
const scrollContainerRef = useRef(null);
const [showLeftArrow, setShowLeftArrow] = useState(false);
const [showRightArrow, setShowRightArrow] = useState(true);
const [selectedEvent, setSelectedEvent] = useState(null);
// 默认选中第一个事件
useEffect(() => {
if (events && events.length > 0 && !selectedEvent) {
setSelectedEvent(events[0]);
}
}, [events, selectedEvent]);
// 滚动到左侧
const scrollLeft = () => {
@@ -214,11 +223,15 @@ const DynamicNewsCard = forwardRef(({
index={index}
isFollowing={false}
followerCount={event.follower_count || 0}
onEventClick={onEventClick}
onEventClick={(clickedEvent) => {
setSelectedEvent(clickedEvent);
if (onEventClick) onEventClick(clickedEvent);
}}
onTitleClick={(e) => {
e.preventDefault();
e.stopPropagation();
onEventClick(event);
setSelectedEvent(event);
if (onEventClick) onEventClick(event);
}}
onToggleFollow={() => {}}
timelineStyle={getTimelineBoxStyle()}
@@ -229,6 +242,13 @@ const DynamicNewsCard = forwardRef(({
</Flex>
</Box>
)}
{/* 详情面板 */}
{!loading && events && events.length > 0 && (
<Box mt={6}>
<DynamicNewsDetailPanel event={selectedEvent} />
</Box>
)}
</CardBody>
</Card>
);

View File

@@ -0,0 +1,201 @@
// src/views/Community/components/DynamicNewsDetailPanel.js
// 动态新闻详情面板组件(固定展示,非弹窗)
import React from 'react';
import {
Box,
Card,
CardBody,
VStack,
HStack,
Text,
Badge,
Tag,
Divider,
Heading,
List,
ListItem,
Button,
useColorModeValue,
} from '@chakra-ui/react';
import moment from 'moment';
import { getImportanceConfig } from '../../../constants/importanceLevels';
import HistoricalEvents from '../../EventDetail/components/HistoricalEvents';
import TransmissionChainAnalysis from '../../EventDetail/components/TransmissionChainAnalysis';
import { eventService } from '../../../services/eventService';
/**
* 动态新闻详情面板组件
* @param {Object} props
* @param {Object} props.event - 事件对象(包含详情数据)
*/
const DynamicNewsDetailPanel = ({ event }) => {
const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const headingColor = useColorModeValue('gray.700', 'gray.200');
const textColor = useColorModeValue('gray.600', 'gray.400');
if (!event) {
return (
<Card bg={cardBg} borderColor={borderColor} borderWidth="1px">
<CardBody>
<Text color={textColor} textAlign="center">请选择一个事件查看详情</Text>
</CardBody>
</Card>
);
}
const importance = getImportanceConfig(event.importance);
// 渲染涨跌幅标签
const renderPriceTag = (value, label) => {
if (value === null || value === undefined) return `${label}: --`;
const color = value > 0 ? 'red' : value < 0 ? 'green' : 'gray';
const prefix = value > 0 ? '+' : '';
return (
<Tag colorScheme={color} mr={2}>
{label}: {prefix}{value.toFixed(2)}%
</Tag>
);
};
return (
<Card bg={cardBg} borderColor={borderColor} borderWidth="1px">
<CardBody>
<VStack align="stretch" spacing={4}>
{/* 标题 */}
<Heading size="md" color={headingColor}>
{event.title}
</Heading>
<Divider />
{/* 基本信息 */}
<VStack align="stretch" spacing={2}>
<HStack spacing={4} flexWrap="wrap">
<Text fontSize="sm" color={textColor}>
<strong>创建时间</strong>
{moment(event.created_at).format('YYYY-MM-DD HH:mm:ss')}
</Text>
<Text fontSize="sm" color={textColor}>
<strong>重要性</strong>
<Badge colorScheme={importance.level === 'S' ? 'purple' : importance.level === 'A' ? 'red' : importance.level === 'B' ? 'orange' : 'green'} ml={1}>
{importance.level}
</Badge>
</Text>
<Text fontSize="sm" color={textColor}>
<strong>浏览数</strong>{event.view_count || 0}
</Text>
</HStack>
{/* 涨跌幅统计 */}
<HStack spacing={2} flexWrap="wrap">
{renderPriceTag(event.related_avg_chg, '平均涨幅')}
{renderPriceTag(event.related_max_chg, '最大涨幅')}
{renderPriceTag(event.related_week_chg, '周涨幅')}
</HStack>
</VStack>
<Divider />
{/* 事件描述 */}
{event.description && (
<Box>
<Heading size="sm" color={headingColor} mb={2}>
事件描述
</Heading>
<Text fontSize="sm" color={textColor} lineHeight="tall">
{event.description}
</Text>
</Box>
)}
{/* 相关概念 */}
{event.keywords && event.keywords.length > 0 && (
<Box>
<Heading size="sm" color={headingColor} mb={2}>
相关概念
</Heading>
<HStack spacing={2} flexWrap="wrap">
{event.keywords.map((keyword, index) => (
<Tag key={index} colorScheme="blue" size="sm">
{keyword}
</Tag>
))}
</HStack>
</Box>
)}
{/* 相关股票 */}
{event.related_stocks && event.related_stocks.length > 0 && (
<Box>
<Heading size="sm" color={headingColor} mb={2}>
相关股票
</Heading>
<List spacing={2}>
{event.related_stocks.map((stock, index) => (
<ListItem
key={index}
p={2}
borderWidth="1px"
borderColor={borderColor}
borderRadius="md"
>
<HStack justify="space-between">
<VStack align="start" spacing={0}>
<Text fontSize="sm" fontWeight="bold">
{stock.stock_name} ({stock.stock_code})
</Text>
<Text fontSize="xs" color={textColor}>
{stock.relation_desc || '相关股票'}
</Text>
</VStack>
<Button
size="sm"
colorScheme="blue"
onClick={() => {
const stockCode = stock.stock_code.split('.')[0];
window.open(`https://valuefrontier.cn/company?scode=${stockCode}`, '_blank');
}}
>
查看详情
</Button>
</HStack>
</ListItem>
))}
</List>
</Box>
)}
{/* 历史事件对比 */}
<Box>
<Heading size="sm" color={headingColor} mb={2}>
历史事件对比
</Heading>
<HistoricalEvents
eventId={event.id}
eventTitle={event.title}
historicalEvents={event.historical_events || []}
eventService={eventService}
/>
</Box>
{/* 传导链分析 */}
<Box>
<Heading size="sm" color={headingColor} mb={2}>
传导链分析
</Heading>
<TransmissionChainAnalysis
eventId={event.id}
eventService={eventService}
/>
</Box>
</VStack>
</CardBody>
</Card>
);
};
export default DynamicNewsDetailPanel;