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' // 路由名字 name 
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

// 不需要權限的路由
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: '*',
    redirect: '/404',
    hidden: true,
    children: []
  },

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

然後通過 token 獲取當前登錄用戶的個人信息,在router被掛載到Vue之前和需要權限的路由表做對比,篩選出當前角色的動態路由表,

// main.js
//  獲取角色信息,根據用戶權限動態加載路由
router.beforeEach((to, from, next) => {
  if (store.getters.token) { // 查看 token 是否存在
    store.dispatch('setToken', store.getters.token) // 每次操作都重新寫入 token,延長有效會話時間
    if (to.path === '/login') {
      next({path: '/'})
    } else {
      if (!store.getters.info.role) { // 查看是否有當前用戶角色,如果沒有則獲取角色信息
        !async function getAddRouters () {
          await store.dispatch('getInfo', store.getters.token) // 通過token獲取角色信息
          await store.dispatch('newRoutes', store.getters.info.role) // 通過權限篩選新路由表
          await router.addRoutes(store.getters.addRouters) // 動態加載新路由表
          next({path: '/index'})
        }()
      } 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, token) {
      // 省略 axios 請求代碼 通過 token 向後臺請求用戶權限等信息,這裏用假數據賦值
      state.info = {
        role: 'superAdmin',
        permissions: '超級管理員'
      }
      // 將 info 存儲在 sessionStorage裏, 按鈕指令權限將會用到
      sessionStorage.setItem('info', JSON.stringify(store.getters.info))
    },
    setRole (state, options) {  // 切換角色,測試權限管理
      state.info = {
        role: options.role,
        permissions: options.permissions
      }
      sessionStorage.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

按鈕級別權限驗證

通過自定義指令獲取當前按鈕所需的有哪些權限,然後和當前用戶的權限對比,如果沒有權限則刪除按鈕

// btnPermission.js
import Vue from 'vue'

Vue.directive('roleBtn',{
  bind:function (el,binding) {
    let roleArr = binding.value; // 獲取按鈕所需權限
    let userRole =  JSON.parse(sessionStorage.getItem('info')).role // 獲取當前用戶權限
    if (roleArr && roleArr.indexOf(userRole) !== -1) {
      return false
    } else {
      el.parentNode.removeChild(el);
    }
  }
})
export default Vue

使用自定義指令權限

<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>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章