并入后台管理端(vue)和小程序端的代码(template-10550)

This commit is contained in:
2025-08-08 23:04:17 +08:00
parent a0f4fcc03f
commit b0f9eaefa1
1452 changed files with 241138 additions and 0 deletions

View File

@@ -0,0 +1,245 @@
# 前端订单提交实现文档
## 概述
本文档描述了前端订单提交的完整实现,包括单商品下单、购物车批量下单等场景。
## 核心改进
### 1. 统一的API接口
**新的订单创建接口:**
```typescript
// 订单商品项
interface OrderGoodsItem {
goodsId: number;
quantity: number;
skuId?: number;
specInfo?: string;
}
// 创建订单请求
interface OrderCreateRequest {
goodsItems: OrderGoodsItem[];
addressId?: number;
payType: number;
couponId?: number;
comments?: string;
deliveryType?: number;
selfTakeMerchantId?: number;
title?: string;
}
// 微信支付返回数据
interface WxPayResult {
prepayId: string;
orderNo: string;
timeStamp: string;
nonceStr: string;
package: string;
signType: string;
paySign: string;
}
```
### 2. 标题长度限制
**工具函数:**
```typescript
// 截取文本,限制长度
export function truncateText(text: string, maxLength: number = 30): string {
if (!text) return '';
if (text.length <= maxLength) return text;
return text.substring(0, maxLength);
}
// 生成订单标题
export function generateOrderTitle(goodsNames: string[], maxLength: number = 30): string {
if (!goodsNames || goodsNames.length === 0) return '商品订单';
let title = goodsNames.length === 1
? goodsNames[0]
: `${goodsNames[0]}${goodsNames.length}件商品`;
return truncateText(title, maxLength);
}
```
## 实现细节
### 1. 单商品下单
**文件:** `src/shop/orderConfirm/index.tsx`
**核心逻辑:**
```typescript
const onWxPay = async (goods: ShopGoods) => {
// 1. 校验收货地址
if (!address) {
Taro.showToast({ title: '请选择收货地址', icon: 'error' });
return;
}
Taro.showLoading({title: '支付中...'});
try {
// 2. 构建订单数据
const orderData: OrderCreateRequest = {
goodsItems: [{ goodsId: goods.goodsId!, quantity: 1 }],
addressId: address.id,
payType: 1,
comments: goods.name,
deliveryType: 0
};
// 3. 创建订单
const result = await createOrder(orderData);
// 4. 微信支付
if (result && result.prepayId) {
await Taro.requestPayment({
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: result.signType,
paySign: result.paySign,
});
// 5. 支付成功处理
Taro.showToast({ title: '支付成功', icon: 'success' });
setTimeout(() => {
Taro.switchTab({url: '/pages/order/order'});
}, 2000);
}
} catch (error: any) {
Taro.showToast({ title: error.message || '下单失败', icon: 'error' });
} finally {
Taro.hideLoading();
}
};
```
### 2. 购物车批量下单
**文件:** `src/shop/orderConfirmCart/index.tsx`
**核心逻辑:**
```typescript
const onCartPay = async () => {
// 1. 校验
if (!address || !cartItems || cartItems.length === 0) {
// 错误处理
return;
}
try {
// 2. 构建批量商品数据
const orderData: OrderCreateRequest = {
goodsItems: cartItems.map(item => ({
goodsId: item.goodsId!,
quantity: item.quantity || 1
})),
addressId: address.id,
payType: 1,
comments: '购物车下单',
deliveryType: 0
};
// 3. 创建订单并支付
const result = await createOrder(orderData);
// ... 支付逻辑
} catch (error) {
// 错误处理
}
};
```
## 数据流程
### 1. 前端提交流程
```
用户点击支付
校验地址和商品
构建OrderCreateRequest
调用createOrder API
后端返回WxPayResult
调用微信支付
支付成功跳转
```
### 2. 后端处理流程
```
接收OrderCreateRequest
参数校验
构建订单主表
保存订单商品明细
库存扣减
生成订单标题(≤30字)
创建微信支付
返回支付参数
```
## 关键特性
### 1. 数据安全
- 前端只传递商品ID和数量
- 价格、库存等敏感信息由后端实时获取
- 防止前端数据篡改
### 2. 业务完整性
- 统一的订单创建流程
- 完整的错误处理机制
- 支持多种下单场景
### 3. 用户体验
- 清晰的加载状态
- 友好的错误提示
- 自动跳转到订单页面
## 扩展功能
### 1. 支持的下单类型
- 单商品立即购买
- 购物车批量下单
- 自提订单
- 使用优惠券下单
### 2. 支持的配送方式
- 快递配送 (deliveryType: 0)
- 到店自提 (deliveryType: 1)
### 3. 支持的支付方式
- 微信支付 (payType: 1)
- 余额支付 (payType: 0)
- 其他支付方式...
## 注意事项
1. **标题长度限制**订单标题最多30个汉字超过自动截取
2. **库存校验**:后端会实时校验商品库存
3. **地址校验**:确保收货地址属于当前用户
4. **错误处理**:完善的异常捕获和用户提示
5. **支付安全**:支付参数由后端生成,前端不可篡改
## 测试建议
1. 测试单商品下单流程
2. 测试购物车批量下单
3. 测试各种异常情况(库存不足、地址无效等)
4. 测试支付成功和失败的处理
5. 测试订单标题长度限制功能

View File

@@ -0,0 +1,103 @@
# 订单列表功能完善说明
## 完善的功能
### 1. 订单商品正确显示
- **问题**: 原来只显示订单基本信息,没有显示具体的商品信息
- **解决方案**:
- 扩展了订单接口,添加了 `OrderWithGoods` 类型
- 在加载订单列表时,同时获取每个订单的商品信息
- 使用 `listShopOrderGoods` API 获取订单商品详情
- 显示商品图片、名称、规格、数量和价格
### 2. 订单状态正确显示
- **问题**: 原来固定显示"待付款"状态
- **解决方案**:
- 添加了 `getOrderStatusText` 函数,根据订单的 `payStatus``deliveryStatus``orderStatus` 动态显示状态
- 支持的状态包括:待付款、待发货、待收货、已收货、已完成、已取消、退款申请中、退款成功等
### 3. 确认收货功能
- **新增功能**:
- 当订单状态为"待收货"时,显示"确认收货"按钮
- 点击确认收货后,更新订单状态为"已收货"和"已完成"
- 操作成功后显示提示信息并刷新列表
### 4. 取消订单功能
- **新增功能**:
- 当订单状态为"待付款"时,显示"取消订单"按钮
- 点击取消订单后,更新订单状态为"已取消"
- 操作成功后显示提示信息并刷新列表
### 5. 操作按钮优化
- **改进**: 根据订单状态动态显示不同的操作按钮
- 待付款:显示"取消订单"和"立即支付"按钮
- 待收货:显示"确认收货"按钮
- 已完成:显示"申请退款"按钮(预留功能)
### 6. 订单详情页面修复
- **问题**: 订单详情页面使用了错误的API
- **解决方案**:
- 修改为使用正确的 `listShopOrderGoods` API
- 直接显示商品信息,无需额外查询商品详情
- 优化了商品信息的显示格式
## 技术改进
### 1. 类型安全
- 添加了 `OrderWithGoods` 接口扩展
- 完善了 `OrderListProps` 接口定义
- 使用了正确的 TypeScript 类型
### 2. 错误处理
- 添加了完善的错误处理机制
- 操作失败时显示友好的错误提示
- 防止因单个订单商品获取失败而影响整个列表
### 3. 用户体验
- 添加了操作成功的提示信息
- 操作完成后自动刷新列表
- 阻止事件冒泡,避免误触
### 4. 数据一致性
- 操作完成后通知父组件刷新数据
- 确保订单状态的实时更新
## 使用说明
### 订单状态说明
- **待付款**: `payStatus = 0`
- **待发货**: `payStatus = 1 && deliveryStatus = 10`
- **待收货**: `deliveryStatus = 20`
- **已收货**: `deliveryStatus = 30`
- **已完成**: `orderStatus = 1`
- **已取消**: `orderStatus = 2`
### API 依赖
- `pageShopOrder`: 分页查询订单
- `listShopOrderGoods`: 查询订单商品
- `updateShopOrder`: 更新订单状态
### 组件结构
```
src/pages/order/
├── order.tsx # 订单主页面
├── components/
│ └── OrderList.tsx # 订单列表组件
└── test-order.tsx # 测试页面(可选)
```
## 测试建议
1. 创建不同状态的测试订单
2. 验证订单商品信息显示是否正确
3. 测试确认收货功能
4. 测试取消订单功能
5. 验证订单状态切换是否正常
## 后续优化建议
1. 添加订单搜索功能
2. 实现立即支付功能
3. 添加申请退款功能
4. 优化商品图片加载和缓存
5. 添加订单操作的二次确认

View File

@@ -0,0 +1,264 @@
# 支付逻辑重构指南
## 概述
本文档描述了支付逻辑的重构过程,将原本分散的支付代码统一整合,提高了代码的可维护性和复用性。
## 重构前后对比
### 重构前的问题
1. **代码重复**:每个支付方式都有重复的订单构建逻辑
2. **维护困难**:支付逻辑分散在多个方法中
3. **扩展性差**:添加新的支付方式需要修改多处代码
### 重构后的优势
1. **统一入口**:所有支付都通过 `onPay` 方法处理
2. **代码复用**:订单构建逻辑统一封装
3. **易于扩展**:新增支付方式只需在工具类中添加
4. **错误处理统一**:所有支付的错误处理逻辑一致
## 核心改进
### 1. 统一的支付工具类
**文件:** `src/utils/payment.ts`
```typescript
// 支付类型枚举
export enum PaymentType {
BALANCE = 0, // 余额支付
WECHAT = 1, // 微信支付
ALIPAY = 3, // 支付宝支付
}
// 统一支付处理类
export class PaymentHandler {
static async pay(
orderData: OrderCreateRequest,
paymentType: PaymentType,
callback?: PaymentCallback
): Promise<void> {
// 统一的支付处理逻辑
}
}
```
### 2. 订单数据构建函数
```typescript
// 单商品订单
export function buildSingleGoodsOrder(
goodsId: number,
quantity: number = 1,
addressId?: number,
options?: {
comments?: string;
deliveryType?: number;
couponId?: number;
}
): OrderCreateRequest
// 购物车订单
export function buildCartOrder(
cartItems: Array<{ goodsId: number; quantity: number }>,
addressId?: number,
options?: { ... }
): OrderCreateRequest
```
### 3. 简化的支付入口
**重构前:**
```typescript
const onWxPay = async (goods: ShopGoods) => {
// 校验逻辑
// 构建订单数据
// 调用创建订单API
// 处理微信支付
// 错误处理
}
const onBalancePay = async (goods: ShopGoods) => {
// 重复的校验逻辑
// 重复的订单构建逻辑
// 处理余额支付
// 重复的错误处理
}
const onPay = async (goods: ShopGoods) => {
if (payment?.type == 0) {
await onBalancePay(goods)
}
if (payment?.type == 1) {
await onWxPay(goods)
}
}
```
**重构后:**
```typescript
const onPay = async (goods: ShopGoods) => {
// 基础校验
if (!address || !payment) {
// 错误提示
return;
}
// 构建订单数据
const orderData = buildSingleGoodsOrder(
goods.goodsId!,
1,
address.id,
{ comments: goods.name }
);
// 选择支付类型
const paymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT;
// 执行支付
await PaymentHandler.pay(orderData, paymentType);
};
```
## 使用示例
### 1. 单商品下单
```typescript
// 在 orderConfirm/index.tsx 中
const onPay = async (goods: ShopGoods) => {
if (!address || !payment) return;
const orderData = buildSingleGoodsOrder(
goods.goodsId!,
1,
address.id,
{
comments: goods.name,
deliveryType: 0
}
);
const paymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT;
await PaymentHandler.pay(orderData, paymentType);
};
```
### 2. 购物车批量下单
```typescript
// 在 orderConfirmCart/index.tsx 中
const onPay = async () => {
if (!address || !cartItems?.length) return;
const orderData = buildCartOrder(
cartItems.map(item => ({
goodsId: item.goodsId!,
quantity: item.quantity || 1
})),
address.id,
{
comments: '购物车下单',
deliveryType: 0
}
);
const paymentType = payment?.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT;
await PaymentHandler.pay(orderData, paymentType);
};
```
### 3. 使用优惠券下单
```typescript
const onPayWithCoupon = async (goods: ShopGoods, couponId: number) => {
const orderData = buildSingleGoodsOrder(
goods.goodsId!,
1,
address.id,
{
comments: `使用优惠券购买${goods.name}`,
couponId: couponId
}
);
await PaymentHandler.pay(orderData, PaymentType.WECHAT);
};
```
### 4. 自提订单
```typescript
const onSelfPickupOrder = async (goods: ShopGoods, merchantId: number) => {
const orderData = buildSingleGoodsOrder(
goods.goodsId!,
1,
address.id,
{
comments: `自提订单 - ${goods.name}`,
deliveryType: 1,
selfTakeMerchantId: merchantId
}
);
await PaymentHandler.pay(orderData, PaymentType.WECHAT);
};
```
## 扩展新的支付方式
### 1. 添加支付类型
```typescript
// 在 PaymentType 枚举中添加
export enum PaymentType {
BALANCE = 0,
WECHAT = 1,
ALIPAY = 3,
UNIONPAY = 4, // 新增银联支付
}
```
### 2. 实现支付处理方法
```typescript
// 在 PaymentHandler 类中添加
private static async handleUnionPay(result: any): Promise<void> {
// 银联支付逻辑
}
```
### 3. 在支付分发中添加
```typescript
// 在 PaymentHandler.pay 方法中添加
switch (paymentType) {
case PaymentType.WECHAT:
await this.handleWechatPay(result);
break;
case PaymentType.BALANCE:
await this.handleBalancePay(result);
break;
case PaymentType.UNIONPAY: // 新增
await this.handleUnionPay(result);
break;
// ...
}
```
## 优势总结
1. **代码简洁**:支付入口方法从 50+ 行减少到 20+ 行
2. **逻辑清晰**:订单构建、支付处理、错误处理分离
3. **易于测试**:每个功能模块独立,便于单元测试
4. **维护性强**:修改支付逻辑只需在工具类中修改
5. **扩展性好**:新增支付方式无需修改业务代码
## 注意事项
1. **向后兼容**:确保重构后的接口与原有调用方式兼容
2. **错误处理**:统一的错误处理机制,确保用户体验一致
3. **类型安全**:使用 TypeScript 枚举确保支付类型的类型安全
4. **测试覆盖**:对重构后的代码进行充分的测试

View File

@@ -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信息
- [ ] 库存扣减逻辑适配多规格
- [ ] 价格计算逻辑适配多规格
- [ ] 测试多规格商品完整流程

View File

@@ -0,0 +1,248 @@
/**
* 订单服务实现类示例
* 展示如何保存订单商品信息的业务逻辑
*/
@Service
@Transactional(rollbackFor = Exception.class)
public class ShopOrderServiceImpl implements ShopOrderService {
@Autowired
private ShopOrderMapper shopOrderMapper;
@Autowired
private OrderGoodsMapper orderGoodsMapper;
@Autowired
private ShopGoodsService shopGoodsService;
@Autowired
private ShopUserAddressService addressService;
@Autowired
private WxPayService wxPayService;
/**
* 创建订单
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Map<String, String> createOrder(OrderCreateRequest request, User loginUser) {
// 1. 参数校验
validateOrderRequest(request, loginUser);
// 2. 构建订单对象
ShopOrder shopOrder = buildShopOrder(request, loginUser);
// 3. 应用业务规则
applyBusinessRules(shopOrder, loginUser);
// 4. 保存订单主表
boolean saved = shopOrderMapper.insert(shopOrder) > 0;
if (!saved) {
throw new BusinessException("订单保存失败");
}
// 5. 保存订单商品明细 - 核心业务逻辑
saveOrderGoods(shopOrder, request.getGoodsItems());
// 6. 创建微信支付订单
try {
return wxPayService.createWxOrder(shopOrder);
} catch (Exception e) {
log.error("创建微信支付订单失败,订单号:{}", shopOrder.getOrderNo(), e);
throw new BusinessException("创建支付订单失败:" + e.getMessage());
}
}
/**
* 保存订单商品明细 - 核心实现
*/
private void saveOrderGoods(ShopOrder shopOrder, List<OrderGoodsItem> goodsItems) {
List<OrderGoods> orderGoodsList = new ArrayList<>();
BigDecimal totalPrice = BigDecimal.ZERO;
int totalQuantity = 0;
List<String> goodsNames = new ArrayList<>();
for (OrderGoodsItem item : goodsItems) {
// 1. 获取商品最新信息进行校验
ShopGoods goods = shopGoodsService.getById(item.getGoodsId());
if (goods == null) {
throw new BusinessException("商品不存在:" + item.getGoodsId());
}
// 2. 商品状态校验
if (goods.getIsShow() != 1) {
throw new BusinessException("商品已下架:" + goods.getName());
}
// 3. 库存校验
if (goods.getStock() < item.getQuantity()) {
throw new BusinessException("商品库存不足:" + goods.getName());
}
// 4. 价格计算(以数据库中的价格为准)
BigDecimal itemPrice = new BigDecimal(goods.getPrice());
BigDecimal itemTotalPrice = itemPrice.multiply(new BigDecimal(item.getQuantity()));
// 5. 构建订单商品记录
OrderGoods orderGoods = new OrderGoods();
orderGoods.setOrderId(shopOrder.getOrderId());
orderGoods.setGoodsId(item.getGoodsId());
orderGoods.setTotalNum(item.getQuantity());
orderGoods.setPayPrice(itemTotalPrice.toString());
orderGoods.setType(0); // 0商城
orderGoods.setPayStatus("0"); // 0未付款
orderGoods.setOrderStatus(0); // 0未完成
orderGoods.setUserId(shopOrder.getUserId());
orderGoods.setTenantId(shopOrder.getTenantId());
orderGoods.setCreateTime(LocalDateTime.now());
// 6. SKU信息处理如果有规格
if (item.getSkuId() != null) {
// 处理SKU相关逻辑
// orderGoods.setSkuId(item.getSkuId());
// orderGoods.setSpecInfo(item.getSpecInfo());
}
orderGoodsList.add(orderGoods);
// 7. 累计计算
totalPrice = totalPrice.add(itemTotalPrice);
totalQuantity += item.getQuantity();
goodsNames.add(goods.getName());
// 8. 扣减库存(根据业务需求,可能在支付成功后扣减)
if (goods.getDeductStockType() == 10) { // 10下单减库存
goods.setStock(goods.getStock() - item.getQuantity());
shopGoodsService.updateById(goods);
}
}
// 9. 批量保存订单商品
if (!orderGoodsList.isEmpty()) {
orderGoodsMapper.insertBatch(orderGoodsList);
}
// 10. 更新订单总价和数量
shopOrder.setTotalPrice(totalPrice.toString());
shopOrder.setPayPrice(totalPrice.toString()); // 暂时不考虑优惠
shopOrder.setTotalNum(totalQuantity);
// 11. 生成订单标题限制30个汉字
String title = generateOrderTitle(goodsNames);
shopOrder.setTitle(title);
// 12. 更新订单主表
shopOrderMapper.updateById(shopOrder);
}
/**
* 生成订单标题限制长度不超过30个汉字
*/
private String generateOrderTitle(List<String> goodsNames) {
if (goodsNames.isEmpty()) {
return "商品订单";
}
String title;
if (goodsNames.size() == 1) {
title = goodsNames.get(0);
} else {
title = goodsNames.get(0) + "" + goodsNames.size() + "件商品";
}
// 限制标题长度最多30个汉字
if (title.length() > 30) {
title = title.substring(0, 30);
}
return title;
}
/**
* 参数校验
*/
private void validateOrderRequest(OrderCreateRequest request, User loginUser) {
if (request.getGoodsItems() == null || request.getGoodsItems().isEmpty()) {
throw new BusinessException("商品信息不能为空");
}
if (request.getAddressId() == null) {
throw new BusinessException("收货地址不能为空");
}
// 校验收货地址是否属于当前用户
ShopUserAddress address = addressService.getById(request.getAddressId());
if (address == null || !address.getUserId().equals(loginUser.getUserId())) {
throw new BusinessException("收货地址不存在或不属于当前用户");
}
// 校验商品数量
for (OrderGoodsItem item : request.getGoodsItems()) {
if (item.getGoodsId() == null || item.getQuantity() <= 0) {
throw new BusinessException("商品信息不正确");
}
}
}
/**
* 构建订单对象
*/
private ShopOrder buildShopOrder(OrderCreateRequest request, User loginUser) {
ShopOrder shopOrder = new ShopOrder();
// 基础信息
shopOrder.setOrderNo(generateOrderNo());
shopOrder.setType(0); // 0商城订单
shopOrder.setChannel(0); // 0小程序
shopOrder.setUserId(loginUser.getUserId());
shopOrder.setTenantId(loginUser.getTenantId());
// 用户信息
shopOrder.setRealName(loginUser.getRealName());
shopOrder.setPhone(loginUser.getPhone());
// 地址信息
ShopUserAddress address = addressService.getById(request.getAddressId());
shopOrder.setAddressId(request.getAddressId());
shopOrder.setAddress(address.getProvince() + address.getCity() +
address.getRegion() + address.getAddress());
// 支付信息
shopOrder.setPayType(request.getPayType());
shopOrder.setPayStatus(0); // 0未付款
shopOrder.setOrderStatus(0); // 0未使用
// 配送信息
shopOrder.setDeliveryType(request.getDeliveryType());
if (request.getSelfTakeMerchantId() != null) {
shopOrder.setSelfTakeMerchantId(request.getSelfTakeMerchantId());
}
// 其他信息
shopOrder.setComments(request.getComments());
shopOrder.setCreateTime(LocalDateTime.now());
return shopOrder;
}
/**
* 应用业务规则
*/
private void applyBusinessRules(ShopOrder shopOrder, User loginUser) {
// 设置默认标题(如果没有设置)
if (shopOrder.getTitle() == null) {
shopOrder.setTitle("商品订单");
}
// 其他业务规则...
}
/**
* 生成订单号
*/
private String generateOrderNo() {
return "SO" + System.currentTimeMillis() +
String.format("%04d", new Random().nextInt(10000));
}
}

View File

@@ -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. **规格搜索**: 在规格较多时支持搜索功能

View File

@@ -0,0 +1,317 @@
/**
* 前端订单提交完整示例
* 展示如何使用新的订单API进行下单
*/
import React, { useState, useEffect } from 'react';
import Taro from '@tarojs/taro';
import { Button } from '@nutui/nutui-react-taro';
import { createOrder } from '@/api/shop/shopOrder';
import { OrderCreateRequest, OrderGoodsItem } from '@/api/shop/shopOrder/model';
import { ShopGoods } from '@/api/shop/shopGoods/model';
import { ShopUserAddress } from '@/api/shop/shopUserAddress/model';
import { generateOrderTitle } from '@/utils/common';
interface OrderExampleProps {
goods: ShopGoods;
address: ShopUserAddress;
quantity?: number;
}
const OrderExample: React.FC<OrderExampleProps> = ({
goods,
address,
quantity = 1
}) => {
const [loading, setLoading] = useState(false);
/**
* 单商品下单示例
*/
const handleSingleGoodsOrder = async () => {
if (!address) {
Taro.showToast({
title: '请选择收货地址',
icon: 'error'
});
return;
}
setLoading(true);
try {
// 1. 构建订单请求数据
const orderData: OrderCreateRequest = {
goodsItems: [
{
goodsId: goods.goodsId!,
quantity: quantity
}
],
addressId: address.id,
payType: 1, // 微信支付
comments: `购买${goods.name}`,
deliveryType: 0, // 快递配送
// 可选:自定义订单标题
title: generateOrderTitle([goods.name!])
};
// 2. 调用创建订单API
const result = await createOrder(orderData);
if (result && result.prepayId) {
// 3. 调用微信支付
await Taro.requestPayment({
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: result.signType,
paySign: result.paySign,
});
// 4. 支付成功处理
Taro.showToast({
title: '支付成功',
icon: 'success'
});
setTimeout(() => {
Taro.switchTab({url: '/pages/order/order'});
}, 2000);
}
} catch (error: any) {
console.error('下单失败:', error);
Taro.showToast({
title: error.message || '下单失败',
icon: 'error'
});
} finally {
setLoading(false);
}
};
/**
* 购物车批量下单示例
*/
const handleCartOrder = async (cartItems: Array<{goodsId: number, quantity: number, goodsName: string}>) => {
if (!address) {
Taro.showToast({
title: '请选择收货地址',
icon: 'error'
});
return;
}
if (!cartItems || cartItems.length === 0) {
Taro.showToast({
title: '购物车为空',
icon: 'error'
});
return;
}
setLoading(true);
try {
// 1. 构建订单商品列表
const goodsItems: OrderGoodsItem[] = cartItems.map(item => ({
goodsId: item.goodsId,
quantity: item.quantity
}));
// 2. 生成订单标题
const goodsNames = cartItems.map(item => item.goodsName);
const orderTitle = generateOrderTitle(goodsNames);
// 3. 构建订单请求数据
const orderData: OrderCreateRequest = {
goodsItems,
addressId: address.id,
payType: 1, // 微信支付
comments: '购物车下单',
deliveryType: 0, // 快递配送
title: orderTitle
};
// 4. 调用创建订单API
const result = await createOrder(orderData);
if (result && result.prepayId) {
// 5. 调用微信支付
await Taro.requestPayment({
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: result.signType,
paySign: result.paySign,
});
// 6. 支付成功处理
Taro.showToast({
title: '支付成功',
icon: 'success'
});
// 7. 清空购物车(可选)
// clearCart();
setTimeout(() => {
Taro.switchTab({url: '/pages/order/order'});
}, 2000);
}
} catch (error: any) {
console.error('下单失败:', error);
Taro.showToast({
title: error.message || '下单失败',
icon: 'error'
});
} finally {
setLoading(false);
}
};
/**
* 自提订单示例
*/
const handleSelfPickupOrder = async (merchantId: number) => {
setLoading(true);
try {
const orderData: OrderCreateRequest = {
goodsItems: [
{
goodsId: goods.goodsId!,
quantity: quantity
}
],
addressId: address.id,
payType: 1,
deliveryType: 1, // 自提
selfTakeMerchantId: merchantId,
comments: `自提订单 - ${goods.name}`,
title: generateOrderTitle([goods.name!])
};
const result = await createOrder(orderData);
if (result && result.prepayId) {
await Taro.requestPayment({
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: result.signType,
paySign: result.paySign,
});
Taro.showToast({
title: '下单成功,请到店自提',
icon: 'success'
});
setTimeout(() => {
Taro.switchTab({url: '/pages/order/order'});
}, 2000);
}
} catch (error: any) {
console.error('下单失败:', error);
Taro.showToast({
title: error.message || '下单失败',
icon: 'error'
});
} finally {
setLoading(false);
}
};
/**
* 使用优惠券下单示例
*/
const handleOrderWithCoupon = async (couponId: number) => {
setLoading(true);
try {
const orderData: OrderCreateRequest = {
goodsItems: [
{
goodsId: goods.goodsId!,
quantity: quantity
}
],
addressId: address.id,
payType: 1,
couponId: couponId, // 使用优惠券
deliveryType: 0,
comments: `使用优惠券购买${goods.name}`,
title: generateOrderTitle([goods.name!])
};
const result = await createOrder(orderData);
if (result && result.prepayId) {
await Taro.requestPayment({
timeStamp: result.timeStamp,
nonceStr: result.nonceStr,
package: result.package,
signType: result.signType,
paySign: result.paySign,
});
Taro.showToast({
title: '支付成功',
icon: 'success'
});
setTimeout(() => {
Taro.switchTab({url: '/pages/order/order'});
}, 2000);
}
} catch (error: any) {
console.error('下单失败:', error);
Taro.showToast({
title: error.message || '下单失败',
icon: 'error'
});
} finally {
setLoading(false);
}
};
return (
<div>
<Button
type="primary"
loading={loading}
onClick={handleSingleGoodsOrder}
>
</Button>
<Button
type="success"
loading={loading}
onClick={() => handleCartOrder([
{goodsId: goods.goodsId!, quantity: 1, goodsName: goods.name!}
])}
>
</Button>
<Button
type="warning"
loading={loading}
onClick={() => handleSelfPickupOrder(1)}
>
</Button>
<Button
type="info"
loading={loading}
onClick={() => handleOrderWithCoupon(123)}
>
使
</Button>
</div>
);
};
export default OrderExample;

View File

@@ -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适配工作量
- 数据迁移和兼容性
- 性能影响评估
### 缓解措施
- 详细的后端适配文档
- 完整的测试用例覆盖
- 分阶段部署和验证

View File

@@ -0,0 +1,190 @@
# 订单状态修复总结
## 问题分析
### 1. 数据类型不一致
- **问题**: `payStatus` 字段在模型中定义为 `boolean` 类型,但代码中按数字处理
- **影响**: 导致支付状态判断错误,"待付款"状态显示不正确
### 2. 状态判断逻辑错误
- **问题**: 状态判断优先级不正确,没有按照业务逻辑顺序检查
- **影响**: 订单状态显示混乱,用户看到错误的订单状态
### 3. 操作按钮显示错误
- **问题**: 按钮显示条件与实际订单状态不匹配
- **影响**: 用户在错误的状态下看到不应该出现的操作按钮
### 4. Tab筛选逻辑不完整
- **问题**: 缺少"待收货"状态的筛选,状态分类不够细致
- **影响**: 用户无法准确筛选不同状态的订单
## 修复内容
### 1. 订单状态判断逻辑优化 ✅
**文件**: `src/pages/order/components/OrderList.tsx`
**修复前**:
```typescript
const getOrderStatusText = (order: ShopOrder) => {
if (!order.payStatus) return '待付款';
if (order.payStatus && order.deliveryStatus === 10) return '待发货';
// ... 其他逻辑
};
```
**修复后**:
```typescript
const getOrderStatusText = (order: ShopOrder) => {
// 优先检查订单状态
if (order.orderStatus === 2) return '已取消';
if (order.orderStatus === 4) return '退款申请中';
// ... 其他退款相关状态
// 检查支付状态 (payStatus为boolean类型)
if (!order.payStatus || order.payStatus === false) return '待付款';
// 已付款后检查发货状态
if (order.deliveryStatus === 10) return '待发货';
if (order.deliveryStatus === 20) return '待收货';
if (order.deliveryStatus === 30) return '已收货';
// 最后检查订单完成状态
if (order.orderStatus === 1) return '已完成';
return '未知状态';
};
```
### 2. Tab筛选功能完善 ✅
**新增"待收货"状态**:
```typescript
const tabs = [
{ index: 0, key: '全部', title: '全部' },
{ index: 1, key: '待付款', title: '待付款' },
{ index: 2, key: '待发货', title: '待发货' },
{ index: 3, key: '待收货', title: '待收货' }, // 新增
{ index: 4, key: '已收货', title: '已收货' },
{ index: 5, key: '已完成', title: '已完成' }
];
```
### 3. 操作按钮逻辑修复 ✅
**修复前**:
```typescript
{item.payStatus && (
<Space>
<Button onClick={() => cancelOrder(item)}>取消订单</Button>
<Button type="primary">立即支付</Button>
</Space>
)}
```
**修复后**:
```typescript
{/* 待付款状态:显示取消订单和立即支付 */}
{(!item.payStatus || item.payStatus === false) && item.orderStatus !== 2 && (
<Space>
<Button onClick={() => cancelOrder(item)}>取消订单</Button>
<Button type="primary">立即支付</Button>
</Space>
)}
```
### 4. 订单详情页状态显示修复 ✅
**文件**: `src/shop/orderDetail/index.tsx`
- 统一状态判断逻辑
- 修复函数调用参数
- 确保与订单列表页面的状态显示一致
## 订单状态流程图
```
订单创建
待付款 (payStatus: false)
↓ (用户支付)
待发货 (payStatus: true, deliveryStatus: 10)
↓ (商家发货)
待收货 (payStatus: true, deliveryStatus: 20)
↓ (用户确认收货)
已收货 (payStatus: true, deliveryStatus: 30)
↓ (系统自动或手动完成)
已完成 (orderStatus: 1)
// 异常流程
任意状态 → 已取消 (orderStatus: 2)
已完成 → 退款申请中 (orderStatus: 4)
退款申请中 → 退款成功 (orderStatus: 6)
```
## 字段含义说明
### payStatus (支付状态)
- **类型**: `boolean`
- **值**: `false/0` = 未付款, `true/1` = 已付款
### deliveryStatus (发货状态)
- **类型**: `number`
- **值**:
- `10` = 未发货/待发货
- `20` = 已发货/待收货
- `30` = 已收货
### orderStatus (订单状态)
- **类型**: `number`
- **值**:
- `0` = 未使用
- `1` = 已完成
- `2` = 已取消
- `3` = 取消中
- `4` = 退款申请中
- `5` = 退款被拒绝
- `6` = 退款成功
- `7` = 客户端申请退款
## 测试验证
### 1. 状态显示测试
- [ ] 创建不同状态的测试订单
- [ ] 验证订单列表页面状态显示正确
- [ ] 验证订单详情页面状态显示正确
- [ ] 验证状态文本与实际订单状态匹配
### 2. Tab筛选测试
- [ ] 测试"全部"tab显示所有订单
- [ ] 测试"待付款"tab只显示未支付订单
- [ ] 测试"待发货"tab只显示已支付待发货订单
- [ ] 测试"待收货"tab只显示已发货待收货订单
- [ ] 测试"已收货"tab只显示已收货订单
- [ ] 测试"已完成"tab只显示已完成订单
### 3. 操作按钮测试
- [ ] 待付款状态显示"取消订单"和"立即支付"按钮
- [ ] 待收货状态显示"确认收货"按钮
- [ ] 已完成状态显示"申请退款"按钮
- [ ] 其他状态不显示不相关按钮
### 4. 状态流转测试
- [ ] 测试支付后状态从"待付款"变为"待发货"
- [ ] 测试发货后状态从"待发货"变为"待收货"
- [ ] 测试确认收货后状态从"待收货"变为"已收货"
- [ ] 测试取消订单功能
## 注意事项
1. **数据类型一致性**: 确保前后端对 `payStatus` 字段类型的处理一致
2. **状态优先级**: 按照业务逻辑正确设置状态判断优先级
3. **用户体验**: 确保状态显示清晰,操作按钮符合用户预期
4. **异常处理**: 对于未知状态要有合适的默认显示
## 后续优化建议
1. **状态枚举**: 考虑使用枚举类型定义订单状态,提高代码可读性
2. **状态机**: 实现订单状态机,确保状态流转的合法性
3. **国际化**: 支持订单状态文本的多语言显示
4. **实时更新**: 考虑实现订单状态的实时推送更新