小程序端全局弹窗优先级管理需手动实现调度逻辑,核心是按优先级(0-3级)队列择优显示、高优强制中断低优、页面卸载时清理自身弹窗,避免遮盖错乱与回调丢失。

小程序端的全局弹窗优先级管理,本质是解决「多个弹窗请求并发时谁先显示、谁该被拦截、谁必须强打断」的问题。uni-app 默认不提供这套机制,必须手动实现调度逻辑,否则极易出现遮盖错乱、点击失效、回调丢失等线上问题。
为什么小程序端必须自己管优先级
uni-app 在小程序环境里每个页面是独立 WebView,没有共享的根节点或全局事件总线;uni.showModal 这类原生 API 又不支持排队或取消,直接调用会覆盖前一个弹窗。更麻烦的是,WebSocket 推送、支付回调、登录态校验等场景常在任意页面触发弹窗,但用户可能正处在表单填写中——这时候高优先级弹窗(如“登录已过期”)必须打断低优先级(如“优惠券到账”),否则业务逻辑就断了。
用队列 + 优先级字段控制弹窗调度
核心不是“同时显示多个”,而是“按序、择优、可控地显示一个”。推荐用一个数组模拟队列,每个弹窗项带 priority 字段(数值越大越优先),再配合状态机控制当前是否允许新弹窗进入:
-
priority建议分级:0(普通提示)、1(操作确认)、2(登录中断)、3(支付失败强提醒) - 每次调用弹窗前,先
queue.push({ ...options, priority: 2 }),再执行scheduleNext() -
scheduleNext()从队列里取priority最高的项,且只在当前无弹窗或当前弹窗已关闭后才真正渲染 - 关闭弹窗时,自动触发
scheduleNext(),而不是简单shift()—— 避免高优弹窗被低优弹窗挤掉
示例逻辑片段:
const queue = []
let currentPopup = null
function showPopup(options) {
queue.push({ ...options, priority: options.priority || 0 })
scheduleNext()
}
function scheduleNext() {
if (currentPopup) return
if (queue.length === 0) return
// 取最高优先级项(相同优先级按插入顺序)
const next = queue.reduce((a, b) => a.priority >= b.priority ? a : b)
const index = queue.indexOf(next)
queue.splice(index, 1)
currentPopup = next
// 实际挂载组件或调用 uni.showModal
}
避免 onShow/onHide 生命周期干扰调度
小程序页面切换频繁触发 onShow,如果在里面调用弹窗,容易和队列逻辑冲突。务必做到:
- 所有弹窗入口统一走
showPopup()函数,禁止直接在生命周期里调uni.showToast或手动this.$refs.dialog.open() - 在
onShow中只做状态同步(如检查 token 是否过期),真正弹窗动作仍交由队列调度 - 页面卸载时(
onUnload)要清空该页面相关待触发弹窗(比如某页发起的 confirm 回调,页面已销毁就不该再 resolve)
高优弹窗强制中断低优弹窗的边界处理
当一个 priority=3 的弹窗到来时,不能简单丢弃正在显示的 priority=1 弹窗——它可能已唤起用户操作,直接销毁会导致 confirm 按钮点击无响应。正确做法是:
- 给当前弹窗组件暴露
interrupt()方法,内部执行this.visible = false并 reject 当前 promise - 高优弹窗入场前,先调用
currentPopup?.interrupt?.(),再清理队列中所有priority 的项 - 中断后需重置
currentPopup = null,否则scheduleNext()不会触发
这个细节漏掉,就会出现“登录过期弹窗打不开,因为前面那个优惠券弹窗卡着没关”的线上事故。
优先级不是加个数字就完事,关键在调度器对「中断」「降级」「丢弃」三种行为的明确区分,以及每个页面卸载时对自身弹窗承诺的清理责任——这些地方最容易被忽略,但恰恰决定线上稳定性。











