Vue + Element UI 前端篇(十):动态加载菜单
Vue + Element UI 实现权限管理系统 前端篇(十):动态加载菜单
动态加载菜单
之前我们的导航树都是写死在页面里的,而实际应用中是需要从后台服务器获取菜单数据之后动态生成的。
我们在这里就用上一篇准备好的数据格式Mock出模拟数据,然后动态生成我们的导航菜单。
接口模块化
我们向来讲究模块化,之前接口都集中在,interface.js,我们现在把它改名为 api.js,并把里边原来登录、用户、菜单的相关接口都转移到我们新建的接口模块文件中。
模块化之后的文件结构如下图所示

模块化之后,模块接口写在相应的模块接口文件中,如下面是登录模块
login.js
![]()
import axios from '../axios'/* * 系统登录模块*/// 登录
export const login = data => {return axios({url: '/login',method: 'post',data})
}// 登出
export const logout = () => {return axios({url: '/logout',method: 'get'})
}
![]()
模块化之后,父模块可以像这样引入
api.js
![]()
/* * 接口统一集成模块*/
import * as login from './moudules/login'
import * as user from './moudules/user'
import * as menu from './moudules/menu'// 默认全部导出
export default {login,user,menu
}
![]()
因为我们这里是导出的是父模块,所以在具体接口调用的时候,也需要在原来的基础上加上模块了,像这样。
如上面 api.js 中,我们导出了 login 的整个文件,而 login 文件下有 login,logout 等多个方法。

导航菜单树接口
我们在 menu.js 下创建一个查询导航菜单树的接口。
![]()
import axios from '../axios'/* * 菜单管理模块*/export const findMenuTree = () => {return axios({url: '/menu/findTree',method: 'get'})
}
![]()
api.js 中如果没引入要记得引入。

页面接口调用
接口已经有了,我们在导航菜单组件 MenuBar.vue 中,加载菜单并存入 store 。

页面菜单渲染
还是在 MenuBar.vue 中,页面通过封装的菜单树组件读取store数据,递归生成菜单。

新建菜单树组件,递归生成菜单,并在点击响应函数里面根据菜单URL跳转到指定路由。
components/MenuTree/index.js
![]()
<template><el-submenu v-if="menu.children && menu.children.length >= 1" :index="menu.menuId + ''"><template slot="title"><i :class="menu.icon"></i><span slot="title">{{menu.name}}</span></template><MenuTree v-for="item in menu.children" :key="item.menuId" :menu="item"></MenuTree></el-submenu><el-menu-item v-else :index="menu.menuId + ''" @click="handleRoute(menu)"><i :class="menu.icon"></i><span slot="title">{{menu.name}}</span></el-menu-item>
</template><script>export default {name: 'MenuTree',props: {menu: {type: Object,required: true}},methods: {handleRoute (menu) {// 通过菜单URL跳转至指定路由this.$router.push(menu.url)}}}
</script>
![]()
提供Mock数据
接口有了,页面调用和渲染也写好了,该提供Mock数据了。
mock/modules/menu.js 中 mock findTree接口,data 对应数据太多,这里不贴了。
![]()
export function findTree() {return {url: 'http://localhost:8080/menu/findTree',type: 'get',data: menuTreeData // json 对象数据}
}
![]()
测试效果
启动完成,进入主页,我们看到导航菜单已经成功加载进来了,oh yeah!

然而,我们愉悦的点了点菜单,发现是这样的情况,oh no !

毛都没有,不过显然,聪明的你已经看穿了一切,我们之前只提供了一个叫 /user 的路由,并没有提供 /sys/user 的路由。
好吧,我们稍微修改一下,打开路由配置,把 /user 改成 /sys/user 试试。

果不其然,修改完之后便可以正常跳转到用户界面了。

但不对呀,这里路由配置是写死的,导航菜单是菜单数据动态生成的,这个路由配置也应该是根据菜单数据动态添加的啊,嗯,所以接下来我们就来讨论动态路由配置的问题。
动态路由实现
在 vue 的 route 中提供了 addRoutes 来实现动态路由,打开 MenuBar.vue ,我们在加载导航菜单的同时添加动态路由配置。
MenuBar.vue

其中 addDynamicMenuRoutes 是根据菜单返回动态路由配置的关键代码。
addDynamicMenuRoutes 方法详情:
![]()
/*** 添加动态(菜单)路由* @param {*} menuList 菜单列表* @param {*} routes 递归创建的动态(菜单)路由*/addDynamicMenuRoutes (menuList = [], routes = []) {var temp = []for (var i = 0; i < menuList.length; i++) {if (menuList[i].children && menuList[i].children.length >= 1) {temp = temp.concat(menuList[i].children)} else if (menuList[i].url && /\S/.test(menuList[i].url)) {menuList[i].url = menuList[i].url.replace(/^\//, '')// 创建路由配置var route = {path: menuList[i].url,component: null,name: menuList[i].name,meta: {menuId: menuList[i].menuId,title: menuList[i].name,isDynamic: true,isTab: true,iframeUrl: ''}}// url以http[s]://开头, 通过iframe展示if (isURL(menuList[i].url)) {route['path'] = menuList[i].urlroute['name'] = menuList[i].nameroute['meta']['iframeUrl'] = menuList[i].url} else {try {// 根据菜单URL动态加载vue组件,这里要求vue组件须按照url路径存储// 如url="sys/user",则组件路径应是"@/views/sys/user.vue",否则组件加载不到let array = menuList[i].url.split('/')let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1] .substring(1)route['component'] = resolve => require([`@/views/${url}`], resolve)} catch (e) {}}routes.push(route)}}if (temp.length >= 1) {this.addDynamicMenuRoutes(temp, routes)} else {console.log(routes)}return routes}
![]()
动态菜单页面的组件结构稍微调整下,需要跟菜单url匹配,才能根据菜单url确定组件路径来动态加载组件。

把路由文件清理一下,把动态菜单相关的路由配置处理掉,留下一些固定的全局路由就好。

动态路由测试
启动完成,进入主页,点击用户管理,路由到了用户管理页面。

点击机构管理,路由到了机构管理页面。

好了,到这里动态路由功能已经实现了,给自己鼓个掌吧。
页面刷新出大坑
先前我们是将导航菜单和路由的加载放在菜单栏页面MenuBar.vue中,一切显示和路由也都正常,看起来没什么问题。然而当我们在非根据路径刷新页面时,问题出现了。
如下图所示,我们在用户管理页面的时候,点击刷新浏览器,然后就白茫茫一片了,这是因为浏览器的刷新会导致整个vue重新加载,路由被重新初始化了,后面在Menu.bar添加的动态路由没有了,所以跳转的时候没有找到匹配路由,跳转的是一个不存在的页面,故而白茫茫一片。

专业填坑指南
这显然是动态菜单和路由的加载时机不对,怎么解决这个问题呢,既然问题出在加载时机,那就找一个在页面刷新的时候也能触发重新加载的地方就好了。
这样的地方也不少,像vue加载过程中的钩子函数,路由导航守卫函数等都可以,我们这里就选择在路由导航守卫的 beforeEach 函数内加载,保证每次路由跳转的时候都能够拥有动态菜单和路由。
把原先在MenuBar.vue中加载动态菜单和路由的代码,转移到路由配置 router/index 中来。
beforeEach:

router.beforeEach((to, from, next) => {// 登录界面登录成功之后,会把用户信息保存在会话// 存在时间为会话生命周期,页面关闭即失效。let isLogin = sessionStorage.getItem('user')if (to.path === '/login') {// 如果是访问登录界面,如果用户会话信息存在,代表已登录过,跳转到主页if(isLogin) {next({ path: '/' })} else {next()}} else {// 如果访问非登录界面,且户会话信息不存在,代表未登录,则跳转到登录界面if (!isLogin) {next({ path: '/login' })} else {// 加载动态菜单和路由addDynamicMenuAndRoutes()next()}}
})
![]()
addDynamicMenuAndRoutes:
![]()
/**
* 加载动态菜单和路由
*/
function addDynamicMenuAndRoutes() {api.menu.findMenuTree().then( (res) => {store.commit('setMenuTree', res.data)// 添加动态路由let dynamicRoutes = addDynamicRoutes(res.data)router.options.routes[0].children = router.options.routes[0].children.concat(dynamicRoutes)router.addRoutes(router.options.routes);}).catch(function(res) {alert(res);});
}
![]()
addDynamicRoutes:
![]()
/**
* 添加动态(菜单)路由
* @param {*} menuList 菜单列表
* @param {*} routes 递归创建的动态(菜单)路由
*/
function addDynamicRoutes (menuList = [], routes = []) {var temp = []for (var i = 0; i < menuList.length; i++) {if (menuList[i].children && menuList[i].children.length >= 1) {temp = temp.concat(menuList[i].children)} else if (menuList[i].url && /\S/.test(menuList[i].url)) {menuList[i].url = menuList[i].url.replace(/^\//, '')// 创建路由配置var route = {path: menuList[i].url,component: null,name: menuList[i].name,meta: {menuId: menuList[i].menuId,title: menuList[i].name,isDynamic: true,isTab: true,iframeUrl: ''}}// url以http[s]://开头, 通过iframe展示if (isURL(menuList[i].url)) {route['path'] = menuList[i].urlroute['name'] = menuList[i].nameroute['meta']['iframeUrl'] = menuList[i].url} else {try {// 根据菜单URL动态加载vue组件,这里要求vue组件须按照url路径存储// 如url="sys/user",则组件路径应是"@/views/sys/user.vue",否则组件加载不到let array = menuList[i].url.split('/')let url = array[0].substring(0,1).toUpperCase()+array[0].substring(1) + '/' + array[1].substring(0,1).toUpperCase()+array[1] .substring(1)route['component'] = resolve => require([`@/views/${url}`], resolve)} catch (e) {}}routes.push(route)}}if (temp.length >= 1) {addDynamicRoutes(temp, routes)} else {console.log(routes)}return routes
}
![]()
当然,别忘了把要用到的几个东西引入进来,把导航菜单栏的代码清理一下。

测试效果
启动完成,进入主页,点击用户管理,点击刷新按钮。

刷新后,菜单收起来了,然而页面还是正确的停留在用户管理页面。妈妈再也不用担心我会刷新了!

保存加载状态
现在每次路由跳转前都会重新获取菜单数据生成菜单和路由,及时页面没有刷新也会重复获取,这样很影响性能。我们改良一下,加载成功之后把状态保存到store,每次加载之前先检查store的加载状态,这样就可以避免在非页面刷新的情形下还频发重复的加载了。
在 store 中添加菜单路由加载状态,避免页面未刷新而重复加载。

修改路由配置,在加载之前判断加载状态,只有未加载的情况下才加载,并在加载之后保存加载状态。

求解一个问题
在路由跳转的时候,路由好像是在原路径基础上叠加路由路径跳转的。
如路径在 http://localhost:8090/#/sys/dept 的时候,点击用户管理。
代码对应 this.$router.push(‘’sys/user),路由就赚到了 http://localhost:8090/#/sys/sys/user。
比正确路由多了一个 sys,目前还不到为什么。

目前我是在实际跳转之前,先跳回主页面然后在做真正的跳转。
这样问题可以解决,但无端端多了一步跳转总归不好,求解中。。。

相关文章:
Vue + Element UI 前端篇(十):动态加载菜单
Vue Element UI 实现权限管理系统 前端篇(十):动态加载菜单 动态加载菜单 之前我们的导航树都是写死在页面里的,而实际应用中是需要从后台服务器获取菜单数据之后动态生成的。 我们在这里就用上一篇准备好的数据格式Mock出模…...
图的应用(最小生成树,最短路径,有向无环图)
目录 一.最小生成树 1.生成树 2.无向图的生成树 3.最小生成树算法 二.最短路径 1.单源最短路径---Dijkstra(迪杰斯特拉)算法 2.所有顶点间的最短路径---Floyd(弗洛伊德)算法 三.有向无环图的应用 1.AOV网(拓扑…...
python正则表达式笔记2
由 \ 和一个字符组成的特殊序列在以下列出。 如果普通字符不是ASCII数位或者ASCII字母,那么正则样式将匹配第二个字符。比如,\$ 匹配字符 $. \number 匹配数字代表的组合。每个括号是一个组合,组合从1开始编号。 比如 (.) \1 匹配 the the 或…...
matplotlib 的默认字体和默认字体系列
matplotlib 的默认字体和默认字体系列 查看默认字体和默认字体系列查看默认字体系列下包含的字体查看 plt.rcParams 设置的所有参数查看所有支持的字体格式设置默认字体方法1:方法2 今天给大家介绍一下 matplotlib 包中的默认字体以及默认字体系列。 查看默认字体和…...
STMCUBEMX_IIC_DMA_AT24C64读取和写入
STMCUBEMX_IIC_DMA_AT24C64读取和写入 说明: 1、此例程只是从硬件IIC升级到DMA读写,因为暂时存储的掉电不丢失数据不多,一页就可以够用,不用担心跨页读写的问题 2、使用DMA后,程序确实是变快了,但是也要注意…...
wsl2相关问题
磁盘空间 wsl 删除相关文件后,如删除docker 无用的容器和镜像,windows上磁盘仍然无法自动回收空间 (参考:[microsoft/WSL](https://github.com/microsoft/WSL/issues/4699#issuecomment-627133168)) # 如清除无用do…...
使用idea时,光标变成了不能按空格键,只能修改的vim格式,怎么切换回正常光标
情况1 你可能不小心启用了 IntelliJ IDEA 中的 Vim 插件。你可以尝试以下步骤来禁用它: 在 IntelliJ IDEA 中,选择 "File" -> "Settings" (如果你在 macOS 上,选择 "IntelliJ IDEA" -> &quo…...
vue+antd——实现table表格的打印——分页换行,每页都有表头——基础积累
这里写目录标题 场景效果图功能实现1:html代码功能实现2:css样式功能实现3:js代码补充内容page-break-inside 属性page-break-after属性page-break-before 属性 场景 最近在写后台管理系统时,遇到一个需求,就是要实现…...
linux C MD5计算
#include <stdio.h> #include <string.h> #include <openssl/md5.h>int main() {char str[] "Hello, world!"; // 需要计算MD5哈希值的字符串unsigned char digest[MD5_DIGEST_LENGTH]; // 存储MD5哈希值的数组MD5((unsigned char*)&str, str…...
vue3学习源码笔记(小白入门系列)------ 组件更新流程
目录 说明例子processComponentcomponentUpdateFnupdateComponentupdateComponentPreRender 总结 说明 由于响应式相关内容太多,决定先接着上文组件挂载后,继续分析组件后续更新流程,先不分析组件是如何分析的。 例子 将这个 用例 使用 vi…...
数学建模B多波束测线问题B
数学建模多波束测线问题 1.问题重述: 单波束测深是一种利用声波在水中传播的技术来测量水深的方法。它通过测量从船上发送声波到声波返回所用的时间来计算水深。然而,由于它是在单一点上连续测量的,因此数据在航迹上非常密集,但…...
Pytest 框架执行用例流程浅谈
背景: 根据以下简单的代码示例,我们将从源码的角度分析其中的关键加载执行步骤,对pytest整体流程架构有个初步学习。 代码示例: import pytest def test_add(): assert 1 1 2 def test_sub(): assert 2 - 1 1 通过 pytes…...
C#__资源访问冲突和死锁问题
/// 线程的资源访问冲突:多个线程同时申请一个资源,造成读写错乱。 /// 解决方案:上锁,lock{执行的程序段}:同一时刻,只允许一个线程访问该程序段。 /// 死锁问题: /// 程序中的锁过多…...
机器学习——Logistic Regression
0、前言: Logistic回归是解决分类问题的一种重要的机器学习算法模型 1、基本原理: Logistic Regression 首先是针对二分类任务提出的一种分类方法如果将概率看成一个数值属性,则二元分类问题的概率预测就可以转化为一个回归问题。这种思路最…...
创建husky规范前端项目
创建husky规范前端项目 .husky文件是一个配置文件,用于配置Git钩子。Git钩子是在Git操作时触发的脚本,可以用于自动化一些任务,比如代码格式化、代码检查、测试等。.husky文件可以指定在Git的不同操作(如commit、push等ÿ…...
深浅拷贝与赋值
数据类型 数据类型 在JavaScript中,数据类型有两大类。一类是基本数据类型,一类是引用数据类型。 基本数据类型有六种:number、string、boolean、null、undefined、symbol。 基本数据类型存放在栈中。存放在栈中的数据具有数据大小确定&a…...
bert ranking pairwise demo
下面是用bert 训练pairwise rank 的 demo import torch from torch.utils.data import DataLoader, Dataset from transformers import BertModel, BertTokenizer from sklearn.metrics import pairwise_distances_argmin_minclass PairwiseRankingDataset(Dataset):def __ini…...
GPT引领前沿与应用突破之GPT4科研实践技术与AI绘图
GPT对于每个科研人员已经成为不可或缺的辅助工具,不同的研究领域和项目具有不同的需求。例如在科研编程、绘图领域:1、编程建议和示例代码: 无论你使用的编程语言是Python、R、MATLAB还是其他语言,都可以为你提供相关的代码示例。2、数据可视…...
SpringBoot整合Swagger3
前言 swagger是啥,是干什么的,有什么用,我想在这里我就不用介绍了,下面直接代码演示。 添加依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0…...
detectron2 install path
>>> import detectron2 >>> detectron2_path detectron2.__file__ >>> print(detectron2.__file__)...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
docker详细操作--未完待续
docker介绍 docker官网: Docker:加速容器应用程序开发 harbor官网:Harbor - Harbor 中文 使用docker加速器: Docker镜像极速下载服务 - 毫秒镜像 是什么 Docker 是一种开源的容器化平台,用于将应用程序及其依赖项(如库、运行时环…...
在鸿蒙HarmonyOS 5中实现抖音风格的点赞功能
下面我将详细介绍如何使用HarmonyOS SDK在HarmonyOS 5中实现类似抖音的点赞功能,包括动画效果、数据同步和交互优化。 1. 基础点赞功能实现 1.1 创建数据模型 // VideoModel.ets export class VideoModel {id: string "";title: string ""…...
3.3.1_1 检错编码(奇偶校验码)
从这节课开始,我们会探讨数据链路层的差错控制功能,差错控制功能的主要目标是要发现并且解决一个帧内部的位错误,我们需要使用特殊的编码技术去发现帧内部的位错误,当我们发现位错误之后,通常来说有两种解决方案。第一…...
STM32标准库-DMA直接存储器存取
文章目录 一、DMA1.1简介1.2存储器映像1.3DMA框图1.4DMA基本结构1.5DMA请求1.6数据宽度与对齐1.7数据转运DMA1.8ADC扫描模式DMA 二、数据转运DMA2.1接线图2.2代码2.3相关API 一、DMA 1.1简介 DMA(Direct Memory Access)直接存储器存取 DMA可以提供外设…...
Map相关知识
数据结构 二叉树 二叉树,顾名思义,每个节点最多有两个“叉”,也就是两个子节点,分别是左子 节点和右子节点。不过,二叉树并不要求每个节点都有两个子节点,有的节点只 有左子节点,有的节点只有…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
关键领域软件测试的突围之路:如何破解安全与效率的平衡难题
在数字化浪潮席卷全球的今天,软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件,这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下,实现高效测试与快速迭代?这一命题正考验着…...
Android写一个捕获全局异常的工具类
项目开发和实际运行过程中难免会遇到异常发生,系统提供了一个可以捕获全局异常的工具Uncaughtexceptionhandler,它是Thread的子类(就是package java.lang;里线程的Thread)。本文将利用它将设备信息、报错信息以及错误的发生时间都…...
