diff --git a/apps/web/src/store/pointsStore.ts b/apps/web/src/store/pointsStore.ts index 9ce4d9cb0..f64cd1144 100644 --- a/apps/web/src/store/pointsStore.ts +++ b/apps/web/src/store/pointsStore.ts @@ -1,83 +1,130 @@ /** - * 积分 & 会员状态管理(Zustand) - * - 启动时拉取余额 & 订阅信息 - * - 提供刷新、余额扣减(乐观更新)等工具 + * 积分 & 会员状态管理 + * 对齐后端 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 type { PointsBalance, PointsRulesResponse, SubscriptionCurrent } from "@/api/points/types" -import { getCurrentSubscription, getPointsBalance, getPointsRules } from "@/api/points" +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 { - /** 积分余额 & 会员状态(来自 /points/balance) */ balance: PointsBalance | null - /** 订阅详情(来自 /subscription/current) */ - subscription: SubscriptionCurrent | 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 - /** 乐观扣减:在支付/业务发起前调用,失败时用 refreshBalance 兜底 */ + /** 乐观扣减 */ optimisticDeduct: (points: number) => void - /** 乐观增加(充值成功后调用) */ + /** 乐观增加 */ optimisticAdd: (points: number) => void + /** 清除积分状态(退出登录) */ + reset: () => void } export const usePointsStore = create((set, get) => ({ balance: null, - subscription: null, + dailyUsage: null, + membership: null, rules: null, + subscription: null, loading: false, error: null, init: async () => { - if (get().loading) return + // 已加载过不重复拉取 + if (get().balance && get().rules && get().subscription) return set({ loading: true, error: null }) try { - const [balance, sub, rules] = await Promise.all([ - getPointsBalance(), - getCurrentSubscription(), - getPointsRules(), + const [balance, rules, subscription, dailyUsage, membership] = await Promise.all([ + getPointsBalance().catch(() => null), + getPointsRules().catch(() => null), + getCurrentSubscription().catch(() => null), + getDailyUsage().catch(() => null), + getMembership().catch(() => null), ]) - set({ balance, subscription: sub, rules, loading: false }) - } catch (e) { - set({ error: (e as Error).message, loading: false }) + set({ + balance, + rules, + subscription, + dailyUsage, + membership, + loading: false, + }) + } catch (err) { + set({ error: (err as Error).message || "加载积分信息失败", loading: false }) } }, refreshBalance: async () => { try { - const balance = await getPointsBalance() - set({ balance }) - } catch (e) { - set({ error: (e as Error).message }) + 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 b = get().balance - if (!b) return + const { balance, membership } = get() + if (!balance) return set({ balance: { - ...b, - balance: Math.max(0, b.balance - points), - total_spent: b.total_spent + points, + ...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 b = get().balance - if (!b) return + const { balance, membership } = get() + if (!balance) return set({ balance: { - ...b, - balance: b.balance + points, - total_earned: b.total_earned + points, + ...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, }) }, }))