Tree
树形控件选择
注意
目前控件不支持懒加载,想要支持懒加载则必须让后端在返回子节点的同时返回其所有的子节点状态是勾选半勾选还是不勾选的,这无疑加大了后端的负担。而且和很多的valueType是冲突的,比如child或者path如果在还没加载完子节点的情况下勾选了则需要向后端查询其所有的子节点来保证最后提交数据是正确的。在处理返显时的逻辑更为复杂,需要后端提供额外的接口来获取勾选操作时未加载的节点状态。
注意
请自行保证初始值的正确性,返显的valueType和提交的valueType必须保持一致。
Template 案例
vue
<script lang="ts" setup>
import { createForm } from '@formily/core'
import { autorun, toJS } from '@formily/reactive'
import { isPlainObj } from '@formily/shared'
import { Field, FormProvider } from '@formily/vue'
import { FormItem, FormLayout, Select, Switch, Tree } from '@silver-formily/element-plus'
import { omit } from 'lodash-es'
import { codeToHtml } from 'shiki'
import { ref } from 'vue'
const form = createForm()
const shikiTree = ref('')
const shikiTree2 = ref('')
autorun(async () => {
if (!form.values.tree || !form.values.tree2)
return
const treeValue = toJS(form.values.tree)
const treeStrValue = isPlainObj(treeValue?.[0]) ? JSON.stringify(treeValue, null, 2) : JSON.stringify(treeValue)
shikiTree.value = await codeToHtml(treeStrValue, {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
})
const tree2Value = toJS(form.values.tree2)
const tree2StrValue = isPlainObj(tree2Value?.[0]) ? JSON.stringify(tree2Value, null, 2) : JSON.stringify(tree2Value)
shikiTree2.value = await codeToHtml(tree2StrValue, {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
})
})
const data = [
{
id: 1,
label: 'Level one 1',
children: [
{
id: 4,
label: 'Level two 1-1',
children: [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 10,
label: 'Level three 1-1-2',
},
],
},
],
},
{
id: 2,
label: 'Level one 2',
children: [
{
id: 5,
label: 'Level two 2-1',
},
{
id: 6,
label: 'Level two 2-2',
},
],
},
{
id: 3,
label: 'Level one 3',
children: [
{
id: 7,
label: 'Level two 3-1',
},
{
id: 8,
label: 'Level two 3-2',
},
],
},
]
</script>
<template>
<FormProvider :form="form">
<FormLayout :label-col="4" :wrapper-col="16">
<Field
name="valueType"
title="Tree的值类型"
:decorator="[FormItem]"
:component="[Select]"
initial-value="all"
:data-source="
[{
label: '全部',
value: 'all',
},
{
label: '优先父节点',
value: 'parent',
},
{
label: '仅子节点',
value: 'child',
},
{
label: '路径',
value: 'path',
},
]"
:reactions="field => {
const tree = field.query('tree').take();
if (tree) {
tree.setComponentProps({ ...tree.componentProps, valueType: field.value })
}
}"
/>
<Field
name="optionAsValue"
title="optionAsValue"
:decorator="[FormItem]"
:component="[Switch]"
:initial-value="false"
:reactions="field => {
const tree = field.query('tree').take();
if (tree) {
tree.setComponentProps({ ...tree.componentProps, optionAsValue: field.value })
}
}"
/>
<Field
name="includeHalfChecked"
title="包括半勾选节点"
:decorator="[FormItem]"
:component="[Switch]"
:initial-value="false"
:reactions="field => {
const tree = field.query('tree').take();
if (tree) {
tree.setComponentProps({ ...tree.componentProps, includeHalfChecked: field.value })
}
}"
/>
<Field
name="tree"
title="Tree"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'all',
includeHalfChecked: true,
maxHeight: 150,
}]"
:data-source="data"
:initial-value="[9]"
/>
<details>
<summary>输出结果</summary>
<div v-html="shikiTree" />
</details>
<Field
name="optionAsValue2"
title="optionAsValue"
:decorator="[FormItem]"
:component="[Switch]"
:initial-value="true"
:reactions="field => {
const tree = field.query('tree2').take();
if (tree) {
tree.setComponentProps({ ...tree.componentProps, optionAsValue: field.value })
}
}"
/>
<Field
name="tree2"
title="CheckStrictly"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
checkStrictly: true,
optionFormatter: (node) => omit(node, 'children'),
}]"
:data-source="data"
:initial-value="[
{
id: 1,
label: 'Level one 1',
},
]"
/>
<details>
<summary>筛除children的输出结果</summary>
<div v-html="shikiTree2" />
</details>
</FormLayout>
</FormProvider>
</template>Template 初始值返显案例
vue
<script lang="ts" setup>
import { createForm } from '@formily/core'
import { Field, FormProvider } from '@formily/vue'
import { FormItem, FormLayout, Tree } from '@silver-formily/element-plus'
import { ElText } from 'element-plus'
import { codeToHtml } from 'shiki'
import { ref } from 'vue'
const form = createForm()
const data = [
{
id: 1,
label: 'Level one 1 ---- ID:1',
children: [
{
id: 4,
label: 'Level two 1-1 ---- ID:4',
children: [
{
id: 9,
label: 'Level three 1-1-1 ---- ID:9',
},
{
id: 10,
label: 'Level three 1-1-2 ---- ID:10',
},
],
},
],
},
{
id: 2,
label: 'Level one 2 ---- ID:2',
children: [
{
id: 5,
label: 'Level two 2-1 ---- ID:5',
},
{
id: 6,
label: 'Level two 2-2 ---- ID:6',
},
],
},
{
id: 3,
label: 'Level one 3 ---- ID:3',
children: [
{
id: 7,
label: 'Level two 3-1 ---- ID:7',
},
{
id: 8,
label: 'Level two 3-2 ---- ID:8',
},
],
},
]
const selectedPathValue = [
{
id: 1,
label: 'Level one 1 ---- ID:1',
children: [
{
id: 4,
label: 'Level two 1-1 ---- ID:4',
children: [
{
id: 9,
label: 'Level three 1-1-1 ---- ID:9',
},
],
},
],
},
{
id: 3,
label: 'Level one 3 ---- ID:3',
children: [
{
id: 7,
label: 'Level two 3-1 ---- ID:7',
},
{
id: 8,
label: 'Level two 3-2 ---- ID:8',
},
],
},
]
const selectedPathValueCode = ref('')
codeToHtml(JSON.stringify(selectedPathValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
// eslint-disable-next-line unicorn/prefer-top-level-await
}).then((html) => {
selectedPathValueCode.value = html
})
</script>
<template>
<FormProvider :form="form">
<FormLayout :label-col="4" :wrapper-col="16">
<ElText>all,包括半勾选,初始值:[1, 4, 9, 2, 5]</ElText>
<Field
name="tree1"
title="Tree1"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'all',
includeHalfChecked: true,
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="[1, 4, 9, 2, 5]"
/>
<ElText>all,不包括半勾选,初始值:[9, 5]</ElText>
<Field
name="tree2"
title="Tree2"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'all',
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="[9, 5]"
/>
<ElText>parent,初始值:[1]</ElText>
<Field
name="tree3"
title="Tree3"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'parent',
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="[1]"
/>
<ElText>child,初始值:[8, 9]</ElText>
<Field
name="tree4"
title="Tree4"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'child',
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="[8, 9]"
/>
<ElText>path,初始值:完整的选中路径</ElText>
<details>
<summary>展开</summary>
<div v-html="selectedPathValueCode.toString()" />
</details>
<Field
name="tree5"
title="Tree5"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'path',
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="selectedPathValue"
/>
</FormLayout>
</FormProvider>
</template>Template option 初始值返显案例
vue
<script lang="ts" setup>
import { createForm } from '@formily/core'
import { Field, FormProvider } from '@formily/vue'
import { FormItem, FormLayout, Tree } from '@silver-formily/element-plus'
import { ElText } from 'element-plus'
import { codeToHtml } from 'shiki'
import { ref } from 'vue'
const form = createForm()
const data = [
{
id: 1,
label: 'Level one 1 ---- ID:1',
children: [
{
id: 4,
label: 'Level two 1-1 ---- ID:4',
children: [
{
id: 9,
label: 'Level three 1-1-1 ---- ID:9',
},
{
id: 10,
label: 'Level three 1-1-2 ---- ID:10',
},
],
},
],
},
{
id: 2,
label: 'Level one 2 ---- ID:2',
children: [
{
id: 5,
label: 'Level two 2-1 ---- ID:5',
},
{
id: 6,
label: 'Level two 2-2 ---- ID:6',
},
],
},
{
id: 3,
label: 'Level one 3 ---- ID:3',
children: [
{
id: 7,
label: 'Level two 3-1 ---- ID:7',
},
{
id: 8,
label: 'Level two 3-2 ---- ID:8',
},
],
},
]
const tree1InitialValue = [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 1,
label: 'Level one 1',
children: [
{
id: 4,
label: 'Level two 1-1',
children: [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 10,
label: 'Level three 1-1-2',
},
],
},
],
},
{
id: 4,
label: 'Level two 1-1',
children: [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 10,
label: 'Level three 1-1-2',
},
],
},
]
const tree2InitialValue = [
{
id: 9,
label: 'Level three 1-1-1',
},
]
const tree3InitialValue = [
{
id: 1,
label: 'Level one 1',
children: [
{
id: 4,
label: 'Level two 1-1',
children: [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 10,
label: 'Level three 1-1-2',
},
],
},
],
},
]
const tree4InitialValue = [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 6,
label: 'Level two 2-2',
},
]
const selectedTree1Code = ref('')
const selectedTree2Code = ref('')
const selectedTree3Code = ref('')
const selectedTree4Code = ref('')
codeToHtml(JSON.stringify(tree1InitialValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
// eslint-disable-next-line unicorn/prefer-top-level-await
}).then((html) => {
selectedTree1Code.value = html
})
codeToHtml(JSON.stringify(tree2InitialValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
// eslint-disable-next-line unicorn/prefer-top-level-await
}).then((html) => {
selectedTree2Code.value = html
})
codeToHtml(JSON.stringify(tree3InitialValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
// eslint-disable-next-line unicorn/prefer-top-level-await
}).then((html) => {
selectedTree3Code.value = html
})
codeToHtml(JSON.stringify(tree4InitialValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
// eslint-disable-next-line unicorn/prefer-top-level-await
}).then((html) => {
selectedTree4Code.value = html
})
</script>
<template>
<FormProvider :form="form">
<FormLayout :label-col="4" :wrapper-col="16">
<ElText>all,包括半勾选,初始值:</ElText>
<details>
<summary>展开</summary>
<div v-html="selectedTree1Code" />
</details>
<Field
name="tree1"
title="Tree1"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'all',
optionAsValue: true,
includeHalfChecked: true,
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="tree1InitialValue"
/>
<ElText>all,不包括半勾选,初始值:</ElText>
<details>
<summary>展开</summary>
<div v-html="selectedTree2Code" />
</details>
<Field
name="tree2"
title="Tree2"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'all',
optionAsValue: true,
includeHalfChecked: true,
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="tree2InitialValue"
/>
<ElText>parent,初始值:</ElText>
<details>
<summary>展开</summary>
<div v-html="selectedTree3Code" />
</details>
<Field
name="tree3"
title="Tree3"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'parent',
optionAsValue: true,
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="tree3InitialValue"
/>
<ElText>child,初始值:</ElText>
<details>
<summary>展开</summary>
<div v-html="selectedTree4Code" />
</details>
<Field
name="tree4"
title="Tree4"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'child',
optionAsValue: true,
defaultExpandAll: true,
}]"
:data-source="data"
:initial-value="tree4InitialValue"
/>
</FormLayout>
</FormProvider>
</template>Template 其他特殊状态
vue
<script lang="ts" setup>
import { createForm } from '@formily/core'
import { Field, FormProvider } from '@formily/vue'
import { FormItem, FormLayout, Input, Tree } from '@silver-formily/element-plus'
import { omit } from 'lodash-es'
const form = createForm()
const data = [
{
id: 1,
label: 'Level one 1',
children: [
{
id: 4,
label: 'Level two 1-1',
children: [
{
id: 9,
label: 'Level three 1-1-1',
},
{
id: 10,
label: 'Level three 1-1-2',
},
],
},
],
},
{
id: 2,
label: 'Level one 2',
children: [
{
id: 5,
label: 'Level two 2-1',
},
{
id: 6,
label: 'Level two 2-2',
},
],
},
{
id: 3,
label: 'Level one 3',
children: [
{
id: 7,
label: 'Level two 3-1',
},
{
id: 8,
label: 'Level two 3-2',
},
],
},
]
</script>
<template>
<FormProvider :form="form">
<FormLayout :label-col="4" :wrapper-col="16">
<Field
name="tree"
title="全局禁用"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
valueType: 'all',
includeHalfChecked: true,
}]"
:data-source="data"
:initial-value="[9]"
:disabled="true"
/>
<Field
name="tree2"
title="Loading"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
checkStrictly: true,
optionFormatter: (node) => omit(node, 'children'),
}]"
:data-source="data"
:reactions="(field) => {
field.loading = true
}"
/>
<Field
name="filter"
title="过滤节点"
:decorator="[FormItem]"
:component="[Input, {
placeholder: '请输入节点名称',
}]"
:reactions="(field) => {
const tree3 = field.query('tree3').take()
const inputValue = field.value
if (!tree3)
return
const tree3Instance = tree3.invoke('getTreeRef')
if (!tree3Instance)
return
tree3Instance.value.filter(inputValue)
}"
/>
<Field
name="tree3"
title="调用实例方法"
:decorator="[FormItem]"
:component="[Tree, {
nodeKey: 'id',
checkStrictly: true,
defaultExpandAll: true,
filterNodeMethod: (value: string, data) => {
if (!value) return true
return data.label.includes(value)
},
}]"
:data-source="data"
/>
</FormLayout>
</FormProvider>
</template>API
扩展属性
| 属性名 | 类型 | 描述 | 默认值 |
|---|---|---|---|
| nodeKey | string | 节点的唯一标识符,现在是必填项 | - |
| valueType | enum | 数据类型,仅在checkStrictly为false生效 | 'all' |
| includeHalfChecked | boolean | 是否包含半勾选的节点,仅在valueType为'all'时生效 | false |
| optionAsValue | boolean | 是否将节点值作为选项的值,valueType为path时无效 | false |
| optionFormatter | (node: TreeNode) => TreeNode | 选项格式化函数,仅在optionAsValue为true时生效 | - |
| height | number | ElScorller组件的height属性 | - |
| maxHeight | number | ElScorller组件的maxHeight属性 | - |
其余属性参考 https://cn.element-plus.org/zh-CN/component/tree.html
注意
- 组件是有保留值的,
default-checked-keysshow-checkbox属性以及check事件已经被组件内占用,请避免使用。 - 只有
height和max-height属性会被传到ElScroll组件上,请勿添加别的ElScroll的属性或事件监听。
获取实例
用于获取ElTree实例,具体暴露的方法请参考element-plus文档。使用方式请参考节点过滤的demo。
ts
const treeRef: Ref<TreeInstance> = fieldRef.value.invoke('getTreeRef')插槽
支持原有组件所有插槽,所有插槽在原有基础上额外添加了 field 作用域插槽的值方便做访问。