From 9fd9fcb731fcbbae636bd891c1694bf5bec21a72 Mon Sep 17 00:00:00 2001 From: zdl <3489966805@qq.com> Date: Fri, 31 Oct 2025 14:38:43 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E4=BA=8B=E4=BB=B6?= =?UTF-8?q?=E8=AF=A6=E6=83=85=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mocks/data/events.js | 126 ++++++++++- .../Community/components/DynamicNewsCard.js | 26 ++- .../components/DynamicNewsDetailPanel.js | 201 ++++++++++++++++++ 3 files changed, 346 insertions(+), 7 deletions(-) create mode 100644 src/views/Community/components/DynamicNewsDetailPanel.js diff --git a/src/mocks/data/events.js b/src/mocks/data/events.js index 565d3cc4..dd640f73 100644 --- a/src/mocks/data/events.js +++ b/src/mocks/data/events.js @@ -817,6 +817,105 @@ export function generatePopularKeywords(limit = 20) { })); } +/** + * 生成历史事件对比数据 + * @param {string} industry - 行业 + * @param {number} index - 索引 + * @returns {Array} - 历史事件列表 + */ +function generateHistoricalEvents(industry, index) { + const historicalCount = 3 + (index % 3); // 3-5个历史事件 + const historical = []; + const baseDate = new Date(); + + for (let i = 0; i < historicalCount; i++) { + // 生成过去1-6个月的随机时间 + const monthsAgo = 1 + Math.floor(Math.random() * 6); + const eventDate = new Date(baseDate); + eventDate.setMonth(eventDate.getMonth() - monthsAgo); + + const similarityScore = 0.6 + Math.random() * 0.35; // 60%-95%相似度 + + historical.push({ + id: `hist_${industry}_${index}_${i}`, + title: generateEventTitle(industry, i + index * 10), + created_at: eventDate.toISOString(), + related_avg_chg: parseFloat((Math.random() * 15 - 3).toFixed(2)), + related_max_chg: parseFloat((Math.random() * 25).toFixed(2)), + similarity_score: parseFloat(similarityScore.toFixed(2)), + view_count: Math.floor(Math.random() * 3000) + 500, + }); + } + + // 按相似度排序 + historical.sort((a, b) => b.similarity_score - a.similarity_score); + return historical; +} + +/** + * 生成传导链数据 + * @param {string} industry - 行业 + * @param {number} index - 索引 + * @returns {Object} - 传导链数据 { nodes, edges } + */ +function generateTransmissionChain(industry, index) { + const nodeTypes = ['event', 'industry', 'company', 'policy', 'technology', 'market']; + const impactTypes = ['positive', 'negative', 'neutral', 'mixed']; + const strengthLevels = ['strong', 'medium', 'weak']; + + const nodes = []; + const edges = []; + + // 主事件节点 + nodes.push({ + id: 1, + name: '主事件', + type: 'event', + extra: { is_main_event: true, description: `${industry}重要事件` } + }); + + // 生成5-8个相关节点 + const nodeCount = 5 + (index % 4); + for (let i = 2; i <= nodeCount; i++) { + const nodeType = nodeTypes[i % nodeTypes.length]; + const industryStock = stockPool.find(s => s.industry === industry); + + let nodeName; + if (nodeType === 'company' && industryStock) { + nodeName = industryStock.name; + } else if (nodeType === 'industry') { + nodeName = `${industry}产业`; + } else if (nodeType === 'policy') { + nodeName = '相关政策'; + } else if (nodeType === 'technology') { + nodeName = '技术创新'; + } else if (nodeType === 'market') { + nodeName = '市场需求'; + } else { + nodeName = `节点${i}`; + } + + nodes.push({ + id: i, + name: nodeName, + type: nodeType, + extra: { description: `${nodeName}相关信息` } + }); + + // 创建与主事件或其他节点的连接 + const targetId = i === 2 ? 1 : Math.max(1, Math.floor(Math.random() * (i - 1)) + 1); + edges.push({ + source: targetId, + target: i, + impact: impactTypes[i % impactTypes.length], + strength: strengthLevels[i % strengthLevels.length], + description: `传导路径${i}` + }); + } + + return { nodes, edges }; +} + /** * 生成动态新闻事件(实时要闻·动态追踪专用) * @param {Object} timeRange - 时间范围 { startTime, endTime } @@ -855,23 +954,40 @@ export function generateDynamicNewsEvents(timeRange = null, count = 30) { const relatedMaxChg = (Math.random() * 25).toFixed(2); // 0% 到 25% const relatedWeekChg = (Math.random() * 30 - 10).toFixed(2); // -10% 到 20% - // 为每个事件随机选择2-5个相关股票 + // 为每个事件随机选择2-5个相关股票(完整对象) const relatedStockCount = 2 + (i % 4); const relatedStocks = []; const industryStocks = stockPool.filter(s => s.industry === industry); + const relationDescriptions = [ + '直接受益标的', + '产业链上游企业', + '产业链下游企业', + '行业龙头企业', + '潜在受益标的', + '概念相关个股' + ]; // 优先选择同行业股票 if (industryStocks.length > 0) { for (let j = 0; j < Math.min(relatedStockCount, industryStocks.length); j++) { - relatedStocks.push(industryStocks[j % industryStocks.length].stock_code); + const stock = industryStocks[j % industryStocks.length]; + relatedStocks.push({ + stock_code: stock.stock_code, + stock_name: stock.name, + relation_desc: relationDescriptions[j % relationDescriptions.length] + }); } } // 如果同行业股票不够,从整个 stockPool 中补充 while (relatedStocks.length < relatedStockCount && relatedStocks.length < stockPool.length) { const randomStock = stockPool[relatedStocks.length % stockPool.length]; - if (!relatedStocks.includes(randomStock.stock_code)) { - relatedStocks.push(randomStock.stock_code); + if (!relatedStocks.some(s => s.stock_code === randomStock.stock_code)) { + relatedStocks.push({ + stock_code: randomStock.stock_code, + stock_name: randomStock.name, + relation_desc: relationDescriptions[relatedStocks.length % relationDescriptions.length] + }); } } @@ -896,6 +1012,8 @@ export function generateDynamicNewsEvents(timeRange = null, count = 30) { is_ai_generated: i % 3 === 0, // 33% 的事件是AI生成 industry: industry, related_stocks: relatedStocks, + historical_events: generateHistoricalEvents(industry, i), + transmission_chain: generateTransmissionChain(industry, i), creator: { username: authorPool[i % authorPool.length], avatar_url: null diff --git a/src/views/Community/components/DynamicNewsCard.js b/src/views/Community/components/DynamicNewsCard.js index 02768e41..97483a7d 100644 --- a/src/views/Community/components/DynamicNewsCard.js +++ b/src/views/Community/components/DynamicNewsCard.js @@ -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(({ )} + + {/* 详情面板 */} + {!loading && events && events.length > 0 && ( + + + + )} ); diff --git a/src/views/Community/components/DynamicNewsDetailPanel.js b/src/views/Community/components/DynamicNewsDetailPanel.js new file mode 100644 index 00000000..2bb56d21 --- /dev/null +++ b/src/views/Community/components/DynamicNewsDetailPanel.js @@ -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 ( + + + 请选择一个事件查看详情 + + + ); + } + + 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 ( + + {label}: {prefix}{value.toFixed(2)}% + + ); + }; + + return ( + + + + {/* 标题 */} + + {event.title} + + + + + {/* 基本信息 */} + + + + 创建时间: + {moment(event.created_at).format('YYYY-MM-DD HH:mm:ss')} + + + 重要性: + + {importance.level}级 + + + + 浏览数:{event.view_count || 0} + + + + {/* 涨跌幅统计 */} + + {renderPriceTag(event.related_avg_chg, '平均涨幅')} + {renderPriceTag(event.related_max_chg, '最大涨幅')} + {renderPriceTag(event.related_week_chg, '周涨幅')} + + + + + + {/* 事件描述 */} + {event.description && ( + + + 事件描述 + + + {event.description} + + + )} + + {/* 相关概念 */} + {event.keywords && event.keywords.length > 0 && ( + + + 相关概念 + + + {event.keywords.map((keyword, index) => ( + + {keyword} + + ))} + + + )} + + {/* 相关股票 */} + {event.related_stocks && event.related_stocks.length > 0 && ( + + + 相关股票 + + + {event.related_stocks.map((stock, index) => ( + + + + + {stock.stock_name} ({stock.stock_code}) + + + {stock.relation_desc || '相关股票'} + + + + + + ))} + + + )} + + {/* 历史事件对比 */} + + + 历史事件对比 + + + + + {/* 传导链分析 */} + + + 传导链分析 + + + + + + + ); +}; + +export default DynamicNewsDetailPanel;