forked from gxwebsoft/mp-10550
feat(order): 添加订单自动取消过期功能
- 在OrderList组件中新增autoCancelExpired和paymentTimeoutHours属性 - 实现支付过期订单的自动检测和取消逻辑 - 添加parseTime和isOrderPaymentExpiredSafe辅助函数 - 使用useRef管理自动取消状态避免重复执行 - 支持单次最多处理20笔过期订单避免接口风暴 - 区分resetPage和loadMore场景下的列表状态同步 - 更新useEffect依赖数组包含新的属性参数
This commit is contained in:
@@ -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<number | null>(null)
|
||||
const autoCanceledOrderIdsRef = useRef<Set<number>>(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<number>();
|
||||
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; // 防止重复加载
|
||||
|
||||
@@ -164,6 +164,7 @@ function Order() {
|
||||
onReload={() => reload(searchParams)}
|
||||
searchParams={searchParams}
|
||||
showSearch={showSearch}
|
||||
autoCancelExpired
|
||||
onSearchParamsChange={(newParams) => {
|
||||
console.log('父组件接收到searchParams变化:', newParams);
|
||||
setSearchParams(newParams);
|
||||
|
||||
Reference in New Issue
Block a user