下面我会详细讲解一下“Spring Boot + Mybatis Plus实现树状菜单的方法”的完整攻略。
一、实现思路
-
首先,在数据库中准备好菜单表,并设计好菜单表的结构,一般会包含菜单id、父级菜单id、菜单名称、菜单路径等字段。
-
使用Mybatis Plus的父子关系注解,将菜单表转化成实体类,并继承Mybatis Plus提供的Model类。
-
编写Mapper层的接口以及对应的XML文件,实现对菜单表的增删改查操作。
-
定义一个常量
ROOT_ID
代表顶级菜单的父级id,然后使用递归算法查询并组装出一个树形结构的菜单列表。 -
在前端页面中使用递归算法将树形结构的菜单列表渲染出来。
二、代码实现
1. 菜单表结构
菜单表的结构需要包含以下字段:
id bigint not null comment '菜单id',
parent_id bigint comment '父级菜单id',
name varchar comment '菜单名称',
url varchar comment '菜单路径',
primary key (id)
2. 菜单实体类
使用Mybatis Plus提供的@TableName
、@TableId
、@TableField
注解,将菜单表转化成实体类,并继承Mybatis Plus提供的Model类。
@Data
@TableName("menu")
public class Menu extends Model<Menu> {
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@TableField(value = "parent_id")
private Long parentId;
@TableField(value = "name")
private String name;
@TableField(value = "url")
private String url;
}
3. Mapper层接口以及对应的XML文件
public interface MenuMapper extends BaseMapper<Menu> {
}
<mapper namespace="com.example.mapper.MenuMapper">
<select id="selectTreeMenu" resultType="com.example.entity.Menu">
select * from menu where parent_id = #{id};
</select>
</mapper>
4. 递归算法查询并组装出树形结构的菜单列表
@Service
public class MenuService {
private static final Long ROOT_ID = 0L;
@Autowired
private MenuMapper menuMapper;
/**
* 查询树形结构的菜单列表
*/
public List<Menu> selectTreeMenu() {
// 查询顶级菜单列表
List<Menu> rootMenuList = menuMapper.selectList(
new QueryWrapper<Menu>()
.lambda()
.eq(Menu::getParentId, ROOT_ID));
// 递归查询子菜单
for (Menu rootMenu : rootMenuList) {
List<Menu> childMenuList = selectChildMenu(rootMenu);
rootMenu.setChildren(childMenuList);
}
return rootMenuList;
}
/**
* 递归查询子菜单
*/
private List<Menu> selectChildMenu(Menu menu) {
List<Menu> childMenuList = menuMapper.selectList(
new QueryWrapper<Menu>()
.lambda()
.eq(Menu::getParentId, menu.getId()));
for (Menu childMenu : childMenuList) {
List<Menu> grandchildrenMenuList = selectChildMenu(childMenu);
childMenu.setChildren(grandchildrenMenuList);
}
return childMenuList;
}
}
5. 前端页面渲染
<!DOCTYPE html>
<html lang="zh-cn">
<head>
<meta charset="UTF-8">
<title>树形菜单</title>
<style>
ul {
list-style: none;
}
li {
padding: 10px;
margin: 5px 0;
cursor: pointer;
}
li:hover {
background-color: lightgray;
}
.open {
background: url('../img/down_arrow.png') no-repeat;
background-position: 10px center;
}
.close {
background: url('../img/right_arrow.png') no-repeat;
background-position: 10px center;
}
</style>
</head>
<body>
<div id="treeMenu"></div>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
<script>
Vue.component('TreeMenuNode', {
props: ['node'],
template: `
<li :class="{'open': node.children&&node.children.length>0, 'close': !node.children||node.children.length===0}" @click="toggle">
{{node.name}}
<ul v-if="node.children">
<tree-menu-node v-for="child in node.children" :key="child.id" :node="child"></tree-menu-node>
</ul>
</li>
`,
methods: {
toggle() {
if (this.node.children && this.node.children.length > 0) {
this.$set(this.node, 'children', []);
} else {
axios.get('/treeMenu/' + this.node.id).then(res => {
this.$set(this.node, 'children', res.data);
});
}
}
}
});
var app = new Vue({
el: '#treeMenu',
data() {
return {
treeMenu: []
};
},
mounted() {
axios.get('/treeMenu').then(res => {
this.treeMenu = res.data;
});
}
});
</script>
</body>
</html>
三、示例说明
1. 递归查询子菜单过程
假设有如下的菜单列表数据:
id | parent_id | name | url |
---|---|---|---|
1 | 0 | 一级菜单1 | /menu/1 |
2 | 0 | 一级菜单2 | /menu/2 |
3 | 1 | 二级菜单1 | /menu/1/1 |
4 | 3 | 三级菜单1 | /menu/1/1/1 |
5 | 3 | 三级菜单2 | /menu/1/1/2 |
6 | 2 | 二级菜单2 | /menu/2/1 |
刚开始查询顶级菜单列表时,SQL语句为:
select * from menu where parent_id = 0;
执行结果为:
id | parent_id | name | url |
---|---|---|---|
1 | 0 | 一级菜单1 | /menu/1 |
2 | 0 | 一级菜单2 | /menu/2 |
在递归查询一级菜单的子菜单时,将当前菜单的id作为查询条件,SQL语句为:
select * from menu where parent_id = 1;
执行结果为:
id | parent_id | name | url |
---|---|---|---|
3 | 1 | 二级菜单1 | /menu/1/1 |
然后继续递归查询二级菜单的子菜单,将当前菜单的id作为查询条件,SQL语句为:
select * from menu where parent_id = 3;
执行结果为:
id | parent_id | name | url |
---|---|---|---|
4 | 3 | 三级菜单1 | /menu/1/1/1 |
5 | 3 | 三级菜单2 | /menu/1/1/2 |
此时三级菜单已经是最底层的菜单了,递归结束,将查询出的子菜单列表赋值给二级菜单的children属性:
List<Menu> grandchildrenMenuList = selectChildMenu(childMenu);
childMenu.setChildren(grandchildrenMenuList);
这样在递归回到一级菜单时,二级菜单的children属性已经被赋值,此时一级菜单的子菜单列表就是二级菜单的列表。
2. 渲染树形列表
使用Vue组件,并使用递归算法将树形结构的菜单列表渲染出来。
首先,在HTML中定义Vue根节点的id为treeMenu
,在Vue的data中定义一个treeMenu
数组,然后在mounted钩子中使用axios发送异步请求,获取后端返回的树形菜单列表赋值给treeMenu
。
var app = new Vue({
el: '#treeMenu',
data() {
return {
treeMenu: []
};
},
mounted() {
axios.get('/treeMenu').then(res => {
this.treeMenu = res.data;
});
}
});
接下来定义一个名为TreeMenuNode
的Vue组件,这个组件包含一个名为node
的prop,用于接收从父组件传递过来的菜单节点,然后在组件的模板中,使用v-if判断当前节点是否存在子菜单列表,如果有,则渲染一个ul列表,并用v-for递归渲染子菜单。
在点击li标签时判断当前节点是否已经展开,如果已经展开,则将节点的children属性设置为空数组,否则向后端发送异步请求获取子菜单列表,并将列表赋值给节点的children属性。
Vue.component('TreeMenuNode', {
props: ['node'],
template: `
<li :class="{'open': node.children&&node.children.length>0, 'close': !node.children||node.children.length===0}" @click="toggle">
{{node.name}}
<ul v-if="node.children">
<tree-menu-node v-for="child in node.children" :key="child.id" :node="child"></tree-menu-node>
</ul>
</li>
`,
methods: {
toggle() {
if (this.node.children && this.node.children.length > 0) {
this.$set(this.node, 'children', []);
} else {
axios.get('/treeMenu/' + this.node.id).then(res => {
this.$set(this.node, 'children', res.data);
});
}
}
}
});
最后,将定义好的Vue组件使用<tree-menu-node>
标签在HTML中排列成树形结构即可。
<div id="treeMenu"></div>
<script>
// ... Vue组件定义过程 ...
</script>
这样就能够实现树形菜单的展示了。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Spring Boot + Mybatis Plus实现树状菜单的方法 - Python技术站