大家好,我之前分享了两棵树形结构数据的对比,但因为该文章是聚焦于树形结构的对比,就没有编写预更新(对比预览)部分。还是有不少同学对这部分是比较感兴趣的,那么就跟随这篇文章看看识别出对比后,怎么进行预更新吧。
因为本文与两棵树形结构数据的对比拓展篇,有强关联关系,请先阅读对比篇文章后再来阅读本文效果更佳。
构建视图
既然是预更新,那就得看到效果,不能像上一篇只写 js 代码,我们需要先把两颗树画出来。
这里用的 ui 框架是 element-plus,代码比较简单这里就不过多赘述,具体的一些参数大家可以自行去 element-plus 官网查阅。
<div id="tree">
<!--左侧树-->
<div class="tree-con">
<el-tree :data="data.leftTree" default-expand-all>
<template #default="{ data }">
<span v-if="!data.status">{{ data.title }}</span>
</template>
</el-tree>
</div>
<!--右侧树-->
<div class="tree-con" style="border: 1px solid #f00">
<el-tree
:props="{ class: customNodeClass }"
:data="data.rightTree"
@check-change="checkChange"
show-checkbox
default-expand-all
>
<template #default="{ data }">
<span v-if="!data.status">{{ data.title }}</span>
</template>
</el-tree>
</div>
</div>
大家这会去看页面可能什么也看不到甚至还有报错,因为树结构的数据我们还没写。既然 ui 用了 element-plus 那说明这个 demo 是用 vue3 写的,如果不会也不用紧张,这个 demo 用到 vue3 特性的地方不多,总体跟 js 的代码是很像的。
我们把树数据定义出来,还得定义个 customNodeClass 方法供 element-plus 的 tree 使用展示样式:
const data = reactive({
leftTree: [
{
pid: 0,
id: 1,
level: 1,
key: '1-1',
title: '第一层1-左侧',
},
{
pid: 0,
id: 2,
level: 1,
key: '1-2',
title: '第一层2',
},
{
pid: 0,
id: 3,
level: 1,
key: '1-3',
title: '第一层3',
},
],
rightTree: [
{
pid: 0,
id: 1,
level: 1,
key: '1-1',
title: '第一层1-右侧',
children: [
{
pid: 1,
id: 11,
level: 2,
key: '2-11',
title: '第二层11',
children: [
{
pid: 11,
id: 111,
level: 3,
key: '3-111',
title: '第三层111',
},
],
},
],
},
{
pid: 0,
id: 2,
level: 1,
key: '1-2',
title: '第一层2',
},
{
pid: 0,
id: 4,
level: 1,
key: '1-4',
title: '第一层4',
children: [
{
pid: 4,
id: 41,
level: 2,
key: '2-41',
title: '第二层41',
},
],
},
],
});
/**
* 树节点自定义的类名
* @data {object} 数据
* */
const customNodeClass = (data) => {
if (data.class) return 'change-color';
return null;
};
既然是树,那必然要有 id 和 pid,这里我们再把 children 定义出来,这组数据就直接包含了增删改的情况了,大家通过对比篇文章的代码就能看到效果。 我们现在有了视图页面,加点样式让大家看的更直观点。
<style lang="less">
#tree {
padding-top: 50px;
display: flex;
align-items: center;
background-color: #fff;
.tree-con {
width: 200px;
border: 1px solid #000;
margin-left: 10px;
}
}
.el-button {
margin: 10px;
}
.change-color {
color: #f00;
.el-checkbox__inner {
border-color: #f00;
}
}
.el-tree-node__expand-icon.expanded {
visibility: hidden;
}
</style>
视图效果:
图 1 对比
实现
对比的效果出来了,因为是勾选数据更新而非全量更新,那我们就得识别出勾选的数据,也就是将要预更新到左侧的数据,其实也就是复选框被勾选的时候获取勾选的数据。
获取勾选的数据
我们在 html 部分其实就已经提前定义了复选框的勾选方法 @check-change="checkChange",element-plus 已经 提供了 check-change 方法,每次点击复选框都会触发它,我们定义 checkChange 方法接收下即可:
const pickList = reactive([]); //勾选的数据
/**
* 当复选框被点击的时候触发
* @data {object} 勾选/取消勾选的数据
* @bool {boolean} 是否选中
* */
const checkChange = (data, bool) => {
if (bool) {
//选中
pickList.push(data);
} else {
//取消选中
let index = pickList.indexOf(data);
pickList.splice(index, 1); //将取消选中数据从勾选数组里移除
}
};
数据处理
触发按钮
拿到要更新的数据后,我们还需要个按钮来触发何时进行预更新:
<el-button type="primary" @click="preview">预览</el-button>
展开树结构
我们在按钮上定义 preview 方法,在 js 里编写方法内容。但这个 preview 方法该怎么写呢?树结构是有子节点的,通常的遍历只能遍历这一层没法遍历到下一层,这时候就需要我们对树进行特殊处理,也就是展开铺平树结构数据。
/**
* 展开平铺树结构数据
* @data {array} 树结构数据
* */
const flattenTree = (data) => {
data = JSON.parse(JSON.stringify(data));
let res = [];
//遍历树
while (data.length) {
let node = data.shift(); //每次循环截取树数组首条数据
if (node.children && node.children.length) {
data = data.concat(node.children);
}
delete node.children;
res.push(node);
}
return res;
};
const flatLeft = flattenTree(data.leftTree); //平铺的树
flatLeft 就是我们平铺展开的树,我们拿到后再来试试能否顺利进行预更新。
我们遍历 pickList 将选择的数据与平铺树数据通过唯一值 key 进行对比:
/**
* 预览对比效果
* */
const preview = () => {
pickList.map((item) => {
let i = flatLeft.findIndex((v) => item.key === v.key); //通过唯一值 key 进行对比,拿到要预更新的数据的下标
flatLeft.splice(i, 1, item); //数据替换
});
data.leftTree = flatLeft;
};
平铺数组转树结构
点击按钮,我们发现预更新后数据变得很奇怪,因为我们的目标数据是树结构数据,拿平铺的数据去赋值树结构数据当然会有问题,所以我们还缺少一将平铺数组转树结构的步骤。
/**
* 平铺数组转树结构数据
* @items {array} 平铺数组
* */
const arrayToTree = (items) => {
let res = [];
let getChildren = (res, pid) => {
for (const i of items) {
if (i.pid === pid) {
const newItem = { ...i, children: [] };
res.push(newItem);
getChildren(newItem.children, newItem.id);
}
}
};
getChildren(res, 0);
return res;
};
最后修改下 preview 方法,在给 data.leftTree 赋值前将平铺的数组转为树:
/**
* 预览对比效果
* */
const preview = () => {
pickList.map((item) => {
let i = flatLeft.findIndex((v) => item.key === v.key);
flatLeft.splice(i, 1, item);
});
data.leftTree = arrayToTree(flatLeft);
};
效果
图 2 效果
完整代码
对比部分的代码请看对比篇文章。
<template>
<div id="tree">
<!--左侧树-->
<div class="tree-con">
<el-tree :data="data.leftTree" default-expand-all>
<template #default="{ data }">
<span v-if="!data.status">{{ data.title }}</span>
</template>
</el-tree>
</div>
<!--右侧树-->
<div class="tree-con" style="border: 1px solid #f00">
<el-tree
:props="{ class: customNodeClass }"
:data="data.rightTree"
@check-change="checkChange"
show-checkbox
default-expand-all
>
<template #default="{ data }">
<span v-if="!data.status">{{ data.title }}</span>
</template>
</el-tree>
</div>
</div>
<el-button type="primary" @click="preview">预览</el-button>
</template>
<script setup>
const data = reactive({
leftTree: [
{
pid: 0,
id: 1,
level: 1,
key: '1-1',
title: '第一层1-左侧',
},
{
pid: 0,
id: 2,
level: 1,
key: '1-2',
title: '第一层2',
},
{
pid: 0,
id: 3,
level: 1,
key: '1-3',
title: '第一层3',
},
],
rightTree: [
{
pid: 0,
id: 1,
level: 1,
key: '1-1',
title: '第一层1-右侧',
children: [
{
pid: 1,
id: 11,
level: 2,
key: '2-11',
title: '第二层11',
children: [
{
pid: 11,
id: 111,
level: 3,
key: '3-111',
title: '第三层111',
},
],
},
],
},
{
pid: 0,
id: 2,
level: 1,
key: '1-2',
title: '第一层2',
},
{
pid: 0,
id: 4,
level: 1,
key: '1-4',
title: '第一层4',
children: [
{
pid: 4,
id: 41,
level: 2,
key: '2-41',
title: '第二层41',
},
],
},
],
});
/**
* 树节点自定义的类名
* @data {object} 数据
* */
const customNodeClass = (data) => {
if (data.class) return 'change-color';
return null;
};
/**
* 当复选框被点击的时候触发
* @data {object} 勾选/取消勾选的数据
* @bool {boolean} 是否选中
* */
const checkChange = (data, bool) => {
if (bool) {
//选中
pickList.push(data);
} else {
//取消选中
let index = pickList.indexOf(data);
pickList.splice(index, 1); //将取消选中数据从勾选数组里移除
}
};
/**
* 展开平铺树结构数据
* @data {array} 树结构数据
* */
const flattenTree = (data) => {
data = JSON.parse(JSON.stringify(data));
let res = [];
//遍历树
while (data.length) {
let node = data.shift(); //每次循环截取树数组首条数据
if (node.children && node.children.length) {
data = data.concat(node.children);
}
delete node.children;
res.push(node);
}
return res;
};
/**
* 平铺数组转树结构数据
* @items {array} 平铺数组
* */
const arrayToTree = (items) => {
let res = [];
let getChildren = (res, pid) => {
for (const i of items) {
if (i.pid === pid) {
const newItem = { ...i, children: [] };
res.push(newItem);
getChildren(newItem.children, newItem.id);
}
}
};
getChildren(res, 0);
return res;
};
/**
* 预览对比效果
* */
const preview = () => {
pickList.map((item) => {
let i = flatLeft.findIndex((v) => item.key === v.key);
flatLeft.splice(i, 1, item);
});
data.leftTree = arrayToTree(flatLeft);
};
const pickList = reactive([]); //勾选的数据
const flatLeft = flattenTree(data.leftTree);
</script>
<style lang="less">
#tree {
padding-top: 50px;
display: flex;
align-items: center;
background-color: #fff;
.tree-con {
width: 200px;
border: 1px solid #000;
margin-left: 10px;
}
}
.el-button {
margin: 10px;
}
.change-color {
color: #f00;
.el-checkbox__inner {
border-color: #f00;
}
}
.el-tree-node__expand-icon.expanded {
visibility: hidden;
}
</style>