feat: 事件详情权限加上权限校验

This commit is contained in:
zdl
2025-11-05 21:31:02 +08:00
parent 25a6ff164b
commit ed24a14fbf
4 changed files with 274 additions and 31 deletions

View File

@@ -17,6 +17,7 @@ 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 EventDescriptionSection from './EventDescriptionSection';
import RelatedConceptsSection from './RelatedConceptsSection';
@@ -24,6 +25,8 @@ import RelatedStocksSection from './RelatedStocksSection';
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';
/**
* 动态新闻详情面板主组件
@@ -32,16 +35,34 @@ import TransmissionChainAnalysis from '../../../EventDetail/components/Transmiss
*/
const DynamicNewsDetailPanel = ({ event }) => {
const dispatch = useDispatch();
const { user } = useAuth();
const cardBg = useColorModeValue('white', 'gray.800');
const borderColor = useColorModeValue('gray.200', 'gray.700');
const textColor = useColorModeValue('gray.600', 'gray.400');
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;
// 权限判断函数
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,
@@ -55,10 +76,19 @@ const DynamicNewsDetailPanel = ({ event }) => {
loadChainAnalysis
} = useEventStocks(event?.id, event?.created_at, { autoLoad: false });
// 子区块折叠状态管理(默认折叠)+ 加载追踪
const [isStocksOpen, setIsStocksOpen] = useState(false);
// 相关股票、相关概念、历史事件和传导链的权限
const canAccessStocks = hasAccess('pro');
const canAccessConcepts = hasAccess('pro');
const canAccessHistorical = hasAccess('pro');
const canAccessTransmission = hasAccess('max');
// 子区块折叠状态管理 + 加载追踪
// PRO 会员的相关股票默认展开
const [isStocksOpen, setIsStocksOpen] = useState(canAccessStocks && userTier === 'pro');
const [hasLoadedStocks, setHasLoadedStocks] = useState(false);
const [isConceptsOpen, setIsConceptsOpen] = useState(false);
const [isHistoricalOpen, setIsHistoricalOpen] = useState(false);
const [hasLoadedHistorical, setHasLoadedHistorical] = useState(false);
@@ -75,7 +105,25 @@ const DynamicNewsDetailPanel = ({ event }) => {
}
});
// 相关股票 - 展开时加载
// 锁定点击处理 - 弹出升级弹窗
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);
@@ -87,6 +135,11 @@ const DynamicNewsDetailPanel = ({ event }) => {
}
}, [isStocksOpen, hasLoadedStocks, loadStocksData, event?.id]);
// 相关概念 - 展开/收起(无需加载)
const handleConceptsToggle = useCallback(() => {
setIsConceptsOpen(!isConceptsOpen);
}, [isConceptsOpen]);
// 历史事件对比 - 展开时加载
const handleHistoricalToggle = useCallback(() => {
const newState = !isHistoricalOpen;
@@ -114,13 +167,25 @@ const DynamicNewsDetailPanel = ({ event }) => {
// 事件切换时重置所有子模块状态
useEffect(() => {
console.log('%c🔄 [事件切换] 重置所有子模块状态', 'color: #F59E0B; font-weight: bold;', { eventId: event?.id });
setIsStocksOpen(false);
// PRO 会员的相关股票默认展开,其他情况收起
const shouldOpenStocks = canAccessStocks && userTier === 'pro';
setIsStocksOpen(shouldOpenStocks);
setHasLoadedStocks(false);
// PRO 会员默认展开时,自动加载股票数据
if (shouldOpenStocks) {
console.log('%c📊 [PRO会员] 自动加载相关股票数据', 'color: #10B981; font-weight: bold;', { eventId: event?.id });
loadStocksData();
setHasLoadedStocks(true);
}
setIsConceptsOpen(false);
setIsHistoricalOpen(false);
setHasLoadedHistorical(false);
setIsTransmissionOpen(false);
setHasLoadedTransmission(false);
}, [event?.id]);
}, [event?.id, canAccessStocks, userTier, loadStocksData]);
// 切换关注状态
const handleToggleFollow = useCallback(async () => {
@@ -196,12 +261,20 @@ const DynamicNewsDetailPanel = ({ event }) => {
{/* 事件描述 */}
<EventDescriptionSection description={event.description} />
{/* 相关股票(可折叠) - 懒加载 */}
{/* 相关股票(可折叠) - 懒加载 - 需要 PRO 权限 */}
<CollapsibleSection
title="相关股票"
isOpen={isStocksOpen}
onToggle={handleStocksToggle}
count={stocks?.length || 0}
subscriptionBadge={(() => {
if (!canAccessStocks) {
return <SubscriptionBadge tier="pro" size="sm" />;
}
return null;
})()}
isLocked={!canAccessStocks}
onLockedClick={() => handleLockedClick('相关股票', 'pro')}
>
{loading.stocks || loading.quotes ? (
<Center py={4}>
@@ -219,19 +292,27 @@ const DynamicNewsDetailPanel = ({ event }) => {
)}
</CollapsibleSection>
{/* 相关概念 */}
{/* 相关概念(可折叠) - 需要 PRO 权限 */}
<RelatedConceptsSection
eventTitle={event.title}
effectiveTradingDate={event.trading_date || event.created_at}
eventTime={event.created_at}
isOpen={isConceptsOpen}
onToggle={handleConceptsToggle}
subscriptionBadge={!canAccessConcepts ? <SubscriptionBadge tier="pro" size="sm" /> : null}
isLocked={!canAccessConcepts}
onLockedClick={() => handleLockedClick('相关概念', 'pro')}
/>
{/* 历史事件对比(可折叠) - 懒加载 */}
{/* 历史事件对比(可折叠) - 懒加载 - 需要 PRO 权限 */}
<CollapsibleSection
title="历史事件对比"
isOpen={isHistoricalOpen}
onToggle={handleHistoricalToggle}
count={historicalEvents?.length || 0}
subscriptionBadge={!canAccessHistorical ? <SubscriptionBadge tier="pro" size="sm" /> : null}
isLocked={!canAccessHistorical}
onLockedClick={() => handleLockedClick('历史事件对比', 'pro')}
>
{loading.historicalEvents ? (
<Center py={4}>
@@ -246,11 +327,14 @@ const DynamicNewsDetailPanel = ({ event }) => {
)}
</CollapsibleSection>
{/* 传导链分析(可折叠) - 懒加载 */}
{/* 传导链分析(可折叠) - 懒加载 - 需要 MAX 权限 */}
<CollapsibleSection
title="传导链分析"
isOpen={isTransmissionOpen}
onToggle={handleTransmissionToggle}
subscriptionBadge={!canAccessTransmission ? <SubscriptionBadge tier="max" size="sm" /> : null}
isLocked={!canAccessTransmission}
onLockedClick={() => handleLockedClick('传导链分析', 'max')}
>
<TransmissionChainAnalysis
eventId={event.id}
@@ -259,6 +343,17 @@ const DynamicNewsDetailPanel = ({ event }) => {
</CollapsibleSection>
</VStack>
</CardBody>
{/* 升级弹窗 */}
{upgradeModal.isOpen ? (
<SubscriptionUpgradeModal
isOpen={upgradeModal.isOpen}
onClose={handleCloseUpgradeModal}
requiredLevel={upgradeModal.requiredLevel}
featureName={upgradeModal.featureName}
currentLevel={userTier}
/>
): null }
</Card>
);
};