PanJiaChen /vue-element-admin 权限方案实现总结

项目github地址:github.com/PanJiaChen/...

一个管理后台,拥有不同的角色,权限不同会展示不同的菜单与功能。本篇文章总结,如何进行登录鉴权,路由权限,按钮权限,token管理。

权限方案实现总结:

包括两方面:

1、页面权限

本项目使用的是前端生成路由表,为什么不采用后端动态生成路由表呢?写一个页面还要后端配路由,很讨厌!不想被后端支配!

路由路由分为两种,constantRoutesasyncRoutes
constantRoutes:是公共访问路由数组,每个用户都可以访问。
asyncRoutes一个是权限路由数组,每个路由具有不同的权限。里面meta包含用户的role。

主要逻辑: 拿到用户的token与roles,然后根据用户的roles,去匹配路由中的meta对象中的roles,如果匹配到了,则把该路由加到可访问的路由去, router.addRoutes(accessRoutes)

用户可以访问的路由都放在vuex中统一管理。

用户角色也统一放在vuex中统一管理。

token: 代表该用户登录是否失效

roles: 代表该用户角色,eg: roles:[admin,editor]

为什么要先判断token呢?如果用户登录都失效了,那就不用做权限控制了,先去登录吧。

为什么要判断用户的roles是否存在?因为我们会在拿到roles后,立刻对该用户进行页面权限控制,选出他能访问的页面路由。所以如果此时vuex中已经存在roles说明我们已经对该用户做过了页面权限控制。

js 复制代码
src/router
// 公共路由
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
  },
  ....
],
// 权限路由
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
    },
    children: [
      {
        path: 'page',
        component: () => import('@/views/permission/page'),
        name: 'PagePermission',
        meta: {
          title: 'Page Permission',
          roles: ['admin'] // or you can only set roles in sub nav
        }
      },
      {
        path: 'directive',
        component: () => import('@/views/permission/directive'),
        name: 'DirectivePermission',
        meta: {
          title: 'Directive Permission'
          // if do not set roles, means: this page does not require permission
        }
      },
    ]
  },
  ...
]

实现代码:

js 复制代码
src/permission.js
//通过路由守卫,每次跳转前判断该用户有无访问权限。
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()
})

路由控制逻辑核心代码:

js 复制代码
// store/permission.js
import { asyncRoutes, constantRoutes } from '@/router'

/**
 * Use meta.role to determine if the current user has permission
 * @param roles
 * @param route
 */
// 函数hasPermission用于判断用户是否有权限访问某个路由
function hasPermission(roles, route) {
  // 如果路由的meta属性存在且meta属性中包含roles属性
  if (route.meta && route.meta.roles) {
    // 返回用户角色数组中是否有包含路由meta属性roles数组中的角色
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    // 如果路由的meta属性不存在或meta属性中不包含roles属性,则返回true,表示用户有权限访问该路由
    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
}

二、按钮权限:

通过两种方式实现:

1、自定义指令 通过dirctive自定义指令,来控制页面该元素是否显示。

v-permission实现权限控制逻辑: 主要是判断v-permission= ['admin', 'editor'] 传进去的角色,和当前用户角色roles是否匹配,匹配则被绑定的元素不被移除

js 复制代码
// src\directive\permission\permission.js
import store from '@/store'

function checkPermission(el, binding) {
  const { value } = binding
  const roles = store.getters && store.getters.roles

  if (value && value instanceof Array) {
    if (value.length > 0) {
      const permissionRoles = value

      const hasPermission = roles.some(role => {
        return permissionRoles.includes(role)
      })

      if (!hasPermission) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    }
  } else {
    throw new Error(`need roles! Like v-permission="['admin','editor']"`)
  }
}

export default {
  inserted(el, binding) {
    checkPermission(el, binding)
  },
  update(el, binding) {
    checkPermission(el, binding)
  }
}

/**
* `inserted`:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
* `update`:所在组件的 VNode 更新时调用,**但是可能发生在其子 VNode 更新之前**。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 。
*/

本项目中是局部使用,只在需要的文件里导入了,使用。

js 复制代码
import permission from '@/directive/permission/index.js' // 权限判断指令
export default {
    directives: { permission },
}

但我决定还是全局导入比较好,因为这个元素权限控制,很多页面会用到。 全局使用:

js 复制代码
import permission from '@/directive/permission/index.js'
如果是全局注册指令,则在main.js中注册  Vue.directive('permission', permission)

局部有局部的优点,局部注册指令,则只能在当前组件中使用v-permission,更加模块化,避免全局污染。

2、方法 通过v-if=fn(),根据fn方法返回值来判断这个元素是否展示

js 复制代码
<el-tab-pane v-if="checkPermission(['admin'])" label="Admin">
  Admin can see this
  <el-tag class="permission-sourceCode" type="info">
     v-if="checkPermission(['admin'])"
   </el-tag>
</el-tab-pane>

checkPermission 方法:

js 复制代码
import store from '@/store'

/**
 * @param {Array} value
 * @returns {Boolean}
 * @example see @/views/permission/directive.vue
 */
export default function checkPermission(value) {
  if (value && value instanceof Array && value.length > 0) {
    const roles = store.getters && store.getters.roles
    const permissionRoles = value

    const hasPermission = roles.some(role => {
      return permissionRoles.includes(role)
    })
    return hasPermission
  } else {
    console.error(`need roles! Like v-permission="['admin','editor']"`)
    return false
  }
}
相关推荐
OpenTiny社区14 分钟前
一文解读“Performance面板”前端性能优化工具基础用法!
前端·性能优化·opentiny
拾光拾趣录35 分钟前
🔥FormData+Ajax组合拳,居然现在还用这种原始方式?💥
前端·面试
不会笑的卡哇伊1 小时前
新手必看!帮你踩坑h5的微信生态~
前端·javascript
bysking1 小时前
【28 - 记住上一个页面tab】实现一个记住用户上次点击的tab,上次搜索过的数据 bysking
前端·javascript
Dream耀1 小时前
跨域问题解析:从同源策略到JSONP与CORS
前端·javascript
前端布鲁伊1 小时前
【前端高频面试题】面试官: localhost 和 127.0.0.1有什么区别
前端
HANK1 小时前
Electron + Vue3 桌面应用开发实战指南
前端·vue.js
極光未晚1 小时前
Vue 前端高效分包指南:从 “卡成 PPT” 到 “丝滑如德芙” 的蜕变
前端·vue.js·性能优化
郝亚军1 小时前
炫酷圆形按钮调色器
前端·javascript·css
Spider_Man1 小时前
别再用Express了!用Node.js原生HTTP模块装逼的正确姿势
前端·http·node.js