树形控件无限 子集 情况处理(无法确定内部嵌套层数)ant-design为例

关键词:树形控件、树形组件、vue3、antd-design、eval函数

 

目录

场景描述

效果预览

开始

 着重介绍接下来的数据格式

基础代码介绍

处理树形数据

1. 模拟基础数据

2. 拆解回调函数返回的pos数据

3. 以每个部门中的第一个部门举例:

4. 利用eval拼接字符串代码

完整代码


 

场景描述

在日常编码中经常存在数据量过大不能一次返回,需要在某个节点使用时单独获取对应数据进行渲染的情况
例如在树形控件的使用场景,多所学校存在多个班级与数名学生,不能一次性将所有学校的学生姓名返回,则是选择学校,选择班级后展示对应学生数据。通常这种往往最多只有三四个子集,在数据展示时搞几个 if 处理较为容易

597659dba35d4900ad5f8358dd236c30.png

当在部门情况下则可能存在层层嵌套问题,如下图,那么如何解决这种无法确定具体层数的问题?

8f5d2c5b99f340d593840e3cb119745b.png


我们要解决的情况为假定嵌套99层部门(或文件夹情况),此处以ant-design树形控件为例解决无穷性加载用户及部门问题。

 

效果预览

8726403821aa4e4a8823ea185136040b.png

 

开始

对于某些属性的用法请参考文档: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赋值
  }
}

这样,不管在控件的何处,均可在对应位置填充到新数据

 

 

 

Guess you like

Origin blog.csdn.net/weixin_44640245/article/details/134390011