购物车悬浮加减

This commit is contained in:
liu
2026-08-27 18:08:14 +08:00
parent 78c787d207
commit 260d8086bf
16 changed files with 445 additions and 35 deletions
+66 -4
View File
@@ -5,13 +5,26 @@ import {
clearCartApi,
deleteCartItemApi,
getCartApi,
getCartSummaryApi,
updateCartItemApi,
} from '@/services/cart'
import type { CartItem } from '@/types/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<typeof setTimeout> | null = null
/** 数值 → 2 位小数字符串(与服务端金额/数量口径一致) */
function to2(n: number): string {
return (Math.round(n * 100) / 100).toFixed(2)
}
/** 持久化的购物车快照(服务端为准,此处仅作展示缓存) */
interface StoredCart {
items: CartItem[]
@@ -46,8 +59,8 @@ interface CartState {
loading: boolean
/** 拉取购物车(以服务端为准,金额一律服务端重算) */
fetchCart: () => Promise<void>
/** 加购 */
addItem: (productId: number, quantity: number) => Promise<void>
/** 加购(返回合并后的购物车行,供列表页回写 cart_id/cart_quantity */
addItem: (productId: number, quantity: number) => Promise<CartMutationResult>
/** 修改数量 */
updateQuantity: (id: number, quantity: number) => Promise<void>
/** 删除单项 */
@@ -56,6 +69,12 @@ interface CartState {
clearCart: () => Promise<void>
/** 下单成功后本地清空(不请求接口) */
clearLocal: () => void
/** 用接口附带的汇总块(首页/商品列表响应的 cart 字段)直接更新悬浮球 */
setSummary: (summary: CartSummary) => void
/** 拉取轻量汇总(需登录;未登录跳过,避免 401 跳转) */
fetchSummary: () => Promise<void>
/** 列表行内加减后本地增减悬浮球(乐观展示),并防抖调 fetchSummary 校准 */
applyDelta: (delta: { quantity: number; amount: number; count?: number }) => void
}
/** 空的购物车快照 */
@@ -80,6 +99,18 @@ const useCartStore = create<CartState>((set, get) => {
}
}
/** 写入悬浮球汇总并持久化(列表项快照保持不变) */
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 ?? [],
@@ -111,8 +142,9 @@ const useCartStore = create<CartState>((set, get) => {
/** 加购:服务端校验上架与等级价,成功后重新同步 */
addItem: async (productId, quantity) => {
await addCartApi({ product_id: productId, quantity })
const res = await addCartApi({ product_id: productId, quantity })
await get().fetchCart()
return res.data
},
/** 修改数量 */
@@ -139,6 +171,36 @@ const useCartStore = create<CartState>((set, get) => {
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)
},
}
})