// src/store/slices/subscriptionSlice.js // 订阅信息状态管理 Redux Slice import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { logger } from '../../utils/logger'; import { getApiBase } from '../../utils/apiConfig'; /** * 异步 Thunk: 获取用户订阅信息 */ export const fetchSubscriptionInfo = createAsyncThunk( 'subscription/fetchInfo', async (_, { rejectWithValue }) => { try { const base = getApiBase(); logger.debug('subscriptionSlice', '开始加载订阅信息'); const response = await fetch(base + '/api/subscription/current', { credentials: 'include', }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } const data = await response.json(); if (data.success && data.data) { // 数据标准化处理 const normalizedData = { type: (data.data.type || data.data.subscription_type || 'free').toLowerCase(), status: data.data.status || 'active', days_left: data.data.days_left || 0, is_active: data.data.is_active !== false, end_date: data.data.end_date || null }; logger.info('subscriptionSlice', '订阅信息加载成功', normalizedData); return normalizedData; } else { // API 返回成功但无数据,返回默认免费版 return { type: 'free', status: 'active', days_left: 0, is_active: false, end_date: null }; } } catch (error) { logger.error('subscriptionSlice', '加载订阅信息失败', error); return rejectWithValue(error.message); } } ); /** * Subscription Slice * 管理用户订阅信息和订阅 Modal 状态 */ const subscriptionSlice = createSlice({ name: 'subscription', initialState: { // 订阅信息 info: { type: 'free', status: 'active', days_left: 0, is_active: false, end_date: null }, // 加载状态 loading: false, loaded: false, // 是否已加载过(用于防止重复请求) error: null, // 订阅 Modal 状态 isModalOpen: false, }, reducers: { /** * 打开订阅 Modal */ openModal: (state) => { state.isModalOpen = true; logger.debug('subscriptionSlice', '打开订阅 Modal'); }, /** * 关闭订阅 Modal */ closeModal: (state) => { state.isModalOpen = false; logger.debug('subscriptionSlice', '关闭订阅 Modal'); }, /** * 重置为免费版 (用户登出时调用) */ resetToFree: (state) => { state.info = { type: 'free', status: 'active', days_left: 0, is_active: false, end_date: null }; state.loading = false; state.loaded = false; // 重置已加载标记,下次登录时重新获取 state.error = null; }, }, extraReducers: (builder) => { builder // fetchSubscriptionInfo - pending .addCase(fetchSubscriptionInfo.pending, (state) => { state.loading = true; state.error = null; }) // fetchSubscriptionInfo - fulfilled .addCase(fetchSubscriptionInfo.fulfilled, (state, action) => { state.loading = false; state.loaded = true; // 标记已加载 state.info = action.payload; state.error = null; }) // fetchSubscriptionInfo - rejected .addCase(fetchSubscriptionInfo.rejected, (state, action) => { state.loading = false; state.error = action.payload || 'Unknown error'; // 加载失败时保持当前状态,不重置为免费版 }); }, }); // 导出 actions export const { openModal, closeModal, resetToFree } = subscriptionSlice.actions; // 导出 selectors export const selectSubscriptionInfo = (state) => state.subscription.info; export const selectSubscriptionLoading = (state) => state.subscription.loading; export const selectSubscriptionLoaded = (state) => state.subscription.loaded; export const selectSubscriptionError = (state) => state.subscription.error; export const selectSubscriptionModalOpen = (state) => state.subscription.isModalOpen; // 导出 reducer export default subscriptionSlice.reducer;