diff --git a/apps/web/src/store/pointsStore.ts b/apps/web/src/store/pointsStore.ts new file mode 100644 index 000000000..9ce4d9cb0 --- /dev/null +++ b/apps/web/src/store/pointsStore.ts @@ -0,0 +1,83 @@ +/** + * 积分 & 会员状态管理(Zustand) + * - 启动时拉取余额 & 订阅信息 + * - 提供刷新、余额扣减(乐观更新)等工具 + */ +import { create } from "zustand" +import type { PointsBalance, PointsRulesResponse, SubscriptionCurrent } from "@/api/points/types" +import { getCurrentSubscription, getPointsBalance, getPointsRules } from "@/api/points" + +interface PointsState { + /** 积分余额 & 会员状态(来自 /points/balance) */ + balance: PointsBalance | null + /** 订阅详情(来自 /subscription/current) */ + subscription: SubscriptionCurrent | null + /** 积分消耗规则缓存 */ + rules: PointsRulesResponse | null + loading: boolean + error: string | null + /** 初始化:拉取余额 + 订阅信息 + 规则 */ + init: () => Promise + /** 强制刷新余额 */ + refreshBalance: () => Promise + /** 乐观扣减:在支付/业务发起前调用,失败时用 refreshBalance 兜底 */ + optimisticDeduct: (points: number) => void + /** 乐观增加(充值成功后调用) */ + optimisticAdd: (points: number) => void +} + +export const usePointsStore = create((set, get) => ({ + balance: null, + subscription: null, + rules: null, + loading: false, + error: null, + + init: async () => { + if (get().loading) return + set({ loading: true, error: null }) + try { + const [balance, sub, rules] = await Promise.all([ + getPointsBalance(), + getCurrentSubscription(), + getPointsRules(), + ]) + set({ balance, subscription: sub, rules, loading: false }) + } catch (e) { + set({ error: (e as Error).message, loading: false }) + } + }, + + refreshBalance: async () => { + try { + const balance = await getPointsBalance() + set({ balance }) + } catch (e) { + set({ error: (e as Error).message }) + } + }, + + optimisticDeduct: (points: number) => { + const b = get().balance + if (!b) return + set({ + balance: { + ...b, + balance: Math.max(0, b.balance - points), + total_spent: b.total_spent + points, + }, + }) + }, + + optimisticAdd: (points: number) => { + const b = get().balance + if (!b) return + set({ + balance: { + ...b, + balance: b.balance + points, + total_earned: b.total_earned + points, + }, + }) + }, +}))