feat: 提交历史事件对比组件
This commit is contained in:
@@ -845,6 +845,8 @@ export function generateMockEvents(params = {}) {
|
|||||||
is_ai_generated: i % 4 === 0, // 25% 的事件是AI生成
|
is_ai_generated: i % 4 === 0, // 25% 的事件是AI生成
|
||||||
industry: industry,
|
industry: industry,
|
||||||
related_stocks: relatedStocks, // 添加相关股票列表
|
related_stocks: relatedStocks, // 添加相关股票列表
|
||||||
|
historical_events: generateHistoricalEvents(industry, i), // 添加历史事件
|
||||||
|
transmission_chain: generateTransmissionChain(industry, i), // 添加传导链数据
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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 (
|
||||||
|
<Box>
|
||||||
|
<CollapsibleHeader
|
||||||
|
title={title}
|
||||||
|
isOpen={isOpen}
|
||||||
|
onToggle={onToggle}
|
||||||
|
count={count}
|
||||||
|
/>
|
||||||
|
<Collapse in={isOpen} animateOpacity>
|
||||||
|
<Box mt={2} bg={sectionBg} p={3} borderRadius="md">
|
||||||
|
{children}
|
||||||
|
</Box>
|
||||||
|
</Collapse>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CollapsibleSection;
|
||||||
@@ -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 (
|
||||||
|
<Card bg={cardBg} borderColor={borderColor} borderWidth="1px">
|
||||||
|
<CardBody>
|
||||||
|
<Text color={textColor} textAlign="center">
|
||||||
|
请选择一个事件查看详情
|
||||||
|
</Text>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const importance = getImportanceConfig(event.importance);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card bg={cardBg} borderColor={borderColor} borderWidth="1px">
|
||||||
|
<CardBody>
|
||||||
|
<VStack align="stretch" spacing={3}>
|
||||||
|
{/* 头部信息区 */}
|
||||||
|
<EventHeaderInfo
|
||||||
|
event={event}
|
||||||
|
importance={importance}
|
||||||
|
isFollowing={isFollowing}
|
||||||
|
followerCount={followerCount}
|
||||||
|
onToggleFollow={handleToggleFollow}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 事件描述 */}
|
||||||
|
<EventDescriptionSection description={event.description} />
|
||||||
|
|
||||||
|
{/* 相关概念 */}
|
||||||
|
<RelatedConceptsSection
|
||||||
|
keywords={event.keywords}
|
||||||
|
effectiveTradingDate={event.trading_date}
|
||||||
|
eventTime={event.created_at}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 相关股票(可折叠) */}
|
||||||
|
<RelatedStocksSection
|
||||||
|
stocks={event.related_stocks}
|
||||||
|
quotes={quotes}
|
||||||
|
eventTime={event.created_at}
|
||||||
|
watchlistSet={watchlistSet}
|
||||||
|
isOpen={isStocksOpen}
|
||||||
|
onToggle={() => setIsStocksOpen(!isStocksOpen)}
|
||||||
|
onWatchlistToggle={handleWatchlistToggle}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 历史事件对比(可折叠) */}
|
||||||
|
<CollapsibleSection
|
||||||
|
title="历史事件对比"
|
||||||
|
isOpen={isHistoricalOpen}
|
||||||
|
onToggle={() => setIsHistoricalOpen(!isHistoricalOpen)}
|
||||||
|
count={event.historical_events?.length || 0}
|
||||||
|
>
|
||||||
|
<HistoricalEvents
|
||||||
|
events={event.historical_events || []}
|
||||||
|
/>
|
||||||
|
</CollapsibleSection>
|
||||||
|
|
||||||
|
{/* 传导链分析(可折叠) */}
|
||||||
|
<CollapsibleSection
|
||||||
|
title="传导链分析"
|
||||||
|
isOpen={isTransmissionOpen}
|
||||||
|
onToggle={() => setIsTransmissionOpen(!isTransmissionOpen)}
|
||||||
|
>
|
||||||
|
<TransmissionChainAnalysis
|
||||||
|
eventId={event.id}
|
||||||
|
eventService={eventService}
|
||||||
|
/>
|
||||||
|
</CollapsibleSection>
|
||||||
|
</VStack>
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DynamicNewsDetailPanel;
|
||||||
@@ -110,9 +110,10 @@ const Community = () => {
|
|||||||
const { generateMockEvents } = await import('../../mocks/data/events');
|
const { generateMockEvents } = await import('../../mocks/data/events');
|
||||||
const mockData = generateMockEvents({ page: 1, per_page: 30 });
|
const mockData = generateMockEvents({ page: 1, per_page: 30 });
|
||||||
|
|
||||||
// 调试:检查第一个事件的 related_stocks 数据
|
// 调试:检查第一个事件的 related_stocks 和 historical_events 数据
|
||||||
if (mockData.events[0]?.related_stocks) {
|
if (mockData.events[0]) {
|
||||||
console.log('Mock 数据第一个事件的股票:', mockData.events[0].related_stocks);
|
console.log('Mock 数据第一个事件的股票:', mockData.events[0].related_stocks);
|
||||||
|
console.log('Mock 数据第一个事件的历史事件:', mockData.events[0].historical_events);
|
||||||
}
|
}
|
||||||
|
|
||||||
setDynamicNewsEvents(mockData.events);
|
setDynamicNewsEvents(mockData.events);
|
||||||
|
|||||||
Reference in New Issue
Block a user