207 lines
6.6 KiB
JavaScript
207 lines
6.6 KiB
JavaScript
// src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js
|
||
// 动态新闻详情面板主组件(组装所有子组件)
|
||
|
||
import React, { useState, useCallback } from 'react';
|
||
import { useDispatch, useSelector } from 'react-redux';
|
||
import {
|
||
Card,
|
||
CardBody,
|
||
VStack,
|
||
Text,
|
||
Spinner,
|
||
Center,
|
||
useColorModeValue,
|
||
useToast,
|
||
} from '@chakra-ui/react';
|
||
import { getImportanceConfig } from '../../../../constants/importanceLevels';
|
||
import { eventService } from '../../../../services/eventService';
|
||
import { useEventStocks } from '../StockDetailPanel/hooks/useEventStocks';
|
||
import { toggleEventFollow, selectEventFollowStatus } from '../../../../store/slices/communityDataSlice';
|
||
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 dispatch = useDispatch();
|
||
const cardBg = useColorModeValue('white', 'gray.800');
|
||
const borderColor = useColorModeValue('gray.200', 'gray.700');
|
||
const textColor = useColorModeValue('gray.600', 'gray.400');
|
||
const toast = useToast();
|
||
|
||
// 从 Redux 读取关注状态
|
||
const eventFollowStatus = useSelector(selectEventFollowStatus);
|
||
const isFollowing = event?.id ? (eventFollowStatus[event.id]?.isFollowing || false) : false;
|
||
const followerCount = event?.id ? (eventFollowStatus[event.id]?.followerCount || event.follower_count || 0) : 0;
|
||
|
||
// 使用 Hook 获取实时数据
|
||
const {
|
||
stocks,
|
||
quotes,
|
||
eventDetail,
|
||
historicalEvents,
|
||
expectationScore,
|
||
loading
|
||
} = useEventStocks(event?.id, event?.created_at);
|
||
|
||
// 折叠状态管理
|
||
const [isStocksOpen, setIsStocksOpen] = useState(true);
|
||
const [isHistoricalOpen, setIsHistoricalOpen] = useState(true);
|
||
const [isTransmissionOpen, setIsTransmissionOpen] = useState(false);
|
||
|
||
// 自选股管理(使用 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 handleToggleFollow = useCallback(async () => {
|
||
if (!event?.id) return;
|
||
dispatch(toggleEventFollow(event.id));
|
||
}, [dispatch, event?.id]);
|
||
|
||
// 切换自选股
|
||
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
|
||
eventTitle={event.title}
|
||
effectiveTradingDate={event.trading_date || event.created_at}
|
||
eventTime={event.created_at}
|
||
/>
|
||
|
||
{/* 相关股票(可折叠) */}
|
||
{loading.stocks || loading.quotes ? (
|
||
<Center py={4}>
|
||
<Spinner size="md" color="blue.500" />
|
||
<Text ml={2} color={textColor}>加载股票数据中...</Text>
|
||
</Center>
|
||
) : (
|
||
<RelatedStocksSection
|
||
stocks={stocks}
|
||
quotes={quotes}
|
||
eventTime={event.created_at}
|
||
watchlistSet={watchlistSet}
|
||
isOpen={isStocksOpen}
|
||
onToggle={() => setIsStocksOpen(!isStocksOpen)}
|
||
onWatchlistToggle={handleWatchlistToggle}
|
||
/>
|
||
)}
|
||
|
||
{/* 历史事件对比(可折叠) */}
|
||
<CollapsibleSection
|
||
title="历史事件对比"
|
||
isOpen={isHistoricalOpen}
|
||
onToggle={() => setIsHistoricalOpen(!isHistoricalOpen)}
|
||
count={historicalEvents?.length || 0}
|
||
>
|
||
{loading.historicalEvents ? (
|
||
<Center py={4}>
|
||
<Spinner size="sm" color="blue.500" />
|
||
<Text ml={2} color={textColor} fontSize="sm">加载历史事件...</Text>
|
||
</Center>
|
||
) : (
|
||
<HistoricalEvents
|
||
events={historicalEvents || []}
|
||
expectationScore={expectationScore}
|
||
/>
|
||
)}
|
||
</CollapsibleSection>
|
||
|
||
{/* 传导链分析(可折叠) */}
|
||
<CollapsibleSection
|
||
title="传导链分析"
|
||
isOpen={isTransmissionOpen}
|
||
onToggle={() => setIsTransmissionOpen(!isTransmissionOpen)}
|
||
>
|
||
<TransmissionChainAnalysis
|
||
eventId={event.id}
|
||
eventService={eventService}
|
||
/>
|
||
</CollapsibleSection>
|
||
</VStack>
|
||
</CardBody>
|
||
</Card>
|
||
);
|
||
};
|
||
|
||
export default DynamicNewsDetailPanel;
|