Spring Boot + Mybatis Plus实现树状菜单的方法

yizhihongxing

下面我会详细讲解一下“Spring Boot + Mybatis Plus实现树状菜单的方法”的完整攻略。

一、实现思路

  1. 首先,在数据库中准备好菜单表,并设计好菜单表的结构,一般会包含菜单id、父级菜单id、菜单名称、菜单路径等字段。

  2. 使用Mybatis Plus的父子关系注解,将菜单表转化成实体类,并继承Mybatis Plus提供的Model类。

  3. 编写Mapper层的接口以及对应的XML文件,实现对菜单表的增删改查操作。

  4. 定义一个常量ROOT_ID代表顶级菜单的父级id,然后使用递归算法查询并组装出一个树形结构的菜单列表。

  5. 在前端页面中使用递归算法将树形结构的菜单列表渲染出来。

二、代码实现

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技术站

(0)
上一篇 2023年6月27日
下一篇 2023年6月27日

相关文章

  • 详解java封装继承多态

    详解Java封装、继承、多态 Java是一种非常流行的面向对象编程语言,其中最核心的概念就是封装、继承和多态。在使用Java进行开发过程中,掌握这三个概念是非常重要的。本文将详细讲解Java封装、继承、多态的含义、原理、应用和示例,以帮助读者加深对这三个概念的理解。 什么是封装 封装是面向对象编程的一个重要原则,它是指将数据和对数据的操作封装在一个类中,并且…

    other 2023年6月25日
    00
  • jdk的**技术(jdkproxy)

    JDK动态代理(JDK Proxy)是Java中一种常用的代理模式实现方式,它可以在运行时动态地创建代理类和代理对象,而无需先定义代理类。以下是JDK动态代理的完整攻略: 步骤一:定义接口 首先,需要定义一个接口该接口是代理类和被代理类的公共接口。以下是一个示例接口: public interface UserService { void addUser(S…

    other 2023年5月9日
    00
  • vivox90pro怎么退出开发者模式? vivox90pro关闭开发者模式的技巧

    下面是针对这个问题的完整攻略。 什么是开发者模式? 开发者模式是安卓系统内置的调试选项,可以方便开发人员进行开发和调试工作。在开发者模式下,用户可以进行一些高级设置,包括开启USB调试、查看CPU使用情况、调整分辨率、禁用应用等级权限等。因此,使用开发者模式需要谨慎,避免对系统造成损坏。 如何退出开发者模式? 退出开发者模式非常简单,在设置中可以直接关闭开发…

    other 2023年6月26日
    00
  • python进阶之魔术方法详解

    Python进阶之魔术方法详解 1. 什么是魔术方法 魔术方法是Python中特殊的方法,它们以双下划线 __ 开头和结束,有时也被称为特殊方法或魔法方法。它们用于定义类的行为,可以在实例化、操作符重载、属性访问等多个方面提供自定义的功能。 2. 常用的魔术方法 2.1 构造和初始化方法 构造和初始化方法用于创建和初始化一个对象。最常用的构造和初始化方法是 …

    other 2023年6月28日
    00
  • 如何设置本地连接ip 本机固定IP地址设置方法

    如何设置本地连接IP – 本机固定IP地址设置方法 在本机上设置固定IP地址可以确保网络连接的稳定性和一致性。下面是设置本地连接IP的详细攻略: 步骤1:打开网络和共享中心 首先,打开控制面板并点击“网络和共享中心”。 步骤2:选择本地连接 在“网络和共享中心”窗口中,找到并点击“本地连接”(或其他类似名称的网络连接)。 步骤3:打开属性窗口 在“本地连接”…

    other 2023年7月30日
    00
  • 使用sqlserver中的float类型时发现的问题

    使用SQL Server中的Float类型时发现的问题 当我们在使用SQL Server数据库时,可能会用到浮点型数据类型,其中包括float和real两种类型。这些数据类型非常适合用于大型数据计算和科学计算。 然而,在使用SQL Server中的float类型时,需要注意一些问题。下面将介绍两个常见的问题和解决方案。 问题1:float类型的不准确输出 在…

    其他 2023年3月29日
    00
  • 怎么获得ip地址?释放和重新获得IP地址的方法

    如何获得IP地址 IP地址是用于在互联网上唯一标识设备的一组数字。获得IP地址的方法取决于您是要获取公共IP地址还是私有IP地址。 获得公共IP地址 公共IP地址是由您的互联网服务提供商(ISP)分配给您的。以下是获得公共IP地址的方法: 通过路由器查找:大多数家庭和办公室网络使用路由器来连接到互联网。您可以通过登录到路由器的管理界面来查找公共IP地址。通常…

    other 2023年7月30日
    00
  • 电脑鼠标点击失灵如何修复(附5种解决方案)

    电脑鼠标点击失灵如何修复 电脑上的鼠标是我们日常使用最频繁的输入设备之一,但有时会出现鼠标点击失灵的情况,让使用者很困扰。下面介绍五种解决方案。 1. 更换电脑鼠标 最简单的解决方法,如果鼠标失灵,可以考虑更换一个新的电脑鼠标。这是最快捷的方法。 2. 检查鼠标连接 如果更换鼠标后还是无法工作,那么可以检查鼠标连接端口是否插好。如果是无线鼠标,可以尝试更换电…

    other 2023年6月26日
    00
合作推广
合作推广
分享本页
返回顶部