文章目录
- 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"borderdefault-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;}
}
最终效果: