Files
vf_react/src/hooks/useSubscription.js
2025-10-24 17:10:11 +08:00

224 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// src/hooks/useSubscription.js
import { useState, useEffect, useRef } from 'react';
import { useAuth } from '../contexts/AuthContext';
import { logger } from '../utils/logger';
// 订阅级别映射
const SUBSCRIPTION_LEVELS = {
free: 0,
pro: 1,
max: 2
};
// 功能权限映射
const FEATURE_REQUIREMENTS = {
'related_stocks': 'pro', // 相关标的
'related_concepts': 'pro', // 相关概念
'transmission_chain': 'max', // 事件传导链分析
'historical_events_full': 'pro', // 历史事件对比(完整版)
'concept_html_detail': 'pro', // 概念HTML具体内容
'concept_stats_panel': 'pro', // 概念统计中心
'concept_related_stocks': 'pro', // 概念相关股票
'concept_timeline': 'max', // 概念历史时间轴
'hot_stocks': 'pro' // 热门个股
};
export const useSubscription = () => {
const { user, isAuthenticated } = useAuth();
const [subscriptionInfo, setSubscriptionInfo] = useState({
type: 'free',
status: 'active',
is_active: true,
days_left: 0
});
const [loading, setLoading] = useState(false);
// 获取订阅信息
const fetchSubscriptionInfo = async () => {
if (!isAuthenticated || !user) {
setSubscriptionInfo({
type: 'free',
status: 'active',
is_active: true,
days_left: 0
});
return;
}
// 首先检查用户对象中是否已经包含订阅信息
if (user.subscription_type) {
logger.debug('useSubscription', '从用户对象获取订阅信息', {
subscriptionType: user.subscription_type,
daysLeft: user.subscription_days_left
});
setSubscriptionInfo({
type: user.subscription_type,
status: 'active',
is_active: true,
days_left: user.subscription_days_left || 0
});
return;
}
try {
setLoading(true);
const response = await fetch('/api/subscription/info', {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
const data = await response.json();
if (data.success) {
setSubscriptionInfo(data.data);
}
} else {
// 如果API调用失败回退到用户对象中的信息
logger.warn('useSubscription', 'API调用失败使用用户对象订阅信息', {
status: response.status,
fallbackType: user.subscription_type || 'free'
});
setSubscriptionInfo({
type: user.subscription_type || 'free',
status: 'active',
is_active: true,
days_left: user.subscription_days_left || 0
});
}
} catch (error) {
logger.error('useSubscription', 'fetchSubscriptionInfo', error, {
userId: user?.id
});
// 发生错误时,回退到用户对象中的信息
setSubscriptionInfo({
type: user.subscription_type || 'free',
status: 'active',
is_active: true,
days_left: user.subscription_days_left || 0
});
} finally {
setLoading(false);
}
};
// ⚡ 提取 userId 为独立变量,避免 user 对象引用变化导致无限循环
const userId = user?.id;
const prevUserIdRef = useRef(userId);
const prevIsAuthenticatedRef = useRef(isAuthenticated);
useEffect(() => {
// ⚡ 只在 userId 或 isAuthenticated 真正变化时才请求
const userIdChanged = prevUserIdRef.current !== userId;
const authChanged = prevIsAuthenticatedRef.current !== isAuthenticated;
if (userIdChanged || authChanged) {
logger.debug('useSubscription', 'fetchSubscriptionInfo 触发', {
userIdChanged,
authChanged,
prevUserId: prevUserIdRef.current,
currentUserId: userId,
prevAuth: prevIsAuthenticatedRef.current,
currentAuth: isAuthenticated
});
prevUserIdRef.current = userId;
prevIsAuthenticatedRef.current = isAuthenticated;
fetchSubscriptionInfo();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [isAuthenticated, userId]); // 使用 userId 原始值,而不是 user?.id 表达式
// 获取订阅级别数值
const getSubscriptionLevel = (type = null) => {
const subType = (type || subscriptionInfo.type || 'free').toLowerCase();
return SUBSCRIPTION_LEVELS[subType] || 0;
};
// 检查是否有指定功能的权限
const hasFeatureAccess = (featureName) => {
// 临时调试如果用户对象中有max权限直接解锁所有功能
if (user?.subscription_type === 'max') {
logger.debug('useSubscription', 'Max用户解锁功能', {
featureName,
userId: user?.id
});
return true;
}
if (!subscriptionInfo.is_active) {
return false;
}
const requiredLevel = FEATURE_REQUIREMENTS[featureName];
if (!requiredLevel) {
return true; // 如果功能不需要特定权限,默认允许
}
const currentLevel = getSubscriptionLevel();
const requiredLevelNum = getSubscriptionLevel(requiredLevel);
return currentLevel >= requiredLevelNum;
};
// 检查是否达到指定订阅级别
const hasSubscriptionLevel = (requiredLevel) => {
if (!subscriptionInfo.is_active) {
return false;
}
const currentLevel = getSubscriptionLevel();
const requiredLevelNum = getSubscriptionLevel(requiredLevel);
return currentLevel >= requiredLevelNum;
};
// 获取功能所需的订阅级别
const getRequiredLevel = (featureName) => {
return FEATURE_REQUIREMENTS[featureName] || 'free';
};
// 获取订阅状态文本
const getSubscriptionStatusText = () => {
const type = subscriptionInfo.type || 'free';
switch (type.toLowerCase()) {
case 'free':
return '免费版';
case 'pro':
return 'Pro版';
case 'max':
return 'Max版';
default:
return '未知';
}
};
// 获取升级建议
const getUpgradeRecommendation = (featureName) => {
const requiredLevel = getRequiredLevel(featureName);
const currentType = subscriptionInfo.type || 'free';
if (hasFeatureAccess(featureName)) {
return null;
}
return {
current: currentType,
required: requiredLevel,
message: `此功能需要${requiredLevel === 'pro' ? 'Pro版' : 'Max版'}订阅`
};
};
return {
subscriptionInfo,
loading,
hasFeatureAccess,
hasSubscriptionLevel,
getRequiredLevel,
getSubscriptionStatusText,
getUpgradeRecommendation,
refreshSubscription: fetchSubscriptionInfo
};
};