文章目录
- 1、前端源码
- 2、数据库设计
- 3、后端设计
- 3.1、实体类
- 3.2、Controller层
- 3.3、具体树形列表后端代码实现
1、前端源码
ElementUI Table 链接
在此链接中找到 树形数据与懒加载
查看其JS源码,可知,每个菜单节点的子节点存放于children
字段中,
const tableData: User[] = [
{
id: 1,
date: '2016-05-02',
name: 'wangxiaohu',
address: 'No. 189, Grove St, Los Angeles',
},
{
id: 2,
date: '2016-05-04',
name: 'wangxiaohu',
address: 'No. 189, Grove St, Los Angeles',
},
{
id: 3,
date: '2016-05-01',
name: 'wangxiaohu',
address: 'No. 189, Grove St, Los Angeles',
children: [
{
id: 31,
date: '2016-05-01',
name: 'wangxiaohu',
address: 'No. 189, Grove St, Los Angeles',
},
{
id: 32,
date: '2016-05-01',
name: 'wangxiaohu',
address: 'No. 189, Grove St, Los Angeles',
},
],
},
{
id: 4,
date: '2016-05-03',
name: 'wangxiaohu',
address: 'No. 189, Grove St, Los Angeles',
},
]
将示例代码复制到项目中,此处根据源码做了符合自己项目的修改,主要复制el-table
中的内容
<el-table
:data="list"
style="width: 100%; margin-bottom: 20px"
row-key="id"
border
default-expand-all
>
<el-table-column prop="title" label="菜单标题" />
<el-table-column prop="component" label="路由名称" />
<el-table-column prop="sortValue" label="排序" />
<el-table-column prop="status" label="状态" #default="scope">
{{ scope.row.status == 1 ? '正常' : '停用' }}
</el-table-column>
<el-table-column prop="createTime" label="创建时间" />
<el-table-column label="操作" align="center" width="280" #default="scope" >
<el-button type="success" size="small" @click="addShow(scope.row)">
添加下级节点
</el-button>
<el-button type="primary" size="small" @click="editShow(scope.row)">
修改
</el-button>
<el-button type="danger" size="small" @click="remove(scope.row.id)">
删除
</el-button>
</el-table-column>
</el-table>
<script setup>
import { ref , onMounted } from "vue"
// 定义表格数据模型
const list = ref([])
//页面表单数据
const defaultForm = {
id: '',
parentId: 0,
title: '',
url: '',
component: '',
icon: '',
sortValue: 1,
status: 1,
}
// 钩子函数
onMounted(() => {
fetchData()
})
const fetchData = async () => {
const { code, data, message } = await FindNodes()
list.value = data
}
</script>
前端js
配置文件
import request from '@/utils/request'
const api_name = '/admin/system/sysMenu'
export const FindNodes = () => {
return request({
url: `${api_name}/findNodes`,
method: 'get',
})
}
2、数据库设计
每个菜单有自己的id
,还有其父节点的parent_id
(用于表示父子关系,双亲表示法)
给出SQL DDL 注:基于MySQL 8.0.30
CREATE TABLE `sys_menu` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '编号',
`parent_id` bigint NOT NULL DEFAULT '0' COMMENT '所属上级',
`title` varchar(20) NOT NULL DEFAULT '' COMMENT '菜单标题',
`component` varchar(100) DEFAULT NULL COMMENT '组件名称',
`sort_value` int NOT NULL DEFAULT '1' COMMENT '排序',
`status` tinyint NOT NULL DEFAULT '1' COMMENT '状态(0:禁止,1:正常)',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`is_deleted` tinyint NOT NULL DEFAULT '0' COMMENT '删除标记(0:不可用 1:可用)',
PRIMARY KEY (`id`),
KEY `idx_parent_id` (`parent_id`)
) ENGINE=InnoDB AUTO_INCREMENT=34 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='菜单表'
3、后端设计
3.1、实体类
这边先把公共的属性抽取出来 组成BaseEntity
类,然后将菜单的属性定义在SysMenu
类,并继承BaseEntity
// BaseEntity 类
@Data
public class BaseEntity implements Serializable {
@Schema(description = "唯一标识")
private Long id;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "修改时间")
private Date updateTime;
@Schema(description = "是否删除")
private Integer isDeleted;
}
// SysMenu 类
@Data
public class SysMenu extends BaseEntity {
@Schema(description = "父节点id")
private Long parentId;
@Schema(description = "节点标题")
private String title;
@Schema(description = "组件名称")
private String component;
@Schema(description = "排序值")
private Integer sortValue;
@Schema(description = "状态(0:禁止,1:正常)")
private Integer status;
// 下级列表
@Schema(description = "子节点")
private List<SysMenu> children;
}
3.2、Controller层
常规调用,注意"/findNodes"
路径与前端js
文件中保持一致
// 显示菜单列表方法
@GetMapping("/findNodes")
public Result findNodes(){
List<SysMenu> sysMenuList = sysMenuService.findNodes();
return Result.build(sysMenuList, ResultCodeEnum.SUCCESS);
}
3.3、具体树形列表后端代码实现
代码3.1为实现类中的方法,此代码中sysMenuMapper.findAll()
用于查询所有菜单,其SQL如下
select * from sys_menu where is_deleted = 0 order by sort_value
此外,其中的MenuHelper
为自定义的一个类,在代码3.2中给出,该类的静态方法buildTree
为具体的递归构造树形菜单的方法(该方法的参数为:菜单列表数据)。
buildTree
方法调用了递归函数findChildren
(该递归函数的参数为:已知的父节点,菜单列表数据),其思路是:已知的父节点为N0,再找出子节点N1,并递归地为N1节点的children
属性赋值,然后将N1添加至N0的children
中。
代码3.1:
// 递归查找列表
@Override
public List<SysMenu> findNodes() {
// 1.先查询所有菜单,返回所有list集合
List<SysMenu> sysMenuList = sysMenuMapper.findAll();
if (CollectionUtils.isEmpty(sysMenuList)){
return null;
}
// 2.调用工具类中的方法,返回树形数据结构列表
List<SysMenu> treeList = MenuHelper.buildTree(sysMenuList);
return treeList;
}
代码3.2:
public class MenuHelper {
// 递归实现封装
public static List<SysMenu> buildTree(List<SysMenu> sysMenuList){
// TODO 完成封装过程
List<SysMenu> trees = new ArrayList<>();
for (SysMenu sysMenu : sysMenuList) {
// 找到递归入口
if (sysMenu.getParentId().longValue()==0){
// 根据第一层,找下一层的数据
trees.add(findChildren(sysMenu, sysMenuList));
}
}
return trees;
}
// 返回已经封装好children字段的 菜单节点
private static SysMenu findChildren(SysMenu sysMenu, List<SysMenu> sysMenuList) {
// 初始化
sysMenu.setChildren(new ArrayList<>());
for (SysMenu menu : sysMenuList) {
if(menu.getParentId().longValue()==sysMenu.getId().longValue()){
sysMenu.getChildren().add(findChildren(menu, sysMenuList));
}
}
return sysMenu;
}
}
最终效果: