el-tree(append与getNode与过滤)、深拷贝deepClone、监听addEventListener、
场景:
(1)在父组件Parent里监听到数据变化,调用子组件Child暴露出对应的方法;
(2)子组件里切换el-tab时展示onRef树或inRef树(需要深拷贝数据,导致两个树数据一样)
问题:如果两个树中存在相同的父节点 ID(例如从同一接口获取的初始数据),且未对这些父节点做深拷贝,当调用 append 方法时,可能会错误地将子节点添加到两个树的同一父节点引用中。
(3)根据树id更新树节点,而不是通过遍历,避免搜索到某个节点后,做一些编辑就刷新整个列表。
树结构(每个学生节点下再分子节点In内地或者on外地节点):
Student1 》onData1
》InData1
Student2 》Student2-1 》onData2-1
》Student2 -2 》Student2-2-1 》onData2-2-1
》InData2
》InData2
Parent.vue
<template>
<div>
<Child ref="childRef"></Child >
</div>
</template>
onMounted(() => {
loadData();
});
function loadData(){
//监听学生增加事件
studentData.addEventListener(studentAdd, (e: Event) => {
let dataEvent = e as DataEvent;
if(dataEvent){
if(childRef.value){
//监听事件里获取新增学生信息
const studentData= dataEvent.studentData;
if(studentData){
//增加此数据分别到子组件的onRef树与inRef树里
//(此处需要深拷贝,因为节点对象的 parent 引用需要隔离,否则实际共享同一 parent 对象!
//此时,向任一树追加子节点时,两个树的父节点 children 数组会共享引用,就是改一个树另一个树也改变,困扰我好久..)
childRef.value.addOnNode(deepClone(studentData));
childRef.value.addInNode(deepClone(studentData));
}
}
}
});
//监听学生修改事件
studentData.addEventListener(studentUpdate, (e: Event) => {
let dataEvent = e as DataEvent;
if(dataEvent){
if(childRef.value){
//监听事件里获取修改学生信息
const studentData= dataEvent.studentData;
childRef.value.updateChildNode(studentData);
}
}
}
});
//监听学生移除事件
studentData.addEventListener(studentRemove, (e: Event) => {
let dataEvent = e as DataEvent;
if(dataEvent){
if(childRef.value){
//监听事件里获取修改学生信息
const studentData= dataEvent.studentData;
childRef.value..removeChildNode(studentData);
}
}
});
//监听外地增加事件
studentData.onData.addEventListener(studentOnAdd, (e: Event) => {
let dataEvent = e as DataEvent;
if(dataEvent){
if(childRef.value){
//监听事件里获取新增外地信息
const studentData= dataEvent.studentData;
if(studentData){
childRef.value.addOnNode(studentData);
}
}
}
});
....
}
function deepClone(target: any) {
// 定义一个变量
let result: any;
// 如果当前需要深拷贝的是一个对象的话
if (typeof target === 'object') {
// 如果是一个数组的话
if (Array.isArray(target)) {
result = []; // 将result赋值为一个数组,并且执行遍历
for (let i in target) {
// 递归克隆数组中的每一项
result.push(this.deepClone(target[i]))
}
// 判断如果当前的值是null的话;直接赋值为null
} else if(target===null) {
result = null;
// 判断如果当前的值是一个RegExp对象的话,直接赋值
} else if(target.constructor===RegExp){
result = target;
}else {
// 否则是普通对象,直接for in循环,递归赋值对象的所有值
result = {};
for(let key in target){
result[key] = this.deepClone(target[key]);
}
}
// 如果不是对象的话,就是基本数据类型,那么直接赋值
} else {
result = target;
}
// 返回最终结果
return result;
},
Child.vue
<template>
<div>
<el-tabs class="tabs" @tab-click="handleClick" v-model="unitModal">
<el-tab-pane label="外地" name="on">
<el-input
v-model="onFilterText"
placeholder="搜索"
clearable
/>
<el-scrollbar>
<el-tree
ref="onRef"
:data="onStudent"
node-key="id"
:filter-node-method="filterOnNode"
default-expand-all
>
<template #default="{ node, data }">
<slot :node="node" :data="data">
<img :src="data.icon" style="height: 25px;width: 25px">
<span style="margin: 0 10px;">{{ data.name }}</span>
</slot>
</template>
</el-tree>
</el-scrollbar>
</el-tab-pane>
<el-tab-pane label="内地" name="in">
<el-input
v-model="inFilterText"
placeholder="搜索"
/>
<el-scrollbar>
<el-tree
ref="inRef"
:data="inStudent"
node-key="id"
:filter-node-method="filterInNode"
default-expand-all
>
<template #default="{ node, data }">
<slot :node="node" :data="data">
<img :src="data.icon" style="height: 25px;width: 25px">
<span style="margin: 0 10px;">{{ data.name }}</span>
</slot>
</template>
</el-tree>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
</div>
</template>
const handleClick = (tab: TabsPaneContext) => {
}
//外地过滤
const onFilterText = ref("")
watch(onFilterText, (val) => {
onRef.value!.filter(val)
})
const filterOnNode = (value: string, data: Tree) => {
if (!value) return true
return data.name.includes(value);
}
//内地过滤
const inFilterText = ref("")
watch(inFilterText, (val) => {
inRef.value!.filter(val)
})
const filterInNode = (value: string, data: Tree) => {
if (!value) return true
return data.name.includes(value);
}
//外地树
function addOnNode(data: any) {
if(onRef.value){
//是否有父节点,没有的话通过parentId拿到编制树的父节点
if(data.parentId){
const key = data.parentId;
const node = onRef.value.getNode(key);
if (node) {
//通过父节点为编制树追加一个子节点
const nodeParent = onRef.value.getNode(data.parentId);
onRef.value.append(data,nodeParent);
}
}else{
onRef.value.append(data,null);
}
}
}
//内地树
function addInNode(data: any) {
if(inRef.value){
//是否有父节点,没有的话通过parentId拿到编制树的父节点
if(data.parentId){
const key = data.parentId;
const node = inRef.value.getNode(key);
if (node) {
//通过父节点为编制树追加一个子节点
const nodeParent = inRef.value.getNode(data.parentId);
inRef.value.append(data,nodeParent);
}
}else{
inRef.value.append(data,null);
}
}
}
//修改节点
function updateChildNode(data: any){
if(onRef.value){
const key = data.id;
const node = onRef.value.getNode(key);
if (node) {
// 保留子节点数据
const childrenBackup = node.data.children || [];
// 合并数据
Object.assign(node.data, {
...data,
children: childrenBackup
});
}
}
if(inRef.value){
const key = data.id;
const node = inRef.value.getNode(key);
if (node) {
// 保留子节点数据
const childrenBackup = node.data.children || [];
// 合并数据
Object.assign(node.data, {
...data,
children: childrenBackup
});
}
}
}
//移除节点
function removeChildNode(data: any){
if(onRef.value){
onRef.value.remove(data);
}
if(inRef.value){
inRef.value.remove(data);
}
}
defineExpose({
addOnNode,
addInNode,
updateChildNode,
removeChildNode
})