// 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, 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 获取实时数据(禁用自动加载,改为手动触发) const { stocks, quotes, eventDetail, historicalEvents, expectationScore, loading, loadStocksData, loadHistoricalData, loadChainAnalysis } = useEventStocks(event?.id, event?.created_at, { autoLoad: false }); // 🎯 加载事件详情(增加浏览量)- 与 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'); // 子区块折叠状态管理 + 加载追踪 // 初始值为 false,由 useEffect 根据权限动态设置 const [isStocksOpen, setIsStocksOpen] = useState(false); const [hasLoadedStocks, setHasLoadedStocks] = 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 && !hasLoadedStocks) { console.log('%c📊 [相关股票] 首次展开,加载股票数据', 'color: #10B981; font-weight: bold;', { eventId: event?.id }); loadStocksData(); setHasLoadedStocks(true); } }, [isStocksOpen, hasLoadedStocks, loadStocksData, event?.id]); // 相关概念 - 展开/收起(无需加载) const handleConceptsToggle = useCallback(() => { setIsConceptsOpen(!isConceptsOpen); }, [isConceptsOpen]); // 历史事件对比 - 展开时加载 const handleHistoricalToggle = useCallback(() => { const newState = !isHistoricalOpen; setIsHistoricalOpen(newState); if (newState && !hasLoadedHistorical) { console.log('%c📜 [历史事件] 首次展开,加载历史事件数据', 'color: #3B82F6; font-weight: bold;', { eventId: event?.id }); loadHistoricalData(); setHasLoadedHistorical(true); } }, [isHistoricalOpen, hasLoadedHistorical, loadHistoricalData, event?.id]); // 传导链分析 - 展开时加载 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); setHasLoadedHistorical(false); setHasLoadedTransmission(false); // 相关股票默认展开(有权限时) if (canAccessStocks) { setIsStocksOpen(true); // 立即加载股票数据 console.log('%c📊 [相关股票] 事件切换,加载股票数据', 'color: #10B981; font-weight: bold;', { eventId: event?.id }); loadStocksData(); setHasLoadedStocks(true); } else { setIsStocksOpen(false); } setIsConceptsOpen(false); setIsHistoricalOpen(false); setIsTransmissionOpen(false); }, [event?.id, canAccessStocks, userTier, loadStocksData, 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;