キーワード: ツリー コントロール、ツリー コンポーネント、vue3、antd-design、eval 関数
目次
2. コールバック関数から返された pos データを逆アセンブルします。
シーンの説明
日常のコーディングでは、データ量が多すぎて一度に返すことができず、特定のノードを使用するときにレンダリングのために対応するデータを個別に取得する必要がある状況がよくあります。
たとえば、ツリー コントロールを使用するシナリオでは、複数の学校に複数のクラスと複数の生徒が存在し、すべての学校の生徒の名前を一度に返すことはできません。代わりに、学校を選択して、対応する生徒データを表示します。クラス選択後。通常、サブセットは最大でも 3 つまたは 4 つしかなく、データを表示するときにいくつかの if プロセスを実行する方が簡単です
部門の場合、下図のような階層ごとの入れ子問題が発生する場合がありますが、具体的な階層数が特定できないという問題はどのように解決すればよいでしょうか。
解決したい状況は、99 レベルのネストされた部門 (またはフォルダー) を想定しており、ここでは ant-design ツリー コントロールを例として、ユーザーと部門の無限ロードの問題を解決します。
エフェクトのプレビュー
始める
一部のプロパティの使用法については、次のドキュメントを参照してください。Ant Design Vue — Ant Design と Vue.js に基づいたエンタープライズ クラスの UI コンポーネント
次のデータ形式に注目
// 在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 イベントは、コントロールがチェック ボックスをクリックするとトリガーされ、現在のノードの一意の位置を取得し、それを逆アセンブルして現在のレイヤー番号を決定します。
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)
}
コントロールによって返される最初のレイヤーの部門位置の開始点は 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 を使用して文字列コードを結合する
上記のサンプル コードは、ターゲットの子の位置を取得し、子に対してデータ操作を実行します。現在の特定のレイヤー数を特定できない場合は、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赋值
}
}
このようにして、コントロール内のどこにいても、対応する位置に新しいデータを入力できます。