import React, { createContext, useContext, useState, useEffect, ReactNode } from 'react' import Taro from '@tarojs/taro' export interface CartItem { id: number // 商品ID(当前子商品ID,或父商品ID如果无规格) parentGoodsId: number // 父商品ID,0表示无父商品(单规格商品) name: string // 商品名称(包含规格信息的完整名称) price: number // 商品价格 image: string // 商品图片 stock: number // 商品库存 quantity: number // 购买数量 } export interface CartState { items: CartItem[] totalAmount: number totalCount: number } interface CartContextType { cart: CartState addToCart: (item: CartItem) => void removeFromCart: (id: number) => void updateQuantity: (id: number, quantity: number) => void switchSpec: (cartItemId: number, newChildGoods: { id: number; name: string; price: number; stock: number; image?: string }, quantity?: number) => void clearCart: () => void isInCart: (id: number) => boolean getItemQuantity: (id: number) => number isLoading: boolean } const CartContext = createContext(undefined) const CART_STORAGE_KEY = 'mini_cart' export const CartProvider: React.FC<{ children: ReactNode }> = ({ children }) => { const [cart, setCart] = useState({ items: [], totalAmount: 0, totalCount: 0 }) const [isLoading, setIsLoading] = useState(true) // 从本地存储加载购物车 useEffect(() => { const loadCart = () => { try { const savedCart = Taro.getStorageSync(CART_STORAGE_KEY) if (savedCart && Array.isArray(savedCart.items)) { // 数据迁移:确保每个购物车项都有parentGoodsId字段 const migratedItems = savedCart.items.map((item: any) => ({ ...item, parentGoodsId: item.parentGoodsId !== undefined ? item.parentGoodsId : 0 // 旧数据默认为0(单规格商品) })) const totalAmount = migratedItems.reduce((sum: number, item: CartItem) => sum + (item.price * item.quantity), 0) const totalCount = migratedItems.reduce((sum: number, item: CartItem) => sum + item.quantity, 0) setCart({ items: migratedItems, totalAmount, totalCount }) } } catch (error) { console.error('加载购物车失败:', error) } finally { setIsLoading(false) } } loadCart() }, []) // 保存购物车到本地存储 const saveCart = (items: CartItem[]) => { const totalAmount = items.reduce((sum, item) => sum + (item.price * item.quantity), 0) const totalCount = items.reduce((sum, item) => sum + item.quantity, 0) const newCart = { items, totalAmount, totalCount } setCart(newCart) try { Taro.setStorageSync(CART_STORAGE_KEY, { items }) } catch (error) { console.error('保存购物车失败:', error) } } // 添加商品到购物车,支持父商品和子商品 // 注意:父子商品的租户一致性验证在API层面进行 const addToCart = (item: CartItem) => { const existingItem = cart.items.find(cartItem => cartItem.id === item.id) if (existingItem) { // 如果商品已存在,增加数量 const newQuantity = Math.min(existingItem.quantity + item.quantity, item.stock) if (newQuantity === existingItem.quantity) { Taro.showToast({ title: '库存不足', icon: 'none' }) return } const newItems = cart.items.map(cartItem => cartItem.id === item.id ? { ...cartItem, quantity: newQuantity } : cartItem ) saveCart(newItems) Taro.showToast({ title: '已更新购物车', icon: 'success' }) } else { // 添加新商品 if (item.quantity > item.stock) { Taro.showToast({ title: '库存不足', icon: 'none' }) return } saveCart([...cart.items, item]) } } // 从购物车移除商品 const removeFromCart = (id: number) => { const newItems = cart.items.filter(item => item.id !== id) saveCart(newItems) Taro.showToast({ title: '已移除商品', icon: 'success' }) } // 更新商品数量 const updateQuantity = (id: number, quantity: number) => { const item = cart.items.find(item => item.id === id) if (!item) return // 当数量小于等于0时,设为1而不是删除商品 if (quantity <= 0) { quantity = 1 } if (quantity > item.stock) { Taro.showToast({ title: '库存不足', icon: 'none' }) return } const newItems = cart.items.map(item => item.id === id ? { ...item, quantity } : item ) saveCart(newItems) } // 清空购物车 const clearCart = () => { saveCart([]) Taro.showToast({ title: '已清空购物车', icon: 'success' }) } // 检查商品是否在购物车中 const isInCart = (id: number) => { return cart.items.some(item => item.id === id) } // 获取购物车中商品数量 const getItemQuantity = (id: number) => { const item = cart.items.find(item => item.id === id) return item ? item.quantity : 0 } // 切换购物车项规格 const switchSpec = ( cartItemId: number, newChildGoods: { id: number; name: string; price: number; stock: number; image?: string }, quantity?: number ) => { try { const item = cart.items.find(item => item.id === cartItemId) if (!item) { console.error('切换规格失败:购物车项不存在', cartItemId) Taro.showToast({ title: '商品不存在', icon: 'none' }) return } // 检查是否是父商品(允许切换规格) if (item.parentGoodsId === 0) { console.error('切换规格失败:单规格商品不支持切换', cartItemId) Taro.showToast({ title: '该商品不支持切换规格', icon: 'none' }) return } // 检查新规格库存是否足够 if (newChildGoods.stock <= 0) { console.error('切换规格失败:规格无库存', { newStock: newChildGoods.stock }) Taro.showToast({ title: '该规格已售罄', icon: 'none' }) return } // 确定要使用的数量:如果提供了quantity参数则使用,否则使用原有数量 const finalQuantity = quantity !== undefined ? quantity : item.quantity // 验证数量有效性 if (finalQuantity <= 0) { console.error('切换规格失败:数量无效', { finalQuantity }) Taro.showToast({ title: '数量不能小于1', icon: 'none' }) return } if (finalQuantity > newChildGoods.stock) { console.error('切换规格失败:库存不足', { finalQuantity, newStock: newChildGoods.stock }) Taro.showToast({ title: `规格库存不足,仅剩${newChildGoods.stock}件`, icon: 'none' }) return } // 验证新规格数据完整性 if (!newChildGoods.id || !newChildGoods.name || newChildGoods.price < 0) { console.error('切换规格失败:规格数据不完整', newChildGoods) Taro.showToast({ title: '规格数据错误', icon: 'none' }) return } // 创建更新后的购物车项 const updatedItem: CartItem = { ...item, id: newChildGoods.id, name: newChildGoods.name, price: newChildGoods.price, stock: newChildGoods.stock, image: newChildGoods.image || item.image, quantity: finalQuantity } // 更新购物车 const newItems = cart.items.map(cartItem => cartItem.id === cartItemId ? updatedItem : cartItem ) saveCart(newItems) Taro.showToast({ title: '已切换规格', icon: 'success' }) } catch (error) { console.error('切换规格时发生异常:', error) Taro.showToast({ title: '切换规格失败,请重试', icon: 'none' }) } } const value = { cart, addToCart, removeFromCart, updateQuantity, switchSpec, clearCart, isInCart, getItemQuantity, isLoading } return ( {children} ) } export const useCart = () => { const context = useContext(CartContext) if (context === undefined) { throw new Error('useCart must be used within a CartProvider') } return context }