From e7caee08c19657c8fdd3ae77179642f195c69998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Tue, 10 Mar 2026 17:18:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(ticket):=20=E4=BF=AE=E5=A4=8D=E8=AE=A2?= =?UTF-8?q?=E5=8D=95=E5=8F=96=E6=B6=88=E6=97=B6=E7=9A=84=E7=A5=A8=E5=88=B8?= =?UTF-8?q?=E5=9B=9E=E6=BB=9A=E9=80=BB=E8=BE=91=E5=92=8C=E5=8A=A0=E8=BD=BD?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E6=8E=A7=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加 orderCancelLoadingById 状态管理订单取消加载状态 - 实现 getTicketUsedQty 函数统一处理票券已使用数量字段 - 完善 rollbackUserTicketAfterOrderCancel 方法支持已使用数量回滚 - 添加防重复提交机制避免订单取消多次触发 - 更新订单修改和取消按钮禁用状态防止并发操作 - 优化票券可用数量和已使用数量的计算逻辑 --- src/user/ticket/index.tsx | 75 +++++++++++++++++++++++++++++++++------ 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/src/user/ticket/index.tsx b/src/user/ticket/index.tsx index 6b854bb..4e6a374 100644 --- a/src/user/ticket/index.tsx +++ b/src/user/ticket/index.tsx @@ -37,6 +37,7 @@ const UserTicketList = () => { const [orderHasMore, setOrderHasMore] = useState(true); const [orderPage, setOrderPage] = useState(1); const [orderTotal, setOrderTotal] = useState(0); + const [orderCancelLoadingById, setOrderCancelLoadingById] = useState>({}); const [activeTab, setActiveTab] = useState<'ticket' | 'order'>(() => { const tab = Taro.getCurrentInstance().router?.params?.tab @@ -320,40 +321,85 @@ const UserTicketList = () => { return Number.isFinite(computed) ? computed : 0; }; + const getTicketUsedQty = (t?: Partial | null) => { + if (!t) return 0; + const anyT: any = t; + const raw = anyT.usedQty ?? anyT.usedNum ?? anyT.usedCount; + const n = Number(raw); + return Number.isFinite(n) ? n : 0; + }; + const rollbackUserTicketAfterOrderCancel = async (order: GltTicketOrder, before?: GltUserTicket | null) => { + const orderId = Number(order?.id); const ticketId = Number(order?.userTicketId); const qty = Math.max(0, Math.floor(Number((order as any)?.totalNum ?? 0))); + if (!Number.isFinite(orderId) || orderId <= 0) return; if (!Number.isFinite(ticketId) || ticketId <= 0) return; if (!Number.isFinite(qty) || qty <= 0) return; + const rollbackKey = `glt_ticket_order_rollback:${orderId}`; + if (Taro.getStorageSync(rollbackKey)) return; + const after = await getGltUserTicket(ticketId); if (!after?.id) return; const beforeAvail = before ? getTicketAvailableQty(before) : undefined; const afterAvail = getTicketAvailableQty(after); + const beforeUsed = before ? getTicketUsedQty(before) : undefined; + const afterUsed = getTicketUsedQty(after); - let need = qty; + let needAvail = qty; if (typeof beforeAvail === 'number') { const delta = afterAvail - beforeAvail; - if (delta >= qty) return; // backend already rolled back - if (delta > 0) need = Math.max(0, qty - delta); + if (delta >= qty) { + Taro.setStorageSync(rollbackKey, Date.now()); + return; // backend already rolled back + } + if (delta > 0) needAvail = Math.max(0, qty - delta); + } + let needUsed = qty; + if (typeof beforeUsed === 'number') { + const delta = beforeUsed - afterUsed; + if (delta >= qty) { + needUsed = 0; // backend already rolled back used qty + } else if (delta > 0) { + needUsed = Math.max(0, qty - delta); + } + } + + if (needAvail <= 0 && needUsed <= 0) { + Taro.setStorageSync(rollbackKey, Date.now()); + return; } - if (need <= 0) return; const currentAvailRaw = Number((after as any)?.availableQty); const baseAvail = Number.isFinite(currentAvailRaw) ? currentAvailRaw : afterAvail; - const nextAvail = (Number.isFinite(baseAvail) ? baseAvail : 0) + need; + const safeBaseAvail = Number.isFinite(baseAvail) ? baseAvail : 0; + const totalRaw = Number((after as any)?.totalQty ?? 0); + const total = Number.isFinite(totalRaw) ? totalRaw : undefined; const frozenRaw = Number((after as any)?.frozenQty ?? 0); const frozen = Number.isFinite(frozenRaw) ? frozenRaw : 0; - const reduceFrozen = Math.min(frozen, need); - const nextFrozen = reduceFrozen > 0 ? Math.max(0, frozen - reduceFrozen) : undefined; + + const currentUsedRaw = Number((after as any)?.usedQty); + const baseUsed = Number.isFinite(currentUsedRaw) ? currentUsedRaw : afterUsed; + const safeBaseUsed = Number.isFinite(baseUsed) ? baseUsed : 0; + let nextUsed = safeBaseUsed - needUsed; + if (nextUsed < 0) nextUsed = 0; + + const maxAvail = typeof total === 'number' ? Math.max(0, total - frozen - nextUsed) : undefined; + + let nextAvail = safeBaseAvail + needAvail; + if (typeof maxAvail === 'number' && Number.isFinite(maxAvail) && nextAvail > maxAvail) nextAvail = maxAvail; + if (nextAvail < 0) nextAvail = 0; await updateGltUserTicket({ ...after, availableQty: nextAvail, - ...(nextFrozen !== undefined ? { frozenQty: nextFrozen } : {}) + usedQty: nextUsed }); + + Taro.setStorageSync(rollbackKey, Date.now()); }; // Allow users to modify/cancel before delivery starts (e.g. 待派单 / 待配送). @@ -391,6 +437,7 @@ const UserTicketList = () => { Taro.showToast({ title: '仅配送未开始的订单可取消', icon: 'none' }); return; } + if (orderCancelLoadingById[order.id]) return; const modal = await Taro.showModal({ title: '取消订单', @@ -400,6 +447,7 @@ const UserTicketList = () => { if (!modal.confirm) return; try { + setOrderCancelLoadingById((prev) => ({ ...prev, [order.id as number]: true })); Taro.showLoading({ title: '取消中...' }); let beforeTicket: GltUserTicket | null = null; if (order.userTicketId) { @@ -427,6 +475,7 @@ const UserTicketList = () => { Taro.showToast({ title: '取消失败,请重试', icon: 'none' }); } finally { Taro.hideLoading(); + setOrderCancelLoadingById((prev) => ({ ...prev, [order.id as number]: false })); } }; @@ -703,7 +752,10 @@ const UserTicketList = () => {