Java实现树结构(为前端实现级联菜单或者是下拉菜单接口)

news2025/7/8 13:43:19

Java实现树结构(为前端实现级联菜单或者是下拉菜单接口)

在这里插入图片描述

我们常常会遇到这样一个问题,就是前端要实现的样式是一个级联菜单或者是下拉树,如图

在这里插入图片描述
在这里插入图片描述
这样的数据接口是怎么实现的呢,是什么样子的呢?
我们可以看看 Elemui中的假数据

<el-cascader :options="options" clearable></el-cascader>

<script>
  export default {
    data() {
      return {
        options: [{
          value: 'zhinan',
          label: '指南',
          children: [{
            value: 'shejiyuanze',
            label: '设计原则',
            children: [{
              value: 'yizhi',
              label: '一致'
            }, {
              value: 'fankui',
              label: '反馈'
            }, {
              value: 'xiaolv',
              label: '效率'
            }, {
              value: 'kekong',
              label: '可控'
            }]
          }, {
            value: 'daohang',
            label: '导航',
            children: [{
              value: 'cexiangdaohang',
              label: '侧向导航'
            }, {
              value: 'dingbudaohang',
              label: '顶部导航'
            }]
          }]
        }, {
          value: 'zujian',
          label: '组件',
          children: [{
            value: 'basic',
            label: 'Basic',
            children: [{
              value: 'layout',
              label: 'Layout 布局'
            }, {
              value: 'color',
              label: 'Color 色彩'
            }, {
              value: 'typography',
              label: 'Typography 字体'
            }, {
              value: 'icon',
              label: 'Icon 图标'
            }, {
              value: 'button',
              label: 'Button 按钮'
            }]
          }, {
            value: 'form',
            label: 'Form',
            children: [{
              value: 'radio',
              label: 'Radio 单选框'
            }, {
              value: 'checkbox',
              label: 'Checkbox 多选框'
            }, {
              value: 'input',
              label: 'Input 输入框'
            }, {
              value: 'input-number',
              label: 'InputNumber 计数器'
            }, {
              value: 'select',
              label: 'Select 选择器'
            }, {
              value: 'cascader',
              label: 'Cascader 级联选择器'
            }, {
              value: 'switch',
              label: 'Switch 开关'
            }, {
              value: 'slider',
              label: 'Slider 滑块'
            }, {
              value: 'time-picker',
              label: 'TimePicker 时间选择器'
            }, {
              value: 'date-picker',
              label: 'DatePicker 日期选择器'
            }, {
              value: 'datetime-picker',
              label: 'DateTimePicker 日期时间选择器'
            }, {
              value: 'upload',
              label: 'Upload 上传'
            }, {
              value: 'rate',
              label: 'Rate 评分'
            }, {
              value: 'form',
              label: 'Form 表单'
            }]
          }, {
            value: 'data',
            label: 'Data',
            children: [{
              value: 'table',
              label: 'Table 表格'
            }, {
              value: 'tag',
              label: 'Tag 标签'
            }, {
              value: 'progress',
              label: 'Progress 进度条'
            }, {
              value: 'tree',
              label: 'Tree 树形控件'
            }, {
              value: 'pagination',
              label: 'Pagination 分页'
            }, {
              value: 'badge',
              label: 'Badge 标记'
            }]
          }, {
            value: 'notice',
            label: 'Notice',
            children: [{
              value: 'alert',
              label: 'Alert 警告'
            }, {
              value: 'loading',
              label: 'Loading 加载'
            }, {
              value: 'message',
              label: 'Message 消息提示'
            }, {
              value: 'message-box',
              label: 'MessageBox 弹框'
            }, {
              value: 'notification',
              label: 'Notification 通知'
            }]
          }, {
            value: 'navigation',
            label: 'Navigation',
            children: [{
              value: 'menu',
              label: 'NavMenu 导航菜单'
            }, {
              value: 'tabs',
              label: 'Tabs 标签页'
            }, {
              value: 'breadcrumb',
              label: 'Breadcrumb 面包屑'
            }, {
              value: 'dropdown',
              label: 'Dropdown 下拉菜单'
            }, {
              value: 'steps',
              label: 'Steps 步骤条'
            }]
          }, {
            value: 'others',
            label: 'Others',
            children: [{
              value: 'dialog',
              label: 'Dialog 对话框'
            }, {
              value: 'tooltip',
              label: 'Tooltip 文字提示'
            }, {
              value: 'popover',
              label: 'Popover 弹出框'
            }, {
              value: 'card',
              label: 'Card 卡片'
            }, {
              value: 'carousel',
              label: 'Carousel 走马灯'
            }, {
              value: 'collapse',
              label: 'Collapse 折叠面板'
            }]
          }]
        }, {
          value: 'ziyuan',
          label: '资源',
          children: [{
            value: 'axure',
            label: 'Axure Components'
          }, {
            value: 'sketch',
            label: 'Sketch Templates'
          }, {
            value: 'jiaohu',
            label: '组件交互文档'
          }]
        }]
      }
    }
  }
</script>

可以看见,这样的数据我们直接用SQL查出来是会损耗SQL性能的,我们这里展示如何在业务层做数据处理,实现树结构,这里就以若依的菜单数据为例,做一个基本演示!
查询数据我们分为两种,一种是获取指定的菜单,一种是获取全部的,获取指定的菜单我们需要写一个递归SQL,比如

在这里插入图片描述
我们如果获取全部那就是正常的查询所以,但是只要目录管理下面的菜单结构,不要其他的,那么这个SQL就是这样的:
如果想从sys_menu表中查询并获取菜单树的数据,可以使用递归查询。以下是一个基于MySQL的示例查询,该查询假设每个记录都有唯一的menu_id标识符和parent_id表示父菜单ID:

WITH RECURSIVE MenuCTE AS (
  SELECT 
    menu_id,
    menu_name,
    parent_id,
    order_num,
    path,
    component,
    query,
    is_frame,
    is_cache,
    menu_type,
    visible,
    status,
    perms,
    icon,
    create_by,
    create_time,
    update_by,
    update_time,
    remark
  FROM sys_menu
  WHERE parent_id = 0  -- 根节点的条件

  UNION ALL

  SELECT 
    m.menu_id,
    m.menu_name,
    m.parent_id,
    m.order_num,
    m.path,
    m.component,
    m.query,
    m.is_frame,
    m.is_cache,
    m.menu_type,
    m.visible,
    m.status,
    m.perms,
    m.icon,
    m.create_by,
    m.create_time,
    m.update_by,
    m.update_time,
    m.remark
  FROM sys_menu m
  JOIN MenuCTE cte ON m.parent_id = cte.menu_id
)
SELECT * FROM MenuCTE;

这个查询使用了MySQL的递归CTE(Common Table Expressions)功能,通过WITH RECURSIVE来逐级查询父菜单与子菜单的关系。查询结果包含了所有菜单及其层次结构关系。
我们分析一下这个SQL
这是一个使用递归CTE(Common Table Expressions)的SQL查询,用于获取具有层次结构关系的菜单数据。以下是对查询各部分的解释:

  1. WITH RECURSIVE MenuCTE AS: 这是一个递归CTE的开始,MenuCTE 是一个临时表名。递归CTE用于递归地查询表中的数据。

  2. SELECT ... FROM sys_menu WHERE parent_id = 0: 这是递归CTE的初始查询部分,它选择根节点(parent_id = 0)的菜单记录。

  3. UNION ALL: 这是联结两个查询结果集的关键字,它将上述初始查询结果与后续递归查询的结果联结在一起。

  4. SELECT ... FROM sys_menu m JOIN MenuCTE cte ON m.parent_id = cte.menu_id: 这是递归查询的部分,通过连接sys_menu表自身,并使用递归关系 m.parent_id = cte.menu_id 来获取每个菜单的子菜单。

  5. 最后,整个递归CTE的最后部分是 SELECT * FROM MenuCTE,它选择了所有递归CTE的结果,包括根节点和其下的所有子节点。

该查询的结果是包含所有菜单数据的表,每一行都表示一个菜单项,具有其父菜单的引用关系,形成了一个层次结构。这对于表示树形结构的数据非常有用,例如用于构建具有层次关系的菜单系统。
当然,毫无疑问,获取出来的数据就是普通列表,下面我们就进行处理:

    public static List<SysMenu> buildMenuTree(List<SysMenu> menuList) {
        Map<Long, SysMenu> menuMap = new HashMap<>();

        // 创建一个菜单ID到菜单对象的映射
        for (SysMenu menu : menuList) {
            menuMap.put(menu.getMenuId(), menu);
        }

        // 构建菜单树
        List<SysMenu> menuTree = new ArrayList<>();
        for (SysMenu menu : menuList) {
            Long parentId = menu.getParentId();
            if (parentId != null && menuMap.containsKey(parentId)) {
                SysMenu parentMenu = menuMap.get(parentId);
                parentMenu.getChildren().add(menu);
            } else {
                menuTree.add(menu); // 没有父菜单或父菜单未找到,将其作为根节点添加到树中
            }
        }

        return menuTree;
    }

这段 Java 代码是一个用于构建菜单树的方法,它接受一个包含 SysMenu 对象的列表作为输入,然后返回一个构建好的菜单树的列表。

让我对这段代码进行详细解读:

  1. 创建映射表:

    Map<Long, SysMenu> menuMap = new HashMap<>();
    

    在这里,创建了一个 HashMap 对象 menuMap,用于将菜单ID映射到相应的 SysMenu 对象。

  2. 建立映射关系:

    for (SysMenu menu : menuList) {
        menuMap.put(menu.getMenuId(), menu);
    }
    

    通过遍历输入的菜单列表 menuList,将每个菜单的 menuId 与对应的 SysMenu 对象建立映射关系。

  3. 构建菜单树:

    List<SysMenu> menuTree = new ArrayList<>();
    for (SysMenu menu : menuList) {
        Long parentId = menu.getParentId();
        if (parentId != null && menuMap.containsKey(parentId)) {
            SysMenu parentMenu = menuMap.get(parentId);
            parentMenu.getChildren().add(menu);
        } else {
            menuTree.add(menu); // 没有父菜单或父菜单未找到,将其作为根节点添加到树中
        }
    }
    

    遍历菜单列表 menuList,对于每个菜单,检查其 parentId 是否存在并且在 menuMap 中有对应的父菜单。如果是,将当前菜单添加到其父菜单的子菜单列表 children 中;如果不是,说明当前菜单是根节点,将其添加到 menuTree 中。

  4. 返回菜单树:

    return menuTree;
    

    返回构建好的菜单树列表 menuTree

这样,该方法将输入的扁平的菜单列表转换为一个带有层次结构的菜单树,其中每个菜单节点都包含了其子菜单的引用。这种结构更适合在用户界面上展示树形菜单。
这是我们自己写的,然后看看人家若依自己的,用到了递归:

    /**
     * 构建前端所需要树结构
     *
     * @param menus 菜单列表
     * @return 树结构列表
     */
    public List<SysMenu> buildMenuTree(List<SysMenu> menus) {
        List<SysMenu> returnList = new ArrayList<SysMenu>();
        List<Long> tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList());
        for (Iterator<SysMenu> iterator = menus.iterator(); iterator.hasNext(); ) {
            SysMenu menu = (SysMenu) iterator.next();
            // 如果是顶级节点, 遍历该父节点的所有子节点
            if (!tempList.contains(menu.getParentId())) {
                recursionFn(menus, menu);
                returnList.add(menu);
            }
        }
        if (returnList.isEmpty()) {
            returnList = menus;
        }
        return returnList;
    }

    /**
     * 递归列表
     *
     * @param list 分类表
     * @param t    子节点
     */

    private void recursionFn(List<SysMenu> list, SysMenu t) {
        // 得到子节点列表
        List<SysMenu> childList = getChildList(list, t);
        t.setChildren(childList);
        for (SysMenu tChild : childList) {
            if (hasChild(list, tChild)) {
                recursionFn(list, tChild);
            }
        }
    }

这段源码是一个用于构建菜单树的方法,输入是一个 List<SysMenu>,表示扁平结构的菜单列表,输出是一个构建好的菜单树。

让我对这段源码进行详细解读:

  1. 初始化:

    List<SysMenu> returnList = new ArrayList<SysMenu>();
    List<Long> tempList = menus.stream().map(SysMenu::getMenuId).collect(Collectors.toList());
    
    • returnList 是最终返回的菜单树列表。
    • tempList 是将 menus 列表中的菜单ID提取出来的列表。
  2. 遍历菜单列表:

    for (Iterator<SysMenu> iterator = menus.iterator(); iterator.hasNext(); ) {
        SysMenu menu = (SysMenu) iterator.next();
    

    使用迭代器遍历 menus 列表中的每个菜单。

  3. 判断是否为顶级节点:

    if (!tempList.contains(menu.getParentId())) {
    

    如果当前菜单的 parentId 不在 tempList 中,说明它是顶级节点。

  4. 递归构建子节点:

    recursionFn(menus, menu);
    

    调用 recursionFn 方法递归构建当前顶级节点的子节点。

  5. 将当前节点添加到返回列表:

    returnList.add(menu);
    

    将当前菜单节点添加到最终返回的菜单树列表中。

  6. 处理空的返回列表:

    if (returnList.isEmpty()) {
        returnList = menus;
    }
    

    如果最终返回的菜单树列表为空,说明输入的菜单列表本身就是一个树,直接将其作为返回结果。

  7. 返回最终结果:

    return returnList;
    

    返回构建好的菜单树列表。

第二段代码是一个递归方法recursionFn

  1. getChildList 方法:

    List<SysMenu> childList = getChildList(list, t);
    

    通过调用 getChildList 方法获取当前节点 t 的子节点列表。

  2. 设置子节点列表:

    t.setChildren(childList);
    

    将获取到的子节点列表设置到当前节点 tchildren 属性中。

  3. 递归处理子节点:

for (SysMenu tChild : childList) {
    if (hasChild(list, tChild)) {
        recursionFn(list, tChild);
    }
}

遍历当前节点的子节点列表,对每个子节点进行递归处理。如果子节点还有子节点(通过 hasChild 方法判断),则继续递归调用 recursionFn 方法。

整个递归方法的作用是从当前节点开始,递归地设置其子节点列表,并对每个子节点的子节点进行递归处理,以构建完整的树形结构。这种递归方式有助于处理树状结构的数据,例如在构建菜单树时,每个菜单节点都包含了其下级菜单的引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1347520.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【jdk与tomcat配置文件夹共享防火墙设置(入站出站规则)】

目录 一、jdk与tomcat配置 1.1 jdk配置 1.2 tomcat配置 二、文件夹共享 2.1 为什么需要配置文件夹共享功能 2.2 操作步骤 2.2.1 高级共享 2.2.2 普通共享 2.3 区别 三、防火墙设置&#xff08;入站规则&出站规则&#xff09; 3.1 入站规则跟出站规则 3.2 案例…

【低代码平台】10个开源免费Airtable 的替代方案

Airtable是一个易于使用的简单低代码平台&#xff0c;有助于团队协作管理复杂的数据表&#xff0c;并创建定制的工作流程。把它想象成一个类固醇上的云电子表格。 Airtable还简化了数据输入过程&#xff0c;连接和集成第三方服务和应用程序&#xff0c;并提供了许多数据导入/导…

第二部分 离散型随机变量

目录 求分布律里的未知数 例1 例2 根据X的分布律写Y的分布律 例3 根据(X,Y)的分布律写Z的分布律 例4 根据(X,Y)的分布律写边缘分布律 例5 X与Y相互独立时的联合分布律 例6 根据分布律求期望、方差 例7 求分布律里的未知数 例1 已知X的分布律为 X-202P0.40.3k ,试求k 解 0.40…

普中STM32-PZ6806L 使用FlyMcu串口烧录程序

简介 我的串口下载电路坏掉了, 所以研究了下如何通过USB转TTL进行程序的下载, 为后续Bootloader部分做准备;连接 我的板几乎是十年前买的&#xff0c; 所以电路与现有网上的资料有些差异, 所以仅供参考 USB 转 TTL线 与开发板 连接&#xff0c; 如图图中 ①, 需要去掉第一个…

[2024区块链开发入门指引] - 比特币运行原理

一份为小白用户准备的免费区块链基础教程 工欲善其事,必先利其器 Web3开发中&#xff0c;各种工具、教程、社区、语言框架.。。。 种类繁多&#xff0c;是否有一个包罗万象的工具专注与Web3开发和相关资讯能毕其功于一役&#xff1f; 参见另一篇博文&#x1f449; 2024最全面…

RFC6749-OAuth2.0

前言 最近在项目中需要实现SSO(单点登录)功能,以实现一处注册,即可在任何平台之间登录的功能。我们项目中并没有直接对接第三方认证系统而是通过集成keycloak 完成一系类安全协议的对接工作。如果我们在代码级别自己完成各种安全协议的对接是一项十分大的工程。不仅要走统一的…

提取 PE 文件的各种信息

前段时间项目需要实现对 Windows PE 文件版本信息的提取&#xff0c;如文件说明、文件版本、产品名称、版权、原始文件名等信息。获取这些信息在 Windows 下当然有一系列的 API 函数供调用&#xff0c;简单方便。 我们先看一下PE文件结构&#xff0c;PE文件由DOS首部&#xff0…

【网络面试(4)】协议栈和套接字及连接阶段的三次握手原理

1. 协议栈 一直对操作系统系统的内核协议栈理解的比较模糊&#xff0c;借着这一篇博客做一下简单梳理&#xff0c; 我觉得最直白的理解就是&#xff0c;内核协议栈就是操作系统中的一个网络控制软件&#xff0c;就是一段程序代码&#xff0c;它负责和网卡驱动程序交互&#xff…

【nodejs】前后端身份认证

前后端身份认证 一、web开发模式 服务器渲染&#xff0c;前后端分离。 不同开发模式下的身份认证&#xff1a; 服务端渲染推荐使用Session认证机制前后端分离推荐使用JWT认证机制 二、session认证机制 1.HTTP协议的无状态性 了解HTTP协议的无状态性是进一步学习Session认…

appium入门基础

介绍 appium支持在不同平台的UI自动化&#xff0c;如web,移动端,桌面端等。还支持使用java&#xff0c;python&#xff0c;js等语言编写自动化代码。主要用于自动化测试脚本&#xff0c;省去重复的手动操作。 Appium官网 安装 首先必须环境有Node.js用于安装Appium。 总体来…

接入Cloudflare后Nginx和Django获取用户真实IP的办法

可以用Nginx的real_ip的相关命令来实现这个需求。 01-real_ip命令集详解 real_ip命令的使用分为两个步骤: 01-1-设置从哪些代理IP获取真实IP 第1个步骤&#xff1a;通过set_real_ip_from命令设置从哪些代理IP请求获取真实的IP,比如下面的命令&#xff1a; set_real_ip_from…

2022年全球软件质量效能大会(QECon北京站2022)-核心PPT资料下载

一、峰会简介 当前&#xff0c;新一轮科技革命和产业变革正在重塑全球经济格局&#xff0c;以云计算为代表的新一代信息技术创新活跃&#xff0c;与实体经济深度融合&#xff0c;推动泛在连接、数据驱动、智能引领的数字经济新形式孕育而生。 新兴技术的出现给测试乃至整个软…

探索 Vue 异步组件的世界:解锁高效开发的秘密(下)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…

JavaScript 基础通关

快速熟悉 JavaScript 的基础语法&#xff0c;比较高级的比如事件放在后面的笔记中。 JavaScript 1. JavaScript 介绍 1.1 JavaScript 基本介绍 JavaScript 是一门运行在客户端&#xff08;浏览器&#xff09;的编程语言&#xff0c;实现人机交互的效果。实现网页特效、表单验…

SpringBoot学习(一)

注&#xff1a;此为笔者学习狂神说SpringBoot的笔记&#xff0c;其中包含个人的笔记和理解&#xff0c;仅做学习笔记之用&#xff0c;更多详细资讯请出门左拐B站&#xff1a;狂神说!!! Spring Boot 是一种基于 Spring 框架的开发模式&#xff0c;旨在简化 Spring 应用程序的创…

学生管理系统(vue + springboot)

学生管理系统&#xff08;vuespringboot&#xff09;资源-CSDN文库 项目介绍 这是一个采用前后端分离开发的项目&#xff0c;前端采用 Vue 开发、后端采用 Spring boot Mybatis 开发。 项目部署 ⭐️如果你有 docker 的话&#xff0c;直接 docker compose up 即可启动&#…

NullByte

信息收集 # nmap -sn 192.168.1.0/24 -oN live.nmap Starting Nmap 7.94 ( https://nmap.org ) at 2023-12-29 09:23 CST Nmap scan report for 192.168.1.1 Host is up (0.00038s latency). MAC Address: 00:50:56:C0:00:08 (VMware) Nmap scan report for …

使用软件解决T490笔记本57摄氏度温度墙的问题

项目场景&#xff1a; 提示&#xff1a;这里简述项目相关背景&#xff1a; 客户使用LenovoT490跑GQRX SDR&#xff0c;接入SDR在5MHz采样率下&#xff0c;机器卡顿。这对于10代i7CPU显然是不正常的。后续发现上网页也卡&#xff0c;卸载杀毒、重装系统、BIOS电源设置、系统最…

uniapp的css样式图片大小截图展示

目录 截取图片前截取图片后第一种方式&#xff1a;代码第二种方式&#xff1a;代码最后 截取图片前 截取图片后 第一种方式&#xff1a;代码 <view class"swiper-box-img"><image class"swiper-box-img-img" :src"item.file_path" mod…

10TB海量JSON数据从OSS迁移至MaxCompute

前提条件 开通MaxCompute。 在DataWorks上完成创建业务流程&#xff0c;本例使用DataWorks简单模式。详情请参见创建业务流程。 将JSON文件重命名为后缀为.txt的文件&#xff0c;并上传至OSS。本文中OSS Bucket地域为华东2&#xff08;上海&#xff09;。示例文件如下。 {&qu…