一、数据库中创建菜单表

注意核心是 parent_menu_id记录了父菜单的 id。
插入一些数据:
| id | name | chinese_name | parent_menu_id | menu_status |
|---|---|---|---|---|
| 1 | literature | 文学 | 0 | 1 |
| 2 | noval | 小说 | 1 | 1 |
| 3 | essay | 散文随笔 | 1 | 1 |
| 4 | youth_literature | 青春文学 | 1 | 1 |
| 5 | biography | 传记 | 1 | 1 |
| 6 | cartoon | 动漫 | 1 | 1 |
| 7 | suspenseful_reasoning | 悬疑推理 | 1 | 1 |
| 8 | science_fiction | 科幻 | 1 | 1 |
| 9 | martial_arts | 武侠 | 1 | 1 |
| 10 | world_famous | 世界名著 | 1 | 1 |
| 11 | humanity_social_science | 人文社科 | 0 | 1 |
| 12 | history | 历史 | 11 | 1 |
| 13 | psychology | 心理学 | 11 | 1 |
parent_menu_id 为 0表示当前菜单为一级菜单,没有父菜单。
id 为 6 的「动漫」菜单是一个二级菜单,父菜单 id 为 1 说明父菜单是「文学」。
id 为 12 的「历史」菜单也是一个二级菜单,父菜单 id 为 11 说明父菜单是「人文社科」。
二、新建 vo 实体类
新建 vo 目录,然后在目录下新建 MenuTreeVO.java树形类。
@Data@TableName("t_menu")public class MenuTreeVO {@TableId("id")private Integer id;/*** 菜单的英文名称*/@TableField("name")private String name;/*** 菜单的中文名*/@TableField("chinese_name")private String chineseName;/*** 父菜单 id*/@TableField("parent_menu_id")private int parentMenuId;/*** 菜单可见性*/@TableField("menu_status")private Boolean menuStatus;/*** 该菜单的所有子菜单,注明非数据库中的字段*/@TableField(exist = false)private List<MenuTreeVO> childMenu;}
增加一个字段 List<MenuTreeVO> childMenu表明当前菜单的子菜单列表。
使用 @TableField(exist = false)表明非数据库中的字段。
三、新建 mapper
@Mapperpublic interface MenuTableMapper extends BaseMapper<MenuTreeVO> {}
四、新建 MenuTreeUtil.java
新建 util 目录,然后在目录下新建 MenuTreeUtil.java工具类,目的是转为 tree 结构。
/*** @author yunhu* @date 2022-5-29*/public class MenuTreeUtil {/*** 所有的菜单*/private static List<MenuTreeVO> allList = null;/*** 转换为树形结构* @param list 所有的节点* @return 树结构菜单*/public static List<MenuTreeVO> toTree(List<MenuTreeVO> list) {allList = new ArrayList<>(list);// 获取所有的一级菜单,父菜单 id 为 0List<MenuTreeVO> roots = new ArrayList<>();// 遍历for (MenuTreeVO menuTreeVO: list) {if (menuTreeVO.getParentMenuId() == 0) {roots.add(menuTreeVO);}}// 删除一级菜单allList.removeAll(roots);// 对每一个一级菜单添加二级菜单for (MenuTreeVO menuTreeVO: roots) {// 设置子菜单menuTreeVO.setChildMenu(getCurrentChildrenMenu(menuTreeVO));}return roots;}/*** 通过父菜单获取子菜单列表* @param parentMenu 父菜单* @return 子菜单列表*/private static List<MenuTreeVO> getCurrentChildrenMenu(MenuTreeVO parentMenu) {// 判断当前节点是否已经存在子结点List<MenuTreeVO> childMenuList;if (parentMenu.getChildMenu() == null) {childMenuList = new ArrayList<>();;} else {childMenuList = parentMenu.getChildMenu();}// 遍历所有的菜单,除了一级菜单,之前删过for (MenuTreeVO childMenu: allList) {if (parentMenu.getId() == childMenu.getParentMenuId()) {// 某个菜单的父菜单 id 等于当前菜单,这个菜单就是子菜单childMenuList.add(childMenu);}}allList.removeAll(childMenuList);return childMenuList;}}
五、使用
/*** 从数据库中获取所有的菜单数据后,转为树形结构* @author 云胡* @return 树形菜单结构数据*/@GetMapping(value = "/getAllMenu")@ResponseBodypublic List<MenuTreeVO> getAllSupplier(){// 先获取所有的数据表数据List<MenuTreeVO> allMenuTreeVoList = menuTableMapper.selectList(null);List<MenuTreeVO> menuTreeVOTreeList = MenuTreeUtil.toTree(allMenuTreeVoList);if (CollectionUtils.isNotEmpty(menuTreeVOTreeList)) {return menuTreeVOTreeList;}else {return null;}}
返回的 json 数据:
[{"id": 1,"name": "literature","chineseName": "文学","parentMenuId": 0,"menuStatus": true,"childMenu": [{"id": 2,"name": "noval","chineseName": "小说","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 3,"name": "essay","chineseName": "散文随笔","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 4,"name": "youth_literature","chineseName": "青春文学","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 5,"name": "biography","chineseName": "传记","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 6,"name": "cartoon","chineseName": "动漫","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 7,"name": "suspenseful_reasoning","chineseName": "悬疑推理","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 8,"name": "science_fiction","chineseName": "科幻","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 9,"name": "martial_arts","chineseName": "武侠","parentMenuId": 1,"menuStatus": true,"childMenu": null},{"id": 10,"name": "world_famous","chineseName": "世界名著","parentMenuId": 1,"menuStatus": true,"childMenu": null}]},{"id": 11,"name": "humanity_social_science","chineseName": "人文社科","parentMenuId": 0,"menuStatus": true,"childMenu": [{"id": 12,"name": "history","chineseName": "历史","parentMenuId": 11,"menuStatus": true,"childMenu": null},{"id": 13,"name": "psychology","chineseName": "心理学","parentMenuId": 11,"menuStatus": true,"childMenu": null}]}]
六、前端渲染
6.1 前端获取 JSON 数据
setup() {const menuData = ref();onMounted(() => {axios({method: "GET",url: "http://localhost:8082/getAllMenu"}).then(res => {console.log(res);// 必须在加上 .valuemenuData.value = res.data;ElMessage.success("获取成功");}).catch(err => {console.log("err = " + err);ElMessage.error("获取失败");});});return {menuData};}
6.2 渲染到组件上
6.2.1 父组件
<template><div><el-row><el-col :span="3"><h3 class="mb-2">图书分类</h3><el-menu:router="true"background-color="#c6e2ff"default-active="2"class="NavigationDefaultActive"mode="vertical"><menus :menuItem="menuData" /></el-menu></el-col></el-row></div></template>
6.2.2 子组件渲染出菜单
<template><div><el-sub-menu v-for="item in menuItem" :key="item.id" :index="'/'+item.id"><template #title><span>{{item.chineseName}}</span></template><!-- 二级菜单 --><el-menu-itemv-for="item2 in item.childMenu":key="item2.id":index="'/'+item2.id">{{item2.chineseName }}</el-menu-item></el-sub-menu><!-- </el-col> --></div></template>
七、结果


