Files
vf_react/src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js
2025-11-04 19:39:46 +08:00

207 lines
6.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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;