Files
KCGL/inventory-web/src/components/PendingTransferNotifier/index.vue

195 lines
6.7 KiB
Vue
Raw Normal View History

feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
<template>
<!-- 无渲染组件:只负责全局待办强提醒,不产生任何 DOM -->
</template>
<script setup lang="ts">
/**
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
* 借库转交 · 全局强提醒(双向)
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
*
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
* 两类提醒,一次轮询同时取回(GET .../transfer/pending-count):
*
* ① 转交被拒绝 —— **我发起**、对方拒收。物品责任仍在我手上,
* 不告知就会误以为已经交接出去,责任链出现静默断点。
* 用 reject_seen_at 做持久标记,确认后不再提醒。
* ② 待我接收 —— **别人转给我**、等我确认。用 sessionStorage 按数量去重。
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
*
* ★ 为什么用 ElMessageBox 而不是 ElNotification:
* Notification 缩在右上角,在仓库作业现场极易被忽略。改为**中央 Modal +
* 遮罩**,强制打断注意力,符合蓝领作业现场的触达要求。
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
*
* ★ 为什么挂在 Layout 而非登录页:
* Layout 只在已登录区域渲染,天然保证「有 token 才查」;
* 且它是路由切换时**常驻**的组件(AppMain 换页不会卸载它),
* 轮询定时器不会被反复创建销毁。
*
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
* ★ 防叠加(模块级标志 + DOM 探测):见 isReminderOnScreen。
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
*/
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
import { onMounted, onUnmounted } from 'vue'
import { useRouter } from 'vue-router'
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
import { ElMessageBox } from 'element-plus'
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
import { getPendingTransferCount, ackTransferRejects } from '@/api/transaction'
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
import { useUserStore } from '@/stores/user'
const router = useRouter()
const userStore = useUserStore()
const SS_KEY = 'pendingTransferNotifiedCount'
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
const REMINDER_TITLES = ['待办通知:借库转交', '转交被拒绝']
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
// 轮询间隔:需求只要求「初始化时查一次」,但那样已打开的页面永远收不到提醒
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
// (SPA 只在首次进入时初始化)。加一轮低频轮询才能真正做到「第一时间响应」;
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
// 有去重逻辑兜底,轮询不会造成重复打扰。不需要的话删掉定时器即可。
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
const POLL_MS = 2 * 60 * 1000
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
// ★ 模块级而非组件级:即便组件被重复挂载(HMR、多 Layout 实例),
// 也能保证同一时刻只有一个提醒弹窗。
let reminderOpen = false
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
let timer: ReturnType<typeof setInterval> | null = null
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
// 按标题探测屏幕上是否已有本组件的弹窗(覆盖模块标志失效的极端情况,
// 例如 HMR 后旧实例残留的弹窗)
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
const isReminderOnScreen = (): boolean => {
if (reminderOpen) return true
try {
return Array.from(document.querySelectorAll('.el-message-box__title'))
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
.some(el => REMINDER_TITLES.some(t => (el.textContent || '').includes(t)))
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
} catch {
return false
}
}
/**
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
* 弹出「转交被拒绝」提醒。
* @returns 是否真的弹出了
*/
const showRejectNotice = (rejects: any[]): boolean => {
if (isReminderOnScreen()) return false
reminderOpen = true
const shown = rejects.slice(0, 5)
const lines = shown.map(r =>
`· ${r.material_name || r.sku || '物品'}(${r.borrow_no})—— 接收人:${r.to_user_name || '—'}`
)
const more = rejects.length > shown.length ? `\n…另有 ${rejects.length - shown.length} 笔` : ''
ElMessageBox.confirm(
`以下转交已被对方拒绝,物品仍在您名下,请另行安排:\n\n${lines.join('\n')}${more}`,
'转交被拒绝',
{
confirmButtonText: '去处理',
cancelButtonText: '知道了',
type: 'warning',
closeOnClickModal: false,
closeOnPressEscape: true,
showClose: true,
}
)
.then(() => {
router.push('/operation/records')
})
.catch(() => {
// 【知道了】/ 关闭:仅收起弹窗
})
.finally(async () => {
reminderOpen = false
// ★ 两条路径都算「已知悉」:否则每次登录都会再弹同一条。
// 若 ack 失败,下一轮轮询还会再提醒 —— 宁可多提醒一次,也不能漏。
try {
await ackTransferRejects(rejects.map((r: any) => r.id))
} catch {
// 静默
}
})
return true
}
/**
* 弹出「待我接收」提醒。
* @returns 是否真的弹出了 —— 调用方据此决定要不要记 sessionStorage,
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
* 避免「记了却没弹」把提醒永久吞掉。
*/
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
const showPendingReminder = (count: number): boolean => {
if (isReminderOnScreen()) return false
reminderOpen = true
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
ElMessageBox.confirm(
`您有 ${count} 件物品等待接收确认,请及时处理。`,
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
'待办通知:借库转交',
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
{
confirmButtonText: '去处理',
cancelButtonText: '稍后处理',
type: 'warning',
// 强提醒:点遮罩不关(避免误触即消失),但保留右上角关闭按钮与 Esc,
// 用户始终有明确的退出路径。
closeOnClickModal: false,
closeOnPressEscape: true,
showClose: true,
distinguishCancelAndClose: false,
}
)
.then(() => {
router.push('/operation/records') // 【去处理】
})
.catch(() => {
// 【稍后处理】/ 关闭:仅收起弹窗,不阻断用户当前工作
})
.finally(() => {
reminderOpen = false
})
return true
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
}
const check = async () => {
if (!userStore.token) return
let count = 0
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
let rejects: any[] = []
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
try {
const res: any = await getPendingTransferCount()
count = Number(res?.count ?? res?.data?.count ?? 0)
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
rejects = Array.isArray(res?.rejects)
? res.rejects
: (Array.isArray(res?.data?.rejects) ? res.data.rejects : [])
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
} catch {
return // 静默:不打扰用户,也不刷屏报错
}
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
// ★ 拒绝提醒优先于待接收提醒:前者是「责任已回到你手上」的状态变更,
// 后者是「等你确认」的待办;而且一次只弹一个弹窗,避免叠加打扰。
// 本次弹了拒绝提醒就直接返回,待接收的那条留给下一轮(此时拒绝已 ack)。
if (rejects.length && showRejectNotice(rejects)) return
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
if (!count) {
// 已处理完:清掉记录,下次再来新转交能重新提醒
sessionStorage.removeItem(SS_KEY)
return
}
const notified = Number(sessionStorage.getItem(SS_KEY) || 0)
if (count === notified) return // 数量没变 → 本会话已提醒过,不再轰炸
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
// ★ 先弹,弹成功了才记账。反过来会把提醒永久吞掉。
feat(borrow): 全局提醒支持双向 —— 发起方也能收到「转交被拒绝」 提醒组件从单向(待我接收)扩展为双向,一次轮询同时取回两类: ① 转交被拒绝 —— 我发起、对方拒收,物品责任仍在我手上 ② 待我接收 —— 别人转给我、等我确认 弹窗(沿用中央 Modal + 遮罩,不进则已、进则打断): 标题「转交被拒绝」,列出被拒物品(物料名 + 单号 + 接收人,超过 5 笔折叠计数), 按钮【去处理】(跳借还记录) /【知道了】。 ★ 优先级:拒绝提醒优先于待接收提醒,且**一次只弹一个弹窗**。 前者是「责任已回到你手上」的状态变更,后者是「等你确认」的待办; 本次弹了拒绝就直接 return,待接收那条留给下一轮(此时拒绝已 ack), 避免两个 Modal 叠加打扰。 ★ 两条退出路径都算「已知悉」并 ack —— 否则每次登录都会再弹同一条, 从提醒退化成骚扰。ack 失败不阻断,下一轮还会再提醒(宁可多提醒一次, 也不能漏)。 防叠加沿用上一轮的模块标志 + DOM 探测,标题白名单扩为两个。 顺带:流转明细时间线为转交节点补状态标签(已拒绝/待接收),被拒的转交 不再与成功的长得一模一样。 验证(node 复刻判定链) 既有拒绝、又有 2 件待接收 → 弹[转交被拒绝];确认后 ack; 下一轮 → 弹[待办通知] count=2;再轮询 → 不弹(未变)。 拒绝优先、不叠加、ack 后待接收提醒正常补上。
2026-09-17 10:44:07 +08:00
if (showPendingReminder(count)) {
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
sessionStorage.setItem(SS_KEY, String(count))
}
// 没弹出(已有弹窗在屏)时不记账:留给下一轮轮询重试
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
}
onMounted(() => {
check()
timer = setInterval(check, POLL_MS)
})
onUnmounted(() => {
if (timer) clearInterval(timer)
timer = null
style(borrow): 待办提醒改为中央强弹窗(Modal + 遮罩) 背景 ---- 右上角 ElNotification 在仓库作业现场视觉提示太弱,极易被操作员忽略, 待接收的物品就一直在系统里悬着。 改动 ---- ElNotification -> ElMessageBox.confirm: · 屏幕正中 + 灰色半透明遮罩,强制打断注意力; · 标题「待办通知:借库转交」,内容「您有 X 件物品等待接收确认,请及时处理。」; · 按钮【去处理】/【稍后处理】;前者 router.push('/operation/records'), 后者仅收起弹窗,不阻断当前工作; · closeOnClickModal=false(点遮罩不关,避免误触即消失), 但保留 showClose 与 Esc —— 强提醒不等于关不掉,用户始终有明确退出路径。 ★ 防叠加(两道,模块级标志 + DOM 探测) reminderOpen 用**模块级**变量而非组件级:即便组件被重复挂载 (HMR、多 Layout 实例)也能保证同一时刻只有一个提醒弹窗。 另按标题探测屏幕上是否已有同名弹窗,覆盖标志失效的极端情况。 ★ 一处必须修正的时序隐患:记录时机 原逻辑是「先写 sessionStorage 已提醒数量,再弹窗」。加了并发防护后, 弹窗可能被跳过(已有弹窗在屏),而数量却已记下 —— 数量不变 → 下次不再弹, **这条提醒被永久吞掉**。 现改为「先弹,弹成功了才记账」:showReminder() 返回是否真的弹出, 没弹出就不记账,留给下一轮轮询重试。 验证(node 复刻判定链,全场景通过) 3 → 弹;仍 3 → 不弹;弹窗开着时变 4 → 不弹且不记账 → 收起后轮询 4 → 弹;归零 → 清记录;新来 2 → 弹。 弹窗序列 [3,4,2],无重复、无叠加;同一轮内并发两次 check 只弹一次。
2026-09-17 10:40:00 +08:00
// 组件卸载时若弹窗还开着,一并收掉,避免遮罩残留挡住界面
if (reminderOpen) {
try { ElMessageBox.close() } catch { /* 忽略 */ }
reminderOpen = false
}
feat(borrow): 全局待办强提醒(接收人不再处于盲区) 新增无渲染组件 PendingTransferNotifier,挂在 Layout(路由切换常驻、且只在 已登录区域渲染,天然保证「有 token 才查」)。 UI ---- ElNotification,type=warning、duration=0(不自动关闭,须用户处理或手动点掉): 标题:待办通知:借库转交 内容:您有 X 件物品等待接收确认,请及时处理。【去处理 >】 点击【去处理】关闭通知并 router.push('/operation/records')(借还记录页)。 message 用 VNode 构造而非 dangerouslyUseHTMLString —— 不必把数量拼进 HTML 字符串。 防骚扰(两道) ---- · 会话级去重:sessionStorage 记「上次已提醒过的数量」,只有数量**变化**才再弹。 用 sessionStorage 而非 Pinia —— 前者跨刷新存活,后者会重置,刷新即轰炸。 · 数量归零时清掉记录,下次新转交能重新提醒。 ★ 加了 2 分钟低频轮询(超出需求所写,但需求目标需要它): 需求只要求「初始化时查一次」,而 SPA 只在首次进入时初始化 —— 已打开页面的 用户永远收不到提醒,与「第一时间响应」的目标相悖。有去重逻辑兜底, 轮询不会造成重复打扰。不需要的话删掉定时器即可。 错误一律静默:提醒是锦上添花,不能因接口抖动弹错误框刷屏。 验证:去重状态机用 node 复刻验证 —— 3→2→1 各弹一次,刷新与轮询均不重复, 归零后再来新转交能重新提醒。
2026-09-17 10:30:13 +08:00
})
</script>