diff --git a/src/mocks/data/events.js b/src/mocks/data/events.js
index 652de263..cc6d2d0d 100644
--- a/src/mocks/data/events.js
+++ b/src/mocks/data/events.js
@@ -845,6 +845,8 @@ export function generateMockEvents(params = {}) {
is_ai_generated: i % 4 === 0, // 25% 的事件是AI生成
industry: industry,
related_stocks: relatedStocks, // 添加相关股票列表
+ historical_events: generateHistoricalEvents(industry, i), // 添加历史事件
+ transmission_chain: generateTransmissionChain(industry, i), // 添加传导链数据
});
}
diff --git a/src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js b/src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js
new file mode 100644
index 00000000..c4a7274d
--- /dev/null
+++ b/src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js
@@ -0,0 +1,41 @@
+// src/views/Community/components/DynamicNewsDetail/CollapsibleSection.js
+// 通用可折叠区块组件
+
+import React from 'react';
+import {
+ Box,
+ Collapse,
+ useColorModeValue,
+} from '@chakra-ui/react';
+import CollapsibleHeader from './CollapsibleHeader';
+
+/**
+ * 通用可折叠区块组件
+ * @param {Object} props
+ * @param {string} props.title - 标题文本
+ * @param {boolean} props.isOpen - 是否展开
+ * @param {Function} props.onToggle - 切换展开/收起的回调
+ * @param {number} props.count - 可选的数量徽章
+ * @param {React.ReactNode} props.children - 子内容
+ */
+const CollapsibleSection = ({ title, isOpen, onToggle, count = null, children }) => {
+ const sectionBg = useColorModeValue('gray.50', 'gray.750');
+
+ return (
+
+
+
+
+ {children}
+
+
+
+ );
+};
+
+export default CollapsibleSection;
diff --git a/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js
new file mode 100644
index 00000000..9e712c2d
--- /dev/null
+++ b/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js
@@ -0,0 +1,207 @@
+// src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js
+// 动态新闻详情面板主组件(组装所有子组件)
+
+import React, { useState, useMemo, useCallback } from 'react';
+import {
+ Card,
+ CardBody,
+ VStack,
+ Text,
+ useColorModeValue,
+ useToast,
+} from '@chakra-ui/react';
+import { getImportanceConfig } from '../../../../constants/importanceLevels';
+import { eventService } from '../../../../services/eventService';
+import EventHeaderInfo from './EventHeaderInfo';
+import EventDescriptionSection from './EventDescriptionSection';
+import RelatedConceptsSection from './RelatedConceptsSection';
+import RelatedStocksSection from './RelatedStocksSection';
+import CollapsibleSection from './CollapsibleSection';
+import HistoricalEvents from '../../../EventDetail/components/HistoricalEvents';
+import TransmissionChainAnalysis from '../../../EventDetail/components/TransmissionChainAnalysis';
+
+/**
+ * 动态新闻详情面板主组件
+ * @param {Object} props
+ * @param {Object} props.event - 事件对象(包含详情数据)
+ */
+const DynamicNewsDetailPanel = ({ event }) => {
+ const cardBg = useColorModeValue('white', 'gray.800');
+ const borderColor = useColorModeValue('gray.200', 'gray.700');
+ const textColor = useColorModeValue('gray.600', 'gray.400');
+ const toast = useToast();
+
+ // 折叠状态管理
+ const [isStocksOpen, setIsStocksOpen] = useState(true);
+ const [isHistoricalOpen, setIsHistoricalOpen] = useState(false);
+ const [isTransmissionOpen, setIsTransmissionOpen] = useState(false);
+
+ // 关注状态管理
+ const [isFollowing, setIsFollowing] = useState(false);
+ const [followerCount, setFollowerCount] = useState(0);
+
+ // 自选股管理(使用 localStorage)
+ const [watchlistSet, setWatchlistSet] = useState(() => {
+ try {
+ const saved = localStorage.getItem('stock_watchlist');
+ return saved ? new Set(JSON.parse(saved)) : new Set();
+ } catch {
+ return new Set();
+ }
+ });
+
+ // 生成模拟行情数据
+ const quotes = useMemo(() => {
+ if (!event?.related_stocks) return {};
+
+ const quotesData = {};
+ event.related_stocks.forEach(stock => {
+ // 优先使用 stock.daily_change,否则生成随机涨跌幅
+ const change = stock.daily_change
+ ? parseFloat(stock.daily_change)
+ : (Math.random() * 10 - 3); // -3% ~ +7%
+
+ quotesData[stock.stock_code] = {
+ change: change,
+ price: 10 + Math.random() * 90 // 模拟价格 10-100
+ };
+ });
+
+ return quotesData;
+ }, [event?.related_stocks]);
+
+ // 切换关注状态
+ const handleToggleFollow = async () => {
+ try {
+ if (isFollowing) {
+ // 取消关注
+ await eventService.unfollowEvent(event.id);
+ setIsFollowing(false);
+ setFollowerCount(prev => Math.max(0, prev - 1));
+ } else {
+ // 添加关注
+ await eventService.followEvent(event.id);
+ setIsFollowing(true);
+ setFollowerCount(prev => prev + 1);
+ }
+ } catch (error) {
+ console.error('切换关注状态失败:', error);
+ }
+ };
+
+ // 切换自选股
+ const handleWatchlistToggle = useCallback(async (stockCode, isInWatchlist) => {
+ try {
+ const newWatchlist = new Set(watchlistSet);
+
+ if (isInWatchlist) {
+ newWatchlist.delete(stockCode);
+ toast({
+ title: '已移除自选股',
+ status: 'info',
+ duration: 2000,
+ isClosable: true,
+ });
+ } else {
+ newWatchlist.add(stockCode);
+ toast({
+ title: '已添加至自选股',
+ status: 'success',
+ duration: 2000,
+ isClosable: true,
+ });
+ }
+
+ setWatchlistSet(newWatchlist);
+ localStorage.setItem('stock_watchlist', JSON.stringify(Array.from(newWatchlist)));
+ } catch (error) {
+ console.error('切换自选股失败:', error);
+ toast({
+ title: '操作失败',
+ description: error.message,
+ status: 'error',
+ duration: 3000,
+ isClosable: true,
+ });
+ }
+ }, [watchlistSet, toast]);
+
+ // 空状态
+ if (!event) {
+ return (
+
+
+
+ 请选择一个事件查看详情
+
+
+
+ );
+ }
+
+ const importance = getImportanceConfig(event.importance);
+
+ return (
+
+
+
+ {/* 头部信息区 */}
+
+
+ {/* 事件描述 */}
+
+
+ {/* 相关概念 */}
+
+
+ {/* 相关股票(可折叠) */}
+ setIsStocksOpen(!isStocksOpen)}
+ onWatchlistToggle={handleWatchlistToggle}
+ />
+
+ {/* 历史事件对比(可折叠) */}
+ setIsHistoricalOpen(!isHistoricalOpen)}
+ count={event.historical_events?.length || 0}
+ >
+
+
+
+ {/* 传导链分析(可折叠) */}
+ setIsTransmissionOpen(!isTransmissionOpen)}
+ >
+
+
+
+
+
+ );
+};
+
+export default DynamicNewsDetailPanel;
diff --git a/src/views/Community/index.js b/src/views/Community/index.js
index 41c6cd82..e88cb2b1 100644
--- a/src/views/Community/index.js
+++ b/src/views/Community/index.js
@@ -110,9 +110,10 @@ const Community = () => {
const { generateMockEvents } = await import('../../mocks/data/events');
const mockData = generateMockEvents({ page: 1, per_page: 30 });
- // 调试:检查第一个事件的 related_stocks 数据
- if (mockData.events[0]?.related_stocks) {
+ // 调试:检查第一个事件的 related_stocks 和 historical_events 数据
+ if (mockData.events[0]) {
console.log('Mock 数据第一个事件的股票:', mockData.events[0].related_stocks);
+ console.log('Mock 数据第一个事件的历史事件:', mockData.events[0].historical_events);
}
setDynamicNewsEvents(mockData.events);