手把手带你用vue-admin-template实现动态权限管理(一)

手把手带你用vue-admin-template实现动态权限管理(一)

前言

​ 最近一直在研究用VueElement-Ui搭建一个后台管理系统,于是在github网站上找到 了一个star高居64.3K 的项目vue-element-admin。怀着激动的心情赶紧下载下来跑了一下,效果属实不错,还有官方的说明文档网站vue-element-admin

前期储备

​ 本项目技术栈基于 ES2015+vuevuexvue-routervue-cliaxioselement-ui掌握这些或者说熟练运用会对于剖析这个项目非常有帮助。

本节目标

​ 本篇文章的目标我们着重分析一下这个项目是如何实现动态权限管理的。

涉及说明文件
序号 文件目录 说明
1 vue-element-admin-master\src\router\index.js 路由文件
2 vue-element-admin-master\src\permission.js 路由守卫文件
3 vue-element-admin-master\src\store\modules\permission.js 动态权限筛选文件
4 vue-element-admin-master\src\store\modules\user.js 用户角色和用户信息文件
5 vue-element-admin-master\src\store\getters.js 所有vuex中暴露出来的值
说明
用户角色和用户信息文件:user.js
javascript 复制代码
import { login, logout, getInfo } from '@/api/user'
import { getToken, setToken, removeToken } from '@/utils/auth'
import router, { resetRouter } from '@/router'

const state = {
  token: getToken(),
  roles: []
}

const mutations = {
  SET_TOKEN: (state, token) => {
    state.token = token
  },
  ......
  SET_ROLES: (state, roles) => {
    state.roles = roles
  }
}

const actions = {
  // user login
  login({ commit }, userInfo) {
    const { username, password } = userInfo
    return new Promise((resolve, reject) => {
      login({ username: username.trim(), password: password }).then(response => {
        const { data } = response
        commit('SET_TOKEN', data.token)
        setToken(data.token)
        resolve()
      }).catch(error => {
        reject(error)
      })
    })
  },
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo(state.token).then(response => {
        const { data } = response
        if (!data) {
          reject('Verification failed, please Login again.')
        }
        const { roles, name, avatar, introduction } = data

        // roles must be a non-empty array
        if (!roles || roles.length <= 0) {
          reject('getInfo: roles must be a non-null array!')
        }
        commit('SET_ROLES', roles)
        ......
        resolve(data)
      }).catch(error => {
        reject(error)
      })
    })
  },
  ........
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

login()方法登录并保存token,这个token会作为全局接口请求的公共参数。

getInfo()这个主要是获取用户的角色,后面会根据角色取筛选访问的页面。

路由文件:index.js
javascript 复制代码
import Vue from 'vue'
import Router from 'vue-router'

Vue.use(Router)

/* Layout */
import Layout from '@/layout'

/* Router Modules */
import componentsRouter from './modules/components'
import chartsRouter from './modules/charts'
import tableRouter from './modules/table'
import nestedRouter from './modules/nested'

/**
 * constantRoutes
 * a base page that does not have permission requirements
 * all roles can be accessed
 */
export const constantRoutes = [
  {
    path: '/redirect',
    component: Layout,
    hidden: true,
    children: [
      {
        path: '/redirect/:path(.*)',
        component: () => import('@/views/redirect/index')
      }
    ]
  },
  {
    path: '/login',
    component: () => import('@/views/login/index'),
    hidden: true
  },
    ......
  {
    path: '/',
    component: Layout,
    redirect: '/dashboard',
    children: [
      {
        path: 'dashboard',
        component: () => import('@/views/dashboard/index'),
        name: 'Dashboard',
        meta: { title: 'Dashboard', icon: 'dashboard', affix: true }
      }
    ]
  }
]

/**
 * asyncRoutes
 * the routes that need to be dynamically loaded based on user roles
 */
export const asyncRoutes = [
  {
    path: '/permission',
    component: Layout,
    redirect: '/permission/page',
    alwaysShow: true, // will always show the root menu
    name: 'Permission',
    meta: {
      title: 'Permission',
      icon: 'lock',
      roles: ['admin', 'editor'] // you can set roles in root nav
    },
    ......
  /** when your routing map is too long, you can split it into small modules **/
  componentsRouter,
  chartsRouter,
  nestedRouter,
  tableRouter,

  // 404 page must be placed at the end !!!
  { path: '*', redirect: '/404', hidden: true }
]

const createRouter = () => new Router({
  // mode: 'history', // require service support
  scrollBehavior: () => ({ y: 0 }),
  routes: constantRoutes
})

const router = createRouter()

// Detail see: https://github.com/vuejs/vue-router/issues/1234#issuecomment-357941465
export function resetRouter() {
  const newRouter = createRouter()
  router.matcher = newRouter.matcher // reset router
}

export default router

constantRoutes这里面的路由时所有用户的都要的不涉及权限

asyncRoutes这里面定义的路由将来是要根据用户角色筛选出来的,只要时根据每个路由meta中的roles: ['admin', 'editor']去筛选

动态权限筛选文件:store\modules\permission.js
javascript 复制代码
import { asyncRoutes, constantRoutes } from '@/router'

/**
 * Use meta.role to determine if the current user has permission
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

/**
 * Filter asynchronous routing tables by recursion
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes, roles) {
  const res = []

  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}

const state = {
  routes: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      let accessedRoutes
      if (roles.includes('admin')) {
        accessedRoutes = asyncRoutes || []
      } else {
        accessedRoutes = filterAsyncRoutes(asyncRoutes, roles)
      }
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

generateRoutes({ commit }, roles)这个方法根据传过来的角色去筛选asyncRoutes ,这个也就是上面提到的所有需要筛选的路由数组,先判断如果是admin角色就将路由全部加载。如果不是就调用**filterAsyncRoutes(asyncRoutes, roles)方法开始递归过滤,这个方法中又调用了 hasPermission(roles, route)就是根据上面提到的根据每个路由meta中的roles: ['admin', 'editor']**和自己的角色对比筛选出来指定能访问的路由组装成数组返回。

最后通过routes: [],addRoutes: []这两个数组将筛选好的路由以及筛选好的路由结合上静态的路由全部保存起来。

路由守卫文件:permission.js
javascript 复制代码
import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'

NProgress.configure({ showSpinner: false }) // NProgress Configuration

const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist

router.beforeEach(async(to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
    } else {
      // determine whether the user has obtained his permission roles through getInfo
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // get user info
          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
          const { roles } = await store.dispatch('user/getInfo')

          // generate accessible routes map based on roles
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
          // dynamically add accessible routes
          router.addRoutes(accessRoutes)

          // hack method to ensure that addRoutes is complete
          // set the replace: true, so the navigation will not leave a history record
          next({ ...to, replace: true })
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/

    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})

router.afterEach(() => {
  // finish progress bar
  NProgress.done()
})

router.beforeEach所有路由的跳转都会进入这个方法中

if (hasToken) 这里就会先判断是否有token如果没有直接跳转到登录页面

if (to.path === '/login')如果有token,根据to.path也就是你将要跳转到的页面路由如果是登录那就通过next()放行

else 如果不是登录的路由,进去通过所有vuex中暴露出来的值也就是store\getters.js获取到user.js中的角色是否为空如果不为空

说明store\modules\permission.js 这里面已经筛选过路由直接通过next()放行

else 如果没有获取到角色,通过const { roles } = await store.dispatch('user/getInfo')这个去store\modules\user.js 获取角色信息,然后再调用 const accessRoutes = await store.dispatch('permission/generateRoutes', roles)去文件store\modules\permission.js筛选出指定的路由返回 ,最后再调用 **router.addRoutes(accessRoutes)加载路由,接着调用 next({ ...to, replace: true })让router.beforeEach()重新执行一遍。这里千万不要调用next()方法放行,不然会出现页面跳转空白的情况,还有 next({ ...to, replace: true })调用这个方法一定要保证上面的const { roles } = await store.dispatch('user/getInfo')**能获取到角色信息,不然就会进入死循环!!!

为什么要将路由筛选和判单放到**router.beforeEach()**这样岂不是每点击一个路由都会进来,没错是这样的,放到路由守卫中是因为用户在刷新浏览器的时候vuex上挂载的用户角色和筛选的路由全部都会置空,所有需要在路由守卫中判断和筛选。

结语

vue-element-admin这项目是基于本地实现的动态权限,所以后台开发人员只需要返回给你一个角色就可以了,剩下的就是你根据角色筛选路由就行了。弊端就是如果要修改其中一个角色的访问权限,前端需要改动重新发布。实际项目中有不少都是后台控制权限,这就需要我们将路由表放到数据库中。

下一篇我们将通过node实现动态权限和路由的表结构设计,从而实现后台通过接口返回路由表给前端。

相关推荐
小政爱学习!20 分钟前
封装axios、环境变量、api解耦、解决跨域、全局组件注入
开发语言·前端·javascript
魏大帅。25 分钟前
Axios 的 responseType 属性详解及 Blob 与 ArrayBuffer 解析
前端·javascript·ajax
花花鱼31 分钟前
vue3 基于element-plus进行的一个可拖动改变导航与内容区域大小的简单方法
前端·javascript·elementui
k093335 分钟前
sourceTree回滚版本到某次提交
开发语言·前端·javascript
EricWang13581 小时前
[OS] 项目三-2-proc.c: exit(int status)
服务器·c语言·前端
September_ning1 小时前
React.lazy() 懒加载
前端·react.js·前端框架
web行路人1 小时前
React中类组件和函数组件的理解和区别
前端·javascript·react.js·前端框架
超雄代码狂1 小时前
ajax关于axios库的运用小案例
前端·javascript·ajax
长弓三石2 小时前
鸿蒙网络编程系列44-仓颉版HttpRequest上传文件示例
前端·网络·华为·harmonyos·鸿蒙
小马哥编程2 小时前
【前端基础】CSS基础
前端·css