文章目录
- tree-selector
- 1. 新增表单组件
- 2. 在父组件中引用
- 3. 父组件添加新增按钮
- 4. 树形组件
- 4.1 前端代码
- 4.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); }