chore(points): align types/api with final backend contract - apps/web/src/store/pointsStore.ts

This commit is contained in:
2026-09-16 10:20:08 +08:00
parent 5cefc5d630
commit 0abd23466a
+83 -36
View File
@@ -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<void>
/** 强制刷新余额 */
/** 刷新余额(充值/消费后调用) */
refreshBalance: () => Promise<void>
/** 乐观扣减:在支付/业务发起前调用,失败时用 refreshBalance 兜底 */
/** 乐观扣减 */
optimisticDeduct: (points: number) => void
/** 乐观增加(充值成功后调用) */
/** 乐观增加 */
optimisticAdd: (points: number) => void
/** 清除积分状态(退出登录) */
reset: () => void
}
export const usePointsStore = create<PointsState>((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,
})
},
}))