// src/components/EventDetailPanel/DynamicNewsDetailPanel.js // 动态新闻详情面板主组件(组装所有子组件) import React, { useState, useCallback, useEffect, useReducer } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Card, CardBody, VStack, Text, Spinner, Center, Wrap, WrapItem, Box, } from '@chakra-ui/react'; import { getImportanceConfig } from '@constants/importanceLevels'; import { eventService } from '@services/eventService'; import { useEventStocks } from '@views/Community/components/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 '@views/EventDetail/components/HistoricalEvents'; import TransmissionChainAnalysis from '@views/EventDetail/components/TransmissionChainAnalysis'; import SubscriptionBadge from '@components/SubscriptionBadge'; import SubscriptionUpgradeModal from '@components/SubscriptionUpgradeModal'; import { PROFESSIONAL_COLORS } from '@constants/professionalTheme'; import { useWatchlist } from '@hooks/useWatchlist'; import EventCommentSection from '@components/EventCommentSection'; // 折叠区块状态管理 - 使用 useReducer 整合 const initialSectionState = { stocks: { isOpen: true, hasLoaded: false, hasLoadedQuotes: false }, concepts: { isOpen: false }, historical: { isOpen: false, hasLoaded: false }, transmission: { isOpen: false, hasLoaded: false } }; const sectionReducer = (state, action) => { switch (action.type) { case 'TOGGLE': return { ...state, [action.section]: { ...state[action.section], isOpen: !state[action.section].isOpen } }; case 'SET_LOADED': return { ...state, [action.section]: { ...state[action.section], hasLoaded: true } }; case 'SET_QUOTES_LOADED': return { ...state, stocks: { ...state.stocks, hasLoadedQuotes: true } }; case 'RESET_ALL': return { stocks: { isOpen: true, hasLoaded: false, hasLoadedQuotes: false }, concepts: { isOpen: false }, historical: { isOpen: false, hasLoaded: false }, transmission: { isOpen: false, hasLoaded: false } }; default: return state; } }; /** * 动态新闻详情面板主组件 * @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; // 使用 useWatchlist Hook 管理自选股 const { handleAddToWatchlist, handleRemoveFromWatchlist, isInWatchlist, loadWatchlistQuotes } = useWatchlist(); // 获取用户会员等级(修复:字段名从 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); } } 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'); // 子区块折叠状态管理 - 使用 useReducer 整合 const [sectionState, dispatchSection] = useReducer(sectionReducer, initialSectionState); // 锁定点击处理 - 弹出升级弹窗 const handleLockedClick = useCallback((featureName, requiredLevel) => { setUpgradeModal({ isOpen: true, requiredLevel, featureName }); }, []); // 关闭升级弹窗 const handleCloseUpgradeModal = useCallback(() => { setUpgradeModal({ isOpen: false, requiredLevel: 'pro', featureName: '' }); }, []); // 相关股票 - 展开时加载行情(需要 PRO 权限) const handleStocksToggle = useCallback(() => { const willOpen = !sectionState.stocks.isOpen; dispatchSection({ type: 'TOGGLE', section: 'stocks' }); // 展开时加载行情数据(如果还没加载过) if (willOpen && !sectionState.stocks.hasLoadedQuotes && stocks.length > 0) { refreshQuotes(); dispatchSection({ type: 'SET_QUOTES_LOADED' }); } }, [sectionState.stocks, stocks.length, refreshQuotes]); // 相关概念 - 展开/收起(无需加载) const handleConceptsToggle = useCallback(() => { dispatchSection({ type: 'TOGGLE', section: 'concepts' }); }, []); // 历史事件对比 - 数据已预加载,只需切换展开状态 const handleHistoricalToggle = useCallback(() => { dispatchSection({ type: 'TOGGLE', section: 'historical' }); }, []); // 传导链分析 - 展开时加载 const handleTransmissionToggle = useCallback(() => { const willOpen = !sectionState.transmission.isOpen; dispatchSection({ type: 'TOGGLE', section: 'transmission' }); if (willOpen && !sectionState.transmission.hasLoaded) { loadChainAnalysis(); dispatchSection({ type: 'SET_LOADED', section: 'transmission' }); } }, [sectionState.transmission, loadChainAnalysis]); // 事件切换时重置所有子模块状态 useEffect(() => { // 加载事件详情(增加浏览量) loadEventDetail(); // 加载自选股数据(用于判断股票是否已关注) loadWatchlistQuotes(); // 重置所有折叠区块状态 dispatchSection({ type: 'RESET_ALL' }); // 相关股票默认展开,预加载股票列表和行情数据 if (canAccessStocks) { loadStocksData(); dispatchSection({ type: 'SET_LOADED', section: 'stocks' }); dispatchSection({ type: 'SET_QUOTES_LOADED' }); } // 历史事件默认折叠,但预加载数据(显示数量吸引点击) if (canAccessHistorical) { loadHistoricalData(); dispatchSection({ type: 'SET_LOADED', section: 'historical' }); } }, [event?.id, canAccessStocks, canAccessHistorical, userTier, loadStocksData, loadHistoricalData, loadEventDetail, loadWatchlistQuotes]); // 切换关注状态 const handleToggleFollow = useCallback(async () => { if (!event?.id) return; dispatch(toggleEventFollow(event.id)); }, [dispatch, event?.id]); // 切换自选股(使用 useWatchlist Hook) const handleWatchlistToggle = useCallback(async (stockCode, stockName, currentlyInWatchlist) => { if (currentlyInWatchlist) { await handleRemoveFromWatchlist(stockCode); } else { await handleAddToWatchlist(stockCode, stockName); } }, [handleAddToWatchlist, handleRemoveFromWatchlist]); // 空状态 if (!event) { return ( 请选择一个事件查看详情 ); } const importance = getImportanceConfig(event.importance); return ( {/* 无头部模式:显示右上角精简信息栏 */} {!showHeader && ( )} {/* 头部信息区 - 优先使用完整详情数据(包含最新浏览量) - 可配置显示/隐藏 */} {showHeader && ( )} {/* 事件描述 */} {/* 相关股票(可折叠) - 懒加载 - 需要 PRO 权限 - 支持精简/详细模式 */} : 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;