vue element admin 動態路由,后端thinkphp5.1托管權限和路由


轉載請注明出處 https://www.cnblogs.com/leesen/p/14640405.html,或將追究侵權責任

一、背景

 

背景部分引自 vue element admin 花褲衩

先說一說我權限控制的主體思路,前端會有一份路由表,它表示了每一個路由可訪問的權限。當用戶登錄之后,通過 token 獲取用戶的 role ,動態根據用戶的 role 算出其對應有權限的路由,再通過router.addRoutes動態掛載路由。但這些控制都只是頁面級的,說白了前端再怎么做權限控制都不是絕對安全的,后端的權限驗證是逃不掉的。

我司現在就是前端來控制頁面級的權限,不同權限的用戶顯示不同的側邊欄和限制其所能進入的頁面(也做了少許按鈕級別的權限控制),后端則會驗證每一個涉及請求的操作,驗證其是否有該操作的權限,每一個后台的請求不管是 get 還是 post 都會讓前端在請求 header里面攜帶用戶的 token,后端會根據該 token 來驗證用戶是否有權限執行該操作。若沒有權限則拋出一個對應的狀態碼,前端檢測到該狀態碼,做出相對應的操作。


作者:花褲衩
鏈接:https://juejin.cn/post/6844903478880370701
來源:掘金
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
 
 
二、后端部分
 
 
既然后端托管,那么后端除了用戶表之外,就還需要菜單表和身份表
mysql表結構如下:
菜單表
CREATE TABLE `cmf_ls_menu` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `path` varchar(255) DEFAULT '#',
  `pid` int(11) DEFAULT '0',
  `url` varchar(255) DEFAULT '#',
  `redirect` varchar(255) DEFAULT '#',
  `hidden` varchar(10) DEFAULT NULL,
  `alwaysShow` varchar(10) DEFAULT NULL,
  `meta` text,
  `component` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=12 DEFAULT CHARSET=utf8;

*菜單表的各個字段對應的是前端route/index.js對應的格式,字段意義同前端,其中pid表示菜單上級的id,頂級菜單為0

 

用戶表

CREATE TABLE `cmf_ls_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `username` varchar(255) DEFAULT NULL,
  `avatar` varchar(255) DEFAULT NULL,
  `name` varchar(255) DEFAULT NULL,
  `password` varchar(255) DEFAULT NULL,
  `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  `roles` varchar(255) DEFAULT '[]',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8;

*其中 roles 是 cmf_ls_user_role 表的id的集合,這里是用一個字段簡單實現,復雜業務可能需要有一張用戶和身份的對照表

 

身份表

CREATE TABLE `cmf_ls_user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) DEFAULT NULL,
  `description` varchar(255) DEFAULT NULL,
  `menu_ids` varchar(255) DEFAULT NULL,
  `create_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

*menu_ids 是 cmf_ls_menu 表的id的集合

 

其余php控制器、模型等就不在貼出來了,以下把主要的前端獲取動態路由的返回的數據格式貼出來:

{
    "code": 200,
    "msg": "",
    "data": [{
        "id": 1,
        "name": "users",
        "path": "/users",
        "pid": 0,
        "url": "#",
        "redirect": "/users/list",
        "hidden": false,
        "alwaysShow": false,
        "meta": {
            "icon": "el-icon-s-help",
            "status": true,
            "title": "RBAC權限管理"
        },
        "component": "#",
        "label": "RBAC權限管理",
        "children": [{
            "id": 2,
            "name": "userlist",
            "path": "userlist",
            "pid": 1,
            "url": "#",
            "redirect": "#",
            "hidden": false,
            "alwaysShow": false,
            "meta": {
                "icon": "el-icon-s-help",
                "status": true,
                "title": "用戶列表"
            },
            "component": "/rbac/userlist",
            "label": "用戶列表",
            "children": []
        }, {
            "id": 3,
            "name": "menulist",
            "path": "menulist",
            "pid": 1,
            "url": "#",
            "redirect": "#",
            "hidden": false,
            "alwaysShow": false,
            "meta": {
                "icon": "el-icon-s-help",
                "status": true,
                "title": "菜單管理"
            },
            "component": "/rbac/menulist",
            "label": "菜單管理",
            "children": []
        }, {
            "id": 10,
            "name": "RoleList",
            "path": "rolelist",
            "pid": 1,
            "url": "#",
            "redirect": "#",
            "hidden": false,
            "alwaysShow": false,
            "meta": {
                "icon": "",
                "status": true,
                "title": "身份管理"
            },
            "component": "/rbac/rolelist",
            "label": "身份管理",
            "children": []
        }]
    }, {
        "id": 4,
        "name": "icon",
        "path": "/icon",
        "pid": 0,
        "url": "#",
        "redirect": "#",
        "hidden": false,
        "alwaysShow": false,
        "meta": {
            "icon": "icon",
            "status": true,
            "title": "icon"
        },
        "component": "#",
        "label": "icon",
        "children": [{
            "id": 9,
            "name": "Icons",
            "path": "index",
            "pid": 4,
            "url": "#",
            "redirect": "#",
            "hidden": false,
            "alwaysShow": false,
            "meta": {
                "icon": "icon",
                "status": true,
                "title": "Icons"
            },
            "component": "/icons/index",
            "label": "Icons",
            "children": []
        }]
    }, {
        "id": 5,
        "name": "Permission",
        "path": "/permission",
        "pid": 0,
        "url": "#",
        "redirect": "/permission/page",
        "hidden": false,
        "alwaysShow": false,
        "meta": {
            "icon": "lock",
            "status": true,
            "title": "Permission"
        },
        "component": "#",
        "label": "Permission",
        "children": [{
            "id": 6,
            "name": "PagePermission",
            "path": "page",
            "pid": 5,
            "url": "#",
            "redirect": "#",
            "hidden": false,
            "alwaysShow": false,
            "meta": {
                "icon": "",
                "status": true,
                "title": "Page Permission"
            },
            "component": "/permission/page",
            "label": "Page Permission",
            "children": []
        }, {
            "id": 7,
            "name": "DirectivePermission",
            "path": "directive",
            "pid": 5,
            "url": "#",
            "redirect": "#",
            "hidden": false,
            "alwaysShow": false,
            "meta": {
                "icon": "",
                "status": true,
                "title": "Directive Permission"
            },
            "component": "/permission/directive",
            "label": "Directive Permission",
            "children": []
        }]
    }]
}

php部分實現了無限級菜單,用戶多選身份,身份下配置菜單權限,從而達到不同身份不同菜單的結果。

 

 

三、前端部分

 

首先前端需要把/src/router/index.js 中的asyncRouters即動態路由部分清空

export const asyncRoutes = [];

然后在/src/api/user.js中添加如下方法,定義一個獲取后端動態菜單的接口,接口路徑和參數換成你自己的

export function getAuthMenu(data){
  return request({
    url: '/ls/menu/getAuthMenuByRoles',
    method: 'post',
    data
  })
}

下面是/src/store/modules/permission.js的完整代碼

import { asyncRoutes, constantRoutes } from '@/router'
import { getAuthMenu } from '@/api/user'
import Layout from '@/layout'

/**
 * Use meta.role to determine if the current user has permission
 * @param roles
 * @param route
 */
function hasPermission(roles, route) {
  if (route.meta && route.meta.roles) {
    return roles.some(role => route.meta.roles.includes(role))
  } else {
    return true
  }
}

export const loadView = view => {
  // 路由懶加載
  return resolve => require(['@/views' + view], resolve)
}
/**
 * 后台查詢的菜單數據拼裝成路由格式的數據
 * @param routes
 */
export function generaMenu(routes, data) {
  data.forEach(item => {
    const menu = {
      path: item.path,
      component: item.component === '#' ? Layout : loadView(item.component),
      hidden: item.hidden,
      children: [],
      name : item.name,
      meta: item.meta
    }
    if(item.pid == 0){
      menu.redirect = item.redirect;
    }

    if (item.children) {
      generaMenu(menu.children, item.children)
    }
    routes.push(menu)
  })
  routes.push({ path: '*', redirect: '/404', hidden: true })
}

/**
 * Filter asynchronous routing tables by recursion
 * @param routes asyncRoutes
 * @param roles
 */
export function filterAsyncRoutes(routes, roles) {
  const res = []

  routes.forEach(route => {
    const tmp = { ...route }
    if (hasPermission(roles, tmp)) {
      if (tmp.children) {
        tmp.children = filterAsyncRoutes(tmp.children, roles)
      }
      res.push(tmp)
    }
  })

  return res
}

const state = {
  routes: [],
  addRoutes: []
}

const mutations = {
  SET_ROUTES: (state, routes) => {
    state.addRoutes = routes
    state.routes = constantRoutes.concat(routes)
  }
}

const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      const loadMenuData = []
      getAuthMenu(state.token).then(response => {
        let data = response
        if (response.code !== 200) {
          alert(JSON.stringify('菜單數據加載異常'))
        } else {
          data = response.data
          Object.assign(loadMenuData, data)
          const tempAsyncRoutes = Object.assign([], asyncRoutes)
          generaMenu(tempAsyncRoutes, loadMenuData)
          let accessedRoutes
          if (roles.includes('admin')) {
            accessedRoutes = tempAsyncRoutes || []
          } else {
            accessedRoutes = filterAsyncRoutes(tempAsyncRoutes, roles)
          }
          commit('SET_ROUTES', accessedRoutes)
          resolve(accessedRoutes)
        }
      }).catch(error => {
        console.log(error)
      })
    })
  }
}

export default {
  namespaced: true,
  state,
  mutations,
  actions
}

里面的 generateRoutes 方法 和 generaMenu 方法是用來獲取后端返回的菜單,然后處理成前端需要的router的格式

 

 

最后是后台菜單界面截圖和說明

用戶管理

 

 身份設置

 

 身份管理

 

身份權限設置

 

 這里使用了 el-tree ,選中菜單后給后台傳遞的就是,tree.getCheckedKeys(),也就是包含各個選中菜單的id的集合(子級全選那么父級也會選中,子級沒有全選那么父級會是半選,此時獲取的選中key里就不會有父級,我們就將這個數據原樣傳遞給后台。后台返回的動態菜單里,需要遞歸往上根據菜單PID判斷查找父級的id,最終實現即時子菜單不是全選的,返回的菜單里也有父級,當然子菜單一個都沒選的話,父級肯定也不需要了,否則前端的菜單是不會展示沒父級的選中菜單的)

 

 

然后是菜單的管理

 

 

 

 pid因為是無限級,所以后台需要返回如下樹結構

 

 

 其中 頂級菜單 是前端固定拼到后台給的樹結構中的,可能會如下圖

 

 

其中splpre字段就是子菜單的前綴,頂級無前綴,下級有一個 ' - ',再下級就是 ‘ - - ’,以此類推(這里是后端給的,主要為了讓樹結構看起來清晰)

 

需要特別說明的是添加菜單這里,如果是頂級菜單無下級,那么肯定需要在component里填上組件的路徑,如文件路徑 /src/view/user/index.vue ,就需要在component字段寫上 /user/index,因為permission.js中loadview方法拼接了模板;如果是父級菜單有下級,那么component填上#就行。

另外需要特別說明的是 redirect字段,如果是頂級菜單有下級,則需要填上默認能跳轉到子菜單的path。

 

 

 

如果前端改完后報 Module build failed (from ./node_modules/eslint-loader/index.js): TypeError: Cannot read property 'range' of null,

一般是由下圖部分引起的(如果使用() => import(`@/views${item.component}`)導入的話)

 

 本人親測,通過對babel-eslint 降級到7.2.3成功處理這個問題。。。具體方法百度

 

至此簡易的后台配置vue element admin 動態菜單 動態路由 動態權限 就完成了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM