Vue弹窗先控状态
一次 Vue 后台页面出现了很烦人的重复弹窗。用户点击“提交审核”,弹窗提示正在提交;网络慢了一点,用户切换路由再回来,弹窗又弹了一次;提交成功后,旧弹窗没有完全关闭,页面状态却已经刷新。最后代码里到处是 visible = false 和 loading = false,每个分支都像是在补洞。
弹窗问题经常被当成 UI 小问题,实际上它是状态设计问题。一个弹窗不只是显示或隐藏,它还承载业务动作、异步请求、用户确认、失败恢复、路由关系和权限判断。如果所有状态都压成一个布尔值,复杂一点的场景就会失控。
visible 只表示显示,不表示业务状态
很多 Vue 组件把弹窗写成 v-model:visible,这没错,但 visible 只能说明弹窗是否显示。它不能说明用户是否已经确认,提交是否正在进行,服务端是否已经处理,失败是否可重试。把这些含义都塞进 visible,后面一定会乱。
更稳的做法是拆出状态。比如 idle、editing、submitting、success、failed、locked。弹窗显示只是这些状态的表现之一。进入 submitting 后,关闭按钮是否可点、确认按钮是否禁用、路由切换是否拦截,都由业务状态决定,而不是到处判断 loading。
Vue 的响应式让状态更新很方便,也容易让状态散落。父组件控制 visible,子组件控制表单,store 里还有提交结果,路由守卫又改一次。弹窗越复杂,越需要一个明确状态源。否则你很难解释当前弹窗为什么开着。
异步提交要区分失败类型
提交失败不一定都能重新点。参数错误应该让用户修改;权限失效要引导登录或刷新权限;网络超时要进入结果确认;服务端处理中可能要轮询状态。所有失败都恢复成“可提交”,会导致重复请求和错误提示混乱。
建议把提交函数写成状态转换,而不是一串 try/catch 里的赋值。请求发出前进入 submitting;明确失败进入 failed;结果未知进入 pendingConfirm;成功进入 success。每个状态对应 UI 可执行动作。这样弹窗不会因为某个 catch 分支忘记赋值而停在奇怪状态。
如果弹窗提交会改变服务端数据,最好加幂等键或业务单号。用户重复点击、页面重开、网络重试都可能带来重复提交。前端能做的是稳定传递同一个操作标识,后端需要用它去重。
路由切换要决定弹窗是否跟随
后台页面里常见一种情况:弹窗打开时用户切到别的菜单。这个弹窗应该关闭、保留、阻止跳转,还是变成全局任务?不同业务答案不一样。没有提前决定,就会出现幽灵弹窗。
低风险查看型弹窗,路由切换直接关闭即可。编辑型弹窗,如果有未保存内容,要提示用户确认离开。提交中的弹窗,如果请求不能取消,要让状态进入后台任务或结果确认。涉及支付、审批、删除的弹窗,更不能简单随着组件销毁而丢状态。
这也影响组件放在哪里。纯展示弹窗可以放页面组件内;跨路由任务弹窗可能要放到布局层或全局 store;需要恢复的弹窗要把关键参数写入 URL 或本地状态。组件位置不是审美问题,是状态生命周期问题。
关闭动作也要分语义
用户点叉、点取消、点蒙层、按 Esc、浏览器返回、提交成功自动关闭,这些都叫关闭,但语义完全不同。点叉可能是放弃编辑,提交成功关闭是流程完成,浏览器返回可能是离开页面。它们不应该都调用同一个 close()。
可以把关闭动作拆成 cancel、dismiss、complete、leave、forceClose。每个动作决定是否清理表单、是否保留草稿、是否通知父组件、是否打点。这样后续加需求时,不会在一个 close 函数里堆满 if。
弹窗验收要测中断场景
弹窗测试不要只测打开和提交成功。要测网络慢、提交失败、重复点击、路由切换、返回键、权限过期、组件卸载、数据刷新。尤其是提交中切走再回来,最容易暴露状态归属问题。
还有一个实用检查:刷新页面后,弹窗是否应该恢复?如果不应该,相关状态有没有清理?如果应该,恢复所需参数是否足够?这能逼你把弹窗状态和业务状态分清楚。
最后的判断标准:如果一个弹窗只能靠 visible 和 loading 解释,它还不够稳。复杂业务弹窗应该有清楚的状态图、关闭语义和异常路径。这样它才不是页面上的临时浮层,而是业务流程的一部分。
组件库弹窗也不会替你解决业务状态。无论使用 Element、Ant Design Vue 还是自研组件,组件只负责展示和交互事件,状态归属仍然要由业务层定义。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
如果弹窗里包含表单,表单校验状态也要和弹窗生命周期分开。关闭弹窗是否清空校验,重新打开是否恢复草稿,都应该有明确规则,而不是靠组件默认行为。
