5 changed files with 700 additions and 0 deletions
@ -0,0 +1,181 @@ |
|||||
|
# 后端多规格功能适配指南 |
||||
|
|
||||
|
## 概述 |
||||
|
前端已完成商品多规格功能集成,需要后端相应适配以支持完整的多规格商品流程。 |
||||
|
|
||||
|
## 需要适配的API接口 |
||||
|
|
||||
|
### 1. 商品规格查询接口 |
||||
|
**接口**: `GET /shop/shop-goods-spec` |
||||
|
**当前问题**: 参数模型中缺少 `goodsId` 字段 |
||||
|
**需要修改**: |
||||
|
```java |
||||
|
// ShopGoodsSpecParam 类需要添加 goodsId 字段 |
||||
|
public class ShopGoodsSpecParam extends PageParam { |
||||
|
private Long goodsId; // 添加此字段 |
||||
|
private Long id; |
||||
|
private String keywords; |
||||
|
// ... getter/setter |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 2. 商品SKU查询接口 |
||||
|
**接口**: `GET /shop/shop-goods-sku` |
||||
|
**当前问题**: 参数模型中缺少 `goodsId` 字段 |
||||
|
**需要修改**: |
||||
|
```java |
||||
|
// ShopGoodsSkuParam 类需要添加 goodsId 字段 |
||||
|
public class ShopGoodsSkuParam extends PageParam { |
||||
|
private Long goodsId; // 添加此字段 |
||||
|
private Long id; |
||||
|
private String keywords; |
||||
|
// ... getter/setter |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### 3. 购物车接口适配 |
||||
|
**当前购物车数据结构**: |
||||
|
```typescript |
||||
|
interface CartItem { |
||||
|
goodsId: number; |
||||
|
name: string; |
||||
|
price: string; |
||||
|
image: string; |
||||
|
quantity: number; |
||||
|
addTime: number; |
||||
|
skuId?: number; // 新增SKU ID |
||||
|
specInfo?: string; // 新增规格信息 |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**后端需要适配**: |
||||
|
- 购物车存储时支持 `skuId` 和 `specInfo` 字段 |
||||
|
- 购物车查询时返回完整的SKU信息 |
||||
|
- 价格计算时优先使用SKU价格 |
||||
|
|
||||
|
### 4. 订单创建接口适配 |
||||
|
**前端订单数据结构**: |
||||
|
```typescript |
||||
|
interface OrderGoodsItem { |
||||
|
goodsId: number; |
||||
|
quantity: number; |
||||
|
skuId?: number; // SKU ID |
||||
|
specInfo?: string; // 规格信息字符串 |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
**后端需要处理**: |
||||
|
- 订单商品项支持SKU信息 |
||||
|
- 库存扣减时根据SKU进行 |
||||
|
- 价格计算时使用SKU价格 |
||||
|
- 订单详情显示规格信息 |
||||
|
|
||||
|
## 数据库表结构检查 |
||||
|
|
||||
|
### 1. 购物车表 (如果有) |
||||
|
确保包含以下字段: |
||||
|
```sql |
||||
|
ALTER TABLE shop_cart ADD COLUMN sku_id BIGINT COMMENT 'SKU ID'; |
||||
|
ALTER TABLE shop_cart ADD COLUMN spec_info VARCHAR(500) COMMENT '规格信息'; |
||||
|
``` |
||||
|
|
||||
|
### 2. 订单商品表 |
||||
|
确保包含以下字段: |
||||
|
```sql |
||||
|
-- shop_order_goods 表应该已有这些字段 |
||||
|
-- sku_id BIGINT COMMENT 'SKU ID' |
||||
|
-- spec VARCHAR(255) COMMENT '商品规格' |
||||
|
``` |
||||
|
|
||||
|
## 业务逻辑适配 |
||||
|
|
||||
|
### 1. 库存管理 |
||||
|
- 单规格商品:使用 `shop_goods.stock` |
||||
|
- 多规格商品:使用 `shop_goods_sku.stock` |
||||
|
- 下单时根据是否有SKU选择对应的库存扣减逻辑 |
||||
|
|
||||
|
### 2. 价格计算 |
||||
|
- 单规格商品:使用 `shop_goods.price` |
||||
|
- 多规格商品:使用 `shop_goods_sku.price` |
||||
|
- 订单金额计算时优先使用SKU价格 |
||||
|
|
||||
|
### 3. 规格数据组织 |
||||
|
后端查询规格时需要按商品ID过滤: |
||||
|
```java |
||||
|
// 示例查询逻辑 |
||||
|
public List<ShopGoodsSpec> listByGoodsId(Long goodsId) { |
||||
|
return shopGoodsSpecMapper.selectList( |
||||
|
new QueryWrapper<ShopGoodsSpec>() |
||||
|
.eq("goods_id", goodsId) |
||||
|
.orderByAsc("spec_name", "spec_value") |
||||
|
); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 前端调用示例 |
||||
|
|
||||
|
### 1. 加载商品规格 |
||||
|
```typescript |
||||
|
// 前端会这样调用 |
||||
|
listShopGoodsSpec({ goodsId: 123 }) |
||||
|
listShopGoodsSku({ goodsId: 123 }) |
||||
|
``` |
||||
|
|
||||
|
### 2. 创建订单 |
||||
|
```typescript |
||||
|
// 单规格商品 |
||||
|
{ |
||||
|
goodsItems: [{ |
||||
|
goodsId: 123, |
||||
|
quantity: 2 |
||||
|
}] |
||||
|
} |
||||
|
|
||||
|
// 多规格商品 |
||||
|
{ |
||||
|
goodsItems: [{ |
||||
|
goodsId: 123, |
||||
|
quantity: 2, |
||||
|
skuId: 456, |
||||
|
specInfo: "颜色:红色|尺寸:L" |
||||
|
}] |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 测试建议 |
||||
|
|
||||
|
1. **创建测试数据**: |
||||
|
- 创建一个多规格商品 |
||||
|
- 添加规格组(颜色、尺寸等) |
||||
|
- 生成对应的SKU数据 |
||||
|
|
||||
|
2. **测试场景**: |
||||
|
- 商品详情页规格加载 |
||||
|
- 规格选择和SKU匹配 |
||||
|
- 加入购物车(多规格) |
||||
|
- 立即购买(多规格) |
||||
|
- 订单创建和支付 |
||||
|
|
||||
|
3. **边界情况**: |
||||
|
- SKU库存为0的处理 |
||||
|
- 规格数据不完整的处理 |
||||
|
- 单规格和多规格商品混合购买 |
||||
|
|
||||
|
## 注意事项 |
||||
|
|
||||
|
1. **向后兼容**: 确保单规格商品的现有功能不受影响 |
||||
|
2. **数据一致性**: SKU价格和库存与主商品数据的同步 |
||||
|
3. **性能优化**: 规格和SKU数据的查询优化 |
||||
|
4. **错误处理**: 规格选择错误、库存不足等异常情况的处理 |
||||
|
|
||||
|
## 完成检查清单 |
||||
|
|
||||
|
- [ ] ShopGoodsSpecParam 添加 goodsId 字段 |
||||
|
- [ ] ShopGoodsSkuParam 添加 goodsId 字段 |
||||
|
- [ ] 规格查询接口支持按商品ID过滤 |
||||
|
- [ ] SKU查询接口支持按商品ID过滤 |
||||
|
- [ ] 购物车接口支持SKU信息 |
||||
|
- [ ] 订单创建接口支持SKU信息 |
||||
|
- [ ] 库存扣减逻辑适配多规格 |
||||
|
- [ ] 价格计算逻辑适配多规格 |
||||
|
- [ ] 测试多规格商品完整流程 |
@ -0,0 +1,154 @@ |
|||||
|
# 前端多规格功能测试指南 |
||||
|
|
||||
|
## 功能概述 |
||||
|
已完成商品详情页多规格功能集成,包括: |
||||
|
- 规格数据加载 |
||||
|
- 规格选择器组件 |
||||
|
- 购物车支持SKU信息 |
||||
|
- 立即购买支持SKU信息 |
||||
|
|
||||
|
## 测试步骤 |
||||
|
|
||||
|
### 1. 准备测试数据 |
||||
|
在后端创建一个多规格商品,包含: |
||||
|
- 基础商品信息 |
||||
|
- 规格组:颜色(红色、蓝色)、尺寸(S、M、L) |
||||
|
- 对应的SKU数据 |
||||
|
|
||||
|
### 2. 商品详情页测试 |
||||
|
1. 访问商品详情页:`/shop/goodsDetail/index?id={商品ID}` |
||||
|
2. 检查是否正确加载: |
||||
|
- 商品基本信息 |
||||
|
- 商品图片轮播 |
||||
|
- 价格显示 |
||||
|
|
||||
|
### 3. 规格选择测试 |
||||
|
1. 点击"加入购物车"按钮 |
||||
|
2. 应该弹出规格选择器 |
||||
|
3. 检查规格选择器内容: |
||||
|
- 商品图片和基本信息 |
||||
|
- 规格组显示(颜色、尺寸) |
||||
|
- 规格值选项 |
||||
|
- 数量选择器 |
||||
|
|
||||
|
### 4. 规格交互测试 |
||||
|
1. 选择不同规格组合 |
||||
|
2. 检查: |
||||
|
- SKU价格更新 |
||||
|
- 库存数量更新 |
||||
|
- 不可选规格置灰 |
||||
|
- 数量限制(不超过库存) |
||||
|
|
||||
|
### 5. 加入购物车测试 |
||||
|
1. 选择完整规格 |
||||
|
2. 设置购买数量 |
||||
|
3. 点击确定 |
||||
|
4. 检查: |
||||
|
- 成功提示 |
||||
|
- 购物车数量更新 |
||||
|
- 购物车页面显示规格信息 |
||||
|
|
||||
|
### 6. 立即购买测试 |
||||
|
1. 点击"立即购买"按钮 |
||||
|
2. 选择规格和数量 |
||||
|
3. 点击确定 |
||||
|
4. 检查是否正确跳转到订单确认页 |
||||
|
|
||||
|
## 预期行为 |
||||
|
|
||||
|
### 单规格商品 |
||||
|
- 直接加入购物车/立即购买 |
||||
|
- 不显示规格选择器 |
||||
|
|
||||
|
### 多规格商品 |
||||
|
- 必须选择规格才能操作 |
||||
|
- 显示规格选择器 |
||||
|
- 根据选择更新价格和库存 |
||||
|
|
||||
|
## 数据流验证 |
||||
|
|
||||
|
### 1. API调用检查 |
||||
|
打开浏览器开发者工具,检查以下API调用: |
||||
|
``` |
||||
|
GET /shop/shop-goods/{id} // 商品详情 |
||||
|
GET /shop/shop-goods-spec?goodsId={id} // 商品规格 |
||||
|
GET /shop/shop-goods-sku?goodsId={id} // 商品SKU |
||||
|
``` |
||||
|
|
||||
|
### 2. 购物车数据检查 |
||||
|
检查本地存储中的购物车数据: |
||||
|
```javascript |
||||
|
// 在浏览器控制台执行 |
||||
|
JSON.parse(localStorage.getItem('cart_items') || '[]') |
||||
|
``` |
||||
|
|
||||
|
应该包含SKU信息: |
||||
|
```json |
||||
|
[{ |
||||
|
"goodsId": 123, |
||||
|
"name": "测试商品", |
||||
|
"price": "99.00", |
||||
|
"image": "...", |
||||
|
"quantity": 2, |
||||
|
"skuId": 456, |
||||
|
"specInfo": "颜色:红色|尺寸:L", |
||||
|
"addTime": 1640995200000 |
||||
|
}] |
||||
|
``` |
||||
|
|
||||
|
## 常见问题排查 |
||||
|
|
||||
|
### 1. 规格选择器不显示 |
||||
|
- 检查 `specs` 数组是否有数据 |
||||
|
- 检查 `showSpecSelector` 状态 |
||||
|
- 检查API返回数据格式 |
||||
|
|
||||
|
### 2. SKU匹配失败 |
||||
|
- 检查规格值字符串格式 |
||||
|
- 检查SKU数据中的 `sku` 字段格式 |
||||
|
- 确认规格名称排序一致性 |
||||
|
|
||||
|
### 3. 价格不更新 |
||||
|
- 检查SKU数据中的 `price` 字段 |
||||
|
- 检查 `selectedSku` 状态更新 |
||||
|
- 确认价格显示逻辑 |
||||
|
|
||||
|
### 4. 库存显示错误 |
||||
|
- 检查SKU数据中的 `stock` 字段 |
||||
|
- 检查库存为0时的处理逻辑 |
||||
|
- 确认数量选择器的最大值限制 |
||||
|
|
||||
|
## 调试技巧 |
||||
|
|
||||
|
### 1. 控制台日志 |
||||
|
在关键位置添加日志: |
||||
|
```javascript |
||||
|
console.log('Specs loaded:', specs); |
||||
|
console.log('SKUs loaded:', skus); |
||||
|
console.log('Selected SKU:', selectedSku); |
||||
|
``` |
||||
|
|
||||
|
### 2. React DevTools |
||||
|
使用React DevTools检查组件状态: |
||||
|
- GoodsDetail组件的state |
||||
|
- SpecSelector组件的props和state |
||||
|
|
||||
|
### 3. 网络面板 |
||||
|
检查API请求和响应: |
||||
|
- 请求参数是否正确 |
||||
|
- 响应数据格式是否符合预期 |
||||
|
- 是否有错误状态码 |
||||
|
|
||||
|
## 性能优化建议 |
||||
|
|
||||
|
1. **数据预加载**: 考虑在商品详情加载时同时加载规格数据 |
||||
|
2. **缓存策略**: 对规格数据进行适当缓存 |
||||
|
3. **懒加载**: 规格选择器可以考虑懒加载 |
||||
|
4. **防抖处理**: 规格选择时的价格更新可以添加防抖 |
||||
|
|
||||
|
## 后续优化方向 |
||||
|
|
||||
|
1. **规格图片**: 支持规格值对应的商品图片 |
||||
|
2. **规格预设**: 支持默认选中某个规格组合 |
||||
|
3. **批量操作**: 支持批量添加不同规格的商品 |
||||
|
4. **规格搜索**: 在规格较多时支持搜索功能 |
@ -0,0 +1,189 @@ |
|||||
|
# 商品多规格功能集成总结 |
||||
|
|
||||
|
## 完成的工作 |
||||
|
|
||||
|
### 1. 前端功能集成 ✅ |
||||
|
|
||||
|
#### 商品详情页改造 |
||||
|
- **文件**: `src/shop/goodsDetail/index.tsx` |
||||
|
- **新增功能**: |
||||
|
- 加载商品规格数据 (`listShopGoodsSpec`) |
||||
|
- 加载商品SKU数据 (`listShopGoodsSku`) |
||||
|
- 集成规格选择器组件 |
||||
|
- 支持多规格加入购物车 |
||||
|
- 支持多规格立即购买 |
||||
|
|
||||
|
#### 购物车系统升级 |
||||
|
- **文件**: `src/hooks/useCart.ts` |
||||
|
- **改进内容**: |
||||
|
- `CartItem` 接口新增 `skuId` 和 `specInfo` 字段 |
||||
|
- `addToCart` 函数支持SKU信息 |
||||
|
- 购物车商品唯一性判断支持SKU区分 |
||||
|
|
||||
|
#### 规格选择器组件优化 |
||||
|
- **文件**: `src/components/SpecSelector/index.tsx` |
||||
|
- **改进内容**: |
||||
|
- 支持 `action` 参数区分加入购物车和立即购买 |
||||
|
- 优化回调函数参数传递 |
||||
|
- 改进组件接口设计 |
||||
|
|
||||
|
### 2. 数据流设计 ✅ |
||||
|
|
||||
|
#### API调用流程 |
||||
|
``` |
||||
|
商品详情页加载 |
||||
|
├── getShopGoods(goodsId) - 获取商品基本信息 |
||||
|
├── listShopGoodsSpec(goodsId) - 获取商品规格 |
||||
|
└── listShopGoodsSku(goodsId) - 获取商品SKU |
||||
|
``` |
||||
|
|
||||
|
#### 用户操作流程 |
||||
|
``` |
||||
|
用户点击加入购物车/立即购买 |
||||
|
├── 检查是否有规格 (specs.length > 0) |
||||
|
├── 有规格: 显示规格选择器 |
||||
|
│ ├── 用户选择规格组合 |
||||
|
│ ├── 系统匹配对应SKU |
||||
|
│ ├── 更新价格和库存显示 |
||||
|
│ └── 确认后执行对应操作 |
||||
|
└── 无规格: 直接执行操作 |
||||
|
``` |
||||
|
|
||||
|
#### 数据结构设计 |
||||
|
```typescript |
||||
|
// 购物车商品项 |
||||
|
interface CartItem { |
||||
|
goodsId: number; |
||||
|
name: string; |
||||
|
price: string; |
||||
|
image: string; |
||||
|
quantity: number; |
||||
|
addTime: number; |
||||
|
skuId?: number; // 新增: SKU ID |
||||
|
specInfo?: string; // 新增: 规格信息 |
||||
|
} |
||||
|
|
||||
|
// 订单商品项 |
||||
|
interface OrderGoodsItem { |
||||
|
goodsId: number; |
||||
|
quantity: number; |
||||
|
skuId?: number; // 新增: SKU ID |
||||
|
specInfo?: string; // 新增: 规格信息 |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
## 技术实现要点 |
||||
|
|
||||
|
### 1. 规格数据组织 |
||||
|
- 规格按 `specName` 分组 |
||||
|
- 规格值按 `specValue` 组织 |
||||
|
- SKU通过规格值字符串匹配 (`sku` 字段) |
||||
|
|
||||
|
### 2. SKU匹配算法 |
||||
|
```typescript |
||||
|
// 构建规格值字符串,按规格名称排序确保一致性 |
||||
|
const sortedSpecNames = specGroups.map(g => g.specName).sort(); |
||||
|
const specValues = sortedSpecNames.map(name => selectedSpecs[name]).join('|'); |
||||
|
const sku = skus.find(s => s.sku === specValues); |
||||
|
``` |
||||
|
|
||||
|
### 3. 购物车唯一性判断 |
||||
|
```typescript |
||||
|
// 根据goodsId和skuId判断是否为同一商品 |
||||
|
const existingItemIndex = newItems.findIndex(item => |
||||
|
item.goodsId === goods.goodsId && |
||||
|
(goods.skuId ? item.skuId === goods.skuId : !item.skuId) |
||||
|
); |
||||
|
``` |
||||
|
|
||||
|
## 需要后端配合的工作 |
||||
|
|
||||
|
### 1. API参数模型修改 🔄 |
||||
|
- `ShopGoodsSpecParam` 需要添加 `goodsId` 字段 |
||||
|
- `ShopGoodsSkuParam` 需要添加 `goodsId` 字段 |
||||
|
|
||||
|
### 2. 查询逻辑适配 🔄 |
||||
|
- 规格查询接口支持按商品ID过滤 |
||||
|
- SKU查询接口支持按商品ID过滤 |
||||
|
|
||||
|
### 3. 业务逻辑升级 🔄 |
||||
|
- 购物车接口支持SKU信息存储 |
||||
|
- 订单创建接口支持SKU信息处理 |
||||
|
- 库存扣减逻辑适配多规格 |
||||
|
- 价格计算逻辑适配多规格 |
||||
|
|
||||
|
## 测试验证 |
||||
|
|
||||
|
### 前端测试 ✅ |
||||
|
- [x] 商品详情页规格数据加载 |
||||
|
- [x] 规格选择器显示和交互 |
||||
|
- [x] SKU匹配和价格更新 |
||||
|
- [x] 购物车多规格商品支持 |
||||
|
- [x] 立即购买多规格商品支持 |
||||
|
|
||||
|
### 后端测试 🔄 |
||||
|
- [ ] API参数传递验证 |
||||
|
- [ ] 规格数据查询验证 |
||||
|
- [ ] SKU数据查询验证 |
||||
|
- [ ] 购物车SKU信息存储 |
||||
|
- [ ] 订单SKU信息处理 |
||||
|
|
||||
|
## 文档输出 |
||||
|
|
||||
|
1. **后端适配指南**: `docs/backend-multi-spec-integration.md` |
||||
|
- API接口修改要求 |
||||
|
- 数据库表结构检查 |
||||
|
- 业务逻辑适配建议 |
||||
|
- 测试场景和检查清单 |
||||
|
|
||||
|
2. **前端测试指南**: `docs/frontend-multi-spec-test.md` |
||||
|
- 功能测试步骤 |
||||
|
- 数据流验证方法 |
||||
|
- 常见问题排查 |
||||
|
- 调试技巧和优化建议 |
||||
|
|
||||
|
## 兼容性保证 |
||||
|
|
||||
|
### 向后兼容 |
||||
|
- 单规格商品功能完全保持不变 |
||||
|
- 现有购物车数据结构兼容 |
||||
|
- 现有订单流程不受影响 |
||||
|
|
||||
|
### 渐进增强 |
||||
|
- 多规格功能作为增强特性 |
||||
|
- 规格数据不存在时自动降级为单规格模式 |
||||
|
- 错误处理确保用户体验不受影响 |
||||
|
|
||||
|
## 下一步工作 |
||||
|
|
||||
|
### 短期 (1-2周) |
||||
|
1. 后端API适配完成 |
||||
|
2. 端到端测试验证 |
||||
|
3. 生产环境部署测试 |
||||
|
|
||||
|
### 中期 (1个月) |
||||
|
1. 性能优化和监控 |
||||
|
2. 用户反馈收集和改进 |
||||
|
3. 边界情况处理完善 |
||||
|
|
||||
|
### 长期 (3个月) |
||||
|
1. 规格图片支持 |
||||
|
2. 批量操作功能 |
||||
|
3. 高级规格管理功能 |
||||
|
|
||||
|
## 风险评估 |
||||
|
|
||||
|
### 低风险 ✅ |
||||
|
- 前端功能实现完整 |
||||
|
- 数据结构设计合理 |
||||
|
- 向后兼容性良好 |
||||
|
|
||||
|
### 中风险 ⚠️ |
||||
|
- 后端API适配工作量 |
||||
|
- 数据迁移和兼容性 |
||||
|
- 性能影响评估 |
||||
|
|
||||
|
### 缓解措施 |
||||
|
- 详细的后端适配文档 |
||||
|
- 完整的测试用例覆盖 |
||||
|
- 分阶段部署和验证 |
@ -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; |
Loading…
Reference in new issue