优化下单流程
This commit is contained in:
0
src/components/SpecSelector/index.scss
Normal file
0
src/components/SpecSelector/index.scss
Normal file
176
src/components/SpecSelector/index.tsx
Normal file
176
src/components/SpecSelector/index.tsx
Normal file
@@ -0,0 +1,176 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { View } from '@tarojs/components';
|
||||
import { Popup, Button, Radio, Image, Space, Cell, CellGroup } from '@nutui/nutui-react-taro';
|
||||
import { ShopGoodsSku } from '@/api/shop/shopGoodsSku/model';
|
||||
import { ShopGoodsSpec } from '@/api/shop/shopGoodsSpec/model';
|
||||
import { ShopGoods } from '@/api/shop/shopGoods/model';
|
||||
import './index.scss';
|
||||
|
||||
interface SpecSelectorProps {
|
||||
visible?: boolean;
|
||||
onClose: () => void;
|
||||
goods: ShopGoods;
|
||||
specs: ShopGoodsSpec[];
|
||||
skus: ShopGoodsSku[];
|
||||
onConfirm: (selectedSku: ShopGoodsSku, quantity: number, action?: 'cart' | 'buy') => void;
|
||||
action?: 'cart' | 'buy';
|
||||
}
|
||||
|
||||
interface SpecGroup {
|
||||
specName: string;
|
||||
values: string[];
|
||||
}
|
||||
|
||||
const SpecSelector: React.FC<SpecSelectorProps> = ({
|
||||
visible = true,
|
||||
onClose,
|
||||
goods,
|
||||
specs,
|
||||
skus,
|
||||
onConfirm,
|
||||
action = 'cart'
|
||||
}) => {
|
||||
const [selectedSpecs, setSelectedSpecs] = useState<Record<string, string>>({});
|
||||
const [selectedSku, setSelectedSku] = useState<ShopGoodsSku | null>(null);
|
||||
const [quantity, setQuantity] = useState(1);
|
||||
const [specGroups, setSpecGroups] = useState<SpecGroup[]>([]);
|
||||
|
||||
// 组织规格数据
|
||||
useEffect(() => {
|
||||
if (specs.length > 0) {
|
||||
const groups: Record<string, Set<string>> = {};
|
||||
|
||||
specs.forEach(spec => {
|
||||
if (spec.specName && spec.specValue) {
|
||||
if (!groups[spec.specName]) {
|
||||
groups[spec.specName] = new Set();
|
||||
}
|
||||
groups[spec.specName].add(spec.specValue);
|
||||
}
|
||||
});
|
||||
|
||||
const groupsArray = Object.entries(groups).map(([specName, values]) => ({
|
||||
specName,
|
||||
values: Array.from(values)
|
||||
}));
|
||||
|
||||
setSpecGroups(groupsArray);
|
||||
}
|
||||
}, [specs]);
|
||||
|
||||
// 根据选中规格找到对应SKU
|
||||
useEffect(() => {
|
||||
if (Object.keys(selectedSpecs).length === specGroups.length && skus.length > 0) {
|
||||
// 构建规格值字符串,按照规格名称排序确保一致性
|
||||
const sortedSpecNames = specGroups.map(g => g.specName).sort();
|
||||
const specValues = sortedSpecNames.map(name => selectedSpecs[name]).join('|');
|
||||
|
||||
const sku = skus.find(s => s.sku === specValues);
|
||||
setSelectedSku(sku || null);
|
||||
} else {
|
||||
setSelectedSku(null);
|
||||
}
|
||||
}, [selectedSpecs, skus, specGroups]);
|
||||
|
||||
// 选择规格值
|
||||
const handleSpecSelect = (specName: string, specValue: string) => {
|
||||
setSelectedSpecs(prev => ({
|
||||
...prev,
|
||||
[specName]: specValue
|
||||
}));
|
||||
};
|
||||
|
||||
// 确认选择
|
||||
const handleConfirm = () => {
|
||||
if (!selectedSku) {
|
||||
return;
|
||||
}
|
||||
onConfirm(selectedSku, quantity, action);
|
||||
};
|
||||
|
||||
// 检查规格值是否可选(是否有对应的SKU且有库存)
|
||||
const isSpecValueAvailable = (specName: string, specValue: string) => {
|
||||
const testSpecs = { ...selectedSpecs, [specName]: specValue };
|
||||
|
||||
// 如果还有其他规格未选择,则认为可选
|
||||
if (Object.keys(testSpecs).length < specGroups.length) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 构建规格值字符串
|
||||
const sortedSpecNames = specGroups.map(g => g.specName).sort();
|
||||
const specValues = sortedSpecNames.map(name => testSpecs[name]).join('|');
|
||||
|
||||
const sku = skus.find(s => s.sku === specValues);
|
||||
return sku && sku.stock && sku.stock > 0 && sku.status === 0;
|
||||
};
|
||||
|
||||
return (
|
||||
<Popup
|
||||
visible={visible}
|
||||
position="bottom"
|
||||
onClose={onClose}
|
||||
style={{ height: '60vh' }}
|
||||
>
|
||||
<View className="spec-selector">
|
||||
{/* 商品信息 */}
|
||||
<View className="spec-selector__header p-4">
|
||||
<Space className="flex">
|
||||
<Image
|
||||
src={selectedSku?.image || goods.image || ''}
|
||||
width="80"
|
||||
height="80"
|
||||
radius="8"
|
||||
/>
|
||||
<View className="goods-detail">
|
||||
<View className="goods-name font-medium text-lg">{goods.name}</View>
|
||||
<View className="text-red-500">
|
||||
¥{selectedSku?.price || goods.price}
|
||||
</View>
|
||||
<View className="goods-stock text-gray-500">
|
||||
库存:{selectedSku?.stock || goods.stock}
|
||||
</View>
|
||||
</View>
|
||||
</Space>
|
||||
</View>
|
||||
|
||||
{/* 规格选择 */}
|
||||
<CellGroup className="spec-selector__content">
|
||||
<Cell>
|
||||
<Space direction="vertical">
|
||||
<View className={'title'}>套餐</View>
|
||||
<Radio.Group defaultValue="1" direction="horizontal">
|
||||
<Radio shape="button" value="1">
|
||||
选项1
|
||||
</Radio>
|
||||
<Radio shape="button" value="2">
|
||||
选项2
|
||||
</Radio>
|
||||
<Radio shape="button" value="3">
|
||||
选项3
|
||||
</Radio>
|
||||
</Radio.Group>
|
||||
</Space>
|
||||
</Cell>
|
||||
</CellGroup>
|
||||
{/* 底部按钮 */}
|
||||
<View className="fixed bottom-7 w-full">
|
||||
<View className={'px-4'}>
|
||||
<Button
|
||||
type="success"
|
||||
size="large"
|
||||
className={'w-full'}
|
||||
block
|
||||
// disabled={!selectedSku || !selectedSku.stock || selectedSku.stock <= 0}
|
||||
onClick={handleConfirm}
|
||||
>
|
||||
确定
|
||||
</Button>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
</Popup>
|
||||
);
|
||||
};
|
||||
|
||||
export default SpecSelector;
|
||||
Reference in New Issue
Block a user