vue中如何實現後臺管理系統的權限控制

vuejs單頁應用的權限管理實踐
html

1、前言


在廣告機項目中,角色的權限管理是卡了挺久的一個難點。首先咱們肯定的權限控制分爲兩大部分,其中根據粒的大小分的更細:vue



  • 接口訪問的權限控制


  • 頁面的權限控制node



    • 菜單中的頁面是否能被訪問

    • 頁面中的按鈕(增、刪、改)的權限控制是否顯示




下面咱們就看一看是如何實現這些個權限控制的。ios


2、接口訪問的權限控制


接口權限就是對用戶的校驗。正常來講,在用戶登陸時服務器須要給前臺返回一個Token,而後在之後前臺每次調用接口時都須要帶上這個Token,git


而後服務端獲取到這個Token後進行比對,若是經過則能夠訪問。github


現有的作法是在登陸成功的回調中將後臺返回的Token直接存儲到sessionStorag​e,然在請求時將Token取出放入headers中傳給後臺,代碼以下:vue-router

this.$http({
          method: 'get',
          url: 'test/query?id=20',
          withCredentials: true,
          headers: {
            token: sessionStorage.getItem('token'),
            name: sessionStorage.getItem('name')    //應後臺需求傳的用戶名
          }
        }).then(response => {
          //請求成功後的操做
        })

後來在一些文章中發現axios能夠在攔截器中直接將Token塞入config.headers.Authorization中,做爲全局傳入。下面是代碼部分:vuex

//main.js
import axios from 'axios'

// 實例化Axios,並進行超時設置
const service = axios.create({
    timeout: 5000
})
// baseURL
// axios.defaults.baseURL = 'https://api.github.com';

// http request 攔截器
// 每次請求都爲http頭增長Authorization字段,其內容爲token
service.interceptors.request.use(
    config => {
        if (store.state.user.token) {
            config.headers.Authorization = `token ${store.state.user.token}`;
        }
        return config
    },
    err => {
        return Promise.reject(err)
    }
);
export default service

3、頁面權限控制

在前面已經說到,頁面權限控制又分爲兩種:數據庫

  • 菜單中的頁面是否能被訪問
  • 頁面中的按鈕(增、刪、改)的權限控制是否顯示

這些權限通常是在固定頁面進行配置,保存後記錄到數據庫中。axios


按鈕權限暫且不提,頁面訪問權限在實現中又能夠分爲兩種方式:

  • 顯示全部菜單,當用戶訪問不在本身權限內的菜單時,提示權限不足
  • 只顯示當前用戶能訪問的權限內菜單,若是用戶經過URL進行強制訪問,則會直接進入404

既然展示出來後不能點,那算幾個意思,逗我玩兒呢?所謂眼不見爲淨,綜合考慮後,確定是方案二比較符合良好的用戶體驗。

好,咱們如今梳理一下大體的頁面訪問權限的流程:

在這裏插入圖片描述

在對流程梳理完成後咱們開始進行詳細的編寫。

一、建立路由表

建立路由表實際上沒有什麼難度,照着vue-router官方文檔給的示例直接寫就好了。可是由於有部分頁面是不須要訪問權限的,

因此須要將登陸、40四、維護等頁面寫到默認的路由中,而將其它的須要權限的頁面寫到一個變量或者一個文件中,這樣可

以有效的減輕後續的維護壓力。

下面將index.js的代碼貼上,異步路由將適量減小,以避免佔過多篇幅。

// router/index.js
import Vue from 'vue'
import Router from 'vue-router'
import App from '@/App'
import store from '../store/index'

Vue.use(Router);

//手動跳轉的頁面白名單
const whiteList = [
  '/'
];
//默認不須要權限的頁面
const constantRouterMap = [
  {
    path: '/',
    name: '登陸',
    component: (resolve) => require(['@/components/login'], resolve)
  },
  {
    path: '/index',
    name: 'nav.Home',
    component: (resolve) => require(['@/components/index'], resolve)
  },
  {
    path: '/templateMake',
    name: '模板製做',
    component: (resolve) => require(['@/components/Template/templateMake'], resolve)
  },
  {
    path: '/programMack',
    name: '節目製做',
    component: (resolve) => require(['@/components/Template/programMack'], resolve)
  },
  {
    path: '/release',
    name: '節目發佈',
    component: (resolve) => require(['@/components/Program/release'], resolve)
  }
]

//註冊路由
export const router = new Router({
  routes: constantRouterMap
});

//異步路由(須要權限的頁面)
export const asyncRouterMap = [

  {
    path: '/resource',
    name: 'nav.Resource',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Resource/resource'], resolve)
  },
  {
    path: '/template',
    name: 'nav.Template',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Template/template'], resolve)
  },
  {
    path: '/generalSet',
    name: 'nav.System',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/SystemSet/generalSet'], resolve)
  },
  {
    path: '',
    name: 'nav.Log',
    component: App,
    children: [
      {
        path: '/userLog',
        name: 'nav.UserLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/userLog'], resolve),
      },
      {
        path: '/operatingLog',
        name: 'nav.SystemLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/operatingLog'], resolve),
      },
    ]
  }
  ]
];

注意事項:這裏有一個須要很是注意的地方就是 404 頁面必定要最後加載,若是放在constantRouterMap一同聲明瞭404,後面的因此頁面都會被攔截到404,詳細的問題見addRoutes when you've got a wildcard route for 404s does not work

二、頁面訪問權限

在開始時咱們梳理了一個大體的頁面訪問權限流程。下面咱們先實現最核心的部分:

在這裏插入圖片描述

咱們首先獲取用戶權限列表,在這裏咱們將接觸到vuex狀態管理,官方文檔有詳細介紹,這裏就不過多描述了,下面請看代碼:

// store/index.js
import Axios from 'axios'
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex);
const axios = Axios.create();

const state = {
  mode: 'login',
  list: []
};

const getters = {};

const mutations = {
  setMode: (state, data) => {
    state.mode = data
  },
  setList: (state, data) => {
    state.list = data
  }
};

const actions = {
  // 獲取權限列表
  getPermission({commit}) {
    return new Promise((resolve, reject) => {
      axios({
        url: '/privilege/queryPrivilege?id=' + sessionStorage.getItem('privId'),
        methods: 'get',
        headers: {
          token: sessionStorage.getItem('token'),
          name: sessionStorage.getItem('name')
        }
      }).then((res) => {
        // 存儲權限列表
        commit('setList', res.data.cust.privileges[0].children);
        resolve(res.data.cust.privileges[0].children)
      }).catch(() => {
        reject()
      })
    })
  }
};

export default new Vuex.Store({
  state,
  mutations,
  actions,
  getters
})

好了,咱們如今請求後臺拿到了權限數據,並將數據存放到了vuex中,下面咱們須要利用返回數據匹配以前寫的異步路由表,將匹配結果和靜態路由表結合,開成最終的實際路由表。

其中最關鍵的是利用vue-router2.2.0版本新添加的一個addRoutes方法,咱們看看官方文檔如何解釋此方法的:

router.addRoutes(routes) 2.2.0+
動態添加更多的路由規則。參數必須是一個符合 routes 選項要求的數組。

那咱們如今就能夠開始使用addRoutes進行路由匹配了。下面看代碼:

// router/index.js
/**
 * 根據權限匹配路由
 * @param {array} permission 權限列表(菜單列表)
 * @param {array} asyncRouter 異步路由對象
 */
function routerMatch(permission, asyncRouter) {
  return new Promise((resolve) => {
    const routers = [];
    // 建立路由
    function createRouter(permission) {
         // 根據路徑匹配到的router對象添加到routers中便可
      permission.forEach((item) => {
        if (item.children && item.children.length) {
          createRouter(item.children)
        }
        let path = item.path;
        // 循環異步路由,將符合權限列表的路由加入到routers中
        asyncRouter.find((s) => {
          if (s.path === '') {
            s.children.find((y) => {
              if (y.path === path) {
                y.meta.permission = item.permission;
                routers.push(s);
              }
            })
          }
          if (s.path === path) {
            s.meta.permission = item.permission;
            routers.push(s);
          }
        })
      })
    }

    createRouter(permission)
    resolve([routers])
  })
}

而後咱們編寫導航鉤子

// router/index.js
router.beforeEach((to, form, next) => {
  if (sessionStorage.getItem('token')) {
    if (to.path === '/') {
      router.replace('/index')
    } else {
      console.log(store.state.list.length);
      if (store.state.list.length === 0) {
          //若是沒有權限列表,將從新向後臺請求一次
        store.dispatch('getPermission').then(res => {
            //調用權限匹配的方法
          routerMatch(res, asyncRouterMap).then(res => {
              //將匹配出來的權限列表進行addRoutes
            router.addRoutes(res[0]);
            next(to.path)
          })
        }).catch(() => {
          router.replace('/')
        })
      } else {
        if (to.matched.length) {
          next()
        } else {
          router.replace('/')
        }
      }
    }
  } else {
    if (whiteList.indexOf(to.path) >= 0) {
      next()
    } else {
      router.replace('/')
    }
  }
});

到這裏咱們已經完成了對頁面訪問的權限控制,接下來咱們來說解一下操做按扭的權限部分。

4、數據操做權限

是否還記得前面的路由配置中咱們多出來的一個代碼,下面咱們拿出來看看:

//異步路由(須要權限的頁面)
export const asyncRouterMap = [

  {
    path: '/resource',
    name: 'nav.Resource',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Resource/resource'], resolve)
  },
  {
    path: '/template',
    name: 'nav.Template',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/Template/template'], resolve)
  },
  {
    path: '/generalSet',
    name: 'nav.System',
    meta: {
      permission: []
    },
    component: (resolve) => require(['@/components/SystemSet/generalSet'], resolve)
  },
  {
    path: '',
    name: 'nav.Log',
    component: App,
    children: [
      {
        path: '/userLog',
        name: 'nav.UserLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/userLog'], resolve),
      },
      {
        path: '/operatingLog',
        name: 'nav.SystemLog',
        meta: {
          permission: []
        },
        component: (resolve) => require(['@/components/Log/operatingLog'], resolve),
      },
    ]
  }
  ]
];

爲每一個路由頁面增長meta字段。在routerMatch函數中將匹配到的詳細權限字段賦值到這裏。這樣在每一個頁面的route對象中就會獲得這個字段。

asyncRouter.find((s) => {
          if (s.path === '') {
            s.children.find((y) => {
              if (y.path === path) {
                  //賦值
                y.meta.permission = item.permission;
                routers.push(s);
              }
            })
          }
          if (s.path === path) {
            s.meta.permission = item.permission;
            routers.push(s);
          }
        })

接下來咱們編寫一個vue自定義指令對頁面中須要進行鑑權的元素進行判斷,好比相似這樣的:

<a @click="upload" v-allow="'3'"></a> /* 3表明一個上傳權限的ID,權限中有3則顯示按鈕 */

咱們直接註冊一個全局指令,利用vnode來訪問vue的方法。代碼以下:

//main.js
//按扭權限指令
Vue.directive('allow', {
  inserted: (el, binding, vnode) => {
    let permissionList = vnode.context.$route.meta.permission;
    if (!permissionList.includes(binding.value)) {
      el.parentNode.removeChild(el)
    }
  }
})

至此爲止,權限控制流程就已經徹底結束了,在最後咱們再看一下完整的權限控制流程圖吧.

5、路由控制完整流程圖

在這裏插入圖片描述

6、參考文獻

  1. Vue + ElementUI 手擼後臺管理網站之權限控制
  2. 手摸手,帶你用vue擼後臺之權限控制

來源:https://segmentfault.com/a/1190000016445363

相關文章
相關標籤/搜索