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