Tree
Tree picker
Note
Lazy loading is not supported at the moment. Supporting it would require the backend to return not only child nodes, but also the checked / half-checked / unchecked status for every descendant node at the same time, which would add a significant burden to the backend. It also conflicts with several valueType modes. For example, when valueType is child or path, checking a partially loaded node would require the frontend to query every descendant before submission in order to keep the final value correct. Rehydration is also more complex, because the backend would need extra APIs to provide state for nodes that were not loaded at selection time.
Note
Please make sure your initial value is correct. The valueType used for rehydration must match the valueType used for submission.
Template Example
<script lang="ts" setup>
import { createForm, isField } from '@silver-formily/core'
import { FormItem, FormLayout, Select, Switch, Tree } from '@silver-formily/element-plus'
import { autorun, toJS } from '@silver-formily/reactive'
import { isPlainObj } from '@silver-formily/shared'
import { Field, FormProvider } from '@silver-formily/vue'
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 && isField(field)) {
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 && isField(field)) {
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 && isField(field)) {
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 && isField(field)) {
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 Initial Value Example
<script lang="ts" setup>
import { createForm } from '@silver-formily/core'
import { FormItem, FormLayout, Tree } from '@silver-formily/element-plus'
import { Field, FormProvider } from '@silver-formily/vue'
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',
},
}).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 Initial Value Example
<script lang="ts" setup>
import { createForm } from '@silver-formily/core'
import { FormItem, FormLayout, Tree } from '@silver-formily/element-plus'
import { Field, FormProvider } from '@silver-formily/vue'
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',
},
}).then((html) => {
selectedTree1Code.value = html
})
codeToHtml(JSON.stringify(tree2InitialValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
}).then((html) => {
selectedTree2Code.value = html
})
codeToHtml(JSON.stringify(tree3InitialValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
}).then((html) => {
selectedTree3Code.value = html
})
codeToHtml(JSON.stringify(tree4InitialValue, null, 2), {
lang: 'javascript',
themes: {
light: 'min-light',
dark: 'nord',
},
}).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>Additional Template State Examples
<script lang="ts" setup>
import { createForm, isField } from '@silver-formily/core'
import { FormItem, FormLayout, Input, Tree } from '@silver-formily/element-plus'
import { Field, FormProvider } from '@silver-formily/vue'
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) => {
if (!isField(field)) return
field.loading = true
}"
/>
<Field
name="filter"
title="过滤节点"
:decorator="[FormItem]"
:component="[Input, {
placeholder: '请输入节点名称',
}]"
:reactions="(field) => {
if (!isField(field)) return
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
Extended Props
| Prop | Type | Description | Default |
|---|---|---|---|
nodeKey | string | Required unique key for each node | - |
valueType | enum | Output value type. Only effective when checkStrictly is false | 'all' |
includeHalfChecked | boolean | Whether to include half-checked nodes. Only effective when valueType is 'all' | false |
optionAsValue | boolean | Whether to use the whole node option as the selected value. Invalid when valueType is path. | false |
optionFormatter | (node: TreeNode) => TreeNode | Option formatting function. Only effective when optionAsValue is true | - |
height | number | Height prop forwarded to ElScrollbar | - |
maxHeight | number | maxHeight prop forwarded to ElScrollbar | - |
For the rest, see https://element-plus.org/en-US/component/tree.html
Note
- The component reserves some props and events. Avoid using
default-checked-keys,show-checkbox, and thecheckevent directly. - Only
heightandmax-heightare forwarded toElScrollbar. Do not attach other ElScrollbar props or event listeners here.
Get Instance
Used to access the ElTree instance. For exposed methods, see the Element Plus documentation. The node-filtering demo shows the usage pattern.
const treeRef: Ref<TreeInstance> = fieldRef.value.invoke('getTreeRef')Slots
All slots from the original component are supported, and each slot additionally receives the field value in its slot scope for easier access.