关于左侧菜单的开发和动态路由注册
关于左侧菜单的开发和动态路由注册
el-menu的菜单的基本认识和理解
- el-menu
- el-sub-menu
- `<template #title>item four
- el-menu-item
<template #title><span>item four</span></template>
- el-menu-item
<template #title><span>item four</span></template>
- el-menu-item
<template #title><span>item four</span></template>
- el-sub-menu
还原web格式
- 控制面板
- 后台首页
- 后台设置
- 优惠券管理
在后端的菜单
存在两种情况,一种有子元素,一种是没子元素,所以我们就必须进行区分。区分的界限其实就通过一个menu.children.length > 0 说明存在子元素。反之,就没子元素。就直接显示,具体代码如下:
<el-menu :default-active="1" class="border-0" :unique-opened="true" :collapse-transition="false"><el-sub-menu index="1-1"><template #title><el-icon><Location/></el-icon><span>控制面板</span></template><el-menu-item index="1-1-1"><el-icon><Location/></el-icon><span>后台首页</span></el-menu-item><el-menu-item index="1-1-2"><el-icon><User/></el-icon><span>后台设置</span></el-menu-item></el-sub-menu><el-menu-item index="2"><el-icon><Share/></el-icon><span>优惠券管理</span></el-menu-item></el-menu>
菜单接口的设计和递归 - 服务端
1: 数据库表
CREATE TABLE `kss_admin_menu` (`id` bigint(20) NOT NULL COMMENT '主键',`name` varchar(128) DEFAULT '0' COMMENT '菜单名词',`sorted` int(11) DEFAULT NULL COMMENT '菜单排序',`path` varchar(400) DEFAULT NULL COMMENT '菜单链接',`icon` varchar(128) DEFAULT NULL COMMENT '菜单图标',`status` int(11) DEFAULT NULL COMMENT '菜单发布',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`pid` bigint(20) DEFAULT NULL COMMENT '菜单名称',`componentname` varchar(200) DEFAULT NULL COMMENT '组件名称',`pathname` varchar(100) DEFAULT NULL COMMENT '路径名称',`layout` varchar(50) DEFAULT NULL COMMENT '父组件',`indexon` int(11) DEFAULT NULL COMMENT '排序',`showflag` int(1) DEFAULT NULL COMMENT '是否展示',`isdelete` int(1) DEFAULT NULL COMMENT '删除状态 0未删除 1删除',PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='菜单管理 '
2: 创建实体
package com.pug.zixun.pojo;import java.util.Date;
import java.util.List;import lombok.*;
import com.baomidou.mybatisplus.annotation.*;import org.pug.generator.anno.PugDoc;
/*** AdminMenu实体*
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@TableName("kss_admin_menu")
public class AdminMenu implements java.io.Serializable {@PugDoc(name="主键")@TableId(type = IdType.ASSIGN_ID)private Long id;@PugDoc(name="菜单名词")private String name;@PugDoc(name="菜单链接")private String path;@PugDoc(name="路径名称")private String pathname;@PugDoc(name="菜单图标")private String icon;@PugDoc(name="菜单排序")private Integer sorted;@PugDoc(name="菜单发布")private Integer status;@PugDoc(name="创建时间")@TableField(fill = FieldFill.INSERT)private Date createTime;@PugDoc(name="更新时间")@TableField(fill = FieldFill.INSERT_UPDATE)private Date updateTime;@PugDoc(name="菜单名称")private Long pid;@PugDoc(name="删除状态 0未删除 1删除")private Integer isdelete;// 子集@TableField(exist = false)private List<AdminMenu> children;
}
3: AdminMenuMapper
package com.pug.zixun.mapper;import com.pug.zixun.pojo.AdminMenu;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;/*** AdminMenuMapper*
*/
public interface AdminMenuMapper extends BaseMapper<AdminMenu>{}
4: AdminMenuService.java
package com.pug.zixun.service.adminmenu;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.IService;
import com.pug.zixun.pojo.AdminMenu;
import com.pug.zixun.vo.AdminMenuVo;
import com.pug.zixun.bo.AdminMenuBo;
import com.pug.zixun.service.BaseService;
import java.util.List;/*** IAdminMenuService接口*
*/
public interface IAdminMenuService extends IService<AdminMenu>,BaseService{/*** 查询菜单* @return*/List<AdminMenu> findAdminMenuTree();
}
5: 实现类
package com.pug.zixun.service.adminmenu;import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.pug.zixun.mapper.AdminMenuMapper;
import com.pug.zixun.pojo.AdminMenu;
import com.pug.zixun.vo.AdminMenuVo;
import com.pug.zixun.bo.AdminMenuBo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import com.pug.zixun.commons.enums.ResultStatusEnum;
import com.pug.zixun.commons.ex.PugValidatorException;
import com.pug.zixun.commons.utils.fn.asserts.Vsserts;
import org.springframework.util.CollectionUtils;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;/*** AdminMenuServiceImpl实现类*
*/
@Service
@Slf4j
public class AdminMenuServiceImpl extends ServiceImpl<AdminMenuMapper,AdminMenu> implements IAdminMenuService {@Overridepublic List<AdminMenu> findAdminMenuTree(){// 1 :查询表中所有的数据LambdaQueryWrapper<AdminMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();lambdaQueryWrapper.eq(AdminMenu::getStatus,1);List<AdminMenu> allList = this.list(lambdaQueryWrapper); // 思考空间,为什么查询的是所有// 2: 找到所有的根节点 pid = 0List<AdminMenu> rootList = allList.stream().filter(category -> category.getPid().equals(0L)).sorted((a, b) -> a.getSorted() - b.getSorted()).collect(Collectors.toList());// 3 : 查询所有的非根节点List<AdminMenu> subList = allList.stream().filter(category -> !category.getPid().equals(0L)).collect(Collectors.toList());// 4 : 循环根节点去subList去找对应的子节点rootList.forEach(root -> buckForback(root, subList));return rootList;}private void buckForback(AdminMenu root, List<AdminMenu> subList) {// 通过根节点去id和子节点的pid是否相等,如果相等的话,代表是当前根的子集List<AdminMenu> childrenList = subList.stream().filter(category -> category.getPid().equals(root.getId())).sorted((a, b) -> a.getSorted() - b.getSorted()).collect(Collectors.toList());// 如果你当前没一个子集,初始化一个空数组if (!CollectionUtils.isEmpty(childrenList)) {// 查询以后放回去root.setChildren(childrenList);// 再次递归构建即可childrenList.forEach(category -> buckForback(category, subList));} else {root.setChildren(new ArrayList<>());}}}
6:定义菜单controller
package com.pug.zixun.controller.adminmenu;import com.baomidou.mybatisplus.core.metadata.IPage;
import com.pug.zixun.service.adminmenu.IAdminMenuService;
import com.pug.zixun.pojo.AdminMenu;
import com.pug.zixun.vo.AdminMenuVo;
import com.pug.zixun.bo.AdminMenuBo;
import com.pug.zixun.commons.enums.ResultStatusEnum;
import com.pug.zixun.commons.ex.PugValidatorException;
import com.pug.zixun.commons.utils.fn.asserts.Vsserts;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import com.pug.zixun.controller.BaseController;
import java.util.List;
import org.pug.generator.anno.PugDoc;
/*** AdminMenuController*
*/@RestController
@RequiredArgsConstructor
@Slf4j
@PugDoc(name="后台菜单",tabname="kss_admin_menu")
public class AdminMenuController extends BaseController{private final IAdminMenuService adminmenuService;/*** 查询分类的接口信息-tree** @return*/@PostMapping("/menu/tree")@PugDoc(name="查询后台菜单信息")public List<AdminMenu> tree() {return adminmenuService.findAdminMenuTree();}
}
7:分析原理
如何做到无限极菜单。通过表自映射过程
- 控制面板 id=1 pid = 0
- 后台首页 id=2 pid=1
- 后台设置 id=3 pid=1
- 优惠券管理 id=4 pid = 0
- 用户管理 id=5 pid=0
- 用户管理id=6 pid = 5
- 用户设置id=7 pid = 6
- 用户密码设置 id=8 pid = 7
- 用户头像id =9 pid = 6
- 用户审核 id=10 pid = 6
- 用户设置id=7 pid = 6
- 用户添加id = 11 pid = 5
- 用户管理id=6 pid = 5
- 菜单管理 id = 12 pid =0
- 角色管理 id = 13 pid =0
- 权限管理 id = 14 pid =0
- 权限添加 id = 15 pid =14
- 权限列表 id = 16 pid =14
上面的菜单或者未来的百度网盘的目录结构的设计,其实都是一张数据库表。来完成的。
原理就是通过 id 和 pid来形成自映射的过程.
数据递归方式由如下几种
全查,不考虑父子关系,全部在java代码来完成
- 这种数据量比较小的情况,可以考虑
- 场景:菜单查询,分类查询
查询数据库的方式
根据pid=0查询所有的菜单根元素
循环遍历,然后再根据id去查询表中pid=id子元素。
场景:评论,百度目录设置 ,子元素查询和分页更注重异步去查询
- 今天心情不不错,发生大事情 pid = 0 id=1- 是的 id=2 pid = 1- 棒棒的 id=3 pid = 1- 美美的 id=4 pid = 1查看更多(120)- 今天心情不不错,发生大事情 pid = 0 id=2- 是的 id=12 pid = 2- 帮帮的 id=13 pid = 2- 美美 id=14 pid = 2查看更多(12)
因为菜单的数据的是非常小的。所有考虑第一种方案,全查。
查询所有菜单数据
// 查询表中所有的数据
LambdaQueryWrapper<AdminMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
// 把status=1和isdelete=0 查询出来
lambdaQueryWrapper.eq(AdminMenu::getStatus,1);
lambdaQueryWrapper.eq(AdminMenu::getIsdelete,0);
// 查询所有菜单
List<AdminMenu> allList = this.list(lambdaQueryWrapper);
得到代码如下:
过滤所有的根(父)元素
也就是pid=0的元素,如下
// 过滤所有的根(父)元素 。条件是pid = 0 ,// 注意我这里的PID和ID数据类型是Long。在判断的时候记得一定要加一个LList<AdminMenu> rootMenuList = allList.stream().filter(menu -> menu.getPid().equals(0L)).collect(Collectors.toList());
结果如下:
- 控制面板 id=1 pid = 0
- 优惠券管理 id=4 pid = 0
- 用户管理 id=5 pid=0
- 菜单管理 id = 12 pid =0
- 角色管理 id = 13 pid =0
- 权限管理 id = 14 pid =0
遍历父元素开始找子元素
条件:把子元素的pid = 父元素id找出来即可
如何存放?子元素找出来多个还是单个?
- 肯定也是List,肯定是多个。
// 子集 @TableField(exist = false) private List<AdminMenu> children;
代码如下:
// 遍历父元素开始找子元素
rootMenuList = rootMenuList.stream().map(rootMenu -> {List<AdminMenu> childrenMenuList = allList.stream().filter(menu -> menu.getPid().equals(rootMenu.getId())).collect(Collectors.toList());// 这个判断的主要目的:是为了防止空集合,创建要给空集合对象,让json转换的时候由[].方便后续的判断长度==0if (CollectionUtils.isEmpty(childrenMenuList)) {childrenMenuList = new ArrayList<>();}// 记得把找的子元素放入到children集合中rootMenu.setChildren(childrenMenuList);return rootMenu;
}).collect(Collectors.toList());
结构入下:
- 控制面板 id=1 pid = 0
- 后台首页 id=2 pid=1
- 后台设置 id=3 pid=1
- 优惠券管理 id=4 pid = 0
- 用户管理 id=5 pid=0
- 用户管理id=6 pid = 5
- 用户添加id = 11 pid = 5
- 菜单管理 id = 12 pid =0
- 角色管理 id = 13 pid =0
- 权限管理 id = 14 pid =0
- 权限添加 id = 15 pid =14
- 权限列表 id = 16 pid =14
最终递归代码
/*** 查询菜单* @return*/List<AdminMenu> findAdminMenuTree() {// 查询表中所有的数据LambdaQueryWrapper<AdminMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();// 把status=1和isdelete=0 查询出来lambdaQueryWrapper.eq(AdminMenu::getStatus, 1);lambdaQueryWrapper.eq(AdminMenu::getIsdelete, 0);// 查询所有菜单List<AdminMenu> allList = this.list(lambdaQueryWrapper);// 过滤所有的根(父)元素 。条件是pid = 0 ,// 注意我这里的PID和ID数据类型是Long。在判断的时候记得一定要加一个LList<AdminMenu> rootMenuList = allList.stream().filter(menu -> menu.getPid().equals(0L)).collect(Collectors.toList());// 递归调用菜单rootMenuList.forEach(rootMenu -> bucketList(rootMenu,allList));// 返回return rootMenuList;}public void bucketList(AdminMenu rootMenu , List<AdminMenu> allList){// 遍历父元素开始找子元素List<AdminMenu> childrenMenuList = allList.stream().filter(menu -> menu.getPid().equals(rootMenu.getId())).collect(Collectors.toList());// 这个判断的主要目的:是为了防止空集合,创建要给空集合对象,让json转换的时候由[].方便后续的判断长度==0if (CollectionUtils.isEmpty(childrenMenuList)) {// 记得把找的子元素放入到children集合中rootMenu.setChildren(new ArrayList<>());}else{// 记得把找的子元素放入到children集合中rootMenu.setChildren(childrenMenuList);// 开始继续遍历childrenMenuList.forEach((childrenMenu)->bucketList(childrenMenu,allList));}}
- 控制面板 id=1 pid = 0
- 后台首页 id=2 pid=1
- 后台设置 id=3 pid=1
- 优惠券管理 id=4 pid = 0
- 用户管理 id=5 pid=0
- 用户管理id=6 pid = 5
- 用户设置id=7 pid = 6
- 用户密码设置 id=8 pid = 7
- 用户头像id =9 pid = 6
- 用户审核 id=10 pid = 6
- 用户设置id=7 pid = 6
- 用户添加id = 11 pid = 5
- 用户管理id=6 pid = 5
- 菜单管理 id = 12 pid =0
- 角色管理 id = 13 pid =0
- 权限管理 id = 14 pid =0
- 权限添加 id = 15 pid =14
- 权限列表 id = 16 pid =14
测试菜单的接口
http://127.0.0.1:8877/admin/menu/tree
前台对接菜单管理
1: 定义异步请求接口调用
在services/navmenu/AdminMenuService.js 如下:
import request from '@/utils/request'export default {/*** 创建验证码*/loadNavMenu() {return request.post("/menu/tree");}}
2:菜单渲染
找到layouts下面的PugMenu.vue进行异步调用接口,如下:
js部分
import adminMenuService from '@/services/navmenu/AdminMenuService.js'
// 定义响应式数据
const menuList = ref([]);
onMounted(async () => {const severResponse = await adminMenuService.loadNavMenu();// 渲染赋值menuList.value = severResponse.data;
})
vue部分
<el-menu :default-active="1" class="border-0" :unique-opened="true" :collapse-transition="false"><template v-for="(menu,index) in menuList" :key="menu.id" ><!--有子元素的菜单--><el-sub-menu :index="menu.name" v-if="menu.children && menu.children.length > 0"><template #title><el-icon><Location/></el-icon><span>{{menu.name}}</span></template><el-menu-item :index="cmenu.path" v-for="(cmenu,cindex) in menu.children" :key="cmenu.id"><el-icon><Location/></el-icon><span>{{cmenu.name}}</span></el-menu-item></el-sub-menu><!--无子元素的菜单--><el-menu-item :index="menu.path" v-else><el-icon><Share/></el-icon><span>{{menu.name}}</span></el-menu-item></template></el-menu>
关于路由转发
<el-sub-menu :index="menu.name" v-if="menu.children && menu.children.length > 0">
<el-menu-item :index="cmenu.path" v-for="(cmenu,cindex) in menu.children" :key="cmenu.id">
为什么上面的:index=menu.name 有的是 cmenu.path呢?存在的父级元素不参与路由转发。只有子元素才参与,所以子元素:index=“cmenu.path”
转发的过程如下:
- 通过点击子菜单或者没有子元素的菜单,获取菜单的index。(path)
- 然后通过router.push(index) 跳转即可
如下:
// 菜单栏的数据
//const menuList = computed(() => store.state.menu.menuList)
// 点击菜单进行导航
const handleSelectMenu = (index) => {//store.commit("menu/addPath", index);router.push(index);
}
注意记得先把所有的菜单路由和spa页面先进行定义。并且绑定关系哦才可以生效。否则全部跳入404页面
关于菜单图标的问题
使用动态组件实现
图标集合:https://element-plus.gitee.io/zh-CN/component/icon.html#%E5%9B%BE%E6%A0%87%E9%9B%86%E5%90%88
代码
<el-icon><component :is="menu.icon"></component>
</el-icon>
注意:
- 名字定义到数据库的时候,要么是和官方一模一样 ,比如:AddLocation
- 要么就是遵循驼峰小写定义,比如:add-location
关于左侧菜单的开发和动态路由注册相关推荐
- vue08首页导航和左侧菜单+mockjs介绍以及使用+登陆注册跳转
14天阅读挑战赛 努力是为了不平庸~ 目录 1. mockjs 1.1 mockjs介绍 1.2 mockjs使用步骤 1.2.1 安装mockjs依赖 1.2.2 在项目中引入mockjs 1.2. ...
- Vue开发实例(12)之实现动态左侧菜单导航
作者简介 作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通Java.JavaScript,博主也是从零开始一步步把学习成长.深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢 ...
- 从壹开始 [vueAdmin后台] 之三 || 动态路由配置 项目快速开发
回顾 今天VS 2019正式发布,实验一波,你安装了么?Blog.Core 预计今天会升级到 Core 3.0 版本. 哈喽大家周三好!本来今天呢要写 Id4 了,但是写到了一半,突然有人问到了关于 ...
- Vue开发实例(11)之el-menu实现左侧菜单导航
作者简介 作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通Java.JavaScript,博主也是从零开始一步步把学习成长.深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢 ...
- spa项目开发首页导航左侧菜单
前言:今天要分享的知识是spa项目完成首页导航以及左侧菜单 码字不易,转载请说明!!! 目录 目标 效果图 一.mock.js ①什么是Mock.js ②安装mockjs依赖 ③配置开发环境及生产环境 ...
- Vue iView Admin 动态路由菜单加载 前后端分离(springboot 2.x iview admin vue 前后端分离 模型设计器 动态数据权限...
宣传官网 xb.exrick.cn 在线Demo xboot.exrick.cn 开源版Github地址 github.com/Exrick/x-bo- 开发文档 www.kancloud.cn/ex ...
- SPA项目开发之首页导航+左侧菜单
一:mock.js模拟响应ajax请求 1.安装mockjs依赖 npm install mockjs -D #只在开发环境使用 2.配置开发环境及生产环境 为了 ...
- Vue开发实例(15)之动态路由
引言 Vue是现在前端最流行的框架之一,作为前端开发人员应该要熟练的掌握它,如果你是打算学习Vue的开发流程,那么来吧,明哥带你快速上手.带你飞! 即使你并非前端开发人员,对前端的开发流程进行一定的了 ...
- vue实现用户登录验证 + 权限验证 + 动态路由(左侧菜单栏)
1. 技术栈说明 vue2.6 + vue-router + vuex + element-ui 2. 开始:新建项目 前提条件:在个人电脑上安装好nodejs(我的是14.15.1)之后,利用nod ...
最新文章
- C语言中do...while(0)用法小结
- mysql 更新 字段 递增_MySQL使用递增变量更新字段
- csu 1008 - Horcrux
- STM32 - 定时器的设定 - 基础-04 - 输出波形控制 - PWM 模式
- python正则表达式教程_Python中正则表达式的巧妙使用一文包你必掌握正则,
- [周榜单]极乐小程序榜单(第十一期)
- HCIE-Security Day20:GRE协议:实验(一)配置基于静态路由的GRE隧道
- Ubuntu通过apt安装LAMP环境
- Java 语言结构【转】
- EasyUI 1.4.4 DataGrid(大数据量) bufferview滚动时不加载下一页数据解决方案
- cx_Oracle库导入失败引起crontab中python程序运行失败,并且无错误提示
- 微软职位内部推荐-Sr SDE-MOD-Beijing
- 再谈mysql之执行计划explain
- java修改文件的名称_Java修改文件名称
- java db4o,有用过db4O的吗?
- onenote桌面版的安装
- html中怎么隐藏一些符号,html 的一些特殊符号
- DEM、DSM 和 DTM 的区别——GIS 中的高程模型
- codeigniter mysql -1_mysql-Codeigniter-多个数据库连接
- Maya布料解算入门
热门文章
- Hystrix及Hystrix Dashboard
- python sendfile_sendfile详解(转)
- 谷歌Colab pro
- 深度优先遍历(DFS)和广度优先遍历(BFS)
- 教你如何高效地实现信息搜索
- 【Hadoop】Hadoop生态系列之MapReduce概述及MapReduce任务开发与发布
- PCM编码及Waveform音频文件格式
- JAVA毕设项目京津冀畅游网设计(Vue+Mybatis+Maven+Mysql+sprnig+SpringMVC)
- 基于Qt的实时温度传输系统
- 如何成为一名优秀的Python工程师?