在项目实践中,往往需要用户登录后由后端返回用户的权限,来动态配置路由,vue-router提供了两个方法router.addRoutes(),router.addRoute()可以进行动态路由配置,这里需要注意的是vue-router的options属性存放的是路由配置的信息,并不是响应式的,在动态添加路由规则后,router.options.routes属性不会改变,如果需要更新router.options.routes,则需要手动更改。
下面的代码基于vue-router版本:3.5.1,版本的变化可能会存在一些不同,如使用已废弃的router.addRoutes方法会出现警告。
参考:https://router.vuejs.org/zh/api/#router-addroutes

router.addRoutes 和router.addRoute 新路由规则

  1. router.addRoutes 添加多条新路由规则

    router.addRoutes(routes: Array<RouteConfig>)
    

    动态添加更多的路由规则。参数必须是一个符合 routes 选项要求的数组

  2. router.addRoute 添加一条新路由规则
    如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它。

    addRoute(route: RouteConfig): () => void
    

    addRoute方法可用于为现有路由添加子路由规则

    addRoute(parentName: string, route: RouteConfig): () => void
    

在项目实践时,通常在用户登录后获取到用户的权限表,可以将这些数据保存到前端。
下面是一个简单的例子,忽略了很多细节,旨在演示通过路由进行权限控制的方法。

router.beforeEach()中进行全局前置守卫
如果路由地址在白名单中则放行(例外:如果路由是登录并且存在token,重定向到首页)
不在白名单,且无token,重定向到登录页
不在白名单,且有token,放行
  • 在登录时用setTimeout方法模拟异步获取动态路由和token,并保存到localStorage中,调用generateRoutes方法添加路由规则。

  • 在页面手动刷新时,配置的动态路由会消失需要在router.onReady()方法中,再次调用generateRoutes方法。

  • generateRoutes方法遍历动态路由数组,将成员转为RouteConfig,并调用addRoute方法。
    用vue cli搭建了这样一个组件结构
    在这里插入图片描述
    模板具体什么样式无所谓,这里只做动态路由的展示

  • router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
import Home from '../views/Home.vue'
import Layout from '../views/Layout.vue'
import { generateRoutes } from '../utils'

Vue.use(VueRouter)

const constantRoutes = [
  {
    path: '/',
    name: 'Home',
    component: Layout,
    redirect: '/index',
    children: [
      {
        path: 'index',
        component: Home
      },
      {
        path: 'about',
        name: 'About',
        component: () => import('../views/About.vue')
      }
    ]
  },
  {
    path: '/login',
    name: 'login',
    component: () => import('../views/Login.vue')
  },
  {
    path: '/404',
    component: () => import('../views/404Error.vue')
  }
]

const createRouter = () => new VueRouter({
  mode: 'history',
  routes: constantRoutes
})

const router = createRouter()

// 检查是否存在于免登陆白名单
function inWhiteList (toPath) {
  const whiteList = ['/login', '/404']
  const path = whiteList.find((value) => {
    // 使用正则匹配
    const reg = new RegExp('^' + value)
    return reg.test(toPath)
  })
  return !!path
}

router.beforeEach((to, from, next) => {
  console.group('%c%s', 'color:blue', `${new Date().getTime()}  ${to.path} 的全局前置守卫----------`)
  console.log('所有活跃的路由记录列表', router.getRoutes())
  console.groupEnd()
  const token = localStorage.getItem('token')
  // 检查to.path是否存在于免登陆白名单
  if (inWhiteList(to.path)) {
    if (to.path === '/login' && token) {
      // 避免重复登录
      next({ path: '/' })
    } else {
      next()
    }
    return false
  }
  // 判断是否已经登录,未登录则重定向到登录页(通过query传参记录原来的路径)
  if (!token) {
    const toPath = to.fullPath !== '/login' ? to.fullPath : '/'
    next({
      path: '/login',
      query: { redirect: toPath }
    })
  } else {
    next()
  }
})

// 在路由完成初始导航时调用,如果有异步操作放置到这里
router.onReady(() => {
  console.group('%c%s', 'color:red', `${new Date().getTime()}  路由完成初始导航----------`)
  console.log('添加前-所有活跃的路由记录列表', router.getRoutes())
  generateRoutes()
  console.log('添加后-所有活跃的路由记录列表', router.getRoutes())
  console.groupEnd()
})
// 重置路由
export function resetRouter () {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher
}

export default router

Login.vue

<template>
    <div>
        <button @click="signIn">登录</button>
    </div>
</template>

<script>
import { generateRoutes } from '../utils'

export default {
  methods: {
    signIn () {
      const routes = [
        {
          id: 1,
          parentId: 0,
          path: '/sys',
          component: 'Layout.vue',
          redirect: '/sys/user'
        },
        {
          id: 2,
          parentId: 1,
          path: 'user',
          component: 'User.vue'
        }
      ]
      setTimeout(() => {
        localStorage.setItem('token', '123456')
        localStorage.setItem('routes', JSON.stringify(routes))
        console.log('登录成功')
        generateRoutes()
        this.$router.push(this.$route.query.redirect || '/')
      }, 1000)
    }
  }
}
</script>

Layout.vue

<template>
    <div class="layout">
        <div class="header">
            <router-link to="/">Home</router-link> |
            <router-link to="/about">About</router-link> |
            <router-link to="/sys">Sys</router-link> |
            <router-link to="/sys/user">User</router-link>
            <button @click="signOut">登出</button>
        </div>
        <router-view/>
    </div>
</template>

<script>
import { resetRouter } from '../router/index'

export default {
  methods: {
    signOut () {
      localStorage.removeItem('token')
      localStorage.removeItem('routes')
      resetRouter()
      console.log('登出后的路由', this.$router.getRoutes())
      this.$router.push('/login')
    }
  }
}
</script>

generateRoutes()

export function generateRoutes () {
  const _asyncRoutes = JSON.parse(localStorage.getItem('routes'))
  const routes = _asyncRoutes.map(({ id, parentId, path, component, redirect }) => {
    const route = { id, parentId, path, redirect }
    route.component = (resolve) => require([`../views/${component}`], resolve)
    return route
  })
  const asyncRoutes = toTree(routes)
  asyncRoutes.forEach(route => {
    router.addRoute(route)
  })
  router.addRoute({
    path: '*',
    redirect: '/404'
  })
}

这个有个toTree()用于将数组转成tree,来实现路由嵌套

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐