关键词:树形控件、树形组件、vue3、antd-design、eval函数
目录
场景描述
在日常编码中经常存在数据量过大不能一次返回,需要在某个节点使用时单独获取对应数据进行渲染的情况
例如在树形控件的使用场景,多所学校存在多个班级与数名学生,不能一次性将所有学校的学生姓名返回,则是选择学校,选择班级后展示对应学生数据。通常这种往往最多只有三四个子集,在数据展示时搞几个 if 处理较为容易
当在部门情况下则可能存在层层嵌套问题,如下图,那么如何解决这种无法确定具体层数的问题?
我们要解决的情况为假定嵌套99层部门(或文件夹情况),此处以ant-design树形控件为例解决无穷性加载用户及部门问题。
效果预览
开始
对于某些属性的用法请参考文档:Ant Design Vue — An enterprise-class UI components based on Ant Design and Vue.js
着重介绍接下来的数据格式
// 在children中层层嵌套children,每一个children即代表一层
{
title: '',
key: '',
type: 'root',
children: [
{
title: '',
key: '',
type: 'root',
children: []
}
]
}
基础代码介绍
<a-tree
v-model:checkedKeys="checkedKeys"
checkable
:selectable="false"
:tree-data="tree_data.data"
@expand="handleExpand"
>
<template #title="{ title, type }">
<div class="t_bar">
<img v-if="type == 'root'" src="@/assets/images/usericon/root.png" />
<img v-if="type == 'dept'" src="@/assets/images/usericon/dept.png" />
<img v-if="type == 'person'" src="@/assets/images/usericon/person.jpg" />
{
{ title }}
</div>
</template>
</a-tree>
const checkedKeys = ref<string[]>([])
watch(checkedKeys, () => {
console.log('checkedKeys', checkedKeys.value)
//TODO: 需要剔除组件默认自带的pos的key,例如0-0-1,0-0-2-0
})
使用插槽对每一行自定义了图标展示,监听checkedKeys选中数据的变化,@expand在控件展开时触发并返回节点数据
处理树形数据
1. 模拟基础数据
const tree_data = reactive({
data: [
{
title: '某某总部',
key: '0bf7c371-a404-4b6c43609745',
type: 'root',
children: [
{
title: '等等',
key: '0395d118-2e36-45db-bcb6-b55f8d41902f',
type: 'dept',
children: [{}]
},
{
title: '青',
key: '03f5bcd1-df29-4ada-9673-c2891db29cd9',
type: 'dept',
children: [{}]
},
{
title: '部门33',
key: '4d51120d-675f-ee2fc',
type: 'dept',
children: [{}]
},
{
title: '测试',
key: '4e82a-f8f18a752973',
type: 'dept',
children: [{}]
},
{
title: '美术部',
key: '505e19b4-675c292ee2fc',
type: 'dept',
children: [{}]
},
{
title: '练习部',
key: '53dd5965-675f-1e2fc',
type: 'dept',
children: [{}]
},
{
title: '开发部',
key: '57ae1c69-a550-4d37494a',
type: 'dept',
children: [{}]
}
]
}
]
})
2. 拆解回调函数返回的pos数据
控件点击复选框时触发handleExpand事件,获取当前node的唯一pos,进行拆解确定当前所处的层数。
const handleExpand = async (expandedKeys: any, expanded: any) => {
// 利用组件自带的pos属性获取到children的下标
// pos从0-0开始,若点击根组织的第二个部门中的第一个部门 idxs则为[1,0]、第三个部门中的第一个部 门的第四个部门 idxs为[2,0,3]
let idxs = expanded.node.pos.split('-').slice(2) //idsx即为即将执行的children位置
// 获取部门中的部门... 的层数,往里再放人员或者部门
let layers = idxs.length
console.log('层数:' + layers)
}
控件返回的第一层的部门pos起点为0-0-0,部门的部门起点为0-0-0-0,所以我们只需要利用split()函数以“-”切割为数组取第2个开始的数据,以此类推。切割后的数组长度即为当前所处的层数。
3. 以每个部门中的第一个部门举例:
//(第一层)总部门下的部门下的children 代码为
// idxs为[0]
tree_data.data[0].children[idxs[0]].children
//(第二层)总部门的部门下的children的部门下的children 代码为
// idxs为[0,0]
tree_data.data[0].children[idxs[0]].children[idxs[1]].children
//(第三层)总部门的部门下的children的部门下的children的部门下的children 代码为
// idsx为:[0,0,0]
tree_data.data[0].children[idxs[0]].children[idxs[1]].children[idxs[2]].children
以此类推...
4. 利用eval拼接字符串代码
以上举例代码获取到了目标的children的位置,对这个children进行数据操作即可,当我们无法确定当前具体层数时,可以利用for循环拼接字符串代码利用eval进行解析使用
// 拼接代码
let evalCode = 'tree_data.data[0].children'
for (let i = 0; i < layers; i++) { // layers为当前部门的层数
evalCode += `[idxs[${i}]].children`
}
// 执行自定义代码
eval(evalCode + ' = []') // 先重置将要打开折叠的选项,避免加载数据先跳出空数据
// 最终执行结果例:tree_data.data[0].children[idxs[0]].children = []
完整代码
/**
* 节点选择
* @param expandedKeys
* @param expanded
*/
const handleExpand = async (expandedKeys: any, expanded: any) => {
// 利用组件自带的pos属性获取到children的下标
// pos从0-0开始,若点击根组织的第二个部门中的第一个部门 idxs则为[1,0]、第三个部门中的第一个部门的第四个部门 idxs为[2,0,3]
let idxs = expanded.node.pos.split('-').slice(2) //idsx即为即将执行的children位置
// 获取部门中的部门... 的层数,往里再放人员或者部门
let layers = idxs.length
console.log('层数:' + layers)
// 首先确认为展开操作且展开的节点且不为root
if (expanded.expanded && expanded.node.type != 'root') {
console.log(expanded.node.title + ' ' + expanded.node.key)
// 以下的逻辑需要拼接代码执行,即使无限个部门也可正常执行(部门中存在不确定性,例如:部门中可能存在部门,部门的部门可能有存在部门)
// 拼接代码利用eval进行解析使用,部门中的部门层数layers点击时已经处理出具体的结果
// 举例:
// 总部门下的部门下的children 代码为 tree_data.data[0].children[idxs[0]].children
// 总部门的部门下的children的部门下的children 代码为 tree_data.data[0].children[idxs[0]].children
// 总部门的部门下的children的部门下的children的部门下的children 代码为 tree_data.data[0].children[idxs[0]].children[idxs[1]].children
// 以此类推...
// 拼接代码
let evalCode = 'tree_data.data[0].children'
for (let i = 0; i < layers; i++) {
evalCode += `[idxs[${i}]].children`
}
// 执行自定义代码
eval(evalCode + ' = []') // 先重置将要打开折叠的选项,避免加载数据先跳出空数据
// 获取部门下的部门
let dept = await queryDeptTree({ deptId: expanded.node.key })
let init_dept_data: TreeProps['treeData'] | undefined
if (dept.code === ResponseCode.success) {
// 处理部门数据
init_dept_data = dept.data.childDept.map((item: any) => {
return {
title: item.deptName + Math.random(), //FIXME: 代码中的Math.random()仅为开发测试使用,确保key唯一,联调时请务必删除!
key: item.deptId + Math.random(), //FIXME: Math.random()记得删除
type: 'dept',
children: [{}] // 部门此处必须有个空对象,不然默认不展示下拉按钮
}
})
}
// 假设此处发起了一个请求获取到了当前部门的用户列表
let init_dept_data= [{
title: "测试" + Math.random(),
key: "idffg2763" + Math.random(), //FIXME: Math.random()记得删除
type: 'dept',
children: [{}] // 部门此处必须有个空对象,不然默认不展示下拉按钮
},
{
title: "测试" + Math.random(),
key: "idffg2763" + Math.random(), //FIXME: Math.random()记得删除
type: 'dept',
children: [{}] // 部门此处必须有个空对象,不然默认不展示下拉按钮
},
{
title: "测试" + Math.random(),
key: "idffg2763" + Math.random(), //FIXME: Math.random()记得删除
type: 'dept',
children: [{}] // 部门此处必须有个空对象,不然默认不展示下拉按钮
}]
// 假设此处发起了一个请求获取到了当前部门的用户列表
let init_person_data = [{
title: "王五",
key: "idffg2763" + Math.random(), //FIXME: Math.random()记得删除
type: 'person'
},
{
title: "赵六",
key: "idffg2763" + Math.random(), //FIXME: Math.random()记得删除
type: 'person'
},
{
title: "张无忌",
key: "idffg2763" + Math.random(), //FIXME: Math.random()记得删除
type: 'person'
}]
// 执行自定义代码
// 向对应部门节点的children插入部门与用户
eval(evalCode + ' = [...init_dept_data, ...init_person_data]') // children赋值
}
}
这样,不管在控件的何处,均可在对应位置填充到新数据