// src/views/Community/components/DynamicNewsDetail/DynamicNewsDetailPanel.js // 动态新闻详情面板主组件(组装所有子组件) import React, { useState, useCallback, useEffect } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Box, Card, CardBody, VStack, HStack, Text, Spinner, Center, Wrap, WrapItem, 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 { useAuth } from '../../../../contexts/AuthContext'; import EventHeaderInfo from './EventHeaderInfo'; import CompactMetaBar from './CompactMetaBar'; import EventDescriptionSection from './EventDescriptionSection'; import RelatedConceptsSection from './RelatedConceptsSection'; import RelatedStocksSection from './RelatedStocksSection'; import CompactStockItem from './CompactStockItem'; import CollapsibleSection from './CollapsibleSection'; import HistoricalEvents from '../../../EventDetail/components/HistoricalEvents'; import TransmissionChainAnalysis from '../../../EventDetail/components/TransmissionChainAnalysis'; import SubscriptionBadge from '../../../../components/SubscriptionBadge'; import SubscriptionUpgradeModal from '../../../../components/SubscriptionUpgradeModal'; import { PROFESSIONAL_COLORS } from '../../../../constants/professionalTheme'; import EventCommentSection from '../../../../components/EventCommentSection'; /** * 动态新闻详情面板主组件 * @param {Object} props * @param {Object} props.event - 事件对象(包含详情数据) * @param {boolean} props.showHeader - 是否显示头部信息(默认 true) */ const DynamicNewsDetailPanel = ({ event, showHeader = true }) => { const dispatch = useDispatch(); const { user } = useAuth(); const cardBg = PROFESSIONAL_COLORS.background.card; const borderColor = PROFESSIONAL_COLORS.border.default; const textColor = PROFESSIONAL_COLORS.text.secondary; const toast = useToast(); // 获取用户会员等级(修复:字段名从 subscription_tier 改为 subscription_type) const userTier = user?.subscription_type || 'free'; // 从 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; // 🎯 浏览量机制:存储从 API 获取的完整事件详情(包含最新 view_count) const [fullEventDetail, setFullEventDetail] = useState(null); const [loadingDetail, setLoadingDetail] = useState(false); // 权限判断函数 const hasAccess = useCallback((requiredTier) => { const tierLevel = { free: 0, pro: 1, max: 2 }; const result = tierLevel[userTier] >= tierLevel[requiredTier]; return result; }, [userTier]); // 升级弹窗状态 const [upgradeModal, setUpgradeModal] = useState({ isOpen: false, requiredLevel: 'pro', featureName: '' }); // 使用 Hook 获取实时数据 // - autoLoad: false - 禁用自动加载所有数据,改为手动触发 // - autoLoadQuotes: true - 股票数据加载后自动加载行情(相关股票默认展开) const { stocks, quotes, eventDetail, historicalEvents, expectationScore, loading, loadStocksData, loadHistoricalData, loadChainAnalysis, refreshQuotes } = useEventStocks(event?.id, event?.created_at, { autoLoad: false, autoLoadQuotes: true }); // 🎯 加载事件详情(增加浏览量)- 与 EventDetailModal 保持一致 const loadEventDetail = useCallback(async () => { if (!event?.id) return; setLoadingDetail(true); try { const response = await eventService.getEventDetail(event.id); if (response.success) { setFullEventDetail(response.data); console.log('%c📊 [浏览量] 事件详情加载成功', 'color: #10B981; font-weight: bold;', { eventId: event.id, viewCount: response.data.view_count, title: response.data.title }); } } catch (error) { console.error('[DynamicNewsDetailPanel] loadEventDetail 失败:', error, { eventId: event?.id }); } finally { setLoadingDetail(false); } }, [event?.id]); // 相关股票、相关概念、历史事件和传导链的权限 const canAccessStocks = hasAccess('pro'); const canAccessConcepts = hasAccess('pro'); const canAccessHistorical = hasAccess('pro'); const canAccessTransmission = hasAccess('max'); // 子区块折叠状态管理 + 加载追踪 // 相关股票默认展开 const [isStocksOpen, setIsStocksOpen] = useState(true); const [hasLoadedStocks, setHasLoadedStocks] = useState(false); // 股票列表是否已加载(获取数量) const [hasLoadedQuotes, setHasLoadedQuotes] = useState(false); // 行情数据是否已加载 const [isConceptsOpen, setIsConceptsOpen] = useState(false); // 历史事件默认折叠,但预加载数量 const [isHistoricalOpen, setIsHistoricalOpen] = useState(false); const [hasLoadedHistorical, setHasLoadedHistorical] = useState(false); const [isTransmissionOpen, setIsTransmissionOpen] = useState(false); const [hasLoadedTransmission, setHasLoadedTransmission] = 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 handleLockedClick = useCallback((featureName, requiredLevel) => { setUpgradeModal({ isOpen: true, requiredLevel, featureName }); }, []); // 关闭升级弹窗 const handleCloseUpgradeModal = useCallback(() => { setUpgradeModal({ isOpen: false, requiredLevel: 'pro', featureName: '' }); }, []); // 相关股票 - 展开时加载行情(需要 PRO 权限) // 股票列表在事件切换时预加载(显示数量),行情在展开时才加载 const handleStocksToggle = useCallback(() => { const newState = !isStocksOpen; setIsStocksOpen(newState); // 展开时加载行情数据(如果还没加载过) if (newState && !hasLoadedQuotes && stocks.length > 0) { console.log('%c📈 [相关股票] 首次展开,加载行情数据', 'color: #10B981; font-weight: bold;', { eventId: event?.id, stockCount: stocks.length }); refreshQuotes(); setHasLoadedQuotes(true); } }, [isStocksOpen, hasLoadedQuotes, stocks.length, refreshQuotes, event?.id]); // 相关概念 - 展开/收起(无需加载) const handleConceptsToggle = useCallback(() => { setIsConceptsOpen(!isConceptsOpen); }, [isConceptsOpen]); // 历史事件对比 - 数据已预加载,只需切换展开状态 const handleHistoricalToggle = useCallback(() => { const newState = !isHistoricalOpen; setIsHistoricalOpen(newState); // 数据已在事件切换时预加载,这里只需展开 if (newState) { console.log('%c📜 [历史事件] 展开(数据已预加载)', 'color: #3B82F6; font-weight: bold;', { eventId: event?.id, count: historicalEvents?.length || 0 }); } }, [isHistoricalOpen, event?.id, historicalEvents?.length]); // 传导链分析 - 展开时加载 const handleTransmissionToggle = useCallback(() => { const newState = !isTransmissionOpen; setIsTransmissionOpen(newState); if (newState && !hasLoadedTransmission) { console.log('%c🔗 [传导链] 首次展开,加载传导链数据', 'color: #8B5CF6; font-weight: bold;', { eventId: event?.id }); loadChainAnalysis(); setHasLoadedTransmission(true); } }, [isTransmissionOpen, hasLoadedTransmission, loadChainAnalysis, event?.id]); // 事件切换时重置所有子模块状态 useEffect(() => { console.log('%c🔄 [事件切换] 重置所有子模块状态', 'color: #F59E0B; font-weight: bold;', { eventId: event?.id }); // 🎯 加载事件详情(增加浏览量) loadEventDetail(); // 重置所有加载状态 setHasLoadedStocks(false); setHasLoadedQuotes(false); // 重置行情加载状态 setHasLoadedHistorical(false); setHasLoadedTransmission(false); // 相关股票默认展开,预加载股票列表和行情数据 setIsStocksOpen(true); if (canAccessStocks) { console.log('%c📊 [相关股票] 事件切换,预加载股票列表和行情数据', 'color: #10B981; font-weight: bold;', { eventId: event?.id }); loadStocksData(); setHasLoadedStocks(true); // 由于默认展开,直接加载行情数据 setHasLoadedQuotes(true); } // 历史事件默认折叠,但预加载数据(显示数量吸引点击) setIsHistoricalOpen(false); if (canAccessHistorical) { console.log('%c📜 [历史事件] 事件切换,预加载历史事件(获取数量)', 'color: #3B82F6; font-weight: bold;', { eventId: event?.id }); loadHistoricalData(); setHasLoadedHistorical(true); } setIsConceptsOpen(false); setIsTransmissionOpen(false); }, [event?.id, canAccessStocks, canAccessHistorical, userTier, loadStocksData, loadHistoricalData, loadEventDetail]); // 切换关注状态 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 ( 请选择一个事件查看详情 ); } const importance = getImportanceConfig(event.importance); return ( {/* 无头部模式:显示右上角精简信息栏 */} {!showHeader && ( )} {/* 头部信息区 - 优先使用完整详情数据(包含最新浏览量) - 可配置显示/隐藏 */} {showHeader && ( )} {/* 事件描述 */} {/* 相关股票(可折叠) - 懒加载 - 需要 PRO 权限 - 支持精简/详细模式 */} { if (!canAccessStocks) { return ; } return null; })()} isLocked={!canAccessStocks} onLockedClick={() => handleLockedClick('相关股票', 'pro')} showModeToggle={canAccessStocks} defaultMode="detailed" simpleContent={ loading.stocks || loading.quotes ? (
加载股票数据中...
) : ( {stocks?.map((stock, index) => ( ))} ) } > {loading.stocks || loading.quotes ? (
加载股票数据中...
) : ( )}
{/* 相关概念(可折叠) - 需要 PRO 权限 */} : null} isLocked={!canAccessConcepts} onLockedClick={() => handleLockedClick('相关概念', 'pro')} /> {/* 历史事件对比(可折叠) - 懒加载 - 需要 PRO 权限 */} : null} isLocked={!canAccessHistorical} onLockedClick={() => handleLockedClick('历史事件对比', 'pro')} > {loading.historicalEvents ? (
加载历史事件...
) : ( )}
{/* 传导链分析(可折叠) - 懒加载 - 需要 MAX 权限 */} : null} isLocked={!canAccessTransmission} onLockedClick={() => handleLockedClick('传导链分析', 'max')} > {/* 讨论区(评论区) - 所有登录用户可用 */}
{/* 升级弹窗 */} {upgradeModal.isOpen ? ( ): null }
); }; export default DynamicNewsDetailPanel;