import React, { createContext, useContext, useState, useCallback, type ReactNode } from 'react' import Taro from '@tarojs/taro' import { listShopCart, addToCart, updateCartNum, removeShopCart, removeBatchShopCart, updateCartAllChecked } from '@/api/shop/shopCart' import type { ShopGoods, ShopGoodsSku } from '@/api/shop/shopGoods/model' export interface CartItem { id?: number goodsId: number skuId?: number quantity: number product?: ShopGoods sku?: ShopGoodsSku checked?: boolean // 后端返回的字段 goodsName?: string goodsImage?: string skuPrice?: string skuSpec?: string stock?: number } interface CartContextType { items: CartItem[] totalCount: number totalPrice: string selectedItems: CartItem[] selectedCount: number selectedPrice: string loading: boolean refresh: () => Promise addItem: (product: ShopGoods, sku?: ShopGoodsSku, quantity?: number) => Promise removeItem: (goodsId: number, skuId?: number) => Promise updateQuantity: (goodsId: number, skuId: number | undefined, quantity: number) => Promise toggleSelect: (goodsId: number, skuId: number | undefined) => Promise selectAll: (checked: boolean) => Promise clearCart: () => Promise getItemQuantity: (goodsId: number, skuId?: number) => number // 删除选中的商品 removeSelected: () => Promise } const CartContext = createContext(undefined) export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [items, setItems] = useState([]) const [loading, setLoading] = useState(false) // 刷新购物车数据 const refresh = useCallback(async () => { setLoading(true) try { const data = await listShopCart() // 防御性检查:确保返回的是数组 const safeData = Array.isArray(data) ? data : [] // 转换为前端格式,默认都选中 const mappedItems: CartItem[] = safeData.map(item => ({ id: item.id, goodsId: item.goodsId!, skuId: item.skuId, quantity: item.num || 1, goodsName: item.goodsName, goodsImage: item.goodsImage, skuPrice: item.skuPrice, skuSpec: item.skuSpec, stock: item.stock, checked: true, })) setItems(mappedItems) } catch (err) { console.error('刷新购物车失败', err) setItems([]) } finally { setLoading(false) } }, []) // 添加商品到购物车 const addItem = useCallback(async (product: ShopGoods, sku?: ShopGoodsSku, quantity = 1) => { try { const params = { goodsId: product.goodsId!, num: quantity, } as Record // 只有多规格商品才传 skuId,且不能为 0 if (sku?.id && sku.id > 0) { params.skuId = sku.id } await addToCart(params) Taro.showToast({ title: '已加入购物车', icon: 'success' }) // 刷新购物车 await refresh() } catch (err: any) { console.error('[CartContext] 添加失败:', err) Taro.showToast({ title: err.message || '添加失败', icon: 'none' }) throw err } }, [refresh]) // 删除商品 const removeItem = useCallback(async (goodsId: number, skuId?: number) => { const item = items.find(i => i.goodsId === goodsId && i.skuId === skuId) if (!item?.id) return try { await removeShopCart(item.id) // 本地更新 setItems(prev => prev.filter(i => !(i.goodsId === goodsId && i.skuId === skuId))) } catch (err: any) { Taro.showToast({ title: err.message || '删除失败', icon: 'none' }) } }, [items]) // 更新数量 const updateQuantity = useCallback(async (goodsId: number, skuId: number | undefined, quantity: number) => { if (quantity <= 0) { await removeItem(goodsId, skuId) return } const item = items.find(i => i.goodsId === goodsId && i.skuId === skuId) if (!item?.id) return try { await updateCartNum({ id: item.id, num: quantity }) // 本地更新 setItems(prev => prev.map(i => (i.goodsId === goodsId && i.skuId === skuId) ? { ...i, quantity } : i )) } catch (err: any) { Taro.showToast({ title: err.message || '更新失败', icon: 'none' }) } }, [items, removeItem]) // 切换选中状态 const toggleSelect = useCallback(async (goodsId: number, skuId: number | undefined) => { const item = items.find(i => i.goodsId === goodsId && i.skuId === skuId) if (!item || !item.id) return const newChecked = !item.checked // 本地立即更新 setItems(prev => prev.map(i => (i.goodsId === goodsId && i.skuId === skuId) ? { ...i, checked: newChecked } : i )) // 同步到后端 try { await updateCartChecked(item.id, newChecked) } catch (err) { // API失败时回滚本地状态 setItems(prev => prev.map(i => (i.goodsId === goodsId && i.skuId === skuId) ? { ...i, checked: !newChecked } : i )) console.error('更新选中状态失败', err) } }, [items]) // 全选/取消全选 const selectAll = useCallback(async (checked: boolean) => { // 本地立即更新 setItems(prev => prev.map(i => ({ ...i, checked }))) // 尝试调用API try { await updateCartAllChecked(checked) } catch (err) { // API失败时静默处理,本地状态已更新 } }, []) // 清空购物车 const clearCart = useCallback(async () => { try { await removeBatchShopCart(items.map(i => i.id!).filter(Boolean)) setItems([]) } catch (err: any) { Taro.showToast({ title: err.message || '清空失败', icon: 'none' }) } }, [items]) // 删除选中的商品 const removeSelected = useCallback(async () => { const selected = items.filter(i => i.checked && i.id) if (selected.length === 0) return try { await removeBatchShopCart(selected.map(i => i.id!)) setItems(prev => prev.filter(i => !i.checked)) Taro.showToast({ title: `已删除${selected.length}件商品`, icon: 'success' }) } catch (err: any) { Taro.showToast({ title: err.message || '删除失败', icon: 'none' }) } }, [items]) const getItemQuantity = useCallback((goodsId: number, skuId?: number): number => { return items.find(i => i.goodsId === goodsId && i.skuId === skuId)?.quantity || 0 }, [items]) const calcPrice = (list: CartItem[]) => list.reduce((sum, i) => sum + Number(i.skuPrice || i.sku?.price || i.product?.salePrice || i.product?.price || 0) * i.quantity, 0).toFixed(2) const selectedItems = items.filter(i => i.checked) const selectedCount = selectedItems.reduce((s, i) => s + i.quantity, 0) return ( s + i.quantity, 0), totalPrice: calcPrice(items), selectedItems, selectedCount, selectedPrice: calcPrice(selectedItems), loading, refresh, addItem, removeItem, updateQuantity, toggleSelect, selectAll, clearCart, getItemQuantity, removeSelected, }} > {children} ) } export const useCartContext = (): CartContextType => { const context = useContext(CartContext) if (!context) throw new Error('useCartContext must be used within CartProvider') return context }