Nuxt3/Vue3页面离开前触发确认弹窗的正确实现求助
电商购买页离开确认功能修复方案
需求
- 电商商品购买页添加离开确认:用户进入页面后,无论是否操作,尝试离开时触发确认弹窗
- 弹窗包含「Stay(停留)」和「Leave(离开)」按钮,用户选择后执行对应操作
当前问题
- 点击页面内导航链接时弹窗正常触发,但点击浏览器后退按钮时,弹窗触发的同时URL已更新
- 刷新页面会直接跳转,无确认弹窗
- 重复点击导航按钮存在异常
预期效果
- 用户尝试离开页面时弹出确认弹窗,弹窗显示期间不修改URL
- 点击「Leave」则正常跳转/刷新;点击「Stay」则关闭弹窗,停留在当前页面
- 技术栈:Ant Design Modal、Pinia(userStore存储用户信息)
现有实现代码
const modalVisible = ref(false); let navigationResolve = null; const leaveConfirmation = (title, content) => { if (modalVisible.value) { return navigationResolve; } modalVisible.value = true; navigationResolve = new Promise((resolve) => { Modal.confirm({ title: title, content: content, okText: 'Leave', cancelText: 'Stay', // icon: h(ExclamationCircleOutlined), okButtonProps: { style: { backgroundColor: 'red', fontWeight: 'bold', color: 'white', }, }, cancelButtonProps: { style: { backgroundColor: '#50C878', fontWeight: 'bold', color: 'white', }, }, onOk() { resolve(true); }, onCancel() { resolve(false); modalVisible.value = false; navigationResolve = null; }, }); }); return navigationResolve; }; onBeforeRouteLeave((to, from, next) => { if (userStore?.user?.data) { if (isEditing.value) { leaveConfirmation('Do you want to leave?', 'Are you sure you want to leave? Your changes may not be saved.') .then((leave) => { if (leave) { next(); window.history.pushState({ modal: false }, '', `${to.path}`); Modal.destroyAll(); } else { next(false); } }); } else if (!isEditing.value && !handleOrder.value) { leaveConfirmation('Do you want to leave?', 'Are you sure you want to leave? Your order will be cancelled.') .then((leave) => { if (leave) { next(); window.history.pushState({ modal: false }, '', `${to.path}`); Modal.destroyAll(); } else { next(false); } }); } else { next(); } } else { next(); } });
修复方案及代码
问题根源:
- 手动调用
window.history.pushState导致URL提前更新,破坏路由跳转的正常流程 leaveConfirmation中仅在取消时重置状态,确认离开时未重置,导致重复点击导航时复用旧Promise- 未处理页面刷新(beforeunload事件)的场景
修改后的完整代码:
const modalVisible = ref(false); let navigationResolve = null; // 标记用户是否主动确认离开,避免beforeunload重复拦截 let isConfirmedLeave = false; const leaveConfirmation = (title, content) => { if (modalVisible.value) { return navigationResolve; } modalVisible.value = true; navigationResolve = new Promise((resolve) => { Modal.confirm({ title: title, content: content, okText: 'Leave', cancelText: 'Stay', okButtonProps: { style: { backgroundColor: 'red', fontWeight: 'bold', color: 'white', }, }, cancelButtonProps: { style: { backgroundColor: '#50C878', fontWeight: 'bold', color: 'white', }, }, onOk() { isConfirmedLeave = true; resolve(true); // 确认离开后重置状态 modalVisible.value = false; navigationResolve = null; }, onCancel() { resolve(false); modalVisible.value = false; navigationResolve = null; }, // 弹窗关闭时强制重置状态,避免异常情况 afterClose() { modalVisible.value = false; navigationResolve = null; } }); }); return navigationResolve; }; // 处理路由跳转离开的情况 onBeforeRouteLeave((to, from, next) => { if (!userStore?.user?.data) { next(); return; } // 无需拦截的场景直接放行 if (!isEditing.value && handleOrder.value) { next(); return; } const confirmTitle = 'Do you want to leave?'; const confirmContent = isEditing.value ? 'Are you sure you want to leave? Your changes may not be saved.' : 'Are you sure you want to leave? Your order will be cancelled.'; leaveConfirmation(confirmTitle, confirmContent).then((leave) => { if (leave) { next(); Modal.destroyAll(); } else { next(false); } }); }); // 处理页面刷新/关闭的情况 onMounted(() => { const handleBeforeUnload = (e) => { if (!userStore?.user?.data || isConfirmedLeave) { return; } // 无需拦截的场景直接放行 if (!isEditing.value && handleOrder.value) { return; } // 浏览器默认弹窗,无法自定义样式,但能拦截刷新/关闭 e.preventDefault(); e.returnValue = ''; }; window.addEventListener('beforeunload', handleBeforeUnload); onUnmounted(() => { window.removeEventListener('beforeunload', handleBeforeUnload); }); });
关键修改说明
- 移除
window.history.pushState:路由跳转由next()统一处理,手动修改URL会导致浏览器后退时URL提前变化,破坏预期流程 - 完善状态重置逻辑:在
onOk和afterClose中都重置modalVisible和navigationResolve,避免重复点击导航时出现异常弹窗 - 添加
beforeunload事件监听:处理页面刷新、浏览器关闭的场景,确保所有离开操作都触发确认(注:浏览器限制无法自定义此场景的弹窗样式,只能使用默认弹窗) - 添加
isConfirmedLeave标记:避免用户确认离开后,beforeunload事件重复拦截 - 简化路由拦截逻辑:合并重复的判断和弹窗调用,提升代码可读性
内容的提问来源于stack exchange,提问作者zamil
相关产品推荐
相关产品推荐

