You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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();
  }
});

修复方案及代码

问题根源:

  1. 手动调用window.history.pushState导致URL提前更新,破坏路由跳转的正常流程
  2. leaveConfirmation中仅在取消时重置状态,确认离开时未重置,导致重复点击导航时复用旧Promise
  3. 未处理页面刷新(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);
  });
});

关键修改说明

  1. 移除window.history.pushState:路由跳转由next()统一处理,手动修改URL会导致浏览器后退时URL提前变化,破坏预期流程
  2. 完善状态重置逻辑:在onOk和afterClose中都重置modalVisible和navigationResolve,避免重复点击导航时出现异常弹窗
  3. 添加beforeunload事件监听:处理页面刷新、浏览器关闭的场景,确保所有离开操作都触发确认(注:浏览器限制无法自定义此场景的弹窗样式,只能使用默认弹窗)
  4. 添加isConfirmedLeave标记:避免用户确认离开后,beforeunload事件重复拦截
  5. 简化路由拦截逻辑:合并重复的判断和弹窗调用,提升代码可读性

内容的提问来源于stack exchange,提问作者zamil

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 11:30:18