Java + Element-UI 实现简单的树形菜单

2020-05-08 16:07:22来源:博客园 阅读 ()

新老客户大回馈,云服务器低至5折

Java + Element-UI 实现简单的树形菜单

一、简单入门级树形菜单实现(纯后台逻辑)

1、简介

(1)开发环境
  IDEA + JDK1.8 + mysql 1.8
  SpringBoot 2.2.6 + mybatis-plus
  此处仅后台开发(返回 json 数据),前台页面展示后续会讲解。

(2)数据表
  如下,仅供参考,可以添加 修改时间、创建时间、逻辑删除等字段。

DROP DATABASE IF EXISTS test;

CREATE DATABASE test;

USE test;

/* 用于测试 树形的菜单(以商品为例) */
CREATE TABLE tree_menu(
    menu_id             bigint NOT NULL AUTO_INCREMENT COMMENT "当前菜单ID",
    name                char(50) COMMENT "菜单名",
    parent_menu_id      bigint COMMENT "当前菜单的父菜单 ID",
    meun_level          int COMMENT "当前菜单的层级",
    sort                int COMMENT "排序",
    PRIMARY KEY (menu_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT="树形菜单";

 

 

 

(3)插入测试数据
  为了数据直观显示,如下插入数据,每段数据代表一个菜单栏。
注:
  parent_menu_id 从 0 开始,0 表示第一级菜单(没有父菜单)。
  meun_level 从 1 开始,1 表示第一级菜单,2 表示第二级菜单。

INSERT INTO tree_menu(menu_id, name, parent_menu_id, meun_level, sort)
VALUES
    (1, '厨具', 0, 1, 0),
        (2, '刀具', 1, 2, 0),(3, '烹饪工具', 1, 2, 0),(4, '餐具', 1, 2, 0),
          (5, '菜刀', 2, 3, 0),(6, '剪刀', 2, 3, 0),(7, '水果刀', 2, 3, 0),
          (8, '炒菜锅', 3, 3, 0),(9, '压力锅', 3, 3, 0),(10, '平底锅', 3, 3, 0),
          (11, '筷子', 4, 3, 0),(12, '碗', 4, 3, 0),(13, '果盘', 4, 3, 0),
        
    (14, '家用电器', 0, 1, 0),
        (15, '大家电', 14, 2, 0),(16, '生活家电', 14, 2, 0),
          (17, '电视', 15, 3, 0),(18, '电脑', 15, 3, 0),(19, '洗衣机', 15, 3, 0),(20, '冰箱', 15, 3, 0),
          (21, '电风扇', 16, 3, 0),(22, '吸尘器', 16, 3, 0),(23, '饮水机', 16, 3, 0),(24, '加湿器', 16, 3, 0),
    
    (25, '数码', 0, 1, 0),
        (26, '摄像摄影', 25, 2, 0),(27, '影音娱乐', 25, 2, 0),(28, '教育学习', 25, 2, 0),
          (29, '数码相机', 26, 3, 0),(30, '单反相机', 26, 3, 0),(31, '摄像机', 26, 3, 0),(32, '拍立得', 26, 3, 0),
          (33, 'MP3/MP4/MP5/PSP', 27, 3, 0),(34, '音箱', 27, 3, 0),(35, '麦克风', 27, 3, 0),
          (36, '学生平板', 28, 3, 0),(37, '复读机', 28, 3, 0),(38, '电子辞典', 28, 3, 0),(39, '点读机', 28, 3, 0)

 

2、构建基本开发环境

(1)使用 Easycode 插件根据数据表逆向生成相关代码。
参考地址:
  https://www.cnblogs.com/l-y-h/p/12781586.html#_label0_2

 

 

 

(2)测试代码是否能成功调用。
  Step1:给 TreeMenuDao 加上 @Mapper 注解。

 

 

 

  Step2:启动服务并访问 TreeMenuController。

 

 

 

  Step3:访问 http://localhost:9000/treeMenu/selectOne?id=1,查询成功即逆向生成的代码没问题。

 

 

 

3、实现树形菜单(返回 json 数据)

(1)实现思路:
  每条记录里都有 当前菜单 ID,以及 当前菜单的父菜单 ID。
  想要查询出菜单的树形结构,可以根据这两个 ID 来实现。
思路:
  首先一次性从数据库中查询出所有的菜单数据。
  然后定位到 第一级 菜单,递归遍历出其所有的子菜单。

(2)一次性查询出所有的数据。
  Step1:在 service 中添加一个查询所有数据的方法。

/**
 * 查询数据库所有数据
 * @return
 */
List<TreeMenu> queryAll();

 

 

  Step2:在 service 的实现类中重写该方法。

/**
 * 查询数据库所有数据
 * @return 数据库所有数据
 */
@Override
public List<TreeMenu> queryAll() {
    return treeMenuDao.queryAll(null);
}

 

 

  Step3:修改 controller,调用该方法。

/**
 * 获取数据库所有数据
 * @return 所有数据
 */
@GetMapping("selectAll")
public Result selectAll() {
    return Result.ok().data("items", treeMenuService.queryAll());
}

 

 

  Step4:启动服务,访问。打开控制台,可以看到返回的 json 数据。

 

 

(3)对查询的数据进行处理,返回树形的 json 数据。
  Step1:对于菜单实体类,增加一个实体类属性,用于保存其子菜单数据。

/**
 * 用于保存一个菜单的子菜单
 */
@TableField(exist = false)
private List<TreeMenu> treeMenu;

 

 

  Step2:在 service 中添加一个查询所有数据并返回树形 json 的方法。

/**
 * 查询数据库数据,并处理后返回 树形数据
 * @return 树形数据
 */
List<TreeMenu> listWithTree();

 

 

  Step3:在 service 的实现类中重写该方法。

/**
 * 查询数据库数据,并处理后返回 树形数据
 * @return 树形数据
 */
@Override
public List<TreeMenu> listWithTree() {
    // 查找所有菜单数据
    List<TreeMenu> lists = treeMenuDao.queryAll(null);
    // 把数据组合成树形结构
    List<TreeMenu> result = lists.stream()
            // 查找第一级菜单
            .filter(meun -> meun.getMeunLevel() == 1)
            // 查找子菜单并放到第一级菜单中
            .map(menu -> {
                menu.setTreeMenu(getChildren(menu, lists));
                return menu;
            })
            // 根据排序字段排序
            .sorted((menu1, menu2) -> {
                return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
            })
            // 把处理结果收集成一个 List 集合
            .collect(Collectors.toList());
    return result;
}

/**
 * 递归获取子菜单
 * @param root 当前菜单
 * @param all 总的数据
 * @return 子菜单
 */
public List<TreeMenu> getChildren(TreeMenu root, List<TreeMenu> all) {
    List<TreeMenu> children = all.stream()
            // 根据 父菜单 ID 查找当前菜单 ID,以便于找到 当前菜单的子菜单
            .filter(menu -> menu.getParentMenuId() == root.getMenuId())
            // 递归查找子菜单的子菜单
            .map((menu) -> {
                menu.setTreeMenu(getChildren(menu, all));
                return menu;
            })
            // 根据排序字段排序
            .sorted((menu1, menu2) -> {
                return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
            })
            // 把处理结果收集成一个 List 集合
            .collect(Collectors.toList());
    return children;
}

 

 

  Step4:在 controller 中调用该方法。

/**
 * 获取数据库数据,并处理成树形结构
 * @return 树形结构数据
 */
@GetMapping("selectAllWithTree")
public Result selectAllWithTree() {
    return Result.ok().data("items", treeMenuService.listWithTree());
}

 

 

  Step5:启动服务,访问。打开控制台,可以看到返回的树形 json 数据。

 

 

 

 

二、Vue + ElementUI 展示树形数据

1、简介

  前面使用后台处理返回了 树形结构 的 json 数据,现在需要将 json 数据按照一定的方式显示出来即可。
  使用 vue-cli 3.0 创建 vue 项目。
  使用 element-ui 作为页面显示。
  使用 Axios 向后台发送请求并返回数据。

2、 构建基本开发环境

(1)使用 vue-cli (图形化界面)创建一个 vue 项目。
参考地址:
  https://www.cnblogs.com/l-y-h/p/11241503.html

 

 

 

 

(2)添加 element-ui 依赖

【官网:】
    https://element.eleme.cn/#/zh-CN
    
【文档:】
    https://element.eleme.cn/#/zh-CN/component/installation
    
【安装方式一:(npm 安装)】
    npm install element-ui
    
【安装方式二:(CDN 方式引入)】
    <!-- 引入样式 -->
    <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
    <!-- 引入组件库 -->
    <script src="https://unpkg.com/element-ui/lib/index.js"></script> 

此处使用 npm 方式安装。

 

 

(3)在 vue 项目中 引入 element-ui。
  在 main.js 中引入完整的 element-ui。

【main.js】

import Vue from 'vue'
import App from './App.vue'
// 引入 element-ui
import ElementUI from 'element-ui'
// 引入 element-ui 的 css 文件
import 'element-ui/lib/theme-chalk/index.css';
// 声明使用 element-ui
Vue.use(ElementUI);

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

 

 

(4)引入树形控件。
  直接从官网选择一个模板,加以修改即可。
  如下,复制基本模板到 HelloWorld.vue 组件中,并加以修改。

【Tree 树形控件:】
    https://element.eleme.cn/#/zh-CN/component/tree

【HelloWorld.vue】
<template>
    <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>
<script>
  export default {
    data() {
      return {
        data: [{
          label: '一级 1',
          children: [{
            label: '二级 1-1',
            children: [{
              label: '三级 1-1-1'
            }]
          }]
        }, {
          label: '一级 2',
          children: [{
            label: '二级 2-1',
          }]
        }],
        defaultProps: {
          children: 'children',
          label: 'label'
        }
      };
    },
    methods: {
      handleNodeClick(data) {
        console.log(data);
      }
    }
  };
</script> 

 

 

运行项目、查看效果如下。

 

 

3、使用 Axios 向后台发请求,获取 json 数据

(1)vue 项目添加 Axios。

【参考地址:】
    https://www.cnblogs.com/l-y-h/p/11656129.html#_label1
    
【npm 安装:】
    npm install axios

 

(2)使用 Axios 发送请求。

【引入 Axios】
    import axios from 'axios';
    
【HelloWorld.vue】
<template>
    <el-tree :data="data" :props="defaultProps" @node-click="handleNodeClick"></el-tree>
</template>
<script>
    import axios from 'axios';
    export default {
        data() {
            return {
                data: [],
                defaultProps: {
                    children: 'treeMenu',
                    label: 'name'
                }
            };
        },
        methods: {
            handleNodeClick(data) {
                console.log(data);
            }
        },
        created() {
            axios.get(`http://localhost:9000/treeMenu/selectAllWithTree`)
            .then(response => {
                console.log(response);
                this.data = response.data.data.items;
            })
            .catch(error => {
                console.log(error);
            });
        }
    };
</script>

 

 

(3)解决跨域问题。
后端解决:
  可以使用 @CrossOrigin 注解,在 方法上添加该注解。

@CrossOrigin
@GetMapping("selectAllWithTree")
public Result selectAllWithTree() {
    return Result.ok().data("items", treeMenuService.listWithTree());
}

 

前端解决:

【参考地址:】
    https://www.cnblogs.com/l-y-h/p/11815452.html

此处,我使用后端解决,添加了 @CrossOrigin 注解。

 

 

 

 

4、element-ui 常用属性分析

(1)树形控件常用属性分析

【属性:】
    data            Array 类型,用于保存树的 数据。
    props           Object 类型,用于定义配置选项。
注:
    props 可以用来指定 data 里面的属性标签名(其用来指定属性名,而非属性值)。
常用属性:
    label          用于指定节点对象中标签属性的属性名。
    children       用于指定节点对象中子对象属性的属性名。 
    isLeaf         用于指定节点对象中叶子节点的属性名,仅在 lazy = true 时生效。
    disabled      用于指定节点对象中是否禁用节点的属性名。

 

 

 

 

【属性:】
    node-key             String 类型,用于表示唯一的树节点。
    load                 Function 类型,仅 lazy = true 时生效,用于加载子树据。
    lazy                 boolean 类型,默认为 false,是否懒加载子节点。
    show-checkbox        boolean 类型,默认为 false,是否展开复选框(节点是否能被选择)。

【举例:】
<template>
    <el-tree :props="props" :load="loadNode" lazy show-checkbox></el-tree>
</template>
<script>
    export default {
        data() {
            return {
                props: {
                    // 指定 data 中属性名
                    label: 'name',
                    isLeaf: 'leaf'
                },
            };
        },
        methods: {
            loadNode(node, resolve) {
                // 初始加载节点数据
                if (node.level === 0) {
                    return resolve([{
                        name: 'region'
                    },{
                        name: "region2"
                    }]);
                }
                // 点击第一级节点后,触发延时操作,返回子节点
                if (node.level === 1) {
                    setTimeout(() => {
                        const data = [{
                            name: 'leaf'
                        }, {
                            name: 'zone',
                            disabled: true,
                            leaf: true
                        }];
                    
                        resolve(data);
                    }, 500);
                }
                // 点击第二级节点后,没有节点返回,返回 null。
                if (node.level > 1) {
                    return resolve([]);
                }
            }
        }
    };
</script>

 

 

当然,还有其他属性,比如可以自定义树节点数据,可以实现拖拽功能等。
此处不过多叙述,详情可以参考官方文档。

此处仅演示了 获取全部数据 的代码,可以根据项目情况,自行完善增加节点、删除节点、批量删除节点、拖拽节点等操作。


原文链接:https://www.cnblogs.com/l-y-h/p/12852389.html
如有疑问请与原作者联系

标签:

版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有

上一篇:初学mybatis框架配置MySQL数据库笔记

下一篇:LeetCode 297. 二叉树的序列化与反序列化