返回

Vue 2.6程序化路由新标签页打开方案

vue.js

VueJS 2.6 程序化路由与新标签页打开问题

问题现象

使用 Vue Router 时,<router-link> 组件(在许多 UI 框架中以按钮或链接形式存在,例如 <v-btn to="...">)能够根据用户的点击操作,实现页面导航并在当前标签页打开或通过右键菜单在新标签页中打开。然而,当采用程序化路由方式 (this.$router.push(...))时,通常仅支持在当前标签页进行跳转,无法利用浏览器右键菜单的“在新标签页中打开”功能。这使得在某些需要提供多选项的场景中,交互体验有所下降。

问题原因

直接使用 this.$router.push(...) 进行跳转时,Vue Router 将其处理为 JavaScript 的导航事件,而没有与传统 <a> 标签类似的 HTML 上下文。浏览器在新标签页打开的操作,通常需要识别出 <a> 标签的 href 属性,才能在右键点击时弹出“在新标签页打开”的选项。程序化路由由于其本质是脚本行为,并未触发这种默认机制。

解决方案

方案一:利用 <a> 标签的默认行为

可以使用动态生成的 <a> 标签模拟用户点击,从而触发浏览器的新标签页打开逻辑。具体做法是在程序化路由时,先创建一个临时的 <a> 元素,设置其 href 属性为路由的链接地址,然后通过编程方式触发其 click 事件。最后,将临时标签移除。

<template>
  <button @click="goToRouteInNewTab">Go</button>
</template>

<script>
export default {
  methods: {
    goToRouteInNewTab() {
       const routeData = this.$router.resolve({ name: 'route-name' }); // 使用resolve解析name为完整的路由信息,防止潜在问题
        const href = routeData.href; // 获取路由的href值
      const link = document.createElement('a');
      link.href = href;
      link.target = '_blank';
      document.body.appendChild(link); // 将其加入DOM, 防止一些浏览器的优化操作不生效。
      link.click(); //触发点击事件, 新标签页跳转
      document.body.removeChild(link); // 移除临时 a 标签
    },
  },
};
</script>

步骤:

  1. 使用$router.resolve({ name: 'route-name' })解析路由名 : this.$router.resolve()用于解析路由信息,它可以处理多种路由配置,并且返回包含最终 href 属性在内的详细信息。
  2. 获取 href : 使用 .href 属性可以得到要跳转的完整URL地址。
  3. 创建 <a> 元素 : document.createElement('a') 创建一个临时的 <a> 元素,它不属于任何 Vue 组件的 DOM,而是浏览器原生 API 生成。
  4. 设置属性 : 为 <a> 元素设置 href 属性和 target="_blank",使链接在新的标签页中打开。target="_blank" 是控制是否打开新页面的关键属性,新标签打开必须设置。
  5. 添加到DOM并触发点击 :将临时 a 标签添加到文档流中, 防止浏览器优化不生效。 通过调用 .click() 触发点击,效果等同于用户点击了 <a href="...">
  6. 移除临时标签 : 跳转后立即将该标签移除,避免干扰页面渲染和减少内存占用。

这个方法有效模拟了浏览器在新标签页打开 <a> 链接的过程,能够满足在新标签页中打开的需求。

安全提示: 使用 document.createElement 时要注意安全。一般场景下我们信任 VueRouter 输出的 URL。 但是如果是外部 URL 一定要做好安全校验,防止 xss。

方案二:监听鼠标事件进行处理

对于需要区分左键点击 (当前标签页) 和右键点击 (新标签页) 的场景,可以在监听点击事件时,通过检查 event.button 来区分左键(值为 0) 和中/右键 (值为 1,2,3 具体取决于浏览器实现)。如果判定为非左键则调用 方案一 的方式打开。

<template>
  <button @click="handleRoute">Go</button>
</template>
<script>
export default {
  methods: {
      handleRoute(event){
          if(event.button == 0){
              this.$router.push({name: 'route-name'}) // 普通路由
          }else{
             this.goToRouteInNewTab(); // 方案一打开
          }

      },
     goToRouteInNewTab() {
         const routeData = this.$router.resolve({ name: 'route-name' });
        const href = routeData.href; // 获取路由的href值
      const link = document.createElement('a');
      link.href = href;
      link.target = '_blank';
        document.body.appendChild(link);
      link.click();
        document.body.removeChild(link);
     },
    },
};
</script>

步骤:

  1. handleRoute 函数中接受事件参数 : 传递点击事件 eventhandleRoute 方法
  2. 检查 event.button : 判断是左键(event.button === 0)还是中/右键。
  3. 区分跳转逻辑 : 左键使用 $router.push(...) 当前页面跳转,否则使用 方案一 实现新页面跳转。

特点: 可以区分左右键行为,能够更细致控制点击操作逻辑。但是由于使用了js监听行为,存在一定代码量开销。

总结

以上两种方案可以解决在 Vue 2.6 中程序化路由与新标签页打开的需求,开发者可以根据自身需要选择方案。方案一 模拟浏览器 a 标签跳转逻辑较为通用和简便,方案二 可以在特定场景下实现更细致的交互行为。