vue-element-admin 动态菜单改造

news2025/1/21 1:03:29

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 = 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
}

第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()
})

关键代码:

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

改造完成,效果如下:

效果截图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/414452.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剑指 Offer 51. 数组中的逆序对

剑指 Offer 51. 数组中的逆序对 难度&#xff1a;hard\color{red}{hard}hard 题目描述 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 示例 1: 输入: [7…

怎么设计秒杀系统?

秒杀系统需要考虑哪些要素&#xff1f; 要能支持高并发用户体验要好&#xff0c;不要返回异常信息对系统要友好&#xff08;针对秒杀可以做业务上的隔离&#xff0c;单独把秒杀系统部署到独立的集群服务器上&#xff1b;可动态配置业务参数&#xff0c;比如商品金额&#xff0…

电磁兼容(EMC)的标准与测试内容

在国际范围上&#xff0c;电磁兼容标准的制定已经有了70多年的发展历程&#xff0c;最早为了保护无线电通信和广播&#xff0c;国际无线电干扰特别委员会&#xff08;CISPR&#xff09;对各种用电设备和系统提出了相关的电磁干扰发射限值和测量方法。到了20世纪60&#xff5e;7…

机器学习:基于朴素贝叶斯(Naive Bayes)的分类预测

目录 一、简介和环境准备 简介&#xff1a; 环境&#xff1a; 二、实战演练 2.1使用葡萄&#xff08;Wine&#xff09;数据集&#xff0c;进行贝叶斯分类 1.数据导入 2.模型训练 3.模型预测 2.2模拟离散数据集–贝叶斯分类 1.数据导入、分析 2.模型训练、预测 三、原…

TiDB进阶篇-TiDB Server架构

简介 较深入的介绍TiDB Server。 TiDB Server 架构 图解 1.下面是负责SQL语句的解析和优化。 2.下面试负责TiKV存储多版本&#xff0c;过期版本的清理作用。 3.复杂SQL的拆分&#xff08;如果是点查那么就不需要经过DistSQL&#xff09;。 4.事务相关。 5.负责PD和TiKV的通信…

js 事件流程

描述 JavaScript 的执行是单线程的&#xff0c;后面的任务需要等待前面的任务完全完成后&#xff0c;再去执行。DOM 事件&#xff08;文件的加载等&#xff09;、定时器、网络请求等事件&#xff0c;并不会消耗 CPU&#xff0c;这些事件无需等候&#xff0c;所以出现了异步。主…

Java后端新人入职第一天,环境搭建,全看这篇就行了

本文主要是记录一下一个新人java后端开发来到一个新公司,如何快速将自己的相关开发环境搭建好,包括Java、Maven、Tomcat、idea、Redis、Mysql等等,有的公司会有相关版本的要求,不过安装配置步骤基本一样的,我这里就以目前比较流行的版本进行详细说明。 一:基础环境搭建:…

Arduino开发之如何连接GPS模块?

文章目录0、引言1、GPS模块说明2、接调试助手测试GPS模块接收数据3、代码编写4、功能演示0、引言 NEO-6M/7M GPS模块&#xff0c;具有高灵敏度、低功耗、小型化、高追踪灵敏度&#xff0c;大大扩大了其定位的覆盖面&#xff0c;在普通GPS接收模块不能定位的地方&#xff0c;如狭…

编译原理考试大题分析【太原理工大学】

有些基本公式可以看这里&#xff0c;大题内容请以本篇为准&#xff01;https://blog.csdn.net/m0_52861684/article/details/130071191?spm1001.2014.3001.5501 之前说错了&#xff0c;考试题型没有简答题和填空题&#xff0c;只有十个选择题是 20 分&#xff0c;其余全是大题…

ESP32设备驱动-VEML6075紫外线(UV)光传感器驱动

VEML6075紫外线(UV)光传感器驱动 文章目录 VEML6075紫外线(UV)光传感器驱动1、VEML6075介绍2、硬件准备3、软件准备4、驱动实现1、VEML6075介绍 VEML6075 可感应 UVA 和 UVB 光,并使用 CMOS 工艺将光电二极管、放大器和模拟/数字电路集成到单个芯片中。 应用 UV 传感器时,它…

ChatGPT和GPT-4带你选笔记本电脑

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

最近ChatGPT封号太严重了,这里是解封攻略步骤(建议收藏)

这个周末&#xff0c;先是意大利暂时封杀ChatGPT&#xff0c;限制OpenAI处理本国用户信息。 接着&#xff0c;据韩国媒体报道&#xff0c;三星导入ChatGPT不到20天&#xff0c;便曝出机密资料外泄。 还没结束&#xff0c;又有大量网友发现ChatGPT目前停止注册&#xff0c;开始…

【vue】vue中下载文件的方法

文章目录1. 下载后端返回文件1.1 后端为post请求返回二进制流文件URL.createObjectURLFileReader1.2 后端直接返回get请求文件2. 下载本地文件1. 下载后端返回文件 1.1 后端为post请求返回二进制流文件 Blob Blob对象标识一个不可变、原始数据的类文件对象。Blob表示的不一定…

RabbitMQ( 发布订阅模式 ==> FanoutExchange )

本章目录&#xff1a; 何为发布订阅模式FanoutExchange具体使用一、何为发布订阅模式 在上一篇文章中&#xff0c;我们创建了Work Queue并且发送任务&#xff0c;在Work Queue中&#xff0c;每个任务只会被一个消费者消费&#xff0c;任务消费后就被清除了。 而在本篇中&…

0202心跳和服务续约源码解析-nacos2.x-微服务架构

文章目录1 客户端心跳任务2 服务端处理2.1 服务注册时开启客户端心跳检查2.2 客户端发送心跳任务续约2.3 服务实例移除2.4 心跳任务闭环结语1 客户端心跳任务 在上一篇文章0201服务注册源码解析-nacos2.x-微服务架构分析客户端服务注册的时候&#xff0c;流程在NacosNamingSer…

重装系统下载网址

[置顶]无论会不会安装系统&#xff0c;都一定会需要&#xff0c;觉得内容不错欢迎一键三连哦 稳定 | 方便 | 好用 1、MSDN 用过最简单好用&#xff0c;下载不限速&#xff0c;支持迅雷、IDM多种下载方式 https://www.xitongku.com 2、Windows系统下载仓储站 为小白重装系统提供…

KIOPTRIX: LEVEL 4通关详解

环境配置 vulnhub上下载的文件没有vmx 去3的文件里偷一个 记事本打开把所有Kioptrix3_vmware改成Kioptrix4_vmware 然后网卡地址随便改一下 打开后会提示找不到虚拟机,手动选一下就行了 信息收集 漏洞发现 web一上去就是一个登录框 扫路径发现database.sql 但是密码是错的…

高通开发系列 - linux arm64 toolchain交叉编译器编译错误

By: fulinux E-mail: fulinux@sina.com Blog: https://blog.csdn.net/fulinus 喜欢的盆友欢迎点赞和订阅! 你的喜欢就是我写作的动力! 目录 概述下载aarch64交叉编译器编译使能编译环境使能defconfig编译问题1:address-of-packed-member问题2:attribute-alias=问题3:array…

Kubernetes 笔记(14)— 滚动更新、定义应用版本、实现应用更新、管理应用更新、添加更新描述

滚动更新&#xff0c;使用 kubectl rollout 实现用户无感知的应用升级和降级。 1. 定义应用版本 在 Kubernetes 里&#xff0c;版本更新使用的不是 API 对象&#xff0c;而是两个命令&#xff1a;kubectl apply 和 kubectl rollout&#xff0c;当然它们也要搭配部署应用所需要…

人工智能作业之遗传算法

遗传算法1.遗传算法定义2.相关术语3.遗传算法的主要步骤4.遗传算法的参数设计原则5.代码实现1.遗传算法定义 遗传算法&#xff08;Genetic Algorithm, GA&#xff09;起源于对生物系统所进行的计算机模拟研究。它是模仿自然界生物进化机制发展起来的随机全局搜索和优化方法&am…