forked from gxwebsoft/mp-10550
优化下单流程
This commit is contained in:
245
docs/ORDER_FRONTEND_IMPLEMENTATION.md
Normal file
245
docs/ORDER_FRONTEND_IMPLEMENTATION.md
Normal 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. 测试订单标题长度限制功能
|
||||||
264
docs/PAYMENT_REFACTOR_GUIDE.md
Normal file
264
docs/PAYMENT_REFACTOR_GUIDE.md
Normal 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. **测试覆盖**:对重构后的代码进行充分的测试
|
||||||
248
docs/backend-order-service-example.java
Normal file
248
docs/backend-order-service-example.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
317
docs/frontend-order-example.tsx
Normal file
317
docs/frontend-order-example.tsx
Normal 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;
|
||||||
14
metro.config.js
Normal file
14
metro.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config')
|
||||||
|
const { getMetroConfig } = require('@tarojs/rn-supporter')
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metro configuration
|
||||||
|
* https://facebook.github.io/metro/docs/configuration
|
||||||
|
*
|
||||||
|
* @type {import('metro-config').MetroConfig}
|
||||||
|
*/
|
||||||
|
const config = {}
|
||||||
|
|
||||||
|
module.exports = (async function () {
|
||||||
|
return mergeConfig(getDefaultConfig(__dirname), await getMetroConfig(), config)
|
||||||
|
})()
|
||||||
@@ -42,7 +42,9 @@
|
|||||||
"@nutui/nutui-biz": "1.0.0-beta.2",
|
"@nutui/nutui-biz": "1.0.0-beta.2",
|
||||||
"@nutui/nutui-react": "^3.0.16",
|
"@nutui/nutui-react": "^3.0.16",
|
||||||
"@nutui/nutui-react-taro": "^2.7.4",
|
"@nutui/nutui-react-taro": "^2.7.4",
|
||||||
|
"@react-native/metro-config": "^0.73.2",
|
||||||
"@tarojs/components": "4.0.8",
|
"@tarojs/components": "4.0.8",
|
||||||
|
"@tarojs/components-rn": "^4.1.4",
|
||||||
"@tarojs/helper": "4.0.8",
|
"@tarojs/helper": "4.0.8",
|
||||||
"@tarojs/plugin-framework-react": "4.0.8",
|
"@tarojs/plugin-framework-react": "4.0.8",
|
||||||
"@tarojs/plugin-html": "4.0.8",
|
"@tarojs/plugin-html": "4.0.8",
|
||||||
@@ -54,16 +56,22 @@
|
|||||||
"@tarojs/plugin-platform-tt": "4.0.8",
|
"@tarojs/plugin-platform-tt": "4.0.8",
|
||||||
"@tarojs/plugin-platform-weapp": "4.0.8",
|
"@tarojs/plugin-platform-weapp": "4.0.8",
|
||||||
"@tarojs/react": "4.0.8",
|
"@tarojs/react": "4.0.8",
|
||||||
|
"@tarojs/rn-runner": "^4.1.4",
|
||||||
|
"@tarojs/rn-supporter": "^4.1.4",
|
||||||
"@tarojs/runtime": "4.0.8",
|
"@tarojs/runtime": "4.0.8",
|
||||||
|
"@tarojs/runtime-rn": "^4.1.4",
|
||||||
"@tarojs/shared": "4.0.8",
|
"@tarojs/shared": "4.0.8",
|
||||||
"@tarojs/taro": "4.0.8",
|
"@tarojs/taro": "4.0.8",
|
||||||
|
"@tarojs/taro-rn": "^4.1.4",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"echarts-taro3-react": "^1.0.13",
|
"echarts-taro3-react": "^1.0.13",
|
||||||
|
"expo": "~50.0.2",
|
||||||
"js-base64": "^3.7.7",
|
"js-base64": "^3.7.7",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-dom": "^18.3.1",
|
"react-dom": "^18.3.1",
|
||||||
"react-markdown": "^10.1.0",
|
"react-markdown": "^10.1.0",
|
||||||
|
"react-native": "^0.73.1",
|
||||||
"react-router-dom": "^7.1.1"
|
"react-router-dom": "^7.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
6759
pnpm-lock.yaml
generated
6759
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"miniprogramRoot": "dist/",
|
"miniprogramRoot": "dist/",
|
||||||
"projectname": "websoft",
|
"projectname": "template-10550",
|
||||||
"description": "时里院子市集",
|
"description": "时里院子市集",
|
||||||
"appid": "wx5170f9f17a813877",
|
"appid": "wx5170f9f17a813877",
|
||||||
"setting": {
|
"setting": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import request from '@/utils/request';
|
import request from '@/utils/request';
|
||||||
import type { ApiResult, PageResult } from '@/api/index';
|
import type { ApiResult, PageResult } from '@/api/index';
|
||||||
import type { ShopOrder, ShopOrderParam } from './model';
|
import type { ShopOrder, ShopOrderParam, OrderCreateRequest } from './model';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询订单
|
* 分页查询订单
|
||||||
@@ -100,6 +100,33 @@ export async function getShopOrder(id: number) {
|
|||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 微信支付返回数据
|
||||||
|
*/
|
||||||
|
export interface WxPayResult {
|
||||||
|
prepayId: string;
|
||||||
|
orderNo: string;
|
||||||
|
timeStamp: string;
|
||||||
|
nonceStr: string;
|
||||||
|
package: string;
|
||||||
|
signType: string;
|
||||||
|
paySign: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建订单
|
||||||
|
*/
|
||||||
|
export async function createOrder(data: OrderCreateRequest) {
|
||||||
|
const res = await request.post<ApiResult<WxPayResult>>(
|
||||||
|
'/shop/shop-order/create',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.data;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 修改订单
|
* 修改订单
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ export interface ShopOrder {
|
|||||||
orderNo?: string;
|
orderNo?: string;
|
||||||
// 订单类型,0商城订单 1预定订单/外卖 2会员卡
|
// 订单类型,0商城订单 1预定订单/外卖 2会员卡
|
||||||
type?: number;
|
type?: number;
|
||||||
|
// 标题
|
||||||
|
title?: string;
|
||||||
// 快递/自提
|
// 快递/自提
|
||||||
deliveryType?: number;
|
deliveryType?: number;
|
||||||
// 下单渠道,0小程序预定 1俱乐部训练场 3活动订场
|
// 下单渠道,0小程序预定 1俱乐部训练场 3活动订场
|
||||||
@@ -144,6 +146,68 @@ export interface ShopOrder {
|
|||||||
hasTakeGift?: string;
|
hasTakeGift?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单商品项
|
||||||
|
*/
|
||||||
|
export interface OrderGoodsItem {
|
||||||
|
goodsId: number;
|
||||||
|
quantity: number;
|
||||||
|
skuId?: number;
|
||||||
|
specInfo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建订单请求
|
||||||
|
*/
|
||||||
|
export interface OrderCreateRequest {
|
||||||
|
// 商品信息列表
|
||||||
|
goodsItems: OrderGoodsItem[];
|
||||||
|
// 收货地址ID
|
||||||
|
addressId?: number;
|
||||||
|
// 支付方式
|
||||||
|
payType: number;
|
||||||
|
// 优惠券ID
|
||||||
|
couponId?: number;
|
||||||
|
// 备注
|
||||||
|
comments?: string;
|
||||||
|
// 配送方式 0快递 1自提
|
||||||
|
deliveryType?: number;
|
||||||
|
// 自提店铺ID
|
||||||
|
selfTakeMerchantId?: number;
|
||||||
|
// 订单标题(可选,后端会自动生成)
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 订单商品项
|
||||||
|
*/
|
||||||
|
export interface OrderGoodsItem {
|
||||||
|
goodsId: number;
|
||||||
|
quantity: number;
|
||||||
|
skuId?: number;
|
||||||
|
specInfo?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建订单请求
|
||||||
|
*/
|
||||||
|
export interface OrderCreateRequest {
|
||||||
|
// 商品信息列表
|
||||||
|
goodsItems: OrderGoodsItem[];
|
||||||
|
// 收货地址ID
|
||||||
|
addressId?: number;
|
||||||
|
// 支付方式
|
||||||
|
payType: number;
|
||||||
|
// 优惠券ID
|
||||||
|
couponId?: number;
|
||||||
|
// 备注
|
||||||
|
comments?: string;
|
||||||
|
// 配送方式 0快递 1自提
|
||||||
|
deliveryType?: number;
|
||||||
|
// 自提店铺ID
|
||||||
|
selfTakeMerchantId?: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单搜索条件
|
* 订单搜索条件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -99,3 +99,17 @@ export async function getOrderGoods(id: number) {
|
|||||||
}
|
}
|
||||||
return Promise.reject(new Error(res.message));
|
return Promise.reject(new Error(res.message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量添加订单商品
|
||||||
|
*/
|
||||||
|
export async function addBatchOrderGoods(data: OrderGoods[]) {
|
||||||
|
const res = await request.post<ApiResult<unknown>>(
|
||||||
|
'/shop/shop-goods/batch',
|
||||||
|
data
|
||||||
|
);
|
||||||
|
if (res.code === 0) {
|
||||||
|
return res.message;
|
||||||
|
}
|
||||||
|
return Promise.reject(new Error(res.message));
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {useEffect, useState} from "react";
|
import {useEffect, useState} from "react";
|
||||||
import Taro from '@tarojs/taro';
|
import Taro from '@tarojs/taro';
|
||||||
import {Button, Space} from '@nutui/nutui-react-taro'
|
import {Button, Space} from '@nutui/nutui-react-taro'
|
||||||
import {TriangleDown, Search} from '@nutui/icons-react-taro'
|
import {TriangleDown} from '@nutui/icons-react-taro'
|
||||||
import {Popup, Avatar, NavBar} from '@nutui/nutui-react-taro'
|
import {Popup, Avatar, NavBar} from '@nutui/nutui-react-taro'
|
||||||
import {getSiteInfo, getUserInfo, getWxOpenId} from "@/api/layout";
|
import {getSiteInfo, getUserInfo, getWxOpenId} from "@/api/layout";
|
||||||
import {TenantId} from "@/config/app";
|
import {TenantId} from "@/config/app";
|
||||||
@@ -141,9 +141,10 @@ const Header = (props: any) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={'fixed top-0 header-bg'} style={{
|
<div className={'fixed top-0 header-bg'} style={{
|
||||||
height: !props.stickyStatus ? '180px' : '94px',
|
height: !props.stickyStatus ? '180px' : '148px',
|
||||||
}}>
|
}}>
|
||||||
{!props.stickyStatus && <MySearch done={reload}/>}
|
<MySearch done={reload}/>
|
||||||
|
{/*{!props.stickyStatus && <MySearch done={reload}/>}*/}
|
||||||
</div>
|
</div>
|
||||||
<NavBar
|
<NavBar
|
||||||
style={{marginTop: `${statusBarHeight}px`, marginBottom: '0px', backgroundColor: 'transparent'}}
|
style={{marginTop: `${statusBarHeight}px`, marginBottom: '0px', backgroundColor: 'transparent'}}
|
||||||
@@ -173,11 +174,6 @@ const Header = (props: any) => {
|
|||||||
<TriangleDown className={'text-white'} size={9}/>
|
<TriangleDown className={'text-white'} size={9}/>
|
||||||
</div>
|
</div>
|
||||||
)}>
|
)}>
|
||||||
{props.stickyStatus && (
|
|
||||||
<Space>
|
|
||||||
<Search className={'text-white mx-2'} size={18}/>
|
|
||||||
</Space>
|
|
||||||
)}
|
|
||||||
</NavBar>
|
</NavBar>
|
||||||
<Popup
|
<Popup
|
||||||
visible={showBasic}
|
visible={showBasic}
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ const GoodsDetail = () => {
|
|||||||
const goodsId = router?.params?.id;
|
const goodsId = router?.params?.id;
|
||||||
|
|
||||||
// 使用购物车Hook
|
// 使用购物车Hook
|
||||||
const { cartCount, addToCart } = useCart();
|
const {cartCount, addToCart} = useCart();
|
||||||
|
|
||||||
// 处理加入购物车
|
// 处理加入购物车
|
||||||
const handleAddToCart = () => {
|
const handleAddToCart = () => {
|
||||||
if (!goods) return;
|
if (!goods) return;
|
||||||
|
|
||||||
if(!Taro.getStorageSync('UserId')){
|
if (!Taro.getStorageSync('UserId')) {
|
||||||
return Taro.showToast({
|
return Taro.showToast({
|
||||||
title: '请先登录',
|
title: '请先登录',
|
||||||
icon: 'none',
|
icon: 'none',
|
||||||
@@ -125,7 +125,7 @@ const GoodsDetail = () => {
|
|||||||
top: "50px",
|
top: "50px",
|
||||||
right: "110px",
|
right: "110px",
|
||||||
}}
|
}}
|
||||||
onClick={() => navTo(`/pages/cart/cart`,true)}>
|
onClick={() => navTo(`/pages/cart/cart`, true)}>
|
||||||
<Badge value={cartCount} top="-2" right="2">
|
<Badge value={cartCount} top="-2" right="2">
|
||||||
<div style={{display: 'flex', alignItems: 'center'}}>
|
<div style={{display: 'flex', alignItems: 'center'}}>
|
||||||
<Cart size={16}/>
|
<Cart size={16}/>
|
||||||
@@ -209,7 +209,7 @@ const GoodsDetail = () => {
|
|||||||
onClick={() => handleAddToCart()}>加入购物车
|
onClick={() => handleAddToCart()}>加入购物车
|
||||||
</div>
|
</div>
|
||||||
<div className={'cart-buy pl-4 pr-5 text-sm'}
|
<div className={'cart-buy pl-4 pr-5 text-sm'}
|
||||||
onClick={() => navTo(`/shop/orderConfirm/index?goodsId=${goods?.goodsId}`,true)}>立即购买
|
onClick={() => navTo(`/shop/orderConfirm/index?goodsId=${goods?.goodsId}`, true)}>立即购买
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -9,10 +9,9 @@ import {listShopUserAddress} from "@/api/shop/shopUserAddress";
|
|||||||
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
||||||
import './index.scss'
|
import './index.scss'
|
||||||
import Gap from "@/components/Gap";
|
import Gap from "@/components/Gap";
|
||||||
import {TenantId} from "@/config/app";
|
import {selectPayment} from "@/api/system/payment";
|
||||||
import {payByBalance, selectPayment} from "@/api/system/payment";
|
|
||||||
import {Payment} from "@/api/system/payment/model";
|
import {Payment} from "@/api/system/payment/model";
|
||||||
import {API_BASE_URL} from "@/config/env";
|
import {PaymentHandler, PaymentType, buildSingleGoodsOrder} from "@/utils/payment";
|
||||||
|
|
||||||
const OrderConfirm = () => {
|
const OrderConfirm = () => {
|
||||||
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
||||||
@@ -20,6 +19,7 @@ const OrderConfirm = () => {
|
|||||||
const [payments, setPayments] = useState<any[]>([])
|
const [payments, setPayments] = useState<any[]>([])
|
||||||
const [payment, setPayment] = useState<Payment>()
|
const [payment, setPayment] = useState<Payment>()
|
||||||
const [isVisible, setIsVisible] = useState<boolean>(false)
|
const [isVisible, setIsVisible] = useState<boolean>(false)
|
||||||
|
|
||||||
const router = Taro.getCurrentInstance().router;
|
const router = Taro.getCurrentInstance().router;
|
||||||
const goodsId = router?.params?.goodsId;
|
const goodsId = router?.params?.goodsId;
|
||||||
|
|
||||||
@@ -48,107 +48,44 @@ const OrderConfirm = () => {
|
|||||||
setIsVisible(false)
|
setIsVisible(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一支付入口
|
||||||
|
*/
|
||||||
const onPay = async (goods: ShopGoods) => {
|
const onPay = async (goods: ShopGoods) => {
|
||||||
// 支付方式
|
// 基础校验
|
||||||
if (payment?.type == 0) {
|
if (!address) {
|
||||||
await onBalancePay(goods)
|
|
||||||
}
|
|
||||||
if (payment?.type == 1) {
|
|
||||||
await onWxPay(goods)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const onBalancePay = async (goods: ShopGoods) => {
|
|
||||||
Taro.showLoading({title: '支付中...'})
|
|
||||||
payByBalance({
|
|
||||||
payType: 0, // 余额支付
|
|
||||||
payPrice: goods.price,
|
|
||||||
totalPrice: goods.price,
|
|
||||||
addressId: address?.id,
|
|
||||||
userId: Taro.getStorageSync('UserId'),
|
|
||||||
tenantId: Number(TenantId)
|
|
||||||
}).then().finally(() => {
|
|
||||||
Taro.showToast({
|
Taro.showToast({
|
||||||
title: '支付成功',
|
title: '请选择收货地址',
|
||||||
icon: 'success',
|
icon: 'error'
|
||||||
duration: 2000
|
});
|
||||||
})
|
return;
|
||||||
Taro.hideLoading()
|
}
|
||||||
setTimeout(() => {
|
|
||||||
Taro.switchTab({url: '/pages/order/order'})
|
|
||||||
}, 2000)
|
|
||||||
}).catch(() => {
|
|
||||||
Taro.hideLoading()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onWxPay = async (goods: ShopGoods) => {
|
if (!payment) {
|
||||||
Taro.showLoading({title: '支付中...'})
|
Taro.showToast({
|
||||||
Taro.request({
|
title: '请选择支付方式',
|
||||||
url: API_BASE_URL + '/shop/shop-order',
|
icon: 'error'
|
||||||
// url: 'http://127.0.0.1:9200/api/shop/shop-order',
|
});
|
||||||
method: 'POST',
|
return;
|
||||||
header: {
|
}
|
||||||
'content-type': 'application/json',
|
|
||||||
'Authorization': Taro.getStorageSync('access_token'),
|
// 构建订单数据
|
||||||
TenantId
|
const orderData = buildSingleGoodsOrder(
|
||||||
},
|
goods.goodsId!,
|
||||||
data: {
|
1,
|
||||||
payType: 1,
|
address.id,
|
||||||
totalPrice: goods.price,
|
{
|
||||||
payPrice: goods.price,
|
|
||||||
userId: Taro.getStorageSync('UserId'),
|
|
||||||
tenantId: TenantId,
|
|
||||||
comments: goods.name,
|
comments: goods.name,
|
||||||
name: goods.name,
|
deliveryType: 0
|
||||||
addressId: address?.id,
|
|
||||||
},
|
|
||||||
success: function (res) {
|
|
||||||
Taro.hideLoading()
|
|
||||||
if(res.data.code != 0){
|
|
||||||
Taro.showToast({
|
|
||||||
title: res.data.message,
|
|
||||||
icon: 'error',
|
|
||||||
duration: 2000
|
|
||||||
})
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
// 支付结果
|
|
||||||
const data = res.data.data
|
|
||||||
console.log(data, 'payInfo')
|
|
||||||
// Taro.showToast({
|
|
||||||
// title: '下单成功',
|
|
||||||
// })
|
|
||||||
//
|
|
||||||
// setTimeout(() => {
|
|
||||||
// Taro.switchTab({
|
|
||||||
// url: '/pages/order/order'
|
|
||||||
// })
|
|
||||||
// }, 1000);
|
|
||||||
// return false;
|
|
||||||
if (data) {
|
|
||||||
Taro.requestPayment({
|
|
||||||
timeStamp: data.timeStamp,
|
|
||||||
nonceStr: data.nonceStr,
|
|
||||||
package: data.package,
|
|
||||||
signType: data.signType,
|
|
||||||
paySign: data.paySign,
|
|
||||||
success: function (res) {
|
|
||||||
if (res.errMsg == "requestPayment:ok") {
|
|
||||||
console.log('购买成功')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
fail: function (res) {
|
|
||||||
console.log(res)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
complete: function () {
|
|
||||||
Taro.hideLoading()
|
|
||||||
}
|
}
|
||||||
})
|
);
|
||||||
}
|
|
||||||
|
// 根据支付方式选择支付类型
|
||||||
|
const paymentType = payment.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT;
|
||||||
|
|
||||||
|
// 执行支付
|
||||||
|
await PaymentHandler.pay(orderData, paymentType);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (goodsId) {
|
if (goodsId) {
|
||||||
|
|||||||
@@ -10,10 +10,15 @@ import {ShopUserAddress} from "@/api/shop/shopUserAddress/model";
|
|||||||
import './index.scss'
|
import './index.scss'
|
||||||
import {useCart} from "@/hooks/useCart";
|
import {useCart} from "@/hooks/useCart";
|
||||||
import Gap from "@/components/Gap";
|
import Gap from "@/components/Gap";
|
||||||
|
import {createOrder} from "@/api/shop/shopOrder";
|
||||||
|
import {OrderCreateRequest} from "@/api/shop/shopOrder/model";
|
||||||
|
import {Payment} from "@/api/system/payment/model";
|
||||||
|
import {PaymentHandler, PaymentType, buildCartOrder} from "@/utils/payment";
|
||||||
|
|
||||||
const OrderConfirm = () => {
|
const OrderConfirm = () => {
|
||||||
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
const [goods, setGoods] = useState<ShopGoods | null>(null);
|
||||||
const [address, setAddress] = useState<ShopUserAddress>()
|
const [address, setAddress] = useState<ShopUserAddress>()
|
||||||
|
const [payment, setPayment] = useState<Payment>()
|
||||||
const router = Taro.getCurrentInstance().router;
|
const router = Taro.getCurrentInstance().router;
|
||||||
const goodsId = router?.params?.goodsId;
|
const goodsId = router?.params?.goodsId;
|
||||||
|
|
||||||
@@ -29,6 +34,47 @@ const OrderConfirm = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一支付入口
|
||||||
|
*/
|
||||||
|
const onPay = async () => {
|
||||||
|
// 基础校验
|
||||||
|
if (!address) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '请选择收货地址',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cartItems || cartItems.length === 0) {
|
||||||
|
Taro.showToast({
|
||||||
|
title: '购物车为空',
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (goodsId) {
|
if (goodsId) {
|
||||||
getShopGoods(Number(goodsId)).then(res => {
|
getShopGoods(Number(goodsId)).then(res => {
|
||||||
@@ -121,7 +167,7 @@ const OrderConfirm = () => {
|
|||||||
<span className={'text-red-500 text-xl font-bold'}>¥{goods.price}</span>
|
<span className={'text-red-500 text-xl font-bold'}>¥{goods.price}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={'buy-btn mx-4'}>
|
<div className={'buy-btn mx-4'}>
|
||||||
<Button type="success" size="large">立即付款</Button>
|
<Button type="success" size="large" onClick={onPay}>立即付款</Button>
|
||||||
</div>
|
</div>
|
||||||
</View>
|
</View>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -89,3 +89,42 @@ export function showShareGuide() {
|
|||||||
confirmText: '知道了'
|
confirmText: '知道了'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 截取字符串,确保不超过指定的汉字长度
|
||||||
|
* @param text 原始文本
|
||||||
|
* @param maxLength 最大汉字长度,默认30
|
||||||
|
* @returns 截取后的文本
|
||||||
|
*/
|
||||||
|
export function truncateText(text: string, maxLength: number = 30): string {
|
||||||
|
if (!text) return '';
|
||||||
|
|
||||||
|
// 如果长度不超过限制,直接返回
|
||||||
|
if (text.length <= maxLength) {
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 超过长度则截取
|
||||||
|
return text.substring(0, maxLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成订单标题
|
||||||
|
* @param goodsNames 商品名称数组
|
||||||
|
* @param maxLength 最大长度,默认30
|
||||||
|
* @returns 订单标题
|
||||||
|
*/
|
||||||
|
export function generateOrderTitle(goodsNames: string[], maxLength: number = 30): string {
|
||||||
|
if (!goodsNames || goodsNames.length === 0) {
|
||||||
|
return '商品订单';
|
||||||
|
}
|
||||||
|
|
||||||
|
let title = '';
|
||||||
|
if (goodsNames.length === 1) {
|
||||||
|
title = goodsNames[0];
|
||||||
|
} else {
|
||||||
|
title = `${goodsNames[0]}等${goodsNames.length}件商品`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return truncateText(title, maxLength);
|
||||||
|
}
|
||||||
|
|||||||
214
src/utils/payment.ts
Normal file
214
src/utils/payment.ts
Normal file
@@ -0,0 +1,214 @@
|
|||||||
|
import Taro from '@tarojs/taro';
|
||||||
|
import { createOrder, WxPayResult } from '@/api/shop/shopOrder';
|
||||||
|
import { OrderCreateRequest } from '@/api/shop/shopOrder/model';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付类型枚举
|
||||||
|
*/
|
||||||
|
export enum PaymentType {
|
||||||
|
BALANCE = 0, // 余额支付
|
||||||
|
WECHAT = 1, // 微信支付
|
||||||
|
ALIPAY = 3, // 支付宝支付
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付结果回调
|
||||||
|
*/
|
||||||
|
export interface PaymentCallback {
|
||||||
|
onSuccess?: () => void;
|
||||||
|
onError?: (error: string) => void;
|
||||||
|
onComplete?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 统一支付处理类
|
||||||
|
*/
|
||||||
|
export class PaymentHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行支付
|
||||||
|
* @param orderData 订单数据
|
||||||
|
* @param paymentType 支付类型
|
||||||
|
* @param callback 回调函数
|
||||||
|
*/
|
||||||
|
static async pay(
|
||||||
|
orderData: OrderCreateRequest,
|
||||||
|
paymentType: PaymentType,
|
||||||
|
callback?: PaymentCallback
|
||||||
|
): Promise<void> {
|
||||||
|
Taro.showLoading({ title: '支付中...' });
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 设置支付类型
|
||||||
|
orderData.payType = paymentType;
|
||||||
|
|
||||||
|
// 创建订单
|
||||||
|
const result = await createOrder(orderData);
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
throw new Error('创建订单失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据支付类型处理
|
||||||
|
switch (paymentType) {
|
||||||
|
case PaymentType.WECHAT:
|
||||||
|
await this.handleWechatPay(result);
|
||||||
|
break;
|
||||||
|
case PaymentType.BALANCE:
|
||||||
|
await this.handleBalancePay(result);
|
||||||
|
break;
|
||||||
|
case PaymentType.ALIPAY:
|
||||||
|
await this.handleAlipay(result);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('不支持的支付方式');
|
||||||
|
}
|
||||||
|
|
||||||
|
// 支付成功处理
|
||||||
|
Taro.showToast({
|
||||||
|
title: '支付成功',
|
||||||
|
icon: 'success'
|
||||||
|
});
|
||||||
|
|
||||||
|
callback?.onSuccess?.();
|
||||||
|
|
||||||
|
// 跳转到订单页面
|
||||||
|
setTimeout(() => {
|
||||||
|
Taro.switchTab({ url: '/pages/order/order' });
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('支付失败:', error);
|
||||||
|
const errorMessage = error.message || '支付失败';
|
||||||
|
|
||||||
|
Taro.showToast({
|
||||||
|
title: errorMessage,
|
||||||
|
icon: 'error'
|
||||||
|
});
|
||||||
|
|
||||||
|
callback?.onError?.(errorMessage);
|
||||||
|
} finally {
|
||||||
|
Taro.hideLoading();
|
||||||
|
callback?.onComplete?.();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理微信支付
|
||||||
|
*/
|
||||||
|
private static async handleWechatPay(result: WxPayResult): Promise<void> {
|
||||||
|
if (!result || !result.prepayId) {
|
||||||
|
throw new Error('微信支付参数错误');
|
||||||
|
}
|
||||||
|
|
||||||
|
await Taro.requestPayment({
|
||||||
|
timeStamp: result.timeStamp,
|
||||||
|
nonceStr: result.nonceStr,
|
||||||
|
package: result.package,
|
||||||
|
signType: result.signType as any, // 类型转换,因为微信支付的signType是字符串
|
||||||
|
paySign: result.paySign,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理余额支付
|
||||||
|
*/
|
||||||
|
private static async handleBalancePay(result: any): Promise<void> {
|
||||||
|
// 余额支付通常在后端直接完成,这里只需要确认结果
|
||||||
|
if (!result || !result.orderNo) {
|
||||||
|
throw new Error('余额支付失败');
|
||||||
|
}
|
||||||
|
// 余额支付成功,无需额外操作
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理支付宝支付
|
||||||
|
*/
|
||||||
|
private static async handleAlipay(_result: any): Promise<void> {
|
||||||
|
// 支付宝支付逻辑,根据实际情况实现
|
||||||
|
throw new Error('支付宝支付暂未实现');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 快捷支付方法
|
||||||
|
*/
|
||||||
|
export const quickPay = {
|
||||||
|
/**
|
||||||
|
* 微信支付
|
||||||
|
*/
|
||||||
|
wechat: (orderData: OrderCreateRequest, callback?: PaymentCallback) => {
|
||||||
|
return PaymentHandler.pay(orderData, PaymentType.WECHAT, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 余额支付
|
||||||
|
*/
|
||||||
|
balance: (orderData: OrderCreateRequest, callback?: PaymentCallback) => {
|
||||||
|
return PaymentHandler.pay(orderData, PaymentType.BALANCE, callback);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 支付宝支付
|
||||||
|
*/
|
||||||
|
alipay: (orderData: OrderCreateRequest, callback?: PaymentCallback) => {
|
||||||
|
return PaymentHandler.pay(orderData, PaymentType.ALIPAY, callback);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建单商品订单数据
|
||||||
|
*/
|
||||||
|
export function buildSingleGoodsOrder(
|
||||||
|
goodsId: number,
|
||||||
|
quantity: number = 1,
|
||||||
|
addressId?: number,
|
||||||
|
options?: {
|
||||||
|
comments?: string;
|
||||||
|
deliveryType?: number;
|
||||||
|
couponId?: number;
|
||||||
|
selfTakeMerchantId?: number;
|
||||||
|
}
|
||||||
|
): OrderCreateRequest {
|
||||||
|
return {
|
||||||
|
goodsItems: [
|
||||||
|
{
|
||||||
|
goodsId,
|
||||||
|
quantity
|
||||||
|
}
|
||||||
|
],
|
||||||
|
addressId,
|
||||||
|
payType: PaymentType.WECHAT, // 默认微信支付,会被PaymentHandler覆盖
|
||||||
|
comments: options?.comments || '',
|
||||||
|
deliveryType: options?.deliveryType || 0,
|
||||||
|
couponId: options?.couponId,
|
||||||
|
selfTakeMerchantId: options?.selfTakeMerchantId
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构建购物车订单数据
|
||||||
|
*/
|
||||||
|
export function buildCartOrder(
|
||||||
|
cartItems: Array<{ goodsId: number; quantity: number }>,
|
||||||
|
addressId?: number,
|
||||||
|
options?: {
|
||||||
|
comments?: string;
|
||||||
|
deliveryType?: number;
|
||||||
|
couponId?: number;
|
||||||
|
selfTakeMerchantId?: number;
|
||||||
|
}
|
||||||
|
): OrderCreateRequest {
|
||||||
|
return {
|
||||||
|
goodsItems: cartItems.map(item => ({
|
||||||
|
goodsId: item.goodsId,
|
||||||
|
quantity: item.quantity
|
||||||
|
})),
|
||||||
|
addressId,
|
||||||
|
payType: PaymentType.WECHAT, // 默认微信支付,会被PaymentHandler覆盖
|
||||||
|
comments: options?.comments || '购物车下单',
|
||||||
|
deliveryType: options?.deliveryType || 0,
|
||||||
|
couponId: options?.couponId,
|
||||||
|
selfTakeMerchantId: options?.selfTakeMerchantId
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user