关于左侧菜单的开发和动态路由注册

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>

还原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 = 11 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 = 11 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

关于左侧菜单的开发和动态路由注册相关推荐

  1. vue08首页导航和左侧菜单+mockjs介绍以及使用+登陆注册跳转

    14天阅读挑战赛 努力是为了不平庸~ 目录 1. mockjs 1.1 mockjs介绍 1.2 mockjs使用步骤 1.2.1 安装mockjs依赖 1.2.2 在项目中引入mockjs 1.2. ...

  2. Vue开发实例(12)之实现动态左侧菜单导航

    作者简介 作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通Java.JavaScript,博主也是从零开始一步步把学习成长.深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢 ...

  3. 从壹开始 [vueAdmin后台] 之三 || 动态路由配置 项目快速开发

    回顾 今天VS 2019正式发布,实验一波,你安装了么?Blog.Core 预计今天会升级到 Core 3.0 版本. 哈喽大家周三好!本来今天呢要写 Id4 了,但是写到了一半,突然有人问到了关于 ...

  4. Vue开发实例(11)之el-menu实现左侧菜单导航

    作者简介 作者名:编程界明世隐 简介:CSDN博客专家,从事软件开发多年,精通Java.JavaScript,博主也是从零开始一步步把学习成长.深知学习和积累的重要性,喜欢跟广大ADC一起打野升级,欢 ...

  5. spa项目开发首页导航左侧菜单

    前言:今天要分享的知识是spa项目完成首页导航以及左侧菜单 码字不易,转载请说明!!! 目录 目标 效果图 一.mock.js ①什么是Mock.js ②安装mockjs依赖 ③配置开发环境及生产环境 ...

  6. 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 ...

  7. SPA项目开发之首页导航+左侧菜单

     一:mock.js模拟响应ajax请求    1.安装mockjs依赖   npm install mockjs -D              #只在开发环境使用 2.配置开发环境及生产环境 为了 ...

  8. Vue开发实例(15)之动态路由

    引言 Vue是现在前端最流行的框架之一,作为前端开发人员应该要熟练的掌握它,如果你是打算学习Vue的开发流程,那么来吧,明哥带你快速上手.带你飞! 即使你并非前端开发人员,对前端的开发流程进行一定的了 ...

  9. vue实现用户登录验证 + 权限验证 + 动态路由(左侧菜单栏)

    1. 技术栈说明 vue2.6 + vue-router + vuex + element-ui 2. 开始:新建项目 前提条件:在个人电脑上安装好nodejs(我的是14.15.1)之后,利用nod ...

最新文章

  1. C语言中do...while(0)用法小结
  2. mysql 更新 字段 递增_MySQL使用递增变量更新字段
  3. csu 1008 - Horcrux
  4. STM32 - 定时器的设定 - 基础-04 - 输出波形控制 - PWM 模式
  5. python正则表达式教程_Python中正则表达式的巧妙使用一文包你必掌握正则,
  6. [周榜单]极乐小程序榜单(第十一期)
  7. HCIE-Security Day20:GRE协议:实验(一)配置基于静态路由的GRE隧道
  8. Ubuntu通过apt安装LAMP环境
  9. Java 语言结构【转】
  10. EasyUI 1.4.4 DataGrid(大数据量) bufferview滚动时不加载下一页数据解决方案
  11. cx_Oracle库导入失败引起crontab中python程序运行失败,并且无错误提示
  12. 微软职位内部推荐-Sr SDE-MOD-Beijing
  13. 再谈mysql之执行计划explain
  14. java修改文件的名称_Java修改文件名称
  15. java db4o,有用过db4O的吗?
  16. onenote桌面版的安装
  17. html中怎么隐藏一些符号,html 的一些特殊符号
  18. DEM、DSM 和 DTM 的区别——GIS 中的高程模型
  19. codeigniter mysql -1_mysql-Codeigniter-多个数据库连接
  20. Maya布料解算入门

热门文章

  1. Hystrix及Hystrix Dashboard
  2. python sendfile_sendfile详解(转)
  3. 谷歌Colab pro
  4. 深度优先遍历(DFS)和广度优先遍历(BFS)
  5. 教你如何高效地实现信息搜索
  6. 【Hadoop】Hadoop生态系列之MapReduce概述及MapReduce任务开发与发布
  7. PCM编码及Waveform音频文件格式
  8. JAVA毕设项目京津冀畅游网设计(Vue+Mybatis+Maven+Mysql+sprnig+SpringMVC)
  9. 基于Qt的实时温度传输系统
  10. 如何成为一名优秀的Python工程师?