From 81c63e0e65a2c71b5a24df599ec8631aaef7300f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Sun, 8 Mar 2026 13:08:35 +0800 Subject: [PATCH] =?UTF-8?q?feat(order):=20=E6=B7=BB=E5=8A=A0=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E8=87=AA=E5=8A=A8=E5=8F=96=E6=B6=88=E8=BF=87=E6=9C=9F?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 在OrderList组件中新增autoCancelExpired和paymentTimeoutHours属性 - 实现支付过期订单的自动检测和取消逻辑 - 添加parseTime和isOrderPaymentExpiredSafe辅助函数 - 使用useRef管理自动取消状态避免重复执行 - 支持单次最多处理20笔过期订单避免接口风暴 - 区分resetPage和loadMore场景下的列表状态同步 - 更新useEffect依赖数组包含新的属性参数 --- src/user/order/components/OrderList.tsx | 110 +++++++++++++++++++++--- src/user/order/order.tsx | 1 + 2 files changed, 98 insertions(+), 13 deletions(-) diff --git a/src/user/order/components/OrderList.tsx b/src/user/order/components/OrderList.tsx index 29f8cd8..485a328 100644 --- a/src/user/order/components/OrderList.tsx +++ b/src/user/order/components/OrderList.tsx @@ -104,6 +104,10 @@ interface OrderListProps { baseParams?: ShopOrderParam; // 只读模式:隐藏“支付/取消/确认收货/退款”等用户操作按钮 readOnly?: boolean; + // 是否自动取消“支付已过期”的待支付订单(仅 user 模式生效) + autoCancelExpired?: boolean; + // 支付超时时间(小时),默认 24 小时 + paymentTimeoutHours?: number; } function OrderList(props: OrderListProps) { @@ -111,6 +115,8 @@ function OrderList(props: OrderListProps) { const pageRef = useRef(1) const [hasMore, setHasMore] = useState(true) const [payingOrderId, setPayingOrderId] = useState(null) + const autoCanceledOrderIdsRef = useRef>(new Set()) + const autoCancelRunningRef = useRef(false) // 根据传入的statusFilter设置初始tab索引 const getInitialTabIndex = () => { if (props.searchParams?.statusFilter !== undefined) { @@ -138,6 +144,26 @@ function OrderList(props: OrderListProps) { return Number.isFinite(n) ? n : undefined; }; + const parseTime = (raw: any): dayjs.Dayjs | null => { + const text = String(raw ?? '').trim(); + if (!text) return null; + const t = /^\d+$/.test(text) + ? dayjs(Number(text) < 1e12 ? Number(text) * 1000 : Number(text)) + : dayjs(text); + return t.isValid() ? t : null; + }; + + const isOrderPaymentExpiredSafe = (order: ShopOrder, timeoutHours: number) => { + if (order.payStatus) return false; + if (toNum(order.orderStatus) === 2) return false; + + const expiration = parseTime(order.expirationTime); + if (expiration) return dayjs().isAfter(expiration); + + if (order.createTime) return isPaymentExpired(order.createTime, timeoutHours); + return false; + }; + // “已完成”应以订单状态为准;不要用商品ID等字段推断完成态,否则会造成 Tab(待发货/待收货) 与状态文案不同步 const isOrderCompleted = (order: ShopOrder) => toNum(order.orderStatus) === 1; @@ -248,24 +274,82 @@ function OrderList(props: OrderListProps) { finalStatusFilter: searchConditions.statusFilter }); - try { - const res = await pageShopOrder(searchConditions); + try { + const timeoutHours = typeof props.paymentTimeoutHours === 'number' ? props.paymentTimeoutHours : 24; + const canAutoCancelExpired = + !!props.autoCancelExpired && + (!props.mode || props.mode === 'user') && + !props.readOnly; + const isPendingPayList = statusParams.statusFilter === 0; - if (res?.list && res?.list.length > 0) { + const fetchOrders = async () => pageShopOrder(searchConditions); + + let res = await fetchOrders(); + let incoming = (res?.list || []) as ShopOrder[]; + let rawIncomingLength = incoming.length; + + // 自动取消“支付已过期”的待支付订单(避免用户看到一堆不可支付的过期单) + if (canAutoCancelExpired && incoming.length && !autoCancelRunningRef.current) { + const expiredToCancel = incoming + .filter(o => !!o?.orderId) + .filter(o => !autoCanceledOrderIdsRef.current.has(o.orderId as number)) + .filter(o => isOrderPaymentExpiredSafe(o, timeoutHours)); + + if (expiredToCancel.length) { + autoCancelRunningRef.current = true; + const justCanceled = new Set(); + try { + // 单次最多处理 20 笔,避免接口风暴 + for (const order of expiredToCancel.slice(0, 20)) { + try { + await updateShopOrder({ orderId: order.orderId, orderStatus: 2 }); + autoCanceledOrderIdsRef.current.add(order.orderId as number); + justCanceled.add(order.orderId as number); + } catch (e) { + console.warn('自动取消过期订单失败:', order?.orderId, e); + } + } + } finally { + autoCancelRunningRef.current = false; + } + + if (justCanceled.size > 0) { + if (resetPage) { + // resetPage 时重新拉取一次,确保列表状态与服务端一致 + res = await fetchOrders(); + incoming = (res?.list || []) as ShopOrder[]; + rawIncomingLength = incoming.length; + Taro.showToast({ title: '已自动取消过期订单', icon: 'none' }); + } else { + // loadMore 时不重新拉取,避免破坏滚动;仅在本地列表中做最小同步 + if (isPendingPayList) { + incoming = incoming.filter(o => !justCanceled.has(o.orderId as number)); + } else { + incoming = incoming.map(o => ( + justCanceled.has(o.orderId as number) ? { ...o, orderStatus: 2 } : o + )); + } + } + } + } + } + + if (rawIncomingLength > 0) { // 订单分页接口已返回 orderGoods:列表直接使用该字段 - const incoming = res.list as ShopOrder[]; - // 使用函数式更新避免依赖 list - setList(prevList => { - const newList = resetPage ? incoming : (prevList || []).concat(incoming); - return newList; - }); + if (incoming.length > 0) { + setList(prevList => (resetPage ? incoming : (prevList || []).concat(incoming))); + } else { + // 本页数据全部被自动取消过滤掉:不清空历史列表,仅保持现状 + setList(prevList => (resetPage ? [] : prevList)); + } - // 正确判断是否还有更多数据 - const hasMoreData = incoming.length >= 10; // 假设每页10条数据 + // 正确判断是否还有更多数据(以服务端返回条数为准) + const hasMoreData = rawIncomingLength >= 10; // 假设每页10条数据 setHasMore(hasMoreData); } else { - setList(prevList => resetPage ? [] : prevList); + // 服务端已无更多数据 + setList(prevList => (resetPage ? [] : prevList)); setHasMore(false); } @@ -281,7 +365,7 @@ function OrderList(props: OrderListProps) { icon: 'none' }); } - }, [tapIndex, props.searchParams]); // 移除 list/page 依赖,避免useEffect触发循环 + }, [tapIndex, props.searchParams, props.baseParams, props.mode, props.readOnly, props.autoCancelExpired, props.paymentTimeoutHours]); // 移除 list/page 依赖,避免useEffect触发循环 const reloadMore = useCallback(async () => { if (loading || !hasMore) return; // 防止重复加载 diff --git a/src/user/order/order.tsx b/src/user/order/order.tsx index 66300ae..b5ca7a1 100644 --- a/src/user/order/order.tsx +++ b/src/user/order/order.tsx @@ -164,6 +164,7 @@ function Order() { onReload={() => reload(searchParams)} searchParams={searchParams} showSearch={showSearch} + autoCancelExpired onSearchParamsChange={(newParams) => { console.log('父组件接收到searchParams变化:', newParams); setSearchParams(newParams);