【前后端的那些事】开源!treeSelect树形结构数据展示


前言:最近写项目,发现了一些很有意思的功能,想写文章,录视频把这些内容记录下。但这些功能太零碎,如果为每个功能都单独搭建一个项目,这明显不合适。于是我想,就搭建一个项目,把那些我想将的小功能全部整合到一起。实现 搭一次环境,处处使用。

本文主要实现一下两个功能

  1. 新增表单, 更新表单组件编写
  2. treeSelect树形结构编写

环境搭建
文章链接

已录制视频
视频链接

仓库地址
https://github.com/xuhuafeifei/fgbg-font-and-back.git

tree-selector

这个功能是table-tree功能的附属产品。是为了能在新增表单中,更方便选择上级节点所开发的功能。因此,我们得先把新增表单组件开发出来

新增、修改逻辑
在这里插入图片描述

tree形组件

在这里插入图片描述

1. 新增表单组件

/src/views/welcome/treeAddOrUpdate.vue

<script setup lang="ts">
import { UnitEntity } from "@/api/tree";
import { ref, reactive } from "vue";
const dialogVisible = ref(false);

let form = reactive(new UnitEntity());
const title = ref("新增表单");

// 定义init方法, 让父组件调用
const init = data => {
  console.log(data);
  if (data) {
    form = data;
    title.value = "编辑表单";
  } else {
    title.value = "新增表单";
  }
  dialogVisible.value = true;
};

// 暴露方法
defineExpose({ init });

// 提交表单
const submit = () => {
  console.log(form);
};
</script>

<template>
  <el-dialog v-model="dialogVisible" :title="title">
    <el-form :model="form">
      <el-form-item label="单元">
        <el-input v-model="form.unit" />
      </el-form-item>
      <el-form-item label="父id">
        <el-input v-model="form.pid" />
      </el-form-item>
    </el-form>
    <el-button @click="submit">提交</el-button>
  </el-dialog>
</template>

<style lang="scss" scoped></style>

2. 在父组件中引用

/src/views/welcome/index.vue

<script setup lang="ts">
import { ref, onMounted, nextTick } from "vue";
import TreeAddOrUpdate from "./treeAddOrUpdate.vue";

const dialogVisible = ref(false);

// 引用子组件
const treeAddOrUpdateRef = ref();

// ...
</script>

<template>
  <!--ref引用组件-->
  <tree-add-or-update v-if="dialogVisible" ref="treeAddOrUpdateRef" />
</template>

3. 父组件添加新增按钮

/src/views/welcome/index.vue

<script setup lang="ts">
// 新增/修改 都可以使用该方法
const addOrUpdate = data => {
  console.log(data);
  dialogVisible.value = true;
  // nextTick保证treeAddOrUpdateRef能够引用到子组件
  nextTick(() => {
    // 调用子组件暴露的init方法, 设置数据
    treeAddOrUpdateRef.value.init(data);
  });
};
</script>

<template>
	<el-button type="primary" @click="addOrUpdate">新增</el-button>
</template>

完成以上步骤,我们就可以点击新增表单,但这个界面对于用户来说其实并不美好。谁知道父id是什么?因此我们采用tree-select的形式来提高界面的可使用性

4. 树形组件

在这里插入图片描述

我们使用的是element plus的TreeSelect组件,具体文档如下:[TreeSelect 树形选择 | Element Plus (element-plus.org)]()

4.1 前端代码
  • /src/api/tree.ts
export class LabelVo {
id: Number;
label: String;
value: String;
children: Array<LabelVo>;
}

/** 获取全部的treeLabel */
export const getLabelTree = () => {
return http.request<R<Array<LabelVo>>>(
  "get",
  baseUrlApi("unit/listTreeSelect")
);
};

/** 根据id查询节点 */
export const getNodeById = (id: Number) => {
return http.request<R<LabelVo>>("get", baseUrlApi(`unit/listNode?id=${id}`));
};
  • /src/views/welcome/treeAddOrUpdate.vue
<template>
<el-tree-select
          v-model="value"
          :data="data"
          check-strictly
          show-checkbox
          @check-change="handleCheckChange"
          style="width: 240px"
        />
</template>

<script>
// 定义init方法, 让父组件调用
const init = data => {
  console.log(data);
  if (data) {
    form = data;
    title.value = "编辑表单";
    // 查询上级节点数据(根据id返回{value, label, id})
    getNodeById(form.pid).then(res => {
      if (res.code === 0) {
        value.value = res.data.value;
      }
    });
  } else {
    title.value = "新增表单";
  }
  console.log(form);
  dialogVisible.value = true;
};

const value = ref();
const data = ref<Array<LabelVo>>();

const handleCheckChange = (data: LabelVo, checked, indeterminate) => {
  console.log(data);
  console.log(checked);
  if (checked) {
    form.pid = data.id;
  }
};
</script>

tip: init方法改动

4.2 后端代码
  • 定义实体类
package com.fgbg.demo.vo;

import lombok.Data;

import java.util.List;

@Data
public class LabelVo {
    private String label;
    private String value;
    private Integer id;
    private Integer pid;
    private List<LabelVo> children;
}

  • 返回tree-selector展示所需数据

        @RequestMapping("/listTreeSelect")
        public R listTreeSelect() {
            List<TbUnit> tbUnitList = unitService.list();
            List<LabelVo> list = tbUnitList.stream().map(e -> {
                LabelVo vo = new LabelVo();
                vo.setValue(e.getUnit());
                vo.setLabel(e.getUnit());
                vo.setId(e.getId());
                vo.setPid(e.getPid());
                return vo;
            }).collect(Collectors.toList());
            // TbUnit -> LabelVo
            // 建立map映射(id->index)
            HashMap<Integer, Integer> map = new HashMap<>();
            for (int index = 0; index < list.size(); index++) {
                Integer id = list.get(index).getId();
                map.put(id, index);
            }
            // ...
            for (int i = 0; i < list.size(); i++) {
                LabelVo node = list.get(i);
                Integer pid = node.getPid();
                // 有父亲
                if (pid != null) {
                    // 找到pid的父亲, 并把当前节点(node)添加到父亲节点的children里面
                    Integer indexParent = map.get(pid);
                    // 获取父亲节点
                    LabelVo parent = list.get(indexParent);
                    if (parent.getChildren() == null) {
                        parent.setChildren(new ArrayList<>());
                    }
                    // 向父亲节点的children字段添加当前node
                    parent.getChildren().add(node);
                }
            }
            // 过滤出一级节点
            List<LabelVo> ans = list.stream().filter(e -> e.getPid() == null).collect(Collectors.toList());
            return R.ok().put("data", ans);
        }
    
    
  • 根据id查询数据

        // 根据id查询节点数据{value id label}
        @RequestMapping("/listNode")
        public R listNode(@RequestParam Integer id) {
            TbUnit unit = unitService.getById(id);
            LabelVo labelVo = new LabelVo();
            labelVo.setLabel(unit.getUnit());
            labelVo.setValue(unit.getUnit());
            labelVo.setId(unit.getId());
            return R.ok().put("data", labelVo);
        }
    
  • 23
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
抱歉,由于我是一名AI语言模型,无法提供完整的编程代码。但是,我可以给您提供一些基本的步骤、示例和参考链接,以帮助您使用Element UI框架来创建treeselect树形菜单。 # 步骤: 1. 安装Element UI库: ```bash npm i element-ui -S ``` 2. 在Vue项目中引入Element UI,并注册所需组件:`TreeSelect`和`Option`。 ```javascript import Vue from 'vue'; import ElementUI from 'element-ui'; import 'element-ui/lib/theme-chalk/index.css'; Vue.use(ElementUI); // 在组件中注册 import Treeselect from 'vue-treeselect' import 'vue-treeselect/dist/vue-treeselect.css' Vue.component('treeselect', Treeselect) ``` 3. 创建数据源(由于treeselect是一个树形结构,因此需要的是具有父/子关系的数据)。 ```javascript data() { return { // 模拟数据treeData: [{ id: 1, label: '一级 1', children: [{ id: 4, label: '二级 1-1', children: [{ id: 9, label: '三级 1-1-1', }, { id: 10, label: '三级 1-1-2', }] }] }, { id: 2, label: '一级 2', children: [{ id: 5, label: '二级 2-1' }, { id: 6, label: '二级 2-2' }] }, { id: 3, label: '一级 3', children: [{ id: 7, label: '二级 3-1' }, { id: 8, label: '二级 3-2' }] }] } } ``` 4. 在模板中使用`treeselect`组件,并将`treeData`传递给它。默认情况下,`treeselect`会自动将数据渲染为树形结构。如果需要自定义节点渲染,可以通过`option-component` prop来传递自定义Vue组件。 ```html <template> <div> <treeselect v-model="selected" :options="treeData" :multiple=true :clearable=true placeholder="请选择" :no-children-text="'暂无子级'" :loading=true :disabled=true /> </div> </template> <script> export default { name: 'MyComponent', data() { return { // 值 selected: null, // 模拟数据treeData: [ { id: 1, label: '一级 1', children: [ { id: 4, label: '二级 1-1', children: [ { id: 9, label: '三级 1-1-1' }, { id: 10, label: '三级 1-1-2' } ] } ] }, { id: 2, label: '一级 2', children: [ { id: 5, label: '二级 2-1' }, { id: 6, label: '二级 2-2' } ] }, { id: 3, label: '一级 3', children: [ { id: 7, label: '二级 3-1' }, { id: 8, label: '二级 3-2' } ] } ] } } } </script> ``` 5. 上述代码可以生成一个带有“一级/二级/三级”级别的简单树形菜单。 # 示例 以下是一个简单的treeselect树形菜单,演示了如何使用Element UI框架。它展示了三个级别的节点,并使用了一些常见的prop和件。您可以根据需要进行修改和扩展。 ```html <template> <div> <h3>treeselect树形菜单:</h3> <treeselect v-model="selected" :options="treeData" :multiple=true :clearable=true placeholder="请选择" :no-children-text="'暂无子级'" :loading=true :disabled=true /> <h3>选中的值:</h3> <pre>{{ selected }}</pre> </div> </template> <script> import Treeselect from 'vue-treeselect'; import 'vue-treeselect/dist/vue-treeselect.css'; export default { components: { Treeselect }, name: 'TreeSelectExample', data() { return { selected: null, treeData: [{ id: 1, label: '一级 1', children: [{ id: 4, label: '二级 1-1', children: [{ id: 9, label: '三级 1-1-1', }, { id: 10, label: '三级 1-1-2', }] }] }, { id: 2, label: '一级 2', children: [{ id: 5, label: '二级 2-1' }, { id: 6, label: '二级 2-2' }] }, { id: 3, label: '一级 3', children: [{ id: 7, label: '二级 3-1' }, { id: 8, label: '二级 3-2' }] } ] }; }, methods: { handleNodeSelected(node) { console.log('节点选中了:', node); }, handleNodeDeselected(node) { console.log('节点取消选中了:', node); }, handleTreeLoaded() { console.log('树形结构已加载'); }, handleInputChange(value) { console.log('输入框值变化了:', value); } } }; </script> ``` # 参考链接 - [vue-treeselect官方文档](https://vue-treeselect.js.org/) - [Element UI TreeSelect](https://element.eleme.io/#/zh-CN/component/tree-select) - [Vue.js官方文档](https://cn.vuejs.org/)

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值