diff --git a/docs/ORDER_IMPROVEMENTS.md b/docs/ORDER_IMPROVEMENTS.md new file mode 100644 index 0000000..8203711 --- /dev/null +++ b/docs/ORDER_IMPROVEMENTS.md @@ -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. 添加订单操作的二次确认 diff --git a/src/api/shop/shopOrder/index.ts b/src/api/shop/shopOrder/index.ts index 36edd19..675e794 100644 --- a/src/api/shop/shopOrder/index.ts +++ b/src/api/shop/shopOrder/index.ts @@ -118,7 +118,7 @@ export interface WxPayResult { */ export async function createOrder(data: OrderCreateRequest) { const res = await request.post>( - '/shop/shop-order/create', + 'http://127.0.0.1:9200/api/shop/shop-order', data ); if (res.code === 0) { @@ -128,7 +128,7 @@ export async function createOrder(data: OrderCreateRequest) { } /** - * 修改订单 + * 修复订单支付状态 */ export async function repairOrder(data: ShopOrder) { const res = await request.put>( diff --git a/src/api/shop/shopOrderGoods/index.ts b/src/api/shop/shopOrderGoods/index.ts new file mode 100644 index 0000000..4c74d18 --- /dev/null +++ b/src/api/shop/shopOrderGoods/index.ts @@ -0,0 +1,101 @@ +import request from '@/utils/request'; +import type { ApiResult, PageResult } from '@/api/index'; +import type { ShopOrderGoods, ShopOrderGoodsParam } from './model'; + +/** + * 分页查询商品信息 + */ +export async function pageShopOrderGoods(params: ShopOrderGoodsParam) { + const res = await request.get>>( + '/shop/shop-order-goods/page', + params + ); + if (res.code === 0) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 查询商品信息列表 + */ +export async function listShopOrderGoods(params?: ShopOrderGoodsParam) { + const res = await request.get>( + '/shop/shop-order-goods', + params + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 添加商品信息 + */ +export async function addShopOrderGoods(data: ShopOrderGoods) { + const res = await request.post>( + '/shop/shop-order-goods', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 修改商品信息 + */ +export async function updateShopOrderGoods(data: ShopOrderGoods) { + const res = await request.put>( + '/shop/shop-order-goods', + data + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 删除商品信息 + */ +export async function removeShopOrderGoods(id?: number) { + const res = await request.del>( + '/shop/shop-order-goods/' + id + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 批量删除商品信息 + */ +export async function removeBatchShopOrderGoods(data: (number | undefined)[]) { + const res = await request.del>( + '/shop/shop-order-goods/batch', + { + data + } + ); + if (res.code === 0) { + return res.message; + } + return Promise.reject(new Error(res.message)); +} + +/** + * 根据id查询商品信息 + */ +export async function getShopOrderGoods(id: number) { + const res = await request.get>( + '/shop/shop-order-goods/' + id + ); + if (res.code === 0 && res.data) { + return res.data; + } + return Promise.reject(new Error(res.message)); +} diff --git a/src/api/shop/shopOrderGoods/model/index.ts b/src/api/shop/shopOrderGoods/model/index.ts new file mode 100644 index 0000000..a4034de --- /dev/null +++ b/src/api/shop/shopOrderGoods/model/index.ts @@ -0,0 +1,70 @@ +import type { PageParam } from '@/api'; + +/** + * 商品信息 + */ +export interface ShopOrderGoods { + // 自增ID + id?: number; + // 关联订单表id + orderId?: number; + // 订单标识 + orderCode?: string; + // 关联商户ID + merchantId?: number; + // 商户名称 + merchantName?: string; + // 商品封面图 + image?: string; + // 关联商品id + goodsId?: number; + // 商品名称 + goodsName?: string; + // 商品规格 + spec?: string; + // + skuId?: number; + // 单价 + price?: string; + // 购买数量 + totalNum?: number; + // 0 未付款 1已付款,2无需付款或占用状态 + payStatus?: number; + // 0未使用,1已完成,2已取消,3取消中,4退款申请中,5退款被拒绝,6退款成功,7客户端申请退款 + orderStatus?: number; + // 是否免费:0收费、1免费 + isFree?: string; + // 系统版本 0当前版本 其他版本 + version?: number; + // 预约时间段 + timePeriod?: string; + // 预定日期 + dateTime?: string; + // 开场时间 + startTime?: string; + // 结束时间 + endTime?: string; + // 毫秒时间戳 + timeFlag?: string; + // 过期时间 + expirationTime?: string; + // 备注 + comments?: string; + // 用户id + userId?: number; + // 租户id + tenantId?: number; + // 更新时间 + updateTime?: string; + // 创建时间 + createTime?: string; +} + +/** + * 商品信息搜索条件 + */ +export interface ShopOrderGoodsParam extends PageParam { + id?: number; + orderId?: number; + keywords?: string; +} diff --git a/src/app.config.ts b/src/app.config.ts index ecad27f..c26bc01 100644 --- a/src/app.config.ts +++ b/src/app.config.ts @@ -45,6 +45,7 @@ export default defineAppConfig({ 'orderDetail/index', 'goodsDetail/index', 'orderConfirm/index', + 'orderConfirmCart/index' ] } ], diff --git a/src/pages/cart/cart.tsx b/src/pages/cart/cart.tsx index 8af8411..6d01e58 100644 --- a/src/pages/cart/cart.tsx +++ b/src/pages/cart/cart.tsx @@ -115,10 +115,17 @@ function Cart() { return; } - // 这里可以跳转到结算页面 - Taro.showToast({ - title: '跳转到结算页面', - icon: 'success' + // 获取选中的商品 + const selectedCartItems = cartItems.filter((item: CartItem) => + selectedItems.includes(item.goodsId) + ); + + // 将选中的商品信息存储到本地,供结算页面使用 + Taro.setStorageSync('checkout_items', JSON.stringify(selectedCartItems)); + + // 跳转到购物车结算页面 + Taro.navigateTo({ + url: '/shop/orderConfirmCart/index' }); }; diff --git a/src/pages/order/components/OrderList.tsx b/src/pages/order/components/OrderList.tsx index b7badd6..762dade 100644 --- a/src/pages/order/components/OrderList.tsx +++ b/src/pages/order/components/OrderList.tsx @@ -1,10 +1,12 @@ -import {Avatar, Cell, Space, Tabs, Button, TabPane} from '@nutui/nutui-react-taro' +import {Avatar, Cell, Space, Tabs, Button, TabPane, Image, Toast} from '@nutui/nutui-react-taro' import {useEffect, useState, CSSProperties} from "react"; import Taro from '@tarojs/taro'; import {InfiniteLoading} from '@nutui/nutui-react-taro' import dayjs from "dayjs"; -import {pageShopOrder} from "@/api/shop/shopOrder"; +import {pageShopOrder, updateShopOrder} from "@/api/shop/shopOrder"; import {ShopOrder} from "@/api/shop/shopOrder/model"; +import {listShopOrderGoods} from "@/api/shop/shopOrderGoods"; +import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model"; import {copyText} from "@/utils/common"; const InfiniteUlStyle: CSSProperties = { @@ -43,16 +45,42 @@ const tabs = [ } ] -function OrderList(props: any) { - const [list, setList] = useState([]) +// 扩展订单接口,包含商品信息 +interface OrderWithGoods extends ShopOrder { + orderGoods?: ShopOrderGoods[]; +} + +interface OrderListProps { + data: ShopOrder[]; + onReload?: () => void; +} + +function OrderList(props: OrderListProps) { + const [list, setList] = useState([]) const [page, setPage] = useState(1) const [hasMore, setHasMore] = useState(true) const [tapIndex, setTapIndex] = useState('0') console.log(props.statusBarHeight, 'ppp') + // 获取订单状态文本 + const getOrderStatusText = (order: ShopOrder) => { + if (order.payStatus === 0) return '待付款'; + if (order.payStatus === 1 && order.deliveryStatus === 10) return '待发货'; + if (order.deliveryStatus === 20) return '待收货'; + if (order.deliveryStatus === 30) return '已收货'; + if (order.orderStatus === 1) return '已完成'; + if (order.orderStatus === 2) return '已取消'; + if (order.orderStatus === 4) return '退款申请中'; + if (order.orderStatus === 6) return '退款成功'; + return '未知状态'; + }; + const getOrderStatusParams = (index: string | number) => { - let params: { payStatus?: number; deliveryStatus?: number; orderStatus?: number } = {}; + let params: { payStatus?: number; deliveryStatus?: number; orderStatus?: number; userId?: number } = {}; + // 添加用户ID过滤 + params.userId = Taro.getStorageSync('UserId'); + switch (index) { case '1': // 待付款 params.payStatus = 0; @@ -77,13 +105,32 @@ function OrderList(props: any) { const reload = async (resetPage = false) => { const currentPage = resetPage ? 1 : page; const params = getOrderStatusParams(tapIndex); - pageShopOrder({ page: currentPage, ...params }).then(res => { - let newList: ShopOrder[] | undefined = []; + pageShopOrder({ page: currentPage, ...params }).then(async res => { + let newList: OrderWithGoods[] | undefined = []; if (res?.list && res?.list.length > 0) { - newList = resetPage ? res.list : list?.concat(res.list); + // 为每个订单获取商品信息 + const ordersWithGoods = await Promise.all( + res.list.map(async (order) => { + try { + const orderGoods = await listShopOrderGoods({ orderId: order.orderId }); + return { + ...order, + orderGoods: orderGoods || [] + }; + } catch (error) { + console.error('获取订单商品失败:', error); + return { + ...order, + orderGoods: [] + }; + } + }) + ); + + newList = resetPage ? ordersWithGoods : list?.concat(ordersWithGoods); setHasMore(true); } else { - newList = res?.list; + newList = []; setHasMore(false); } setList(newList || []); @@ -96,6 +143,39 @@ function OrderList(props: any) { reload(); }; + // 确认收货 + const confirmReceive = async (order: ShopOrder) => { + try { + await updateShopOrder({ + ...order, + deliveryStatus: 30, // 已收货 + orderStatus: 1 // 已完成 + }); + Toast.show('确认收货成功'); + reload(true); // 重新加载列表 + props.onReload?.(); // 通知父组件刷新 + } catch (error) { + console.error('确认收货失败:', error); + Toast.show('确认收货失败'); + } + }; + + // 取消订单 + const cancelOrder = async (order: ShopOrder) => { + try { + await updateShopOrder({ + ...order, + orderStatus: 2 // 已取消 + }); + Toast.show('订单已取消'); + reload(true); // 重新加载列表 + props.onReload?.(); // 通知父组件刷新 + } catch (error) { + console.error('取消订单失败:', error); + Toast.show('取消订单失败'); + } + }; + useEffect(() => { reload(true); // 首次加载或tab切换时重置页码 }, [tapIndex]); // 监听tapIndex变化 @@ -140,33 +220,69 @@ function OrderList(props: any) { } > - {props.data?.map(item => { + {list?.map((item, index) => { return ( - Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}> + Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}>
{e.stopPropagation(); copyText(`${item.orderNo}`)}}>{item.orderNo} - 待付款 {/* 这里可以根据item.orderStatus显示不同的状态 */} + {getOrderStatusText(item)}
{dayjs(item.createTime).format('YYYY年MM月DD日 HH:mm:ss')}
+ + {/* 商品信息 */}
-
+ {item.orderGoods && item.orderGoods.length > 0 ? ( + item.orderGoods.map((goods, goodsIndex) => ( +
+ +
+
{goods.goodsName}
+ {goods.spec &&
规格:{goods.spec}
} +
数量:{goods.totalNum}
+
+
¥{goods.price}
+
+ )) + ) : (
-
{item.realName}
+
+
{item.title || '订单商品'}
+
{item.totalNum}件商品
+
-
{item.totalNum}件商品
-
+ )}
-
实付金额:¥{item.payPrice}
+ +
实付金额:¥{item.payPrice}
+ + {/* 操作按钮 */} - + {item.payStatus === 0 && ( + <> + + + + )} + {item.deliveryStatus === 20 && ( + + )} + {item.orderStatus === 1 && ( + + )}
diff --git a/src/pages/order/order.tsx b/src/pages/order/order.tsx index 9f68c4a..81683f8 100644 --- a/src/pages/order/order.tsx +++ b/src/pages/order/order.tsx @@ -78,7 +78,7 @@ function Order() { {/*订单列表*/} { list.length > 0 && ( - + ) } diff --git a/src/pages/order/test-order.tsx b/src/pages/order/test-order.tsx new file mode 100644 index 0000000..b437d84 --- /dev/null +++ b/src/pages/order/test-order.tsx @@ -0,0 +1,54 @@ +import React from 'react'; +import { Button, Space, Toast } from '@nutui/nutui-react-taro'; +import Taro from '@tarojs/taro'; +import { createOrder } from '@/api/shop/shopOrder'; +import { OrderCreateRequest } from '@/api/shop/shopOrder/model'; + +const TestOrder = () => { + // 创建测试订单 + const createTestOrder = async () => { + try { + const orderData: OrderCreateRequest = { + goodsItems: [ + { + goodsId: 1, + quantity: 2, + skuId: 1, + specInfo: '红色 L码' + } + ], + payType: 1, // 微信支付 + deliveryType: 0, // 快递 + comments: '测试订单' + }; + + const result = await createOrder(orderData); + console.log('订单创建成功:', result); + Toast.show('测试订单创建成功'); + + // 跳转到订单列表 + setTimeout(() => { + Taro.navigateTo({ url: '/pages/order/order' }); + }, 1000); + } catch (error) { + console.error('创建订单失败:', error); + Toast.show('创建订单失败'); + } + }; + + return ( +
+

订单功能测试

+ + + + +
+ ); +}; + +export default TestOrder; diff --git a/src/shop/orderConfirmCart/index.tsx b/src/shop/orderConfirmCart/index.tsx index d6e9009..9a24361 100644 --- a/src/shop/orderConfirmCart/index.tsx +++ b/src/shop/orderConfirmCart/index.tsx @@ -8,7 +8,7 @@ import {View} from '@tarojs/components'; import {listShopUserAddress} from "@/api/shop/shopUserAddress"; import {ShopUserAddress} from "@/api/shop/shopUserAddress/model"; import './index.scss' -import {useCart} from "@/hooks/useCart"; +import {useCart, CartItem} from "@/hooks/useCart"; import Gap from "@/components/Gap"; import {createOrder} from "@/api/shop/shopOrder"; import {OrderCreateRequest} from "@/api/shop/shopOrder/model"; @@ -19,11 +19,13 @@ const OrderConfirm = () => { const [goods, setGoods] = useState(null); const [address, setAddress] = useState() const [payment, setPayment] = useState() + const [checkoutItems, setCheckoutItems] = useState([]); const router = Taro.getCurrentInstance().router; const goodsId = router?.params?.goodsId; const { - cartItems + cartItems, + removeFromCart } = useCart(); const reload = async () => { @@ -34,6 +36,25 @@ const OrderConfirm = () => { } } + // 加载结算商品数据 + const loadCheckoutItems = () => { + try { + const checkoutData = Taro.getStorageSync('checkout_items'); + if (checkoutData) { + const items = JSON.parse(checkoutData) as CartItem[]; + setCheckoutItems(items); + // 清除临时存储的数据 + Taro.removeStorageSync('checkout_items'); + } else { + // 如果没有选中商品数据,使用全部购物车商品 + setCheckoutItems(cartItems); + } + } catch (error) { + console.error('加载结算商品失败:', error); + setCheckoutItems(cartItems); + } + }; + /** * 统一支付入口 */ @@ -47,9 +68,9 @@ const OrderConfirm = () => { return; } - if (!cartItems || cartItems.length === 0) { + if (!checkoutItems || checkoutItems.length === 0) { Taro.showToast({ - title: '购物车为空', + title: '没有要结算的商品', icon: 'error' }); return; @@ -57,7 +78,7 @@ const OrderConfirm = () => { // 构建订单数据 const orderData = buildCartOrder( - cartItems.map(item => ({ + checkoutItems.map(item => ({ goodsId: item.goodsId!, quantity: item.quantity || 1 })), @@ -72,7 +93,14 @@ const OrderConfirm = () => { const paymentType = payment?.type === 0 ? PaymentType.BALANCE : PaymentType.WECHAT; // 执行支付 - await PaymentHandler.pay(orderData, paymentType); + await PaymentHandler.pay(orderData, paymentType, { + onSuccess: () => { + // 支付成功后,从购物车中移除已下单的商品 + checkoutItems.forEach(item => { + removeFromCart(item.goodsId); + }); + } + }); }; useEffect(() => { @@ -83,12 +111,21 @@ const OrderConfirm = () => { console.error("Failed to fetch goods detail:", error); }); } - reload().then() - }, [goodsId]); + reload().then(); + loadCheckoutItems(); + }, [goodsId, cartItems]); + + // 计算总价 + const getTotalPrice = () => { + return checkoutItems.reduce((total, item) => { + return total + (parseFloat(item.price) * item.quantity); + }, 0).toFixed(2); + }; - if (!goods) { - return
加载中...
; - } + // 计算商品总数量 + const getTotalQuantity = () => { + return checkoutItems.reduce((total, item) => total + item.quantity, 0); + }; return (
@@ -122,7 +159,7 @@ const OrderConfirm = () => { - {cartItems.map((goods, _) => ( + {checkoutItems.map((goods, _) => ( { - {'¥' + goods.price}}/> + {'¥' + getTotalPrice()}}/> - -¥10.00 + -¥0.00 )}/> @@ -164,7 +201,7 @@ const OrderConfirm = () => {
实付金额: - ¥{goods.price} + ¥{getTotalPrice()}
diff --git a/src/shop/orderDetail/index.tsx b/src/shop/orderDetail/index.tsx index 4a2b299..e5a29e7 100644 --- a/src/shop/orderDetail/index.tsx +++ b/src/shop/orderDetail/index.tsx @@ -3,20 +3,14 @@ import {Cell, CellGroup, Image, Space, Button} from '@nutui/nutui-react-taro' import Taro from '@tarojs/taro' import {ShopOrder} from "@/api/shop/shopOrder/model"; import {getShopOrder} from "@/api/shop/shopOrder"; -import {listOrderGoods} from "@/api/system/orderGoods"; -import {OrderGoods} from "@/api/system/orderGoods/model"; -import {getShopGoods} from "@/api/shop/shopGoods"; +import {listShopOrderGoods} from "@/api/shop/shopOrderGoods"; +import {ShopOrderGoods} from "@/api/shop/shopOrderGoods/model"; import dayjs from "dayjs"; import './index.scss' -interface OrderGoodsDetail extends OrderGoods { - goodsName?: string; - goodsImage?: string; -} - const OrderDetail = () => { const [order, setOrder] = useState(null); - const [orderGoodsList, setOrderGoodsList] = useState([]); + const [orderGoodsList, setOrderGoodsList] = useState([]); const router = Taro.getCurrentInstance().router; const orderId = router?.params?.orderId; @@ -55,19 +49,9 @@ const OrderDetail = () => { setOrder(res); // 获取订单商品列表 - const goodsRes = await listOrderGoods({ orderId: Number(orderId) }); + const goodsRes = await listShopOrderGoods({ orderId: Number(orderId) }); if (goodsRes && goodsRes.length > 0) { - const goodsDetailsPromises = goodsRes.map(async (item) => { - console.log(item,'item.>>>') - const shopGoods = await getShopGoods(Number(item.goodsId)); - return { - ...item, - goodsName: shopGoods?.name, - goodsImage: shopGoods?.image, - }; - }); - const detailedGoodsList = await Promise.all(goodsDetailsPromises); - setOrderGoodsList(detailedGoodsList); + setOrderGoodsList(goodsRes); } }).catch(error => { console.error("Failed to fetch order detail:", error); @@ -91,11 +75,12 @@ const OrderDetail = () => { {orderGoodsList.map((item, index) => (
- +
{item.goodsName}
+ {item.spec &&
规格:{item.spec}
}
数量:{item.totalNum}
-
¥{item.payPrice}
+
¥{item.price}
diff --git a/src/utils/payment.ts b/src/utils/payment.ts index bbdb401..0a9e632 100644 --- a/src/utils/payment.ts +++ b/src/utils/payment.ts @@ -97,7 +97,7 @@ export class PaymentHandler { * 处理微信支付 */ private static async handleWechatPay(result: WxPayResult): Promise { - if (!result || !result.prepayId) { + if (!result) { throw new Error('微信支付参数错误'); }