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;