背景:现在有一个新需求,需要借助树结构来实现词库的分类管理,树的节点是不同的分类,不同的分类可以有自己的词库,所以父子节点是互不影响的;同样为了选择的方便性,提出了新需求,选择了父级子级需要全选,父级取消勾选子级需要全部取消勾选;分类支持修改名称、增加子节点、删除子节点,多选子节点时需要获取当前所选分类下的所有词库。
一、开发前需要知道的
1、树结构需要借助 element 的 tree 组件
2、树结构需要设置父子级不关联,即 :check-strictly="true"
3、节点选择时如果是非叶子结点(最后一层的子节点,没有 children),需要递归获取子节点,如果进行节点的选择与取消选择
4、自定义节点需要通过 slot 方式活着 render-content 方式实现
二、实现流程
1、引入 tree,传入源数据 data、设置 key,设置默认配置属性 defaultProps
1. 定义 tree
<el-tree
:check-strictly="true"
:data="data"
@check-change="checkChange"
ref="treeRef"
show-checkbox
default-expand-all
node-key="id"
ref="tree"
highlight-current
:props="defaultProps"
>
</el-tree>
2. 设置 data
data = [{id: xx, label: xx, children: {id: number, label: string, children: []}[]}]
3. 设置父子不关联
:check-strictly="true"
4. 设置默认配置
const defaultProps = {
children: "children",
label: "label",
}
2、实现父子勾选子级联动
// 1. 给 el-tree 绑定 check-change 事情
@check-change="checkChange"
// 2. 实现 checkChange 函数
// data:当前节点 checked:节点是否选中 indeterminate:是否有选中的子项
function checkChange(data, checked, indeterminate) {
console.log("data, checked, indeterminate", data, checked, indeterminate);
this.$nextTick(() => {
// 2.1 获取节点详细信息
const nodeInfo = this.$refs.treeRef.getNode(data.id);
const { isLeaf, checked } = nodeInfo;
this.nodeInfo = nodeInfo;
function getAllIds(arr, res = []) {
for (let i = 0; i < arr.length; i++) {
res.push(arr[i].id);
if (arr[i].children && arr[i].children.length) {
getAllIds(arr[i].children, res);
}
}
return res;
}
// 2.2 获取当前已选中的节点
console.log(this.$refs.treeRef.getCheckedKeys());
// 2.3 如果是父级,子级选中跟取消选择
if (!isLeaf) {
const checkedIds = this.$refs.treeRef.getCheckedKeys();
const ids = getAllIds(nodeInfo.data.children);
console.log(ids);
// 2.3.1 父级选中,子级全选
if (checked) {
this.$refs.treeRef.setCheckedKeys(
Array.from(new Set([...checkedIds, ...ids]))
);
} else {
// 2.3.2 父级取消选中,子级全不选
this.$refs.treeRef.setCheckedKeys(
Array.from(new Set(checkedIds.filter((item) => !ids.includes(item))))
);
}
}
});
},
3、通过 slot 实现编辑、新增、删除
1. slot 内容
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button type="text" size="mini" @click="() => edit(node, data)">
edit
</el-button>
<el-button type="text" size="mini" @click="() => append(data)">
Append
</el-button>
<el-button
type="text"
size="mini"
@click="() => remove(node, data)"
>
Delete
</el-button>
</span>
</span>
2. 设置函数
{
// 2.1 编辑,子级可以弹窗回显名称,然后修改名称
edit(node, data) {
console.log(node, data);
},
// 2.2 新增,新增内容自己定,也可以弹窗自定义名称,通过接口返回 id 再拼接
append(data) {
const newChild = { id: this.id++, label: "testtest", children: [] };
if (!data.children) {
this.$set(data, "children", []);
}
data.children.push(newChild);
},
// 2.3 删除,可以先进行提示,提示确定后再删
remove(node, data) {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);
}
}
三、整体代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
rel="stylesheet"
href="https://unpkg.com/element-ui@2.15.14/lib/theme-chalk/index.css"
/>
<style>
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
justify-content: space-between;
font-size: 14px;
padding-right: 8px;
}
</style>
</head>
<body>
<script src="https://unpkg.com/vue@2.7.16/dist/vue.js"></script>
<script src="https://unpkg.com/element-ui@2.15.14/lib/index.js"></script>
<div id="app">
<el-tree
:check-strictly="true"
:data="data"
@check-change="checkChange"
ref="treeRef"
show-checkbox
default-expand-all
node-key="id"
ref="tree"
highlight-current
:props="defaultProps"
>
<span class="custom-tree-node" slot-scope="{ node, data }">
<span>{{ node.label }}</span>
<span>
<el-button type="text" size="mini" @click="() => edit(node, data)">
edit
</el-button>
<el-button type="text" size="mini" @click="() => append(data)">
Append
</el-button>
<el-button
type="text"
size="mini"
@click="() => remove(node, data)"
>
Delete
</el-button>
</span>
</span>
</el-tree>
<div class="buttons">
<el-button @click="getCheckedNodes">通过 node 获取</el-button>
<el-button @click="getCheckedKeys">通过 key 获取</el-button>
<el-button @click="setCheckedNodes">通过 node 设置</el-button>
<el-button @click="setCheckedKeys">通过 key 设置</el-button>
<el-button @click="resetChecked">清空</el-button>
</div>
</div>
<script>
var Main = {
methods: {
edit(node, data) {
console.log(node, data);
},
append(data) {
const newChild = { id: this.id++, label: "testtest", children: [] };
if (!data.children) {
this.$set(data, "children", []);
}
data.children.push(newChild);
},
remove(node, data) {
const parent = node.parent;
const children = parent.data.children || parent.data;
const index = children.findIndex((d) => d.id === data.id);
children.splice(index, 1);
},
checkChange(data, checked, indeterminate) {
this.node = data;
this.checked = checked;
this.indeterminate = indeterminate;
// console.log("data, checked, indeterminate", data, checked, indeterminate);
this.$nextTick(() => {
console.log(data.id);
const nodeInfo = this.$refs.treeRef.getNode(data.id);
const { isLeaf, checked } = nodeInfo;
this.nodeInfo = nodeInfo;
console.log(nodeInfo);
// console.log(
// "nodeInfo, isLeaf, childNodes, checked",
// nodeInfo,
// isLeaf,
// childNodes,
// checked
// );
function getAllIds(arr, res = []) {
for (let i = 0; i < arr.length; i++) {
res.push(arr[i].id);
if (arr[i].children && arr[i].children.length) {
getAllIds(arr[i].children, res);
}
}
return res;
}
console.log(this.$refs.treeRef.getCheckedKeys());
if (!isLeaf) {
const checkedIds = this.$refs.treeRef.getCheckedKeys();
const ids = getAllIds(nodeInfo.data.children);
console.log(ids);
if (checked) {
this.$refs.treeRef.setCheckedKeys(
Array.from(new Set([...checkedIds, ...ids]))
);
} else {
this.$refs.treeRef.setCheckedKeys(
Array.from(
new Set(checkedIds.filter((item) => !ids.includes(item)))
)
);
}
}
});
},
getCheckedNodes() {
console.log(this.$refs.tree.getCheckedNodes());
},
getCheckedKeys() {
console.log(this.$refs.tree.getCheckedKeys());
},
setCheckedNodes() {
this.$refs.tree.setCheckedNodes([
{
id: 5,
label: "二级 2-1",
},
{
id: 9,
label: "三级 1-1-1",
},
]);
},
setCheckedKeys() {
this.$refs.tree.setCheckedKeys([3]);
},
resetChecked() {
this.$refs.tree.setCheckedKeys([]);
},
},
data() {
return {
id: 10,
data: [
{
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",
},
],
},
],
defaultProps: {
children: "children",
label: "label",
},
};
},
};
var Ctor = Vue.extend(Main);
new Ctor().$mount("#app");
</script>
</body>
</html>
四、codepen 在线编辑
https://codepen.io/CAILeiz/pen/MYgpYOW