/** * 积分 & 会员状态管理 * 对齐后端 staging 最终契约(2026-09-16): * - balance: GET /points/balance(无 free_clips_*) * - dailyUsage: GET /usage/daily(每日免费额度) * - membership: GET /points/subscription/membership(聚合会员信息) * - rules: GET /points/rules(base_points + free_user_multiplier) * - subscription: GET /subscription/current(plan_id + billing_cycle) */ import { create } from "zustand" import { ENABLE_CREDIT_SYSTEM } from "@/config/features" import { getPointsBalance, getPointsRules, getDailyUsage, getMembership } from "@/api/points" import { getCurrentSubscription } from "@/api/subscription" import type { PointsBalance, PointsRulesResponse, DailyUsage, MembershipResponse, } from "@/api/points/types" import type { SubscriptionInfo } from "@/api/subscription/types" interface PointsState { balance: PointsBalance | null dailyUsage: DailyUsage | null membership: MembershipResponse | null rules: PointsRulesResponse | null subscription: SubscriptionInfo | null loading: boolean error: string | null /** 初始化:并行拉取 balance / rules / subscription / dailyUsage / membership */ init: () => Promise /** 刷新余额(充值/消费后调用) */ refreshBalance: () => Promise /** 乐观扣减 */ optimisticDeduct: (points: number) => void /** 乐观增加 */ optimisticAdd: (points: number) => void /** 清除积分状态(退出登录) */ reset: () => void } export const usePointsStore = create((set, get) => ({ balance: null, dailyUsage: null, membership: null, rules: null, subscription: null, loading: false, error: null, init: async () => { // 已加载过不重复拉取 // 积分系统关闭时:只要 subscription/membership 已有值就跳过;开启时需 balance+rules+subscription 齐了才跳过 if (ENABLE_CREDIT_SYSTEM) { if (get().balance && get().rules && get().subscription) return } else { if (get().subscription && get().membership) return } set({ loading: true, error: null }) try { // 积分系统关闭时不拉取余额/规则/每日额度,但仍拉会员/订阅用于 VIP 标识展示 const balancePromise = ENABLE_CREDIT_SYSTEM ? getPointsBalance().catch(() => null) : Promise.resolve(null) const rulesPromise = ENABLE_CREDIT_SYSTEM ? getPointsRules().catch(() => null) : Promise.resolve(null) const dailyUsagePromise = ENABLE_CREDIT_SYSTEM ? getDailyUsage().catch(() => null) : Promise.resolve(null) const [balance, rules, subscription, dailyUsage, membership] = await Promise.all([ balancePromise, rulesPromise, getCurrentSubscription().catch(() => null), dailyUsagePromise, getMembership().catch(() => null), ]) set({ balance, rules, subscription, dailyUsage, membership, loading: false, }) } catch (err) { set({ error: (err as Error).message || "加载积分信息失败", loading: false }) } }, refreshBalance: async () => { try { const [balance, dailyUsage, membership, subscription] = await Promise.all([ getPointsBalance(), getDailyUsage().catch(() => null), getMembership().catch(() => null), getCurrentSubscription().catch(() => get().subscription), ]) set({ balance, dailyUsage, membership, subscription }) } catch (err) { set({ error: (err as Error).message || "刷新积分失败" }) } }, optimisticDeduct: (points: number) => { const { balance, membership } = get() if (!balance) return set({ balance: { ...balance, balance: Math.max(0, balance.balance - points), total_spent: balance.total_spent + points, }, membership: membership ? { ...membership, points_balance: Math.max(0, membership.points_balance - points) } : null, }) }, optimisticAdd: (points: number) => { const { balance, membership } = get() if (!balance) return set({ balance: { ...balance, balance: balance.balance + points, total_earned: balance.total_earned + points, }, membership: membership ? { ...membership, points_balance: membership.points_balance + points } : null, }) }, reset: () => { set({ balance: null, dailyUsage: null, membership: null, rules: null, subscription: null, loading: false, error: null, }) }, }))