vue-element-admin實戰 | 第二篇: 最小改動接入後台實現根據權限動態加載菜單

一. 前言

本篇基於 有來商城 youlai-mall 微服務項目,通過對vue-element-admin的權限菜單模塊理解個性定製其後台接口,實現對vue-element-admin工程幾乎不做改動的情況下,無縫接入後台接口實現動態權限菜單的加載。

在進行接下來的工作前,我們需要對原生的vue-element-admin項目改造,移除mock連通後台接口,具體可參考我這篇文章 vue-element-admin實戰 | 第一篇: 移除mock接入後台,搭建有來商城youlai-mall前後端分離管理平台,如果在過程中有遇到問題,歡迎下方留言。

二. 前端調整

至於上文提到的對vue-element-admin幾乎不做改動便可實現我們此篇文章的目的是不是我在扯,決定權給各位,我把對vue-element-admin項目改動的地方通過比對工具比對截圖放上來。

先聲明vue-element-admin此次改動的地方除了一個獲取權限菜單的接口之外,剩餘的改動全在 src/store/modules/permission.js 文件中。

看到了嗎,可以說僅對vue-element-admin做兩處改動,再加上對getRoutes調用後台接口返回的菜單數據的分析,就可以理解和實現動態權限菜單的加載了。

改動代碼片段 + 注釋說明

 import {list as getRoutes} from '@/api/admin/menu'
 import Layout from '@/layout'
   
 generateRoutes({commit}, roles) {
   return new Promise(resolve => {
     // 請求後台數據替換src/router/index.js的asyncRoutes異步路由
     getRoutes({mode: 3}).then(response => {
       // filterAsyncRoutes方法作權限過濾和數據轉換,roles為登錄用戶角色ID集合,如:[1,2]
       let accessedRoutes = filterAsyncRoutes(response.data, roles)
       commit('SET_ROUTES', accessedRoutes)
       resolve(accessedRoutes)
     })
   })
 }
 
 // 遞歸權限過濾和數據轉換
 export function filterAsyncRoutes(routes, roles) {
    const res = []
    routes.forEach(route => {
      const tmp = {...route}
      if (hasPermission(roles, tmp)) {
        const component = tmp.component
        if (route.component) {
          if (component == 'Layout') {
            tmp.component = Layout
          } else {
            // 接口組件字符串轉換成組件對象
            tmp.component = (resolve) => require([`@/views/${component}`], resolve)
          }
          if (tmp.children) {
            tmp.children = filterAsyncRoutes(tmp.children, roles)
          }
        }
        res.push(tmp)
      }
    })
    return res
 } 
  

三. 後端接口

1. 接口數據分析

接下來通過後台接口替換配置在src/router/index.js文件中asyncRoutes異步路由。

首先分析下異步路由的數據結構:

通過對上圖異步路由的數據觀察和了解,得出以下幾點:

  1. path在子路由中前面沒有反斜杠’/’
  2. 根菜單的alwaysShow為true
  3. component組件需通過import完成編譯時導入,接口只能返回組件路徑字符串,所以這裡在接口請求完成後必須有一個組件路徑字符串到組件對象的轉換過程
  4. meta的roles屬性對應的是有該路由訪問權限角色唯一標識的集合,這裡我使用的是角色ID

2. 接口實現

完成以上的數據分析,接下來就是接口的具體實現了,有關SQL和完整代碼請點擊 有來商城 youlai-mall 拉取即可,以下僅僅貼出關鍵代碼和分析。

SysMenuMapper

獲取菜單列表和對應訪問權限的角色ID的集合

SysMenuServiceImpl

將菜單轉換成路由,遞歸生成父子結構樹

SysMenuController

REST對外提供接口

3. 接口測試

使用接口測試工具測試, //localhost:9999/youlai-admin/menus?mode=3 ,這裡通過網關方式訪問,詳情請知悉有來商城 youlai-mall 項目。

完整返回數據,看看是不是很匹配asyncRoutes異步路由的數據格式了

{
    "code": "00000",
    "data": [{
        "path": "/admin",
        "component": "Layout",
        "alwaysShow": true,
        "name": "系統管理",
        "meta": {
            "title": "系統管理",
            "icon": "documentation",
            "roles": [2, 1]
        },
        "children": [{
            "path": "user",
            "component": "admin/user",
            "alwaysShow": false,
            "name": "用戶管理",
            "meta": {
                "title": "用戶管理",
                "icon": "user",
                "roles": [1]
            }
        }, {
            "path": "role",
            "component": "admin/role",
            "alwaysShow": false,
            "name": "角色管理",
            "meta": {
                "title": "角色管理",
                "icon": "peoples",
                "roles": [2, 1]
            }
        }, {
            "path": "dept",
            "component": "admin/dept",
            "alwaysShow": false,
            "name": "部門管理",
            "meta": {
                "title": "部門管理",
                "icon": "tree",
                "roles": [1, 2]
            }
        }, {
            "path": "menu",
            "component": "admin/menu",
            "alwaysShow": false,
            "name": "菜單管理",
            "meta": {
                "title": "菜單管理",
                "icon": "tree-table",
                "roles": [1, 2]
            }
        }, {
            "path": "dict",
            "component": "admin/dict",
            "alwaysShow": false,
            "name": "字典管理",
            "meta": {
                "title": "字典管理",
                "icon": "education",
                "roles": [1, 2]
            }
        }]
    }],
    "msg": "一切ok"
}

最後要做的就是將組件(component)路徑字符串轉換成組件對象即可,再次貼出在上文permission.js改造的代碼:

 if (component == 'Layout') {
    tmp.component = Layout
  } else {
    // 接口組件字符串轉換成組件對象
    tmp.component = (resolve) => require([`@/views/${component}`], resolve)
  }

有一點需要注意的是上面組件動態導入不能使用import,如下:

 tmp.component = () => import(`@/views/${component}`)

不然會報以下錯誤

Error: Cannot find module '@/views/***'

原因是webpack不支持import動態導入了,但是之前確實是可以的,所以有些讓人想不通,這裡使用require動態導入組件即可。

4. 菜單權限測試

參看當前用戶擁有的角色ID為2

用戶管理要求只有角色ID為1才能有其訪問權限

測試下用戶登錄控制台能否看到「用戶管理」菜單

怎麼樣,只擁有角色ID為2的角色是看不到需要角色ID為1才能訪問的「用戶管理」菜單的,也就證明了我們成功通過接入後台接口實現了權限菜單的動態加載的目的。

四. 結語

其實有個問題值得去思考下的。為什麼我們在從後台獲取全部菜單列表的時候需要關聯查詢出有對應訪問權限的角色ID的集合,而不是說動態傳入當前登錄用戶的信息去查詢該用戶擁有的菜單集合呢?這其實是兩種實現方式,區別在於第一種每個用戶都要獲取全部菜單數據,而第二種方式針對每個用戶加載其擁有權限的菜單數據。單看的話第二種方式肯定優於第一種,但這裡我們選用的是第一種加載全部菜單數據。如果你結合緩存去看的話自然就想的通了,所有用戶共享緩存的同一份菜單數據,還是說緩存針對每個用戶存放一份菜單緩存又或者用戶每次都去請求數據庫。後面計劃把菜單數據存放到緩存Redis中然後做一個分析比較。

附完整代碼:

有來商城後端:youlai-mall

有來商城前端:youlai-mall-admin-web

寫了這麼多其實也是想給自己的項目打個廣告,更希望對大家有所幫助,對於技術人來說少走彎路真的很重要,喜歡的朋友給個star,對我來說這份幫助更是自己繼續下去的動力,所以謝謝了。有啥問題下方留言,或直接聯繫我(微信號:haoxianrui)。