item fouritem fouritem four还原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 {
@Override
public List<AdminMenu> findAdminMenuTree(){
// 1 :查询表中所有的数据
LambdaQueryWrapper<AdminMenu> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(AdminMenu::getStatus,1);
List<AdminMenu> allList = this.list(lambdaQueryWrapper); // 思考空间,为什么查询的是所有
// 2: 找到所有的根节点 pid = 0
List<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 和 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。在判断的时候记得一定要加一个L
List<AdminMenu> rootMenuList = allList.stream().filter(menu -> menu.getPid().equals(0L)).collect(Collectors.toList());
结果如下:
条件:把子元素的pid = 父元素id找出来即可
如何存放?子元素找出来多个还是单个?
// 子集
@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转换的时候由[].方便后续的判断长度==0
if (CollectionUtils.isEmpty(childrenMenuList)) {
childrenMenuList = new ArrayList<>();
}
// 记得把找的子元素放入到children集合中
rootMenu.setChildren(childrenMenuList);
return rootMenu;
}).collect(Collectors.toList());
结构入下:
/**
* 查询菜单
* @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。在判断的时候记得一定要加一个L
List<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转换的时候由[].方便后续的判断长度==0
if (CollectionUtils.isEmpty(childrenMenuList)) {
// 记得把找的子元素放入到children集合中
rootMenu.setChildren(new ArrayList<>());
}else{
// 记得把找的子元素放入到children集合中
rootMenu.setChildren(childrenMenuList);
// 开始继续遍历
childrenMenuList.forEach((childrenMenu)->bucketList(childrenMenu,allList));
}
}
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”
转发的过程如下:
如下:
// 菜单栏的数据
//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>
注意:
