Vue-router 权限控制添加动态路由

962 阅读3分钟

前言

最近做后台管理系统的时候需要根据用户权限显示菜单栏,即需要动态添加路由控制,实现过程中发现Vue-router的坑和填坑的过程;

以下是手动添加路由,也可通过后端数据获取动态路由配置,具体实现流程如下:

Router

// 默认路由数组
export const constantRouterMap = [
  {
    path: '/',
    component: () => import('@/views/home'),
    meta: { name: '首页', hideBackBtn: true },
    children: []
  },
  {
    path: '/login',
    name: '/login',
    component: () => import('@/views/login')
  }
]

// Admin权限路由
export const AdminAuthRoute = {
  path: '/home',
  redirect: '/userList', // 默认打开用户管理tab
  component: () => import('@/views/home'),
  children: [
    {
      path: '/userList',
      name: '/userList',
      component: () => import('@/views/userList/index'),
    },
    {
      path: '/policyManager',
      name: '/policyManager',
      component: () => import('@/views/policyManager/index'),
    }
  ]
}

// 其他权限路由
export const OtherAuthRoute = {
  path: '/home',
  redirect: '/userList', // 默认打开用户管理tab
  component: () => import('@/views/home'),
  children: [
    {
      path: '/userList',
      name: '/userList',
      component: () => import('@/views/userList/index'),
    },
  ]
}

export default new Router({
  routes: constantRouterMap
})

Home(一般也叫Layout)页面


//内部也包含<router-view />,其他样式代码不粘贴了,不是重点
<el-main>
   <router-view />
</el-main>

以上代码表示,默认导出path有‘/login’和‘/’,Admin权限是home为页面菜单栏有两个页面选项,Other权限则是home为页面菜单栏有一个页面选项。

router.beforeEach


// 路由拦截器
router.beforeEach(async(to, from, next) => {
  // 未登录
  if (to.path === Paths.loginPath) {
    return next()
  } else {
    if (!store.getters.token) {
      return next({ path: Paths.loginPath })
    }
  }

  // 不需要重复刷新userInfo
  if (store.getters.userInfo && Object.keys(store.getters.addRouterObj).length) {
    if (to.path === '/') {
      return next({ path: Paths.homePath, replace: true })
    } else {
      return next()
    }
  }

  try {
    let data = await store.dispatch('requestUserInfo')
    await store.dispatch('handleRoutes', data.type)
    // 此处如果直接用next()的话,在添加的路由中刷新页面会出现页面空白,此处后面会分析
    return next({ path: to.path })
  } catch (err) {
  // 获取个人信息失败,清空数据,重新登录
    store.commit('resetUser')
    return next({ path: to.path })
  }
})

store->permission


// store/permission.js
import router from '@/router/index'
import {
  AdminAuthRoute,
  OtherAuthRoute
} from '@/router'

const permission = {
  state: {
    addRouterObj: {}
  },
  mutations: {
    SET_ROUTERS: (state, obj) => {
      state.addRouterObj = obj
    }
  },
  actions: {
    // 账户类型 type :1:admin 2:other
    handleRoutes({ commit }, type) {
      return new Promise(resolve => {
        let addRoutes = {}
        if (type === 1) {
          addRoutes = AdminAuthRoute
        } else {
          addRoutes = OtherAuthRoute
        }
        commit('SET_ROUTERS', addRoutes)
        if (Object.keys(addRoutes).length) {
          router.addRoute(addRoutes)
        }
        console.log(router.getRoutes())
        resolve()
      })
    }
  }
}

export default permission

以上代码有几种判断

  • 首先判断登录状态,比较好理解

  • store中有值的时候,不需要再次请求UserInfo,正常可以直接next(),以防用户在地址栏直接删除path去到“/”,转接一下回到"/home"

  • 若store中没有权限参数或者已添加路由对象,则需要请求UserInfo,然后permission根据权限添加路由,最后next()即可

遇到的坑

1.addRoute()无法覆盖相同name的路由

router.vuejs.org/zh/api/#rou…,(文档:如果该路由规则有 name,并且已经存在一个与之相同的名字,则会覆盖它)Vue-router3.x也没有removeRoute的api提供; 以上的例子,用户登录Admin权限的账号,返回登录页,重新登录Other权限的账号,此时菜单栏虽然只显示了一个菜单,但是仍可以通过浏览器输入Admin权限的路由地址进入页面。以下是我的解决方法:

//登出或者401的时候可以通过刷新页面的方式回到登录页,且将router已添加的路由删除掉
location.reload()

tips:可以通过router.getRoutes()查看当前路由记录,查看log就知道是否覆盖的情况

2.登录成功后,$router.push('/'),会报如下错误

Uncaught (in promise) Error: Redirected when going from "/login" to "/" via a navigation guard.

github.com/vuejs/vue-r…,这个issue中也有提及,router.pushbeforeEach 里next修改了路由就会报错,但是不影响跳转,不过也做了处理去catch报错

this.$router.push({ path: '/' }).catch(() => {})

3.addRoute刷新页面出现空白


// 路由拦截器
router.beforeEach(async(to, from, next) => {

  //前置的代码,跟上面一样
  ...

  try {
    let data = await store.dispatch('requestUserInfo')
    await store.dispatch('handleRoutes', data.type)
    return next({ path: to.path })
    // return next()
  } catch (err) {
  // 获取个人信息失败,清空数据,重新登录
    store.commit('resetUser')
    return next({ path: to.path })
  }
})

addRoutes()后,路由还没有被加进去; next({ ...to, replace: true }),进入下一轮beforeEach,此时,路由已经加进去,只要逻辑正确,就可以next() 重点在于要理解next:next() 表示放行,让路由跳转继续;next({path:xxx}) ,表示被拦截,因此会再次进入beforeEach,此时to.path===xx

4.AddRoutes替换为AddRoute

addRoutes(routes: RouteConfig[]): void
addRoute(parent: string, route: RouteConfig): void
addRoute(route: RouteConfig): void

源码解释得比较直接,一个是添加RouteConfig数组,一个是添加RouteConfig对象,还多了个父路由的参数的api选择