> 文章列表 > vue-element-admin 动态菜单改造

vue-element-admin 动态菜单改造

vue-element-admin 动态菜单改造

vue-element-admin 动态菜单改造

vue-element-admin 是一款优秀后台前端解决方案,它基于 vue 和 element-ui实现。开源后台管理系统解决方案项目 Boot-admin的前端模块就是基于vue-element-admin开发而来。

作为一款纯前端的后台界面解决方案,vue-element-admin是通过遍历路由进行渲染,从而得到菜单列表的,我们可以在 router.js 中看到相关代码,即是路由也是菜单。

改造思路:实现前后端分离要求,服务端控制菜单是否显示,前端控制路由信息定义。前端开发时不需要找服务端来新增路由信息,后端不需要关心前端路由的父/子关系、图标等定义信息。

第1步.定义路由

在 src/router/index.js 中将不需要后台控制的路由定义在 constantRoutes 中,如 /login /404 等;而需要后台控制是否显示的路由定义在 asyncRoutes 中。asyncRoutes 中每个节点都添加 srvName 属性,通过它来和服务端返回的菜单信息进行关联。

import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)/* Layout */
import Layout from '@/layout'/* 外部路由文件 */
import sysManageRouter from './modules/sysmanage.js'
import codeGeneratorRouter from './modules/codegenerator.js'
import myWorkRouter from './modules/mywork.js'/*** 同步路由* 不需要后台权限控制的路由,所有角色均可操作*/
export const constantRoutes = [{path: '/redirect',component: Layout,hidden: true,children: [{path: '/redirect/:path(.*)',component: () => import('@/views/redirect/index')}]
},
{path: '/login',component: () => import('@/views/login/index'),hidden: true
},
{path: '/auth-redirect',component: () => import('@/views/login/auth-redirect'),hidden: true
},
{path: '/404',component: () => import('@/views/error-page/404'),hidden: true
},
{path: '/401',component: () => import('@/views/error-page/401'),hidden: true
},
{path: '/',component: Layout,redirect: '/dashboard',children: [{path: 'dashboard',component: () => import('@/views/dashboard/index'),name: 'Dashboard',meta: {title: '仪表板',icon: 'dashboard',affix: true}}]
},
]/*** 异步路由* 基于后台动态控制的路由*/
export const asyncRoutes = [/** 引入系统管理路由模块 **/sysManageRouter,/** 引入代码生成路由模块 **/codeGeneratorRouter,/** 引入工作流路由模块 **/myWorkRouter,// 404 page must be placed at the end !!!{path: '*',redirect: '/404',hidden: true}
]const createRouter = () => new Router({scrollBehavior: () => ({y: 0}),routes: constantRoutes
})const router = createRouter()
export function resetRouter() {const newRouter = createRouter()router.matcher = newRouter.matcher // reset router
}export default router

在 src/router/modules 目录下,新建路由子模块文件

  • 系统管理 sysmanage.js
  • 代码生成 codegenerator.js
  • 工作流 mywork.js

sysmanage.js内容如下:

import Layout from '@/layout'const sysManageRouter = {path: '/manage',name: 'SysManage',component: Layout,redirect: '/manage/basemanage/dictionary',srvName: '/api/system/auth/manage',meta: {title: '系统管理',icon: 'example'},children: [{path: 'basemanage',name: 'BaseManage',srvName: '/api/system/auth/manage/basemanage',component: () => import('@/views/manage/basemanage/index'),redirect: '/manage/basemanage/dictionary',meta: {title: '基础管理',icon: 'tree'},children: [{path: 'dictionary',name: 'DicManage',srvName: '/api/system/auth/manage/basemanage/dictionary',component: () => import('@/views/manage/basemanage/dictionary/index'),meta: {title: '字典管理',icon: 'tree'}},{path: 'region',name: 'DivManage',srvName: '/api/system/auth/manage/basemanage/region',component: () => import('@/views/manage/basemanage/region/index'),meta: {title: '区域管理',icon: 'tree'}},{path: 'organization',name: 'OrgManage',srvName: '/api/system/auth/manage/basemanage/organization',component: () => import('@/views/manage/basemanage/organization/index'),meta: {title: '组织管理',icon: 'tree'}},{path: 'employee',name: 'EmpManage',srvName: '/api/system/auth/manage/basemanage/employee',component: () => import('@/views/manage/basemanage/employee/index'),meta: {title: '人员管理',icon: 'tree'}}]},{path: 'authmanage',name: 'AuthManage',srvName: '/api/system/auth/manage/authmanage',component: () => import('@/views/manage/authmanage/index'),redirect: '/manage/authmanage/menu',meta: {title: '权限管理',icon: 'tree'},children: [{path: 'menu',name: 'MenuManage',srvName: '/api/system/auth/manage/authmanage/menu',component: () => import('@/views/manage/authmanage/menu'),meta: {title: '菜单管理',icon: 'table'}}, {path: 'resource',name: 'ResourceManage',srvName: '/api/system/auth/manage/authmanage/resource',component: () => import('@/views/manage/authmanage/resource'),meta: {title: '功能管理',icon: 'table'}}, {path: 'user',name: 'UserManage',srvName: '/api/system/auth/manage/authmanage/user',component: () => import('@/views/manage/authmanage/user'),meta: {title: '用户管理',icon: 'tree'}},{path: 'role',name: 'RoleManage',srvName: '/api/system/auth/manage/authmanage/role',component: () => import('@/views/manage/authmanage/role'),meta: {title: '角色管理',icon: 'tree'}}, {path: 'userofrole',name: 'UserOfRoleManage',srvName: '/api/system/auth/manage/authmanagele/userofrole',component: () => import('@/views/manage/authmanage/userofrole'),meta: {title: '角色-用户',icon: 'tree'}}, {path: 'resourceofrole',name: 'ResourceOfRoleManage',srvName: '/api/system/auth/manage/authmanage/resourceofrole',component: () => import('@/views/manage/authmanage/resourceofrole/index'),meta: {title: '角色-功能',icon: 'tree'}}]},{path: 'operationmanage',name: 'OperationManage',srvName: '/api/system/auth/manage/operationmanage',component: () => import('@/views/manage/operationmanage/index'),meta: {title: '运行管理',icon: 'tree'},children: [{path: 'online',name: 'OnlineManage',srvName: '/api/system/auth/manage/operationmanage/online',component: () => import('@/views/manage/operationmanage/online/index'),meta: {title: '在线用户',icon: 'tree'}},{path: 'job',name: 'JobManage',srvName: '/api/system/auth/manage/operationmanage/job',component: () => import('@/views/manage/operationmanage/job/index'),meta: {title: '定时任务',icon: 'tree'}},{path: 'task',name: 'TaskManage',srvName: '/api/system/auth/manage/operationmanage/task',component: () => import('@/views/manage/operationmanage/task/index'),meta: {title: '流程任务',icon: 'tree'}},{path: 'histask',name: 'HisTaskManage',srvName: '/api/system/auth/manage/operationmanage/task/his',component: () => import('@/views/manage/operationmanage/histask/index'),meta: {title: '历史任务',icon: 'tree'}},{path: 'log',name: 'LogManage',srvName: '/api/system/auth/manage/operationmanage/log',component: () => import('@/views/manage/operationmanage/log/index'),meta: {title: '系统日志',icon: 'tree'}},{path: 'nacos',name: 'nacos',srvName: '/api/system/auth/manage/operationmanage/nacos',component: () => import('@/views/manage/operationmanage/nacos/index'),meta: {title: 'Nacos',icon: 'link'}},{path: 'admin',name: 'admin',srvName: '/api/system/auth/manage/operationmanage/admin',component: () => import('@/views/manage/operationmanage/admin/index'),meta: {title: 'Admin',icon: 'link'}}]},{path: 'definitionmanage',name: 'DefManage',srvName: '/api/system/auth/manage/definitionmanage',component: () => import('@/views/manage/definitionmanage/index'),meta: {title: '定义管理',icon: 'tree'},children: [{path: 'model',name: 'ModelManage',srvName: '/api/system/auth/manage/definitionmanage/model',component: () => import('@/views/manage/definitionmanage/model/index'),meta: {title: '模型管理',icon: 'tree'}},{path: 'process',name: 'ProcessManage',srvName: '/api/system/auth/manage/definitionmanage/process',component: () => import('@/views/manage/definitionmanage/process/index'),meta: {title: '流程管理',icon: 'tree'}},{path: 'drools',name: 'DroolsManage',srvName: '/api/system/auth/manage/definitionmanage/drools',component: () => import('@/views/manage/definitionmanage/drools/index'),meta: {title: '规则管理',icon: 'tree'}}]},{path: 'datamaintain',name: 'DataMaintain',srvName: '/api/system/auth/manage/datamaintain',component: () => import('@/views/manage/datamaintain/index'),meta: {title: '数据处理',icon: 'tree'},children: [{path: 'sqlinput',name: 'SqlInput',srvName: '/api/system/auth/manage/datamaintain/sqlinput',component: () => import('@/views/manage/datamaintain/sqlinput/index'),meta: {title: '提交',icon: 'tree'}}, {path: 'sqlexec',name: 'sqlexec',srvName: '/api/system/auth/manage/datamaintain/sqlexec',component: () => import('@/views/manage/datamaintain/sqlexec/index'),meta: {title: '执行',icon: 'tree'}}]}]
}
export default sysManageRouter

第2步.服务端接口定义

服务端接口返回数据格式如下:

@Data
public class MenuDTO {private String id;private String srvName;private Boolean show;private String accessControlStyle;
}

节点中 srvName 和前端的路由进行匹配,通过 show 属性来确定显示或隐藏。
服务端无需关心菜单的子/父级关系,只需要将所有的菜单信息输出一个数组即可。

    @GetMapping("/auth/user/menu")public List<MenuDTO> getMenus() throws Exception{BaseUser baseUser = UserTool.getBaseUser();List<MenuDTO> menuDTOList = resourceDataGetter.getMyselfMenuList(baseUser);return menuDTOList;}

第3步.定义 api 请求接口

在 src/api/ 目录下创建 menus.js

import request from '@/utils/request'
export function getMenus(token) {return request({url: '/api/system/auth/user/menu',method: 'get',params: { token }})
}

第4步.配置 store 调用

新增文件 src/store/modules/menus.js

import {Message
} from 'element-ui'
import {getMenus
} from '@/api/menu'
import {getToken
} from '@/utils/auth'
import {asyncRoutes
} from '@/router/index'const getDefaultState = () => {return {token: getToken(),menuList: []}
}const state = getDefaultState()const mutations = {SET_MENUS: (state, menus) => {state.menuList = menus}
}// 动态菜单定义在前端,后台只会返回有权限的菜单列表,通过遍历服务端的菜单数据,没有的将对于菜单进行隐藏,前端新增页面无需先通过服务端进行菜单添加,遵循了前后端分离原则
export function generaMenu(routes, srvMenus) {for (let i = 0; i < routes.length; i++) {const routeItem = routes[i]var showItem = falsefor (let j = 0; j < srvMenus.length; j++) {const srvItem = srvMenus[j]// 前后端数据通过 srvName 属性来匹配if (routeItem.srvName !== undefined && routeItem.srvName === srvItem.srvName && srvItem.show === true) {showItem = trueroutes[i]['hidden'] = falsebreak}}if (showItem === false) {routes[i]['hidden'] = true}if (routeItem['children'] !== undefined && routeItem['children'].length > 0) {generaMenu(routes[i]['children'], srvMenus)}}
}const actions = {getMenus({commit}) {return new Promise((resolve, reject) => {getMenus(state.token).then(response => {if (response.code !== 100) {Message({message: response.message,type: 'error',duration: 5 * 1000})reject(response.message)}const {data} = responseif (!data) {reject('Verification failed, please Login again.')}const srvMenus = datavar pushRouter = asyncRoutesgeneraMenu(pushRouter, srvMenus)commit('SET_MENUS', pushRouter)resolve()}).catch(error => {reject(error)})})}
}export default {namespaced: true,state,mutations,actions
}

第5步.修改路由钩子,渲染动态菜单

修改src/permission.js文件

import router from './router'
import store from './store'
import { Message } from 'element-ui'
import NProgress from 'nprogress' // progress bar
import 'nprogress/nprogress.css' // progress bar style
import { getToken } from '@/utils/auth' // get token from cookie
import getPageTitle from '@/utils/get-page-title'NProgress.configure({ showSpinner: false }) // NProgress Configurationconst whiteList = ['/login', '/auth-redirect'] // no redirect whitelistrouter.beforeEach(async(to, from, next) => {// start progress barNProgress.start()// set page titledocument.title = getPageTitle(to.meta.title)// determine whether the user has logged inconst hasToken = getToken()if (hasToken) {if (to.path === '/login') {// if is logged in, redirect to the home pagenext({ path: '/' })NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939} else {// determine whether the user has obtained his permission roles through getInfoconst hasRoles = store.getters.roles && store.getters.roles.length > 0if (hasRoles) {next()} else {try {const { roles } = await store.dispatch('user/getInfo')// 获取菜单await store.dispatch('menu/getMenus')// 生成动态路由const accessRoutes = await store.dispatch('permission/generateRoutes', roles)// 添加动态路由router.addRoutes(accessRoutes)next({ ...to, replace: true })} catch (error) {await store.dispatch('user/resetToken')Message.error(error || 'Has Error')next(`/login?redirect=${to.path}`)NProgress.done()}}}} else {if (whiteList.indexOf(to.path) !== -1) {next()} else {next(`/login?redirect=${to.path}`)NProgress.done()}}
})router.afterEach(() => {NProgress.done()
})

关键代码:

// 在完成登录获取到用户信息后,开始从获取菜单
await store.dispatch('menu/getMenus')
// 生成动态路由
const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
// 添加动态路由
router.addRoutes(accessRoutes)

改造完成,效果如下:

vue-element-admin 动态菜单改造