Vue登录后添加动态路由并跳转的实践分享

 更新时间:2023年07月19日 10:01:48   作者:前端学习笔记_zxh  
这篇文章讲给大家详细介绍一下Vue如何实现登录后添加动态路由并跳转,文章通过代码示例介绍的非常详细,对我们的学习或工作有一定的的帮助,需要的朋友可以参考下

前言

项目框架:Vue3 + TypeScript

有这样一个需求,系统默认只有最基础的几个路由,如登录、404等,其它路由需要在登录后动态添加。系统没有固定首页,登录完成后跳转至动态菜单的第一个菜单页。

分析

这一逻辑乍一看很简单,其实有很多小坑在里面。其中最容易踩的的坑是动态路由尚未渲染完成就已经触发路由跳转了,这时候肯定是404,因为路由并不存在;另一个容易踩的坑是路由重复加载,此时页面会显示空白,需要手动刷新才能正常显示。

首先想到的就是使用 Promise 函数解决,结果行不通。addRoute 是一个宏任务 和 resolve 是微任务,所以 Promise 结束的时候并不能代表动态路由已经添加完成。

其次又想到使用 async 函数来确保获取到登录成功结果的时候,路由已经添加完成,结果一番尝试后依然行不通。因为添加路由的操作不是异步的,没有返回 Promise 对象,因此这里的 await 是不会产生效果的。(PS:事后使用 Promise.all 解决了这一问题,后面的具体方法上会说。)

最后,想到了一个很笨的解决方法,轮询。实验过后,确定可以实现,但就如开头说的,这会显得很 low ,虽然它最终解决了问题。

实践

登录的操作都是一样的,所以单独拿出来只写一遍。表单就不做介绍了,就从点击登录表单校验通过后说起。

所有登录的代码放到一个页面会显得臃肿,所以具体登录的操作逻辑我把它抽离了出来。在 src/utils 目录下创建一个 auth.ts 文件。

auth.ts

import { useRouteListStore } from '@/store/router'
const routeListStore = useRouteListStore()
// 登录
export async function Login(data: { username: string; password: string; portal: string; corpCode: string }) {
  const { username, password, portal, corpCode } = data
  try {
    // 登录接口
    const res = await getLogin({ username, password, portal, corpCode })
    // ...
    // 这里写保存用户信息及 token 的逻辑
    // ...
    // 添加路由操作,写在 pinia 中,后面会说
    await routeListStore.updateRouteList()
    return res
  } catch (err) {
    return err
  }
}

接下来要写添加路由的具体逻辑。在 src/store 目录下创建一个 router.ts 文件,添加内容如下:(PS:具体文件路径要结合具体的项目内容,以下路径及菜单格式仅作为示例)。

根据处理方式不同,有两种方案。

方案一:使用 async 函数

src/store/router.ts

export const useRouteListStore = defineStore('routeList', {
  state: () => ({
    routeList: [],
    breadcrumb: [],
    getRouter: true // 是否需要重新加载路由
  }),
  actions: {
    // 更新菜单并追加路由
    async updateRouteList() {
      const modules = import.meta.glob('../views/**/*.vue')
      // 此为接口请求获取的菜单
      const list = await getMenus()
      list.forEach((e) => {
        e.route = e.path
        e.component = () => import('@/layout/index.vue')
        e.redirect = `${e.path}/${e.children[0].path}`
        e.children.forEach((item) => {
          item.route = `${e.path}/${item.path}`
          item.component = modules[`../views${item.component}.vue`]
        })
      })
      await addRouteList(list)
      this.getRouter = false
      this.routeList = list
      return true
    },
  }
})

接下来写动态添加路由的逻辑,使用 Promise.all 来确保 Pinia 中返回结果时,动态路由已经加载完成。在 src/router 创建 index.ts 文件,添加内容如下:

src/router/index.ts

export function addRouteList(data: any[] = []) {
  return new Promise((resolve) => {
    const promises = []
    data.forEach((e) => promises.push(router.addRoute(e)))
    Promise.all(promises).then(() => resolve(true))
  })
}

使用 async 函数之后,登录页的操作将会变得很简单。

login.vue

import { Login } from '@/utils/auth'
const onSubmit = () => {
  validate().then(() => {
    Login(formState).then(() => {
      router.push(routerStore.routeList[0].path)
    }).catch(err => {
      message.error(err.message)
    })
  })
}

方案二:使用轮询

轮询的方案相比于使用 async 函数要简单很多,因为它不需要确保登录后拿到结果的那一刻,路由是加载完成的。具体实现代码如下:

src/store/router.ts

export const useRouteListStore = defineStore('routeList', {
  state: () => ({
    routeList: [],
    breadcrumb: [],
    getRouter: true
  }),
  actions: {
    // 更新菜单并追加路由
    updateRouteList() {
      listMenus().then((res) => {
        const list = res.data
        if (list === null) {
          this.getRouter = false
          router.push('/404')
          return
        }
        list.forEach((e) => {
          e.route = e.path
          e.component = () => import('@/layout/index.vue')
          e.children.forEach((item) => {
            item.route = `${e.path}/${item.path}`
            item.component = modules[`../views${item.component}.vue`]
          })
        })
        addRouteList(list)
        this.getRouter = false
        this.routeList = list
      })
  }
})

src/router/index.ts

export function addRouteList(data: any[] = []) {
  data.forEach((e) => {
    router.addRoute(e)
  })
}

轮询的好处是逻辑简单,唯一麻烦的一点就是在登录后添加一个定时器去定期获取路由是否加载完成。之所以要加定时器是因为获取菜单是异步请求,而程序执行时很快的,所以要确保执行路由跳转命令时菜单是加载完成的。

login.vue

import { ref, onBeforeUnmount } from 'vue'
import { useRouter } from 'vue-router'
import { useRouteListStore } from '@/store/router'
const routerStore = useRouteListStore()
import { Login } from '@/utils/auth'
const router = useRouter()
// 每0.5s判断一次菜单是否加载完成,最多判断30次,超过则说明网络环境极差
const timer = ref(null)
const onSubmit = () => {
  validate().then(() => {
    Login(formState).then(() => {
    let i = 0
      timer.value = setInterval(() => {
        if (routerStore.routeList[0].path) {
          router.push(routerStore.routeList[0].path)
        }
        i++
        if (i > 30) {
          clearInterval(timer.value)
          timer.value = null
          i = null
          message.error('当前网络环境较差!')
          spinning.value = false
        }
      }, 500)
    })
  })
}
// 不要忘记清除定时器
onBeforeUnmount(() => {
  clearInterval(timer.value)
  timer.value = null
})

补充

以上代码只能保证系统初次登录后可以正常跳转页面,如果退出当前账号,重新登录或者更换账号登录,会出现路由重复加载的问题,也就是文章开头所说的另一个容易踩的坑。这个坑解决起来并不困难,只要注意到了,很容易就可以解决。

解决思路是添加路由前置守卫,同时在 Pinia 中添加一个字段判断当前路由是否需要重新加载即可。具体代码如下:

import Cookies from 'js-cookie'
import { useRouteListStore } from '@/store/router'
// 前置守卫
router.beforeEach(async (to, from, next) => {
  const token = Cookies.get('token')
  if (!token) {
    next({ path: '/login' })
  } else {
    const routerStore = useRouteListStore()
    routerStore.addBreadcrumb(to)
    // 判断菜单是否存在且是否需要重新加载
    if (routerStore.routeList.length === 0 && routerStore.getRouter) {
      await routerStore.updateRouteList()
      next({ path: to.path, query: to.query })
    } else {
      next()
    }
  }
})

如对本文有疑问或不同看法,欢迎在评论区指出。

以上就是Vue登录后添加动态路由并跳转的实践分享的详细内容,更多关于Vue添加动态路由并跳转的资料请关注脚本之家其它相关文章!

相关文章

  • vue3常用响应式对象的api,你全用过了吗

    vue3常用响应式对象的api,你全用过了吗

    这篇文章主要给大家介绍了关于vue3常用响应式对象api的相关资料,文中通过实例代码介绍的非常详细,对大家学习或者使用vue3具有一定的参考学习价值,需要的朋友可以参考下
    2023-02-02
  • Vue中关于computed计算属性的妙用

    Vue中关于computed计算属性的妙用

    这篇文章主要介绍了Vue中关于computed计算属性的妙用,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-11-11
  • vue基础之事件v-onclick=

    vue基础之事件v-onclick="函数"用法示例

    这篇文章主要介绍了vue基础之事件v-onclick="函数"用法,结合实例形式分析了vue.js事件v-on:click="函数"的data数据添加、点击响应、以及留言本功能相关操作技巧,需要的朋友可以参考下
    2019-03-03
  • Vue中Vue-Baidu-Map基本使用方法实例

    Vue中Vue-Baidu-Map基本使用方法实例

    最近有一个项目需要用到地图来展示位置并进行数据交互,用vue-baidu-map实现出来,下面这篇文章主要给大家介绍了关于Vue中Vue-Baidu-Map基本使用的相关资料,需要的朋友可以参考下
    2023-03-03
  • 解决el-tree节点过滤不显示下级的问题

    解决el-tree节点过滤不显示下级的问题

    这篇文章主要介绍了解决el-tree节点过滤不显示下级的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • Vuejs 用$emit与$on来进行兄弟组件之间的数据传输通信

    Vuejs 用$emit与$on来进行兄弟组件之间的数据传输通信

    本篇文章主要介绍了Vuejs 用$emit 与 $on 来进行兄弟组件之间的数据传输示例,非常具有实用价值,需要的朋友可以参考下。
    2017-02-02
  • 在vs code 中如何创建一个自己的 Vue 模板代码

    在vs code 中如何创建一个自己的 Vue 模板代码

    这篇文章主要介绍了在vs code 中如何创建一个自己的 Vue 模板代码,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • Vue+Openlayers自定义轨迹动画

    Vue+Openlayers自定义轨迹动画

    这篇文章主要为大家详细介绍了Vue+Openlayers自定义轨迹动画,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-09-09
  • vue做网页开场视频的实例代码

    vue做网页开场视频的实例代码

    这篇文章主要介绍了vue做网页开场视频的实例代码,需要的朋友可以参考下
    2017-10-10
  • VUEX 使用 mutations的两种方式

    VUEX 使用 mutations的两种方式

    这篇文章主要介绍了VUEX 使用 mutations的两种方式,实现方式就是利用vuex中的mutations,在mutations中定义一个方法,这个方法就是把点击的index(也就是每个列表的唯一标识),传给state中的当前标识,需要的朋友可以参考下
    2023-01-01

最新评论