import {Avatar, Cell, Space, Empty, Tabs, Button, TabPane, Image, Dialog} from '@nutui/nutui-react-taro' import {useEffect, useState, useCallback, useRef, CSSProperties} from "react"; import {View, Text} from '@tarojs/components' import Taro from '@tarojs/taro'; import {InfiniteLoading} from '@nutui/nutui-react-taro' import dayjs from "dayjs"; import { pageShopOrder, updateShopOrder, createOrder, getShopOrder, prepayShopOrder } from "@/api/shop/shopOrder"; import {OrderCreateRequest, ShopOrder, ShopOrderParam} from "@/api/shop/shopOrder/model"; import {listShopOrderGoods} from "@/api/shop/shopOrderGoods"; import {copyText} from "@/utils/common"; import PaymentCountdown from "@/components/PaymentCountdown"; import {PaymentType} from "@/utils/payment"; import {ErrorType, RequestError} from "@/utils/request"; // 判断订单是否支付已过期 const isPaymentExpired = (createTime: string, timeoutHours: number = 24): boolean => { if (!createTime) return false; const createTimeObj = dayjs(createTime); const expireTime = createTimeObj.add(timeoutHours, 'hour'); const now = dayjs(); return now.isAfter(expireTime); }; const getInfiniteUlStyle = (showSearch: boolean = false): CSSProperties => ({ marginTop: showSearch ? '0' : '0', // 如果显示搜索框,增加更多的上边距 height: showSearch ? '75vh' : '84vh', // 相应调整高度 width: '100%', padding: '0', overflowY: 'auto', overflowX: 'hidden' // 注意:小程序不支持 boxShadow }) // 统一的订单状态标签配置,与后端 statusFilter 保持一致 const tabs = [ { index: 0, key: '全部', title: '全部', description: '所有订单', statusFilter: -1 // 使用-1表示全部订单 }, { index: 1, key: '待付款', title: '待付款', description: '等待付款的订单', statusFilter: 0 // 对应后端:pay_status = false }, { index: 2, key: '待发货', title: '待发货', description: '已付款待发货的订单', statusFilter: 1 // 对应后端:pay_status = true AND delivery_status = 10 }, { index: 3, key: '待收货', title: '待收货', description: '已发货待收货的订单', statusFilter: 3 // 对应后端:pay_status = true AND delivery_status = 20 }, { index: 4, key: '已完成', title: '已完成', description: '已完成的订单', statusFilter: 5 // 对应后端:order_status = 1 }, { index: 5, key: '退货/售后', title: '退货/售后', description: '退货/售后的订单', statusFilter: 6 // 对应后端:order_status = 6 (已退款) } ] interface OrderListProps { onReload?: () => void; searchParams?: ShopOrderParam; showSearch?: boolean; onSearchParamsChange?: (params: ShopOrderParam) => void; // 新增:通知父组件参数变化 // 订单视图模式:用户/门店/骑手 mode?: 'user' | 'store' | 'rider'; // 固定过滤条件(例如 storeId / riderId),会合并到每次请求里 baseParams?: ShopOrderParam; // 只读模式:隐藏“支付/取消/确认收货/退款”等用户操作按钮 readOnly?: boolean; } function OrderList(props: OrderListProps) { const [list, setList] = useState([]) const pageRef = useRef(1) const [hasMore, setHasMore] = useState(true) const [payingOrderId, setPayingOrderId] = useState(null) // 根据传入的statusFilter设置初始tab索引 const getInitialTabIndex = () => { if (props.searchParams?.statusFilter !== undefined) { const tab = tabs.find(t => t.statusFilter === props.searchParams?.statusFilter); return tab ? tab.index : 0; } return 0; }; const [tapIndex, setTapIndex] = useState(() => { const initialIndex = getInitialTabIndex(); console.log('初始化tapIndex:', initialIndex, '对应statusFilter:', props.searchParams?.statusFilter); return initialIndex; }) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) const [cancelDialogVisible, setCancelDialogVisible] = useState(false) const [orderToCancel, setOrderToCancel] = useState(null) const [confirmReceiveDialogVisible, setConfirmReceiveDialogVisible] = useState(false) const [orderToConfirmReceive, setOrderToConfirmReceive] = useState(null) const isReadOnly = props.readOnly || props.mode === 'store' || props.mode === 'rider' // 获取订单状态文本 const getOrderStatusText = (order: ShopOrder) => { // 优先检查订单状态 if (order.orderStatus === 2) return '已取消'; if (order.orderStatus === 4) return '退款申请中'; if (order.orderStatus === 5) return '退款被拒绝'; if (order.orderStatus === 6) return '退款成功'; if (order.orderStatus === 7) return '客户端申请退款'; // 检查支付状态 (payStatus为boolean类型,false/0表示未付款,true/1表示已付款) if (!order.payStatus) return '等待买家付款'; // 已付款后检查发货状态 if (order.deliveryStatus === 10) return '待发货'; if (order.formId === 10074) return '已完成'; if (order.deliveryStatus === 20) { // 若订单没有配送员,沿用原“待收货”语义 if (!order.riderId) return '待收货'; // 配送员确认送达后(sendEndTime有值),才进入“待确认收货” if (order.sendEndTime && order.orderStatus !== 1) return '待确认收货'; return '配送中'; } if (order.deliveryStatus === 30) return '部分发货'; // 最后检查订单完成状态 if (order.orderStatus === 1) return '已完成'; if (order.orderStatus === 0) return '未使用'; return '未知状态'; }; const isOrderCompleted = (order: ShopOrder) => order.orderStatus === 1 || order.formId === 10074; // 获取订单状态颜色 const getOrderStatusColor = (order: ShopOrder) => { // 优先检查订单状态 if (order.orderStatus === 2) return 'text-gray-500'; // 已取消 if (order.orderStatus === 4) return 'text-orange-500'; // 退款申请中 if (order.orderStatus === 5) return 'text-red-500'; // 退款被拒绝 if (order.orderStatus === 6) return 'text-green-500'; // 退款成功 if (order.orderStatus === 7) return 'text-orange-500'; // 客户端申请退款 // 检查支付状态 if (!order.payStatus) return 'text-orange-500'; // 等待买家付款 // 已付款后检查发货状态 if (order.deliveryStatus === 10) return 'text-blue-500'; // 待发货 if (order.deliveryStatus === 20) { if (!order.riderId) return 'text-purple-500'; // 待收货 if (order.sendEndTime && order.orderStatus !== 1) return 'text-purple-500'; // 待确认收货 return 'text-blue-500'; // 配送中 } if (order.deliveryStatus === 30) return 'text-blue-500'; // 部分发货 // 最后检查订单完成状态 if (order.orderStatus === 1) return 'text-green-600'; // 已完成 if (order.orderStatus === 0) return 'text-gray-500'; // 未使用 return 'text-gray-600'; // 默认颜色 }; // 使用后端统一的 statusFilter 进行筛选 const getOrderStatusParams = (index: string | number) => { let params: ShopOrderParam = { ...(props.baseParams || {}) }; // 默认是用户视图:添加 userId 过滤;门店/骑手视图由 baseParams 控制 if (!props.mode || props.mode === 'user') { params.userId = Taro.getStorageSync('UserId'); } // 获取当前tab的statusFilter配置 const currentTab = tabs.find(tab => tab.index === Number(index)); if (currentTab && currentTab.statusFilter !== undefined) { params.statusFilter = currentTab.statusFilter; } // 注意:当statusFilter为undefined时,不要添加到params中,这样API请求就不会包含这个参数 console.log(`Tab ${index} (${currentTab?.title}) 筛选参数:`, params); return params; }; const reload = useCallback(async (resetPage = false, targetPage?: number) => { setLoading(true); setError(null); // 清除之前的错误 const currentPage = resetPage ? 1 : (targetPage || pageRef.current); const statusParams = getOrderStatusParams(tapIndex); // 合并搜索条件,tab的statusFilter优先级更高 const searchConditions: any = { page: currentPage, ...statusParams, ...props.searchParams, // 搜索关键词等其他条件 }; // Tabs 的 statusFilter 优先级最高;全部(-1)时不传该参数(后端按“无筛选”处理) if (statusParams.statusFilter === undefined || statusParams.statusFilter === -1) { delete searchConditions.statusFilter; } else { searchConditions.statusFilter = statusParams.statusFilter; } console.log('订单筛选条件:', { tapIndex, statusParams, searchConditions, finalStatusFilter: searchConditions.statusFilter }); try { const res = await pageShopOrder(searchConditions); if (res?.list && res?.list.length > 0) { // 订单分页接口已返回 orderGoods:列表直接使用该字段 const incoming = res.list as ShopOrder[]; // 使用函数式更新避免依赖 list setList(prevList => { const newList = resetPage ? incoming : (prevList || []).concat(incoming); return newList; }); // 正确判断是否还有更多数据 const hasMoreData = incoming.length >= 10; // 假设每页10条数据 setHasMore(hasMoreData); } else { setList(prevList => resetPage ? [] : prevList); setHasMore(false); } pageRef.current = currentPage; setLoading(false); } catch (error) { console.error('加载订单失败:', error); setLoading(false); setError('加载订单失败,请重试'); // 添加错误提示 Taro.showToast({ title: '加载失败,请重试', icon: 'none' }); } }, [tapIndex, props.searchParams]); // 移除 list/page 依赖,避免useEffect触发循环 const reloadMore = useCallback(async () => { if (loading || !hasMore) return; // 防止重复加载 const nextPage = pageRef.current + 1; pageRef.current = nextPage; await reload(false, nextPage); }, [loading, hasMore, reload]); // 确认收货 - 显示确认对话框 const confirmReceive = (order: ShopOrder) => { setOrderToConfirmReceive(order); setConfirmReceiveDialogVisible(true); }; // 确认收货 - 执行收货操作 const handleConfirmReceive = async () => { if (!orderToConfirmReceive) return; try { setConfirmReceiveDialogVisible(false); await updateShopOrder({ ...orderToConfirmReceive, deliveryStatus: orderToConfirmReceive.deliveryStatus, // 10未发货 20已发货 30部分发货(收货由orderStatus控制) orderStatus: 1 // 已完成 }); Taro.showToast({ title: '确认收货成功', icon: 'success' }); await reload(true); // 重新加载列表 props.onReload?.(); // 通知父组件刷新 // 清空状态 setOrderToConfirmReceive(null); } catch (error) { console.error('确认收货失败:', error); Taro.showToast({ title: '确认收货失败', icon: 'none' }); // 重新显示对话框 setConfirmReceiveDialogVisible(true); } }; // 取消确认收货对话框 const handleCancelReceiveDialog = () => { setConfirmReceiveDialogVisible(false); setOrderToConfirmReceive(null); }; // 申请退款 (待发货状态) const applyRefund = async (order: ShopOrder) => { try { // 更新订单状态为"退款申请中" await updateShopOrder({ orderId: order.orderId, orderStatus: 4 // 退款申请中 }); // 更新本地状态 setList(prev => prev.map(item => item.orderId === order.orderId ? {...item, orderStatus: 4} : item )); // 跳转到退款申请页面 Taro.navigateTo({ url: `/user/order/refund/index?orderId=${order.orderId}&orderNo=${order.orderNo}` }); } catch (error) { console.error('更新订单状态失败:', error); Taro.showToast({ title: '操作失败,请重试', icon: 'none' }); } }; // 查看物流 (待收货状态) // const viewLogistics = (order: ShopOrder) => { // // 跳转到物流查询页面 // Taro.navigateTo({ // url: `/user/order/logistics/index?orderId=${order.orderId}&orderNo=${order.orderNo}&expressNo=${order.transactionId || ''}&expressCompany=SF` // }); // }; // 取消订单 const cancelOrder = (order: ShopOrder) => { setOrderToCancel(order); setCancelDialogVisible(true); }; // 确认取消订单 const handleConfirmCancel = async () => { if (!orderToCancel) return; try { setCancelDialogVisible(false); // 更新订单状态为已取消,而不是删除订单 await updateShopOrder({ ...orderToCancel, orderStatus: 2 // 已取消 }); Taro.showToast({ title: '订单已取消', icon: 'success' }); void reload(true); // 重新加载列表 props.onReload?.(); // 通知父组件刷新 } catch (error) { console.error('取消订单失败:', error); Taro.showToast({ title: '取消订单失败', icon: 'error' }); } finally { setOrderToCancel(null); } }; // 取消对话框的取消操作 const handleCancelDialog = () => { setCancelDialogVisible(false); setOrderToCancel(null); }; // 立即支付 const payOrder = async (order: ShopOrder) => { try { if (!order.orderId) { Taro.showToast({ title: '订单信息错误', icon: 'error' }); return; } if (payingOrderId === order.orderId) { return; } setPayingOrderId(order.orderId); // 尽量以服务端最新状态为准,避免“已取消/已支付”但列表未刷新导致误发起支付 let latestOrder: ShopOrder | null = null; try { latestOrder = await getShopOrder(order.orderId); } catch (_e) { // 忽略:网络波动时继续使用列表数据兜底 } const effectiveOrder = latestOrder ? { ...order, ...latestOrder } : order; if (effectiveOrder.payStatus) { Taro.showToast({ title: '订单已支付', icon: 'none' }); // 同步刷新一次,避免列表显示旧状态 void reload(true); return; } if (effectiveOrder.orderStatus === 2) { Taro.showToast({ title: '订单已取消,无法支付', icon: 'error' }); void reload(true); return; } // 检查订单是否已过期(以最新 createTime 为准) if (effectiveOrder.createTime && isPaymentExpired(effectiveOrder.createTime)) { Taro.showToast({ title: '订单已过期,无法支付', icon: 'error' }); return; } Taro.showLoading({title: '发起支付...'}); // 构建商品数据:优先使用订单分页接口返回的 orderGoods;缺失时再补拉一次,避免goodsItems为空导致后端拒绝/再次支付失败 let orderGoods = effectiveOrder.orderGoods || []; if (!orderGoods.length) { try { orderGoods = (await listShopOrderGoods({orderId: effectiveOrder.orderId})) || []; } catch (e) { // 继续走下面的校验提示 console.error('补拉订单商品失败:', e); } } const goodsItems = orderGoods .filter(g => !!(g as any).goodsId || !!(g as any).itemId) .map(goods => ({ goodsId: (goods.goodsId ?? (goods as any).itemId) as number, quantity: ((goods as any).quantity ?? goods.totalNum ?? 1) as number, // 若后端按SKU计算价格/库存,补齐SKU/规格信息更安全 skuId: (goods as any).skuId ?? (goods as any).sku_id, specInfo: (goods as any).specInfo ?? (goods as any).spec })); if (!goodsItems.length) { Taro.showToast({ title: '订单商品信息缺失,请稍后重试', icon: 'none' }); return; } // 优先:对“已创建但未支付”的订单走“重新发起支付”接口(不应重复创建订单) // 若后端未提供该接口,则降级为重新创建订单(此时不传 orderNo,避免出现“相同订单号重复订单”) let result: any; let usedFallbackCreate = false; try { result = await prepayShopOrder({ orderId: effectiveOrder.orderId!, payType: PaymentType.WECHAT }); } catch (e) { // 订单状态等业务错误:直接提示,不要降级“重新创建订单”导致产生多笔订单 if (e instanceof RequestError && e.type === ErrorType.BUSINESS_ERROR) { throw e; } usedFallbackCreate = true; const orderData: OrderCreateRequest = { goodsItems, addressId: effectiveOrder.addressId, payType: PaymentType.WECHAT, couponId: effectiveOrder.couponId, deliveryType: effectiveOrder.deliveryType, selfTakeMerchantId: effectiveOrder.selfTakeMerchantId, comments: effectiveOrder.comments, title: effectiveOrder.title, storeId: effectiveOrder.storeId, storeName: effectiveOrder.storeName, riderId: effectiveOrder.riderId, warehouseId: effectiveOrder.warehouseId }; result = await createOrder(orderData); } if (!result) { throw new Error('支付发起失败'); } // 验证微信支付必要参数 if (!result.timeStamp || !result.nonceStr || !result.package || !result.paySign) { throw new Error('微信支付参数不完整'); } // 调用微信支付 try { await Taro.requestPayment({ timeStamp: result.timeStamp, nonceStr: result.nonceStr, package: result.package, signType: (result.signType || 'MD5') as 'MD5' | 'HMAC-SHA256', paySign: result.paySign, }); } catch (payError: any) { const msg: string = payError?.errMsg || payError?.message || ''; if (msg.includes('cancel')) { // 用户主动取消,不当作“失败”强提示 Taro.showToast({ title: '已取消支付', icon: 'none' }); return; } throw payError; } // 支付成功 Taro.showToast({ title: '支付成功', icon: 'success' }); // 若因后端不支持“重新发起支付”而降级“重新创建订单”,则原订单会遗留为待支付,支付成功后自动将其标记为已取消,避免列表堆积 if (usedFallbackCreate && effectiveOrder.orderId && !effectiveOrder.payStatus && effectiveOrder.orderStatus !== 2) { try { await updateShopOrder({ orderId: effectiveOrder.orderId, orderStatus: 2 }); } catch (e) { console.warn('自动取消旧待支付订单失败:', e); } } // 重新加载订单列表 void reload(true); props.onReload?.(); // 跳转到订单页面 setTimeout(() => { Taro.navigateTo({url: '/user/order/order'}); }, 2000); } catch (error: any) { console.error('支付失败:', error); let errorMessage = '支付失败,请重试'; const rawMsg: string = error?.errMsg || error?.message || ''; if (rawMsg) { if (rawMsg.includes('cancel')) { errorMessage = '用户取消支付'; } else if (rawMsg.includes('余额不足')) { errorMessage = '账户余额不足'; } else { errorMessage = rawMsg; } } Taro.showToast({ title: errorMessage, icon: 'error' }); } finally { Taro.hideLoading(); setPayingOrderId(null); } }; useEffect(() => { void reload(true); // 首次加载、tab切换或搜索条件变化时重置页码 }, [reload]); // 监听外部statusFilter变化,同步更新tab索引 useEffect(() => { // 获取当前的statusFilter,如果未定义则默认为-1(全部) const currentStatusFilter = props.searchParams?.statusFilter !== undefined ? props.searchParams.statusFilter : -1; const tab = tabs.find(t => t.statusFilter === currentStatusFilter); const targetTabIndex = tab ? tab.index : 0; console.log('外部statusFilter变化:', { statusFilter: currentStatusFilter, originalStatusFilter: props.searchParams?.statusFilter, currentTapIndex: tapIndex, targetTabIndex, shouldUpdate: targetTabIndex !== tapIndex }); if (targetTabIndex !== tapIndex) { setTapIndex(targetTabIndex); // 不需要调用reload,因为tapIndex变化会触发reload } }, [props.searchParams?.statusFilter, tapIndex]); // 监听statusFilter变化 return ( <> { console.log('Tab切换:', paneKey, '类型:', typeof paneKey); const newTapIndex = Number(paneKey); setTapIndex(newTapIndex); // 通知父组件更新 searchParams.statusFilter const currentTab = tabs.find(tab => tab.index === newTapIndex); if (currentTab && props.onSearchParamsChange) { const newSearchParams = { ...props.searchParams, statusFilter: currentTab.statusFilter }; console.log('通知父组件更新searchParams:', newSearchParams); props.onSearchParamsChange(newSearchParams); } }} > { tabs?.map((item, _) => { return ( ) }) } {error ? ( {error} ) : ( { }} onScrollToUpper={() => { }} loadingText={ <> 加载中 } loadMoreText={ list.length === 0 ? ( ) : ( 没有更多了 ) } > {/* 订单列表 */} {list.length > 0 && list ?.filter((item) => { // “待收货”不展示退款中的/已退款订单,这些订单统一放到“退货/售后” if (tapIndex === 3 && (item.orderStatus === 4 || item.orderStatus === 6)) { return false; } // “退货/售后”只展示售后相关状态 if (tapIndex === 5) { return item.orderStatus === 4 || item.orderStatus === 5 || item.orderStatus === 6 || item.orderStatus === 7; } return true; }) ?.map((item, index) => { return ( Taro.navigateTo({url: `/shop/orderDetail/index?orderId=${item.orderId}`})}> { e.stopPropagation(); copyText(`${item.orderNo}`) }}>{item.orderNo} {/* 右侧显示合并的状态和倒计时 */} {!item.payStatus && item.orderStatus !== 2 ? ( ) : ( 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 as any).goodsTitle || (goods as any).title || item.title || '订单商品'} {(goods.spec || (goods as any).specInfo) && ( 规格:{goods.spec || (goods as any).specInfo} )} 数量:{(goods as any).quantity ?? goods.totalNum} x ¥{goods.price || (goods as any).payPrice} )) ) : ( {item.title || '订单商品'} {item.totalNum}件商品 )} 实付金额:¥{item.payPrice} {/* 操作按钮 */} {!isReadOnly && ( {/* 待付款状态:显示取消订单和立即支付 */} {(!item.payStatus) && item.orderStatus !== 2 && ( {(!item.createTime || !isPaymentExpired(item.createTime, 24)) && ( )} )} {/* 待发货状态:显示申请退款 */} {item.payStatus && item.deliveryStatus === 10 && item.orderStatus !== 2 && item.orderStatus !== 4 && !isOrderCompleted(item) && ( )} {/* 待收货状态:显示查看物流和确认收货 */} {item.deliveryStatus === 20 && (!item.riderId || !!item.sendEndTime) && item.orderStatus !== 2 && item.orderStatus !== 6 && !isOrderCompleted(item) && ( {/**/} )} {/* 退款/售后状态:显示查看进度和撤销申请 */} {(item.orderStatus === 4 || item.orderStatus === 7) && ( {/**/} )} )} ) })} )} {/* 取消订单确认对话框 */} 确定要取消这个订单吗? {/* 确认收货确认对话框 */} 确定已经收到商品了吗?确认收货后订单将完成。 ) } export default OrderList