Vue3.0權限管理實現流程【實踐】

Vue3.0權限管理實現流程【實踐】

作者:lxcan

轉發鏈接:https://segmentfault.com/a/1190000022431839

一、整體思路

後端返回用戶權限,前端根據用戶權限處理得到左側菜單;所有路由在前端定義好,根據後端返回的用戶權限篩選出需要掛載的路由,然後使用 addRoutes 動態掛載路由。

二、實現要點

(1)路由定義,分為初始路由和動態路由,一般來說初始路由只有 login,其他路由都掛載在 home 路由之下需要動態掛載。

(2)用戶登錄,登錄成功之後得到 token,保存在 sessionStorage,跳轉到 home,此時會進入路由攔截根據 token 獲取用戶權限列表。、

(3)全局路由攔截,根據當前用戶有沒有 token 和 權限列表進行相應的判斷和跳轉,當沒有 token 時跳到 login,當有 token 而沒有權限列表時去發請求獲取權限等等邏輯。

(4)處理用戶權限,在 store.js 定義一個模塊 permission.js,專門用於處理用戶權限相關的邏輯,用戶權限列表、菜單列表都保存在此模塊;

(5)用戶權限列表、菜單列表的處理,前端的路由要和後端返回的權限有一個唯一標識(一般用路由名做標識符),根據此標識篩選出對應的路由。

(6)左側菜單,要和用戶信息、用戶管理模塊使用的菜單信息一致,統一使用保存在 store 中的變量。

三、具體實現流程

1、準備工作,路由定義

<code> 
 

let

router =

new

Router({

mode

:

'history'

,

routes

: [ {

path

:

'/login'

,

name

:

'login'

,

component

:

()

=>

import

(

'@/views/login.vue'

), }, ] });/<code>
<code> 
 

export

const

dynamicRoutes = [ {

path

:

'/'

,

name

:

'home'

,

component

:

()

=>

import

(

'@/views/home.vue'

),

meta

: {

requiresAuth

:

true

, },

children

: [ {

path

:

'/user-info'

,

name

:

'user-info'

,

component

:

()

=>

import

(

'@/views/user-setting/user-info.vue'

), }, {

path

:

'/user-password'

,

name

:

'user-password'

,

component

:

()

=>

import

(

'@/views/user-setting/user-password.vue'

), }, ] }, {

path

:

'/403'

,

component

:

()

=>

import

(

'@/views/error-page/403'

), }, {

path

:

'*'

,

component

:

()

=>

import

(

'@/views/error-page/404'

), }, ];/<code>

系統主要頁面的路由,後續會將這些路由經過權限篩選,添加到 home 路由的 children 裡面

<code> 

export

default

[ {

path

:

'/deploy-manage'

,

name

:

'deploy-manage'

,

component

:

()

=>

import

(

'@/views/sys-admin/deploy-manage/deploy-manage.vue'

),

meta

: {

permitName

:

'deploy-manage'

, } }, ];/<code>

2、用戶登錄

用戶進入登錄頁,輸入用戶名、密碼、驗證碼,點擊登錄,發送登錄請求,登錄成功之後,將 token 保存在 sessionStorage,然後跳轉到首頁 /home ,進入路由攔截的邏輯。

<code> 
 
vm.$http.login(params, 

data

=>

{ sessionStorage.token = data.token; vm.$router.push({ name:

'home'

}); },

err

=>

{

console

.log(err); });/<code>

3、全局路由攔截

首先從打開本地服務 http://localhost:2001 開始,打開後會進入 login 頁面,那麼判斷的依據是什麼?首先是 token。沒有登錄的用戶是拿不到 token 的,而登錄後的用戶我們會將 token 存到 seesionStorage,因此,根據當前有沒有 token 即可知道是否登錄。

<code> 

router

.beforeEach

((to, from, next) => {

if

(!sessionStorage.token) {

if

(to.matched.length >

0

&& !to.matched.some(item => item.meta.requiresAuth)) {

next

(); }

else

{

next

({

path

:

'/login'

}); } } else {

if

(!store.state.permission.permissionList) {

store

.dispatch

(

'permission/FETCH_PERMISSION'

)

.then

(() => {

next

({

path

: to.path,

query

: to.query }); }); }

else

{

if

(to.path !==

'/login'

) {

if

(to.matched.length ===

0

) {

next

({

path

:

'/403'

}); } else if (queryChange) {

next

({

name

: to.name,

params

: to.params,

query

: to.query }); } else if (sessionStorage.isSysLock ===

'true'

&& to.path !==

'/sys-lock'

) {

next

({

path

:

'/sys-lock'

}); } else {

next

(); } }

else

{

store

.commit

(

'goToLogin'

); } } } });/<code>

(1)當用戶打開 localhost,此時還沒有 token,匹配的是空路由,我們重定向到登錄頁 next({ path: '/login' });(2)用戶在登錄頁刷新頁面,也會進入路由攔截,此時匹配的是 login 路由,而 login 路由是不需要登錄驗證的(requiresAuth 為空或者 false),所以直接跳過執行 next();(3)用戶在登錄頁輸入了用戶名和密碼,登錄成功,保存了 token,跳轉到 /home 路由;(4)此時進入路由攔截,已經有 token了,但是還沒有用戶權限 permissionList,然後發請求獲取用戶權限列表,得到權限後 next({ path: to.path, query: to.query }); 繼續往下走;(5)再次進入路由攔截,此時有 token 和 permissionList 了,就可以根據實際業務進行跳轉了。上面的代碼是判斷當前是不是 login 路由,如果用戶登錄後手動在地址欄輸入 /login,則清除 token 跳轉到登錄頁。其他的邏輯就跟具體業務相關了,就不細講了。

4、處理用戶權限

處理用戶權限,在 store.js 定義一個模塊 permission.js,專門用於處理用戶權限相關的邏輯,用戶權限列表、菜單列表都保存在此模塊;來看看 permission.js 主要做了什麼:

<code> 
 

import

httpRequest

from

'@/assets/js/service/http'

;

import

handleModule

from

'@/assets/js/common/handle-module'

;

import

router, { dynamicRoutes }

from

'@/router/index'

;

import

permissionRouter

from

'@/router/router'

;

export

default

{

actions

: {

async

FETCH_PERMISSION({ commit, state }) { commit(

'setPermission'

, []);

let

data =

await

getUserByToken();

let

userPopedoms = data.userPopedoms || [];

let

userPopeList = userPopedoms.filter(

v

=>

v.requestMapping !==

'user-manage'

&& v.requestMapping !==

'login'

); commit(

'setUserPopedoms'

, userPopeList);

let

routes = handleModule.getRouter(userPopedoms, permissionRouter);

let

homeContainer = dynamicRoutes.find(

v

=>

v.path ===

'/'

); homeContainer.children = routes.concat(homeContainer.children); homeContainer.redirect = homeContainer.children[

0

].name;

let

sidebarMenu = handleModule.getSidebarMenu(userPopeList); commit(

'setMenu'

, sidebarMenu);

let

initialRoutes = router.options.routes; router.addRoutes(dynamicRoutes); commit(

'setPermission'

, [...initialRoutes, ...dynamicRoutes]); } }, };/<code>

(1)首先,let data = await getUserByToken(); 發請求獲取用戶權限,得到 data,data.userPopedoms 格式大致如下:

<code>[
  {
    

"moduleGroupId"

:

1001

,

"moduleGroupName"

:

"部署管理"

,

"requestMapping"

:

"deploy-manage"

, }, {

"moduleGroupId"

:

1100

,

"moduleGroupName"

:

"系統管理"

,

"requestMapping"

:

"sys-manage"

,

"moduleList"

: [ {

"moduleId"

:

1101

,

"moduleName"

:

"系統日誌"

,

"requestMapping"

:

"system-log"

,

"moduleGroupId"

:

1100

, }, {

"moduleId"

:

1102

,

"moduleName"

:

"系統告警"

,

"requestMapping"

:

"sys-alert"

,

"moduleGroupId"

:

1100

, }, ], } ]/<code>

(2)然後,根據我們寫好的路由數組,進行對比,過濾得到我們要的路由。路由格式在上文“路由定義”的 router/router.js 已經提到。還要根據用戶權限處理得到側邊欄菜單。

為此,我們需要兩個處理函數,一個根據用戶權限列表和路由數組過濾得到最終路由,另一個根據用戶權限處理得到側邊欄菜單。所以另外專門創建了一個文件 handle-module.js 存放這兩個函數。

<code> 

const

handleModule = { getRouter(permissionList = [], allRouter = []) {

let

permissions = permissionList.reduce(

(

acc, cur

) =>

{

if

(cur.moduleList && cur.moduleList.length >

0

) cur = cur.moduleList;

return

acc.concat(cur); }, []).map(

v

=>

v.requestMapping);

return

allRouter.filter(

item

=>

permissions.includes(item.meta.permitName)); }, getSidebarMenu(permissionList = []) {

let

sidebarMenu = []; permissionList.forEach(

item

=>

{

let

menuItem = {

name

: item.requestMapping,

title

: item.moduleGroupName, }; menuItem.children = (item.moduleList || []).map(

child

=>

({

name

: child.requestMapping,

title

: child.moduleName, })); sidebarMenu.push(menuItem); });

return

sidebarMenu; } };

export

default

handleModule;/<code>

(3)上面得到過濾後的路由數組後,加入到 path 為 '/' 的 children 下面

<code>{
        

path

:

'/'

,

name

:

'home'

,

component

:

()

=>

import

(

'@/views/home.vue'

),

meta

: {

requiresAuth

:

true

, },

children

: [ {

path

:

'/user-info'

,

name

:

'user-info'

,

component

:

()

=>

import

(

'@/views/user-setting/user-info.vue'

), }, ] }/<code>

(4)上面根據權限生成側邊欄菜單之後,保存在 store 待用。

(5)上面第三步將動態路由加入到 home 的 children 之後,就可以將 dynamicRoutes 加入到路由中了。router.addRoutes(dynamicRoutes);

(6)到了這裡,路由就添加完了,也就是 FETCH_PERMISSION 操作完畢了,就可以在 action.then 裡面調用 next({ path: to.path, query: to.query }); 進去路由,也就是進入 home。我們上面已經將 home 路由重定向為菜單的第一個路由信息,所以會進入系統菜單的第一個頁面。

刷新頁面後,根據 router.beforeEach 的判斷,有 token 但是沒有 permissionList ,會重新觸發 action 去發請求獲取用戶權限,之前的邏輯會重新走一遍,所以沒有問題。

退出登錄後,需要清除 token 並刷新頁面。因為是通過 addRoutes 添加路由的,而 vue-router 沒有刪除路由的 api,所以清除路由、清除 store 中存儲的各種信息,刷新頁面是最保險的。

相關文件的目錄截圖:

Vue3.0權限管理實現流程【實踐】

四、總結

缺點:全局路由守衛裡,每次路由跳轉都要做判斷;每次刷新頁面,需要重新發請求獲取用戶權限;退出登錄時,需要刷新一次頁面將動態添加的路由以及權限信息清空;

優點:菜單與路由分離,菜單的修改、添加、刪除由後端控制,利於後期維護;使用 addRoutes 動態掛載路由,可控制用戶不能在 url 輸入相關地址進行跳轉;

vue權限管理還有其他實現方式,大家可以根據實際業務考慮做調整,以上的實現方式是比較適合我們現有項目的需求的。以上,有問題歡迎提出交流,喜歡的話點個贊哦~

推薦Vue學習資料文章:

聊聊昨晚尤雨溪現場針對Vue3.0 Beta版本新特性知識點彙總

【新消息】Vue 3.0 Beta 版本發佈,你還學的動麼?

Vue真是太好了 壹萬多字的Vue知識點 超詳細!

Vue + Koa從零打造一個H5頁面可視化編輯器——Quark-h5

深入淺出Vue3 跟著尤雨溪學 TypeScript 之 Ref 【實踐】

手把手教你深入淺出vue-cli3升級vue-cli4的方法

Vue 3.0 Beta 和React 開發者分別槓上了

手把手教你用vue drag chart 實現一個可以拖動 / 縮放的圖表組件

Vue3 嚐鮮

總結Vue組件的通信

手把手讓你成為更好的Vue.js開發人員的12個技巧和竅門【實踐】

Vue 開源項目 TOP45

2020 年,Vue 受歡迎程度是否會超過 React?

尤雨溪:Vue 3.0的設計原則

使用vue實現HTML頁面生成圖片

實現全棧收銀系統(Node+Vue)(上)

實現全棧收銀系統(Node+Vue)(下)

vue引入原生高德地圖

Vue合理配置WebSocket並實現群聊

多年vue項目實戰經驗彙總

vue之將echart封裝為組件

基於 Vue 的兩層吸頂踩坑總結

Vue插件總結【前端開發必備】

Vue 開發必須知道的 36 個技巧【近1W字】

構建大型 Vue.js 項目的10條建議

深入理解vue中的slot與slot-scope

手把手教你Vue解析pdf(base64)轉圖片【實踐】

使用vue+node搭建前端異常監控系統

推薦 8 個漂亮的 vue.js 進度條組件

基於Vue實現拖拽升級(九宮格拖拽)

手摸手,帶你用vue擼後臺 系列二(登錄權限篇)

手摸手,帶你用vue擼後臺 系列三(實戰篇)

前端框架用vue還是react?清晰對比兩者差異

Vue組件間通信幾種方式,你用哪種?【實踐】

淺析 React / Vue 跨端渲染原理與實現

10個Vue開發技巧助力成為更好的工程師

手把手教你Vue之父子組件間通信實踐講解【props、$ref 、$emit】

1W字長文+多圖,帶你瞭解vue的雙向數據綁定源碼實現

深入淺出Vue3 的響應式和以前的區別到底在哪裡?【實踐】

乾貨滿滿!如何優雅簡潔地實現時鐘翻牌器(支持JS/Vue/React)

基於Vue/VueRouter/Vuex/Axios登錄路由和接口級攔截原理與實現

手把手教你D3.js 實現數據可視化極速上手到Vue應用

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【上】

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【下】

作者:lxcan

轉發鏈接:https://segmentfault.com/a/1190000022431839


分享到:


相關文章: