首页

根据用户权限不同,动态生成路由导航菜单(一)

seo达人

首先让我们了解一下前端路由:路由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}) // 否则全部重定向到登录页

  }

}



踩坑来了





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

}




日历

链接

blogger

蓝蓝 http://www.lanlanwork.com

存档