import { create } from 'zustand' import Taro from '@tarojs/taro' import { addCartApi, clearCartApi, deleteCartItemApi, getCartApi, getCartSummaryApi, updateCartItemApi, } from '@/services/cart' import type { CartMutationResult } from '@/services/cart' import { getToken } from '@/utils/request' import type { CartItem, CartSummary } from '@/types/cart' /** 存储 key */ const STORAGE_KEY = 'cart_data' /** 汇总请求序号(并发时仅采用最后一次响应) */ let summarySeq = 0 /** 汇总防抖校准定时器(列表加减停止 800ms 后整体拉取一次,以服务端为准) */ let summaryTimer: ReturnType | null = null /** 数值 → 2 位小数字符串(与服务端金额/数量口径一致) */ function to2(n: number): string { return (Math.round(n * 100) / 100).toFixed(2) } /** 持久化的购物车快照(服务端为准,此处仅作展示缓存) */ interface StoredCart { items: CartItem[] totalCount: number totalQuantity: string totalAmount: string } /** 从本地存储恢复购物车快照 */ function loadFromStorage(): StoredCart | null { try { const stored = Taro.getStorageSync(STORAGE_KEY) if (stored && Array.isArray(stored.items)) { return stored as StoredCart } } catch { // noop } return null } interface CartState { items: CartItem[] /** 总项数 */ totalCount: number /** 可购项总数量(服务端字符串金额/数量) */ totalQuantity: string /** 可购项总金额(服务端重算) */ totalAmount: string /** 是否已从服务端同步过(避免每次展示都闪烁 loading) */ loaded: boolean loading: boolean /** 拉取购物车(以服务端为准,金额一律服务端重算) */ fetchCart: () => Promise /** 加购(返回合并后的购物车行,供列表页回写 cart_id/cart_quantity) */ addItem: (productId: number, quantity: number) => Promise /** 修改数量 */ updateQuantity: (id: number, quantity: number) => Promise /** 删除单项 */ removeItem: (id: number) => Promise /** 清空购物车 */ clearCart: () => Promise /** 下单成功后本地清空(不请求接口) */ clearLocal: () => void /** 用接口附带的汇总块(首页/商品列表响应的 cart 字段)直接更新悬浮球 */ setSummary: (summary: CartSummary) => void /** 拉取轻量汇总(需登录;未登录跳过,避免 401 跳转) */ fetchSummary: () => Promise /** 列表行内加减后本地增减悬浮球(乐观展示),并防抖调 fetchSummary 校准 */ applyDelta: (delta: { quantity: number; amount: number; count?: number }) => void } /** 空的购物车快照 */ const EMPTY_SNAPSHOT = { items: [] as CartItem[], totalCount: 0, totalQuantity: '0.00', totalAmount: '0.00', } const useCartStore = create((set, get) => { const cached = loadFromStorage() /** 持久化购物车快照 */ const persist = ( snapshot: Pick, ) => { try { Taro.setStorageSync(STORAGE_KEY, snapshot) } catch { // storage 写入失败不阻塞 } } /** 写入悬浮球汇总并持久化(列表项快照保持不变) */ const applySummary = (summary: CartSummary) => { const next = { items: get().items, totalCount: summary.total_count, totalQuantity: summary.total_quantity, totalAmount: summary.total_amount, } set(next) persist(next) } return { ...EMPTY_SNAPSHOT, items: cached?.items ?? [], totalCount: cached?.totalCount ?? 0, totalQuantity: cached?.totalQuantity ?? '0.00', totalAmount: cached?.totalAmount ?? '0.00', loaded: false, loading: false, /** 拉取购物车(服务端为准) */ fetchCart: async () => { if (get().loading) return set({ loading: true }) try { const res = await getCartApi() const { items, total_count, total_quantity, total_amount } = res.data const next = { items, totalCount: total_count, totalQuantity: total_quantity, totalAmount: total_amount, } set({ ...next, loaded: true }) persist(next) } finally { set({ loading: false }) } }, /** 加购:服务端校验上架与等级价,成功后重新同步 */ addItem: async (productId, quantity) => { const res = await addCartApi({ product_id: productId, quantity }) await get().fetchCart() return res.data }, /** 修改数量 */ updateQuantity: async (id, quantity) => { await updateCartItemApi(id, quantity) await get().fetchCart() }, /** 删除单项 */ removeItem: async (id) => { await deleteCartItemApi(id) await get().fetchCart() }, /** 清空购物车 */ clearCart: async () => { await clearCartApi() set({ ...EMPTY_SNAPSHOT, loaded: true }) persist(EMPTY_SNAPSHOT) }, /** 下单成功后本地清空 */ clearLocal: () => { set(EMPTY_SNAPSHOT) persist(EMPTY_SNAPSHOT) }, /** 写入接口附带的汇总块(首页/商品列表) */ setSummary: (summary) => { applySummary(summary) }, /** 拉取轻量汇总(并发时仅采用最后一次响应) */ fetchSummary: async () => { // 未登录无汇总(接口固定 401,会触发清理登录态),直接跳过 if (!getToken()) return const seq = ++summarySeq const res = await getCartSummaryApi() if (seq !== summarySeq) return // 已有更新的请求,丢弃本次响应 applySummary(res.data) }, /** 列表加减后的本地增减:即时反馈,防抖后以服务端汇总校准 */ applyDelta: ({ quantity, amount, count = 0 }) => { const s = get() applySummary({ total_count: Math.max(0, s.totalCount + count), total_quantity: to2(Math.max(0, Number(s.totalQuantity) + quantity)), total_amount: to2(Math.max(0, Number(s.totalAmount) + amount)), }) if (summaryTimer) clearTimeout(summaryTimer) summaryTimer = setTimeout(() => { summaryTimer = null get().fetchSummary().catch(() => {}) }, 800) }, } }) export default useCartStore