From ce767e39ac141c54b85554624f5c106bab514ed1 Mon Sep 17 00:00:00 2001 From: yueli Date: Fri, 11 Sep 2026 09:06:51 +0800 Subject: [PATCH] =?UTF-8?q?feat(scan):=20=E6=89=AB=E7=A0=81=E6=97=B6?= =?UTF-8?q?=E6=A3=80=E6=B5=8B=E5=8D=95=E6=8D=AE=E5=A4=B1=E6=95=88=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E7=99=BD=E6=89=AB=E4=B8=80=E5=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 场景 ---- 库管正在扫码,这张单被撤回/驳回/执行了。若不检测,库管会一直扫到 提交时才发现单据已作废(后端会拒绝),前面的工作白做。 实现 ---- 新增 checkRequestStillValid():重新拉一次「已通过(status=1)」列表, 看当前单据是否还在其中。不在 → 已被撤回/驳回/执行,立即弹窗告知并 清空界面(草稿后端也已同步清除)。复用现有列表接口,无需新增端点。 触发时机(覆盖两类场景): · 页面重新可见时(visibilitychange)—— PDA 熄屏唤醒、切回标签页 · 每 60 秒一次 —— 库管一直在页面上扫、没切走 三点取舍: · 仅购物车非空时检测(正在作业才打扰),空清单不发请求; · 60 秒轮询而非实时推送 —— 引入 WebSocket 长连接成本过高, 此处最多浪费 1 分钟扫码,相比"扫完几十分钟才发现"已足够; · 检测失败不阻断作业(仅 console.warn)—— 后端提交时仍会做最终校验, 前端检测是为了早发现,不是安全防线。 出库 create.vue 与借库 borrow.vue 两页同款实现。 --- inventory-web/src/views/outbound/create.vue | 62 ++++++++++++++++++- .../src/views/transaction/borrow.vue | 53 +++++++++++++++- 2 files changed, 112 insertions(+), 3 deletions(-) diff --git a/inventory-web/src/views/outbound/create.vue b/inventory-web/src/views/outbound/create.vue index b690bdf..e7ec7c4 100644 --- a/inventory-web/src/views/outbound/create.vue +++ b/inventory-web/src/views/outbound/create.vue @@ -1133,6 +1133,7 @@ const handleSignCancel = () => { showSignatureDialog.value = false } onUnmounted(() => { if (signaturePreviewUrl.value) URL.revokeObjectURL(signaturePreviewUrl.value) if (draftTimer) clearTimeout(draftTimer) + // 其余清理(定时检测、事件监听)见文件末尾的统一 onUnmounted }) // ★ 离开页面前:立即存草稿(防抖中未落盘的内容会丢) @@ -1181,8 +1182,65 @@ const handleBeforeUnload = () => { } catch (e) { /* 尽力而为,失败不影响 */ } } -onMounted(() => { window.addEventListener('beforeunload', handleBeforeUnload) }) -onUnmounted(() => { window.removeEventListener('beforeunload', handleBeforeUnload) }) +// ============================================================================ +// ★ 单据失效检测 +// +// 场景:库管正在扫码,管理员/申请人把这张单撤回了(或已执行完)。 +// 若不检测,库管会一直扫到提交时才发现单据已作废 —— 白扫一场。 +// +// 检测方式:重新拉一次「已通过(status=1)」列表,看当前单据是否还在其中。 +// 不在 → 说明已被撤回/驳回/执行,立即告知并清空界面。 +// 复用现有列表接口,无需新增端点。 +// +// 触发时机: +// · 页面重新可见时(PDA 熄屏唤醒、切回浏览器) +// · 每 60 秒一次(仅在购物车非空、即正在作业时) +// ============================================================================ +const checkRequestStillValid = async () => { + const rid = activeRequestId.value + if (!rid || cartItems.value.length === 0) return // 没在作业,不打扰 + + try { + const res: any = await getApprovalRequestList({ status: 1, page: 1, pageSize: 100 }) + const stillValid = (res.data?.items || []).some((r: any) => r.id === rid) + if (stillValid) return + + // 单据已不在「已通过」列表中 → 失效 + const no = selectedRequest.value?.request_no || rid + cartItems.value = [] + selectedRequest.value = null + activeRequestId.value = null + ElMessageBox.alert( + `申请单【${no}】已被撤回或已执行,不能再继续出库。\n\n` + + `本次已扫的内容已清空(草稿也已随之清除)。\n` + + `如需出库,请重新提交申请。`, + '⚠️ 申请单已失效', + { type: 'warning', confirmButtonText: '知道了' } + ).catch(() => {}) + } catch (e) { + // 网络异常时不打断作业 —— 后端在提交时仍会做最终校验 + console.warn('单据状态检测失败', e) + } +} + +// 页面重新可见时检测(PDA 熄屏唤醒、切回标签页) +const onVisibilityChange = () => { + if (document.visibilityState === 'visible') checkRequestStillValid() +} + +let validCheckTimer: ReturnType | null = null + +onMounted(() => { + window.addEventListener('beforeunload', handleBeforeUnload) + document.addEventListener('visibilitychange', onVisibilityChange) + validCheckTimer = setInterval(checkRequestStillValid, 60000) +}) + +onUnmounted(() => { + if (validCheckTimer) clearInterval(validCheckTimer) + document.removeEventListener('visibilitychange', onVisibilityChange) + window.removeEventListener('beforeunload', handleBeforeUnload) +})