import React, { useState, useEffect, useMemo } from 'react' import { View, Text, Image, ScrollView } from '@tarojs/components' import Taro from '@tarojs/taro' import type { ShopGoods, ShopGoodsSku } from '@/api/shop/shopGoods/model' import type { ShopGoodsSpec } from '@/api/shop/shopGoodsSpec/model' import Price from '@/components/common/Price' interface SkuSelectorProps { visible: boolean product: ShopGoods | null mode?: 'cart' | 'buy' onClose: () => void onConfirm: (sku: ShopGoodsSku, quantity: number) => void } // 将平铺的规格值列表转换为按规格ID分组的格式 interface SpecGroup { specId: number specName: string values: Array<{ specValueId: number; specValue: string }> } // 解析可能为JSON字符串的规格值 const parseSpecValue = (value: string | undefined): string => { if (!value) return '' const trimmed = value.trim() // 处理带引号的JSON字符串,如 "红色" -> 红色 if (trimmed.startsWith('"') && trimmed.endsWith('"') && trimmed.length >= 2) { try { return JSON.parse(trimmed) } catch { return trimmed.slice(1, -1) } } return trimmed } const SkuSelector: React.FC = ({ visible, product, mode = 'cart', onClose, onConfirm, }) => { const [selectedSpecs, setSelectedSpecs] = useState>({}) const [selectedSku, setSelectedSku] = useState(null) const [quantity, setQuantity] = useState(1) const [showOverlay, setShowOverlay] = useState(false) const [slideUp, setSlideUp] = useState(false) // 控制动画 useEffect(() => { if (visible) { setShowOverlay(true) // 延迟一帧触发滑入动画 setTimeout(() => setSlideUp(true), 10) } else { setSlideUp(false) setTimeout(() => setShowOverlay(false), 300) } }, [visible]) // 将平铺的goodsSpecs转换为按规格分组的格式 const specGroups = useMemo(() => { if (!product?.goodsSpecs?.length) return [] const groupMap = new Map() product.goodsSpecs.forEach((spec: ShopGoodsSpec, idx: number) => { if (!groupMap.has(spec.specId!)) { groupMap.set(spec.specId!, { specId: spec.specId!, specName: spec.specName || `规格${groupMap.size + 1}`, values: [] }) } groupMap.get(spec.specId!)!.values.push({ specValueId: spec.id || idx, // 优先使用规格值ID,否则用索引 specValue: parseSpecValue(spec.specValue) }) }) return Array.from(groupMap.values()) }, [product?.goodsSpecs]) // 当弹窗重新打开时,重置状态 useEffect(() => { if (visible) { setSelectedSpecs({}) setSelectedSku(null) setQuantity(1) } }, [visible]) // 根据已选规格查找匹配的 SKU useEffect(() => { if (!product?.goodsSkus?.length) { // 单规格商品,使用商品本身的价格 setSelectedSku(null) return } const specValues = Object.values(selectedSpecs) const specCount = specGroups.length if (specValues.length < specCount) { setSelectedSku(null) return } // 根据选中的 specValueId 找到对应的中文值,拼接后排序匹配 const selectedValues: string[] = [] specGroups.forEach(group => { const valueId = selectedSpecs[group.specId] if (valueId !== undefined) { const value = group.values.find(v => v.specValueId === valueId) if (value) selectedValues.push(value.specValue) } }) const selectedStr = selectedValues.sort().join('|') const matched = product.goodsSkus.find(sku => { const skuValue = sku.sku || '' const skuParts = skuValue.split('|').map(s => parseSpecValue(s.trim())) const skuStr = skuParts.sort().join('|') return skuStr === selectedStr }) setSelectedSku(matched || null) }, [selectedSpecs, product, specGroups]) const handleSpecClick = (specId: number, valueId: number) => { setSelectedSpecs(prev => { const next = { ...prev } if (next[specId] === valueId) { delete next[specId] } else { next[specId] = valueId } return next }) setQuantity(1) } const handleClose = () => { setSlideUp(false) setTimeout(() => { setShowOverlay(false) onClose() }, 300) } const handleConfirm = () => { try { // 单规格商品(无SKU列表 或 SKU列表为空数组) if ((!product?.goodsSkus || product.goodsSkus.length === 0) && product) { const fakeSku: ShopGoodsSku = { id: 0, goodsId: product.goodsId!, price: product.price || product.salePrice, salePrice: product.salePrice || product.price, stock: product.stock, image: product.image, } handleClose() onConfirm(fakeSku, quantity) return } // 多规格商品 - 必须已选择 SKU if (selectedSku) { handleClose() onConfirm(selectedSku, quantity) return } // 有规格但未选择完整 if (specGroups.length > 0) { Taro.showToast({ title: '请选择完整的规格', icon: 'none' }) return } // 其他情况 Taro.showToast({ title: '无法添加商品', icon: 'none' }) } catch (err) { Taro.showToast({ title: '操作失败', icon: 'none' }) } } // 价格优先级:SKU售价 > SKU原价 > 商品售价 > 商品原价 > 0 // 注意:ShopGoods 模型中 price=商品价格(低), salePrice=销售/市场价(高) // ShopGoodsSku 模型中 price=商品价格, salePrice=市场价格(高) // 统一以 price 为准,salePrice 仅作兜底展示 const currentPrice = selectedSku?.price || selectedSku?.salePrice || product?.price || product?.salePrice || '0' const currentStock = selectedSku?.stock ?? product?.stock ?? 0 const currentImage = selectedSku?.image || product?.image || (product?.files?.split(',')[0]) || '' // 单规格商品可以确认,或者多规格商品已选择了 SKU const canConfirm = (!product?.goodsSkus || product.goodsSkus.length === 0) || selectedSku !== null // 获取未选择完整的规格提示 const getUnselectedSpec = () => { if (specGroups.length === 0) return null const unselected = specGroups.find( spec => !selectedSpecs[spec.specId] ) return unselected?.specName } // 获取选中的规格文字描述 const getSelectedText = () => { const selected: string[] = [] specGroups.forEach(group => { const valueId = selectedSpecs[group.specId] if (valueId !== undefined) { const value = group.values.find(v => v.specValueId === valueId) if (value) selected.push(value.specValue) } }) return selected.length > 0 ? selected.join(', ') : '默认' } // 如果不需要显示,直接返回null if (!showOverlay) return null return ( {/* 遮罩层 */} {/* 弹窗内容 */} {/* 关闭按钮 */} × {/* 商品信息 */} 库存: {currentStock} 已选: {getSelectedText()} {/* 规格选择 */} {specGroups.map((group) => ( {group.specName} {group.values.map((value) => { const isActive = selectedSpecs[group.specId] === value.specValueId return ( handleSpecClick(group.specId, value.specValueId)} > {value.specValue} ) })} ))} {/* 无规格时显示提示 */} {specGroups.length === 0 && product?.goodsSkus?.length === 0 && ( 单规格商品 )} {/* 数量 */} 数量 quantity > 1 && setQuantity(prev => prev - 1)} > - {quantity} = currentStock ? 'border-gray-200 text-gray-300' : 'border-gray-300 text-gray-500' }`} onClick={() => quantity < currentStock && setQuantity(prev => prev + 1)} > = currentStock ? 'text-gray-300' : 'text-gray-500'}>+ {/* 确认按钮 */} {getUnselectedSpec() ? `请选择${getUnselectedSpec()}` : (mode === 'cart' ? '加入购物车' : '立即购买')} ) } export default SkuSelector