Java中树形菜单分析和实现

一、背景概述

  • 最近在搭建基于Spring Boot(2.0.2)种子项目框架时,遇到了树形菜单加载问题。特此把解决的问题方案记录下去,供其他小伙伴参考和日后回顾。

二、方案分析

  • 方案选择
    • 一次性加载完,返回前台需要的数据结构
    • 点击加载,默认记载根层级的菜单。后续点击那一级菜单加载其下的子节点
  • 方案实现
    • 在这里只分析一次性加载实现。第二种实现起来更简单。在这里略过。有兴趣可以自己研究一下。

三、具体实现

  1. 表、对象设计

    1. 表结构
      这里写图片描述
    2. 测试数据(忽略内容==)
      这里写图片描述
    3. 对象字段
    private String informationTypeId; // 菜单id
    private Integer informationTypeNum;
    private String informationTypeCode;
    private String informationTypeUrl;
    private String informationTypeName;
    private String informationTypePicBosKey;
    private Integer informationTypeLevel;
    private String type;
    private String model;
    private Boolean isEdit;
    private String parentId;  // 父菜单id
    private String remark;
    private String createUserId;
    private Date createTime;
    private String modifyUserId;
    private Date modifyTime;
    private Date timeStamp;
    private List<InformationTypeEntity> childNodes = new ArrayList<>();  // 子菜单list
    // 省略set,get
    
    1. 查出所有菜单列表
    /**
     * 查询资讯菜单(一次性加载)
     *
     * @throws CustomException
     */
    @RequestMapping(value = "/findAllTypeList")
    public List<InformationTypeEntity> findAllTypeList() throws CustomException {
        // 加载所有菜单列表
        // 根级的菜单列表
        List<InformationTypeEntity> rootList = informationTypeService.findInfoTypeByType("1", null);
        List<InformationTypeEntity> list = new ArrayList<>();
        
        for (int i = 0; i < rootList.size(); i++) {
            InformationTypeEntity typeEntity = rootList.get(i);
            if ("-1".equals(typeEntity.getParentId())) {
                list.add(typeEntity);
            }
        }

        for (InformationTypeEntity entity : list) {
            entity.setChildNodes(getChildNodes(entity.getInformationTypeId(), rootList));
        }
        return list;
    }
  1. 找出根节点菜单列表
    /**
     * 查询资讯菜单(一次性加载)
     *
     * @throws CustomException
     */
    @RequestMapping(value = "/findAllTypeList")
    public List<InformationTypeEntity> findAllTypeList() throws CustomException {
        List<InformationTypeEntity> rootList = informationTypeService.findInfoTypeByType("1", null);
        List<InformationTypeEntity> list = new ArrayList<>();
        for (int i = 0; i < rootList.size(); i++) {
            InformationTypeEntity typeEntity = rootList.get(i);
            // 找出根级菜单,(约定所有根级菜单的parentId属性为-1)
            if ("-1".equals(typeEntity.getParentId())) {
                list.add(typeEntity);
            }
        }
    
        for (InformationTypeEntity entity : list) {
            entity.setChildNodes(getChildNodes(entity.getInformationTypeId(), rootList));
        }
        return list;
    }
  1. 循环找出每个子节点的菜单列表
    /**
     * 查询资讯菜单(一次性加载)
     *
     * @throws CustomException
     */
    @RequestMapping(value = "/findAllTypeList")
    public List<InformationTypeEntity> findAllTypeList() throws CustomException {
        List<InformationTypeEntity> rootList = informationTypeService.findInfoTypeByType("1", null);
        List<InformationTypeEntity> list = new ArrayList<>();
        for (int i = 0; i < rootList.size(); i++) {
            InformationTypeEntity typeEntity = rootList.get(i);
            // 找出根级菜单,(约定所有根级菜单的parentId属性为-1)
            if ("-1".equals(typeEntity.getParentId())) {
                list.add(typeEntity);
            }
        }
        
        // 循环根级菜单,为每个根级菜单下的子菜单list赋值。
        for (InformationTypeEntity entity : list) {
            entity.setChildNodes(getChildNodes(entity.getInformationTypeId(), rootList));
        }
        return list;
    }
  • 递归解决无限层级嵌套(里面的注释我写的很清楚。大概的思路就是除了当前子菜单后,还要考虑子菜单的子菜单,考虑用递归处理。递归结束的条件就是没有子菜单,也就是list的size为0.此种方式支持无线的嵌套)
    /**
     * Return the list of children node from percent menu id
     *
     * @param id  current menu id
     * @param rootList  all list of menus
     * @return
     * @throws CustomException
     */
    private List<InformationTypeEntity> getChildNodes(String id, List<InformationTypeEntity> rootList) throws CustomException {
        // The list of child nodes
        List<InformationTypeEntity> childList = new ArrayList<>();
        // Fill the list of child'nodes which parent id equal params of id
        for (InformationTypeEntity typeEntity : rootList) {
            if (StringUtils.isNotBlank(typeEntity.getParentId())) {
                if (id.equals(typeEntity.getParentId())) {
                    childList.add(typeEntity);
                }
            }
        }
        if (childList.size() == 0) {
            return null;
        }
        // Look up it's child node and fill
        for (InformationTypeEntity entity : childList) {
            entity.setChildNodes(getChildNodes(entity.getInformationTypeId(), rootList));
        }
        return childList;
    }

四、总结

  • 关于树形菜单的实现方案有很多,我只是列举出比较典型的2种方案。
  • 举例的这种方案可以支持无限级嵌套
  • 举例的这种只发送一次sql,可以在一定程度上调高效率,减少数据库压力
  • 理解思路,上面的代码好看优化哈,以后有时间再补充上

五、测试结果返回的数据结果示例如下 (数组中包含多个对象,对象里面嵌套子节点集合…)

其实这样的数据结构会满足大部分前端树形组件的数据结构的要求。

前台菜单形如下图:

在这里插入图片描述

后端返回的结果如下结构:

[
  {
    "informationTypeId": "4",
    "informationTypeNum": 1,
    "informationTypeCode": "001",
    "informationTypeUrl": "/index",
    "informationTypeName": "资讯管理",
    "informationTypePicBosKey": "pic",
    "informationTypeLevel": null,
    "type": "1",
    "model": "1",
    "isEdit": null,
    "parentId": "-1",
    "remark": null,
    "createUserId": null,
    "createTime": null,
    "modifyUserId": null,
    "modifyTime": null,
    "timeStamp": "2018-06-15 10:17:51",
    "childNodes": [
      {
        "informationTypeId": "1",
        "informationTypeNum": 1,
        "informationTypeCode": "001001",
        "informationTypeUrl": "/index1",
        "informationTypeName": "十九大管理",
        "informationTypePicBosKey": "pic1",
        "informationTypeLevel": null,
        "type": "1",
        "model": "1",
        "isEdit": null,
        "parentId": "4",
        "remark": null,
        "createUserId": null,
        "createTime": "2018-06-01 10:15:35",
        "modifyUserId": null,
        "modifyTime": null,
        "timeStamp": "2018-06-15 10:17:56",
        "childNodes": [
          {
            "informationTypeId": "5",
            "informationTypeNum": 1,
            "informationTypeCode": "001001001",
            "informationTypeUrl": "/index/index1",
            "informationTypeName": "中国梦",
            "informationTypePicBosKey": "pic",
            "informationTypeLevel": null,
            "type": "1",
            "model": "2",
            "isEdit": null,
            "parentId": "1",
            "remark": null,
            "createUserId": null,
            "createTime": null,
            "modifyUserId": null,
            "modifyTime": null,
            "timeStamp": "2018-06-15 10:18:53",
            "childNodes": [
              {
                "informationTypeId": "8",
                "informationTypeNum": 1,
                "informationTypeCode": "001001001001",
                "informationTypeUrl": "/index/index/index",
                "informationTypeName": "白日梦",
                "informationTypePicBosKey": "pic",
                "informationTypeLevel": null,
                "type": "1",
                "model": "1",
                "isEdit": null,
                "parentId": "5",
                "remark": null,
                "createUserId": null,
                "createTime": null,
                "modifyUserId": null,
                "modifyTime": null,
                "timeStamp": "2018-06-15 11:21:50",
                "childNodes": [
                  {
                    "informationTypeId": "10",
                    "informationTypeNum": 1,
                    "informationTypeCode": "001001001001001",
                    "informationTypeUrl": "/index/index/index2",
                    "informationTypeName": "李白的白日梦",
                    "informationTypePicBosKey": "pic",
                    "informationTypeLevel": null,
                    "type": "1",
                    "model": "1",
                    "isEdit": null,
                    "parentId": "8",
                    "remark": null,
                    "createUserId": null,
                    "createTime": null,
                    "modifyUserId": null,
                    "modifyTime": null,
                    "timeStamp": "2018-06-15 11:24:05",
                    "childNodes": [
                      {
                        "informationTypeId": "12",
                        "informationTypeNum": 1,
                        "informationTypeCode": "000013123123123123123",
                        "informationTypeUrl": "12341234123123",
                        "informationTypeName": "李白的白日梦儿子1",
                        "informationTypePicBosKey": "23213",
                        "informationTypeLevel": null,
                        "type": "1",
                        "model": "1",
                        "isEdit": null,
                        "parentId": "10",
                        "remark": null,
                        "createUserId": null,
                        "createTime": null,
                        "modifyUserId": null,
                        "modifyTime": null,
                        "timeStamp": "2018-06-15 11:32:36",
                        "childNodes": [
                          {
                            "informationTypeId": "14",
                            "informationTypeNum": 2,
                            "informationTypeCode": "242424",
                            "informationTypeUrl": "24243234234",
                            "informationTypeName": "l李白的白日梦儿子1的儿子11111",
                            "informationTypePicBosKey": "2424",
                            "informationTypeLevel": null,
                            "type": "1",
                            "model": "2",
                            "isEdit": null,
                            "parentId": "12",
                            "remark": null,
                            "createUserId": null,
                            "createTime": null,
                            "modifyUserId": null,
                            "modifyTime": null,
                            "timeStamp": "2018-06-15 11:36:28",
                            "childNodes": null
                          }
                        ]
                      },
                      {
                        "informationTypeId": "13",
                        "informationTypeNum": 2,
                        "informationTypeCode": "2424234234234234",
                        "informationTypeUrl": "2424242424",
                        "informationTypeName": "李白的白日梦儿子2",
                        "informationTypePicBosKey": "23424",
                        "informationTypeLevel": null,
                        "type": "1",
                        "model": "1",
                        "isEdit": null,
                        "parentId": "10",
                        "remark": null,
                        "createUserId": null,
                        "createTime": null,
                        "modifyUserId": null,
                        "modifyTime": null,
                        "timeStamp": "2018-06-15 11:33:07",
                        "childNodes": null
                      }
                    ]
                  },
                  {
                    "informationTypeId": "11",
                    "informationTypeNum": 2,
                    "informationTypeCode": "0010001001001002",
                    "informationTypeUrl": "/index/index/index2",
                    "informationTypeName": "杜甫的白日梦",
                    "informationTypePicBosKey": "pic",
                    "informationTypeLevel": null,
                    "type": "1",
                    "model": "2",
                    "isEdit": null,
                    "parentId": "8",
                    "remark": null,
                    "createUserId": null,
                    "createTime": null,
                    "modifyUserId": null,
                    "modifyTime": null,
                    "timeStamp": "2018-06-15 11:24:58",
                    "childNodes": null
                  }
                ]
              },
              {
                "informationTypeId": "9",
                "informationTypeNum": 2,
                "informationTypeCode": "001001001002",
                "informationTypeUrl": "/index/index/index2",
                "informationTypeName": "黑夜梦",
                "informationTypePicBosKey": "pic",
                "informationTypeLevel": null,
                "type": "1",
                "model": "2",
                "isEdit": null,
                "parentId": "5",
                "remark": null,
                "createUserId": null,
                "createTime": null,
                "modifyUserId": null,
                "modifyTime": null,
                "timeStamp": "2018-06-15 11:22:30",
                "childNodes": null
              }
            ]
          },
          {
            "informationTypeId": "6",
            "informationTypeNum": 2,
            "informationTypeCode": "001001002",
            "informationTypeUrl": "/index/index2",
            "informationTypeName": "扶贫",
            "informationTypePicBosKey": "pic",
            "informationTypeLevel": null,
            "type": "1",
            "model": "2",
            "isEdit": null,
            "parentId": "1",
            "remark": null,
            "createUserId": null,
            "createTime": null,
            "modifyUserId": null,
            "modifyTime": null,
            "timeStamp": "2018-06-15 10:20:23",
            "childNodes": null
          }
        ]
      },
      {
        "informationTypeId": "2",
        "informationTypeNum": 2,
        "informationTypeCode": "001002",
        "informationTypeUrl": "/index2",
        "informationTypeName": "党代会议管理",
        "informationTypePicBosKey": "pic2",
        "informationTypeLevel": null,
        "type": "1",
        "model": "2",
        "isEdit": null,
        "parentId": "4",
        "remark": null,
        "createUserId": null,
        "createTime": "2018-06-13 05:48:42",
        "modifyUserId": null,
        "modifyTime": null,
        "timeStamp": "2018-06-15 10:17:54",
        "childNodes": [
          {
            "informationTypeId": "7",
            "informationTypeNum": 1,
            "informationTypeCode": "001002001",
            "informationTypeUrl": "/index/index",
            "informationTypeName": "党会管理",
            "informationTypePicBosKey": "pic111",
            "informationTypeLevel": null,
            "type": "1",
            "model": "3",
            "isEdit": null,
            "parentId": "2",
            "remark": null,
            "createUserId": null,
            "createTime": null,
            "modifyUserId": null,
            "modifyTime": null,
            "timeStamp": "2018-06-15 10:20:59",
            "childNodes": null
          }
        ]
      },
      {
        "informationTypeId": "3",
        "informationTypeNum": 3,
        "informationTypeCode": "001003",
        "informationTypeUrl": "/index3",
        "informationTypeName": "两学一做",
        "informationTypePicBosKey": "pic3",
        "informationTypeLevel": null,
        "type": "1",
        "model": "1",
        "isEdit": null,
        "parentId": "4",
        "remark": null,
        "createUserId": null,
        "createTime": null,
        "modifyUserId": null,
        "modifyTime": null,
        "timeStamp": "2018-06-15 10:17:53",
        "childNodes": null
      }
    ]
  }
]
  • 19
    点赞
  • 83
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
Java漂亮的树形菜单制作实例(源码),森林状的关系图,文本域,用于显示点击的节点名称,使用了JTree,可以看作是一个jTree的用法演示实例。树形菜单应用广泛,这个Tree制作漂亮,相信会让很多朋友从学习到实现的方法,效果如演示截图所示。下面是相关的代码片段:   DefaultMutableTreeNode root = new DefaultMutableTreeNode("设置"); //生成根节点   DefaultMutableTreeNode node1=new DefaultMutableTreeNode("常规"); //生成节点一   node1.add(new DefaultMutableTreeNode("默认路径")); //增加新节点到节点一上   node1.add(new DefaultMutableTreeNode("保存选项"));   root.add(node1); //增加节点一到根节点上   root.add(new DefaultMutableTreeNode("界面"));   root.add(new DefaultMutableTreeNode("提示声音"));   root.add(new DefaultMutableTreeNode("打印"));   JTree tree = new JTree(root); //得到JTree的实例   DefaultTreeCellRenderer renderer = (DefaultTreeCellRenderer)tree.getCellRenderer(); //得到JTree的Renderer   renderer.setLeafIcon(null); //设置叶子节点图标为空   renderer.setClosedIcon(null); //设置关闭节点的图标为空   renderer.setOpenIcon(null); //设置打开节点的图标为空   tree.addTreeSelectionListener(new TreeSelectionListener() {//选择节点的事件处理   public void valueChanged(TreeSelectionEvent evt) {   TreePath path = evt.getPath(); //得到选择路径   String info=path.getLastPathComponent().toString(); //得到选择的节点名称   jtfInfo.setText(info); //在文本域显示名称   }
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值