Spaces:
Running
Running
| /** | |
| * 用户认证状态管理 Store | |
| * 管理 token、用户信息、VIP 状态,持久化到 localStorage | |
| */ | |
| import { create } from 'zustand'; | |
| import { api } from '@/lib/api'; | |
| export interface AuthState { | |
| // 状态 | |
| token: string | null; | |
| userId: number | null; | |
| username: string | null; | |
| isVip: boolean; | |
| vipExpireAt: string | null; | |
| dailyUsed: number; | |
| dailyRemaining: number | null; // null = VIP 无限制 | |
| dailyLimit: number | null; | |
| isLoading: boolean; | |
| isInitialized: boolean; | |
| // Actions | |
| login: (username: string, password: string) => Promise<void>; | |
| register: (username: string, password: string) => Promise<void>; | |
| logout: () => Promise<void>; | |
| fetchUserInfo: () => Promise<void>; | |
| fetchUsage: () => Promise<void>; | |
| initialize: () => Promise<void>; | |
| } | |
| const TOKEN_KEY = 'auth_token'; | |
| export const useAuthStore = create<AuthState>((set, get) => ({ | |
| token: null, | |
| userId: null, | |
| username: null, | |
| isVip: false, | |
| vipExpireAt: null, | |
| dailyUsed: 0, | |
| dailyRemaining: null, | |
| dailyLimit: null, | |
| isLoading: false, | |
| isInitialized: false, | |
| initialize: async () => { | |
| if (typeof window === 'undefined') { | |
| set({ isInitialized: true }); | |
| return; | |
| } | |
| const savedToken = localStorage.getItem(TOKEN_KEY); | |
| if (!savedToken) { | |
| set({ isInitialized: true }); | |
| return; | |
| } | |
| set({ token: savedToken, isLoading: true }); | |
| try { | |
| const userInfo = await api.me(savedToken); | |
| set({ | |
| userId: userInfo.user_id, | |
| username: userInfo.username, | |
| isVip: userInfo.is_vip, | |
| vipExpireAt: userInfo.vip_expire_at, | |
| isInitialized: true, | |
| isLoading: false, | |
| }); | |
| // 同时获取今日使用量 | |
| try { | |
| const usage = await api.getUsageToday(savedToken); | |
| set({ dailyUsed: usage.used, dailyRemaining: usage.remaining, dailyLimit: usage.limit }); | |
| } catch { /* 静默失败 */ } | |
| } catch { | |
| // Token 过期或无效,清除 | |
| localStorage.removeItem(TOKEN_KEY); | |
| set({ | |
| token: null, | |
| userId: null, | |
| username: null, | |
| isVip: false, | |
| vipExpireAt: null, | |
| isInitialized: true, | |
| isLoading: false, | |
| }); | |
| } | |
| }, | |
| login: async (username: string, password: string) => { | |
| set({ isLoading: true }); | |
| try { | |
| const res = await api.login(username, password); | |
| localStorage.setItem(TOKEN_KEY, res.token); | |
| set({ | |
| token: res.token, | |
| userId: res.user_id, | |
| username: res.username, | |
| isLoading: false, | |
| }); | |
| // Fetch VIP info and usage | |
| try { | |
| const vipInfo = await api.vipStatus(res.token); | |
| set({ isVip: vipInfo.is_vip, vipExpireAt: vipInfo.vip_expire_at }); | |
| const usage = await api.getUsageToday(res.token); | |
| set({ dailyUsed: usage.used, dailyRemaining: usage.remaining, dailyLimit: usage.limit }); | |
| } catch { | |
| // VIP/usage 查询失败不影响登录 | |
| } | |
| } catch (error) { | |
| set({ isLoading: false }); | |
| throw error; | |
| } | |
| }, | |
| register: async (username: string, password: string) => { | |
| set({ isLoading: true }); | |
| try { | |
| const res = await api.register(username, password); | |
| localStorage.setItem(TOKEN_KEY, res.token); | |
| set({ | |
| token: res.token, | |
| userId: res.user_id, | |
| username: res.username, | |
| isVip: false, | |
| vipExpireAt: null, | |
| isLoading: false, | |
| }); | |
| } catch (error) { | |
| set({ isLoading: false }); | |
| throw error; | |
| } | |
| }, | |
| logout: async () => { | |
| const { token } = get(); | |
| if (token) { | |
| try { | |
| await api.logout(token); | |
| } catch { | |
| // 忽略登出接口错误 | |
| } | |
| } | |
| localStorage.removeItem(TOKEN_KEY); | |
| set({ | |
| token: null, | |
| userId: null, | |
| username: null, | |
| isVip: false, | |
| vipExpireAt: null, | |
| dailyUsed: 0, | |
| dailyRemaining: null, | |
| dailyLimit: null, | |
| }); | |
| }, | |
| fetchUsage: async () => { | |
| const { token } = get(); | |
| if (!token) return; | |
| try { | |
| const usage = await api.getUsageToday(token); | |
| set({ dailyUsed: usage.used, dailyRemaining: usage.remaining, dailyLimit: usage.limit }); | |
| } catch { /* 静默失败 */ } | |
| }, | |
| fetchUserInfo: async () => { | |
| const { token } = get(); | |
| if (!token) return; | |
| try { | |
| const userInfo = await api.me(token); | |
| set({ | |
| userId: userInfo.user_id, | |
| username: userInfo.username, | |
| isVip: userInfo.is_vip, | |
| vipExpireAt: userInfo.vip_expire_at, | |
| }); | |
| } catch { | |
| // 静默失败 | |
| } | |
| }, | |
| })); | |