vue根据用户权限配置动态路由

首先让我们了解一下前端路由:路由router全部配置在前端,根据用户权限判断可以进入哪些页面
缺点:

  • vue初始化的时候需要挂载全部路由,对性能有影响
  • 安全性低,用户可以在地址栏跳转到无权访问的页面(可优化)

动态路由则是根据用户信息获取权限,简单来说就是根据用户信息获取其对应的权限,生成对应的路由挂载,然后动态渲染有权限的菜单于侧边栏

实现
  • 定义静态路由(登录或者公用页面)、动态路由,vue初始化时只挂载静态路由
  • 用户登录后,拿到用户token,调接口拿到动态路由权限DynamicRoutes,将DynamicRoutes和定义的动态路由比较,筛选出相应的用户可访问路由表
  • 执行router.addRoutes(DynamicRoutes)添加动态路由
  • 使用vuex存储路由表,根据vuex中可访问的路由渲染侧边栏sidebar
// beforeEach中
if (getToken() && getToken() !== 'undefined') {
  // 权限判断
  if (!store.state.app.menuPermissions) {
    /* 获取后台给的权限数组 */
    return new Promise((resolve, reject) => {
      getPermissionList().then(response => {
        if (response.data.stat === 1) {
          const userRouter = response.data.data
          // 检查并生成新的路由表
          const DynamicRoutes = ChecAndSetPermissionRouter(userRouter)
          // 默认使/重定向到第一个有效的路由
          for (let i = 0, leni = DynamicRoutes.length; i < leni; i++) {
            if (DynamicRoutes[i].children.length > 0) {
              DynamicRoutes[i].path = '/'
              DynamicRoutes[i].redirect = DynamicRoutes[i].children[0].path
              break
            }
          }
          DynamicRoutes.push({ path: '*', redirect: '/404', hidden: true }) // 全局404
          /* 生成左侧导航菜单 */
          store.dispatch('SetMenuPermissions', DynamicRoutes)

          /*  动态添加路由 */
          router.addRoutes(DynamicRoutes)

          // /* 完整的路由表 */
          store.dispatch('SetRouterPemissions', [...constantRouterMap, ...DynamicRoutes])
          next(to)
        }
      }).catch(error => {
        router.push('/404')
        // /* 生成左侧导航菜单 */
        store.dispatch('SetMenuPermissions', [])
        next()
        reject(error)
      })
    })
  }
  if (to.path === '/login') {
    next({ path: '/' })
  } else {
    next()
  }
} else {
  if (whiteList.indexOf(to.path) !== -1) {
    next()
  } else {
    next(`/login?redirect=${to.path}`) // 否则全部重定向到登录页
  }
}

这里因为后台返回的不是标准的路由,所以只能根据后台返回的权限数据中的菜单名字或者其他唯一性字段到默认定义的动态路由中筛选出有权限的路由组成路由表,这里的ChecAndSetPermissionRouter方法就是干这个的(ChecAndSetPermissionRouter方法比较多,放末尾参考)

退出登录
  • 置menuPermissions为null,这样下次登录之后就会重新调用权限接口获取新的权限,否则另一个用户登录之后还是上一个用户的权限路由
  • 重置路由,否则下次addRoutes会提示路由定义重复
this.$store.dispatch('FedLogOut').then(() => {
    this.$store.dispatch('SetMenuPermissions', null)
    // 重置路由,以防再次进入时提示路由重复
    const createRouter = () => new Router({
      mode: 'hash',
      routes: constantRouterMap
    })
    const newRouter = createRouter()
    // resetRouter()
    this.$router.matcher = newRouter.matcher

    this.$router.push('/login')
    // 清除水印
    Watermark.clear()
  })
}

踩坑来了
在这里插入图片描述

Q:为什么404 页面一定要最后加载,放置在静态路由中会怎么样?
放在静态路由里,后面的所以页面都会被拦截到404,所以应该获取动态路由权限之后push
Q:权限获取成功,不跳转新生成的动态路由,跳404?
beforeEach中router.addRoutes之后的next()可能会失效,因为可能next()的时候路由并没有完全add完成,可替换成next(to),重新进入router.beforeEach这个钩子,这时候再通过next()来释放钩子,就能确保所有的路由都已经挂在完成了。
Q:$router.addRoutes()动态添加的路由怎么删除掉?
在开发中,有新增编辑删除菜单并要求左侧边栏菜单及时更新的需求,如果直接addRoutes,warn如下:

解决:addRoutes之前要清除掉上次addRoutes的路由,所以操作菜单调取最新权限后重新初始化router,进行matcher赋值

// DynamicRoutes是最新权限路由
const createRouter = () => new Router({
  mode: 'hash',
  routes: []
})
const newRouter = createRouter()
// resetRouter()
this.$router.matcher = newRouter.matcher
this.$router.addRoutes(DynamicRoutes)

Q:莫名其妙的无限循环
vue-admin-template,遇到二级菜单children为空的权限,报错如下:
解决:按照github-issues上方法,在SidebarItem.vue里改一下data就好了(没想通为啥)

// 更改后如下,return {}
data() {
    this.onlyOneChild = null
    return {}
}

附:ChecAndSetPermissionRouter

import { dynamicRouterMap } from '@/router'

export function ChecAndSetPermissionRouter(permissionDatas) {
  // 获取到权限hashmap
  var permissionHashMap = null
  permissionHashMap = GetPermissionHashMap(permissionDatas)
  // 标记路由表
  var newDynamicRouterMap = []
  newDynamicRouterMap = objDeepCopy(dynamicRouterMap)
  newDynamicRouterMap.forEach(item => {
    MarkRouter(null, item, permissionHashMap)
  })
  // 重设路由表
  for (let i = 0; i < newDynamicRouterMap.length; i++) {
    if (ResetRouter(newDynamicRouterMap, newDynamicRouterMap[i])) {
      i-- // 注意:防止移除后索引错位
    }
  }
  return newDynamicRouterMap
}
function GetPermissionHashMap(permissionDatas) {
  var permissionHashMap = {}
  permissionDatas.forEach(item => {
    SetKeyValueOfNodes(null, item, permissionHashMap)
  })
  return Object.assign({}, permissionHashMap)
}

// 深拷贝,递归重新设置前端路由表,避免数据复用
function objDeepCopy(source) {
  var sourceCopy = source instanceof Array ? [] : {}
  for (var item in source) {
    sourceCopy[item] = typeof source[item] === 'object' ? objDeepCopy(source[item]) : source[item]
  }
  return sourceCopy
}

// 为权限hashmap的属性赋值,新增属性tempKey/tempKey2
function SetKeyValueOfNodes(p, c, permissionHashMap) {
  // 需要匹配的组合类型
  var tempKey = (p ? p.name : 0) + '_' + c.name
  var tempKey2 = c.name + '_' + c.name
  // 赋值
  permissionHashMap[tempKey] = 1
  permissionHashMap[tempKey2] = 1
  // 递归遍历子节点赋值
  if (c.children != null && c.children.length > 0) {
    c.children.forEach(item => {
      SetKeyValueOfNodes(c, item, permissionHashMap)
    })
  }
}

// 标记路由表
function MarkRouter(p, c, permissionHashMap) {
  var key = (p ? p.meta.title : 0) + '_' + c.meta.title
  // 使用拼接的key作为参考标记去匹配有权限的路由表
  if (HasPermission(key, permissionHashMap)) {
    if (p != null) {
      p.keep = true // 保留当前节点
    }
    if (c != null) {
      c.keep = true
    }
  }
  if (c.children && c.children.length > 0) {
    c.children.forEach(item => {
      MarkRouter(c, item, permissionHashMap)
    })
  }
}

// 校验后端接口是否存在当前节点
function HasPermission(key, permissionHashMap) {
  return permissionHashMap[key] === 1
}

// 重置路由表
function ResetRouter(p, c) {
  if (c == null) {
    return false
  }
  if (p.children && !c.keep) {
    p.children.splice(p.children.indexOf(c), 1)
    return true
  } else if (!c.keep) {
    p.splice(p.indexOf(c), 1)
    return true
  }
  if (c.children && c.children.length > 0) {
    for (let i = 0; i < c.children.length; i++) {
      if (ResetRouter(c, c.children[i])) {
        i-- // 注意:防止移除后索引错位
      }
    }
  }
  return false
}
  • 7
    点赞
  • 39
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值