阅读 4178

Vue2.0 + ElementUI 手写权限管理系统后台模板(二)——权限管理

权限验证

页面级别权限

路由:

默认挂载不需要权限的路由,例如:登录、主页。需要权限的页面通过 router.addRoutes(点击查看官方文档) 动态添加更多的路由规则,404拦截页面需要放在路由表的最后,否则 /404 后面的路由会被404拦截,通过路由元信息meta(点击查看官方文档)记录路由需要的权限。为了菜单列表可以被翻译,路由表的 name 属性值通过 i18n 的英文对照表来获取,也可以直接写英文名称,如 name: routeNmae.builtInIcon 可以直接写成 name: "builtInIcon",凭个人喜好

// src/router/index.js
import en from '../i18n/lang/en' // 英文对照表
import Vue from 'vue'
import Router from 'vue-router'
import CommerViews from '@/views/commerViews'
import Login from '@/views/login/index'
import Layout from '@/views/layout/layout'
import HomeMain from '@/views/index/mainIndex'

// 不是必须加载的组件使用懒加载
const Icon = () => import('@/views/icon/index')
const Upload = () => import('@/views/upload/upload')
const Markdown = () => import('@/views/markdown/markdownView')
const NotFound = () => import('@/page404')

Vue.use(Router)
let routeNmae = en.routeNmae    // 从英文翻译对照表获取路由的英文名字,当做路由'name'属性的值 

// 不需要权限的路由
let defaultRouter = [
  { path: '/',
    redirect: '/index',
    hidden: true,
    children: []
  },
  {
    path: '/login',
    component: Login,
    name: '',
    hidden: true,
    children: []
  },
  {
    path: '/index',
    iconCls: 'fa fa-dashboard', // 菜单图标,直接填写字体图标的 class
    name: routeNmae.home,
    component: Layout,
    alone: true,
    children: [
      {
        path: '/index',
        iconCls: 'fa fa-dashboard',
        name: '主页',
        component: HomeMain,
        children: []
      }
    ]
  },
  {
    path: '/404',
    component: NotFound,
    name: '404',
    hidden: true,
    children: []
  },
]

// 需要 addRouters 动态加载的路由 
let addRouter = [
  {
    path: '/',
    iconCls: 'fa fa-server',
    name: routeNmae.multiDirectory,
    component: Layout,
    children: [
      {
        path: '/erji1',
        iconCls: 'fa fa-server',
        name: routeNmae['menu2-1'],
        component: Erji,
        children: []
      },
      {
        path: '/erji3',
        iconCls: 'fa fa-server',
        name: routeNmae['menu2-3'],
        component: CommerViews, // 无限极菜单的容器 超过三级菜单父级容器需要使用 CommerViews
        children: [
          {
            path: '/sanji2',
            iconCls: 'fa fa-server',
            name: routeNmae['menu3-2'],
            component: Sanji2,
            children: []
          },
          {
            path: '/sanji3',
            iconCls: 'fa fa-server',
            name: routeNmae['menu3-3'],
            component: CommerViews,
            children: [
              {
                path: '/siji',
                iconCls: 'fa fa-server',
                name: routeNmae['menu4-1'],
                component: Siji,
                children: []
              },
              {
                path: '/siji1',
                iconCls: 'fa fa-server',
                name: routeNmae['menu4-2'],
                component: CommerViews,
                children: [
                  {
                    path: '/wuji',
                    iconCls: 'fa fa-server',
                    name: routeNmae['menu5-1'],
                    component: Wuji,
                    children: []
                  }
                ]
              }
            ]
          }
        ]
      }
    ]
  },
  
  {
    path: '/',
    iconCls: 'el-icon-edit', // 图标样式class
    name: routeNmae.editor,
    component: Layout,
    meta: {role: ['superAdmin', 'admin']}, // 需要权限 'superAdmin', 'admin'。meta属性可以放在父级,验证父级和所有子菜单,也可以放在子级单独验证某一个子菜单
    children: [
      {
        path: '/markdown',
        iconCls: 'fa fa-file-code-o', // 图标样式class
        name: routeNmae.markdown,
        component: Markdown,
        children: []
      }
    ]
  },
  { path: '*',  // 通配符拦截放在最后,不存在的路由全都指向404页面
    redirect: '/404',
    hidden: true,
    children: []
  },

]
export default new Router({
  routes: defaultRouter
})
export {defaultRouter, addRouter}

复制代码

然后通过 token 获取当前登录用户的个人信息,在router被挂载到Vue之前和需要权限的路由表做对比,筛选出当前角色有权限访问的动态路由表,

// src/router/permission.js
//  获取角色信息,根据用户权限动态加载路由
router.beforeEach((to, from, next) => {
  if (store.getters.token) { // 查看 token 是否存在
    if (to.path === '/login') {
      next({path: '/'})
    } else {
      if (!store.getters.info.role) { // 查看是否有当前用户角色,如果没有则获取角色信息
        !async function getAddRouters () {
         // 省略 axios 请求代码 通过 token 向后台请求用户权限等信息,这里用假数据赋值
          await store.dispatch("getInfo", {
            role: "superAdmin",
            permissions: "超级管理员"
          })
          await store.dispatch("newRoutes", store.getters.info.role) // 通过权限筛选新路由表
          let newAddRouters = store.getters.addRouters
          await router.addRoutes(newAddRouters) // 动态加载新路由表
          next({path: to.path})
        }()
      } else {
        let is404 = to.matched.some(record => { // 404页面拦截
          if(record.meta.role){
          // 没有权限的页面,跳转的404页面
            return record.meta.role.indexOf(store.getters.info.role) === -1
          }
        })
        if(is404){
          next({path: '/404'})
          return false
        }
        next()
      }
    }
  } else {
    if (to.path === '/login') {
      next()
    }
    next({path: '/login'})

  }
})
复制代码

actions: getInfo

// src/vuex/modules/role.js
state: {
    info: '' // 每次刷新都要通过token请求个人信息来筛选动态路由
  },
  mutations: {
    getInfo (state, info) {
      // 省略 axios 请求代码 通过 token 向后台请求用户权限等信息,这里用假数据赋值
     state.info = info
     // 将 info 存储在 localStorage里, 按钮指令权限将会用到
     localStorage.setItem("info", JSON.stringify(store.getters.info))
    },
    setRole (state, options) {  // 切换角色,测试权限管理
      state.info = {
        role: options.role,
        permissions: options.permissions
      }
      localStorage.setItem("info", JSON.stringify(store.getters.info))
      // 权限切换后要根据新权限重新获取新路由,再走一遍流程
      store.dispatch('newRoutes', options.role)
      router.addRoutes(store.getters.addRouters)

    }
  },
  actions: {
    getInfo ({commit}, token) {
      commit('getInfo', token)
    },
    setRole ({commit}, options){// 切换角色,测试权限管理,不需要可以删除
      commit('setRole', options)
    }
  }
复制代码

actions: newRoutes

// src/vuex/modules/routerData.js
import {defaultRouter, addRouter} from '@/router/index'
const routerData = {
state: {
    routers: [],
    addRouters: []
  },
  mutations: {
    setRouters: (state, routers) => {
      state.addRouters = routers  // 保存动态路由用来addRouter
      state.routers = defaultRouter.concat(routers) // 所有有权限的路由表,用来生成菜单列表
    }
  },
  actions: {
    newRoutes ({commit}, role) {
      //  通过递归路由表,删除掉没有权限的路由
      function eachSelect (routers, userRole) {
        for (let j = 0; j < routers.length; j++) {
          if (routers[j].meta && routers[j].meta.role.length && routers[j].meta.role.indexOf(userRole) === -1) {
          // 如果没有权限就删除该路由,如果是父级路由没权限,所有子菜单就更没权限了,所以一并删除
            routers.splice(j, 1)
            j = j !== 0 ? j - 1 : j // 删除掉没有权限的路由后,下标应该停止 +1,保持不变,如果下标是 0的话删除之后依然等于0
          }
          if (routers[j].children && routers[j].children.length) {
           //  如果包含子元素就递归执行
            eachSelect(routers[j].children, userRole)
          }
        }
      }
      // 拷贝这个数组是因为做权限测试的时候可以从低级切回到高级角色,仅限演示,正式开发时省略这步直接使用 addRouter
      // 仅限演示
      let newArr = [...addRouter]
      eachSelect(newArr, role)
      commit('setRouters', newArr)

      // 正式开发
      // eachSelect(addRouter, role)
      // commit('setRouters', addRouter)
    }
  }
}
export default routerData
复制代码

按钮级别权限验证

通过自定义指令获取当前按钮所需的有哪些权限,然后和当前用户的权限对比,如果没有权限则删除按钮

// src/directive/permission/button.js
export default {
  install (Vue, options) {
    Vue.directive("roleBtn", {
      inserted: function (el, binding) {
        let roleArr = binding.value // 获取按钮所需权限
        let userRole = JSON.parse(localStorage.getItem("info")).role// 获取当前用户权限
        if (roleArr && roleArr.indexOf(userRole) !== -1) {
          return false
        } else {
          el.parentNode.removeChild(el)
        }
      }
    })
  }
}
复制代码

使用自定义指令权限

<el-button type="primary" plain size="medium">查看</el-button>
<el-button type="primary" plain size="medium" v-role-btn="['admin']">添加</el-button>
<el-button type="danger" plain size="medium" v-role-btn="['superAdmin']">删除</el-button>
<el-button type="primary" plain size="medium" v-role-btn="['superAdmin','admin']">修改</el-button>
复制代码

系列文章

Vue2.0 + ElementUI 手写权限管理系统后台模板(一)——简述

Vue2.0 + ElementUI 手写权限管理系统后台模板(二)——权限管理

Vue2.0 + ElementUI 手写权限管理系统后台模板(三)——页面搭建

Vue2.0 + ElementUI 手写权限管理系统后台模板(四)——组件结尾