超然楼 超然楼
首页
开源
分享
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
关于
友链
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

soft1314

首页
开源
分享
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
关于
友链
收藏
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 技术

    • 开源后台管理系统解决方案 boot-admin 简介
    • vue-element-admin动态菜单改造
      • Springboot整合Flowable6.x导出bpmn20
      • Flowable导出查看跟踪流程图(1)
      • Flowable导出查看跟踪流程图(2)
      • 整合flowable官方editor-app源码BPMN2建模(1)
      • 整合flowable官方editor-app源码BPMN2建模(2)
      • Oracle逻辑备份exp导出指定表名时需要加括号吗?
      • boot-admin整合Quartz实现动态管理定时任务
      • boot-admin整合Liquibase实现数据库版本管理
      • boot-admin开源项目中有关后端参数校验的最佳实践
      • boot-admin项目数据库缺省字段设计之最佳实践
      • 代码审计工具Fortify基本使用
      • 记一次Oracle归档日志异常增长问题的排查过程
      • 填一个Mybatis-plus动态数据源切换失效的坑
      • Springboot使用AOP编程简介
      • Oracle也有回收站
      • 使用OpenFeign传递二进制流
      • 使用 Spring Security 保护您的 WebFlux 应用程序
    • 生活

    • 思考

    • 博客
    • 技术
    Soft1314
    2023-04-14
    目录

    vue-element-admin动态菜单改造

    vue-element-admin (opens new window) 是一款优秀后台前端解决方案,它基于 vue 和 element-ui实现。Boot-admin (opens new window)的前端模块就是基于vue-element-admin开发而来。

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

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

    源码仓库

    Github (opens new window) Gitee (opens new window)

    # 第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
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98

    在 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
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    215
    216
    217
    218
    219
    220
    221
    222
    223
    224
    225
    226
    227
    228
    229
    230
    231
    232
    233
    234
    235
    236
    237
    238
    239
    240
    241
    242
    243
    244
    245
    246
    247
    248
    249
    250
    251
    252
    253
    254
    255
    256
    257
    258
    259
    260
    261
    262
    263
    264
    265
    266
    267
    268
    269
    270
    271
    272
    273
    274
    275
    276
    277
    278
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288

    # 第2步.服务端接口定义

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

    @Data
    public class MenuDTO {
        private String id;
        private String srvName;
        private Boolean show;
        private String accessControlStyle;
    }
    
    1
    2
    3
    4
    5
    6
    7

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

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

    # 第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 }
      })
    }
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 第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 = false
        for (let j = 0; j < srvMenus.length; j++) {
          const srvItem = srvMenus[j]
    
          // 前后端数据通过 srvName 属性来匹配
          if (routeItem.srvName !== undefined && routeItem.srvName === srvItem.srvName && srvItem.show === true) {
            showItem = true
            routes[i]['hidden'] = false
            break
          }
        }
        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
            } = response
            if (!data) {
              reject('Verification failed, please Login again.')
            }
    
            const srvMenus = data
            var pushRouter = asyncRoutes
            generaMenu(pushRouter, srvMenus)
            commit('SET_MENUS', pushRouter)
            resolve()
          }).catch(error => {
            reject(error)
          })
        })
      }
    }
    
    export default {
      namespaced: true,
      state,
      mutations,
      actions
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94

    # 第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 Configuration
    
    const whiteList = ['/login', '/auth-redirect'] // no redirect whitelist
    
    router.beforeEach(async(to, from, next) => {
      // start progress bar
      NProgress.start()
    
      // set page title
      document.title = getPageTitle(to.meta.title)
    
      // determine whether the user has logged in
      const hasToken = getToken()
    
      if (hasToken) {
        if (to.path === '/login') {
          // if is logged in, redirect to the home page
          next({ path: '/' })
          NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
        } else {
          // determine whether the user has obtained his permission roles through getInfo
          const hasRoles = store.getters.roles && store.getters.roles.length > 0
          if (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()
    })
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65

    关键代码:

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

    # 改造完成,效果如下:

    动态菜单效果

    编辑 (opens new window)
    #vue路由
    上次更新: 2024/04/18
    开源后台管理系统解决方案 boot-admin 简介
    Springboot整合Flowable6.x导出bpmn20

    ← 开源后台管理系统解决方案 boot-admin 简介 Springboot整合Flowable6.x导出bpmn20→

    最近更新
    01
    使用 Spring Security 保护您的 WebFlux 应用程序
    02-07
    02
    Oracle也有回收站
    07-31
    03
    Springboot使用AOP编程简介
    07-31
    更多文章>
    Theme by Vdoing | Copyright © 2023-2024 Soft1314 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式