---
URL: "/zh/components/tree.html"
LLMS_URL: "/zh/components/tree.md"
---

# Tree 树形控件

文件夹、组织架构、生物分类、国家地区等等，世间万物的大多数结构都是树形结构。使用树控件可以完整展现其中的层级关系，并具有展开收起选择等交互功能。

## 组件注册

```js
import { FTree } from '@fesjs/fes-design';

app.use(FTree);
```

## 代码演示

### 基础用法

基础的树形结构展示。

```vue
<template>
    <FGrid>
        <FGridItem :span="12">
            默认为可选中：
            <FTree :data="data1" />
        </FGridItem>
        <FGridItem :span="12">
            选中后无法取消：
            <FTree :data="data2" :cancelable="false" />
        </FGridItem>
    </FGrid>
</template>

<script>
import { h, reactive } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix = null, suffix = null) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data1 = reactive(createData(3));
        const data2 = [
            {
                label: '二生三',
                value: '20',
                children: [
                    {
                        label: '三生万物',
                        value: '2010',
                        prefix: null,
                        suffix: null,
                        children: [
                            {
                                label: '三生万物',
                                value: '312010',
                                prefix: null,
                                suffix: null,
                            },
                            {
                                label: '三生万物',
                                value: '312011',
                                prefix: null,
                                suffix: null,
                            },
                        ],
                    },
                    {
                        label: '三生万物',
                        value: '2011',
                        prefix: null,
                        suffix: null,
                    },
                ],
                prefix: null,
                suffix: null,
            },
            {
                label: '二生三',
                value: '21',
                prefix: null,
                suffix: null,
            },
        ];
        return {
            data1,
            data2,
        };
    },
};
</script>
```

### 虚拟列表

设置 `virtualList` 属性，处理大数据。

```vue
<template>
    {{ checkedKeys }}
    <FTree
        v-model:checkedKeys="checkedKeys"
        :data="data"
        virtualList
        checkable
        cascade
        checkStrictly="parent"
    />
</template>

<script>
import { reactive, ref } from 'vue';

function createData(level = 4, baseKey = '') {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 10 }).map((_, index) => {
        const key = baseKey ? `${baseKey}_${index}` : `${index}`;
        return {
            label: createLabel(level, index),
            value: key,
            children: createData(level - 1, key),
        };
    });
}

function createLabel(level, index) {
    if (level === 4) {
        return `道生一_${index}`;
    }
    if (level === 3) {
        return `一生二_${index}`;
    }
    if (level === 2) {
        return `二生三_${index}`;
    }
    if (level === 1) {
        return `三生万物_${index}`;
    }
}

export default {
    setup() {
        const data = reactive(createData());
        const checkedKeys = ref(['0']);
        return {
            data,
            checkedKeys,
        };
    },
};
</script>

<style scoped>
.fes-tree {
    height: 300px;
}
</style>
```

#### 叶子节点一行展示

```vue
<template>
    <FTree :data="data" inline defaultExpandAll />
</template>

<script>
import { h, reactive } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix = null, suffix = null) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        return {
            data,
        };
    },
};
</script>
```

#### 可选择多个节点

可以选择多个节点。

```vue
<template>
    <FTree :data="data" multiple />
</template>

<script>
import { h, reactive } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix = null, suffix = null) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        return {
            data,
        };
    },
};
</script>
```

### 可勾选

适用于需要选择层级时使用。

```vue
<template>
    <FForm>
        <FFormItem label="父子关联：">
            <FSwitch v-model="cascade" />
        </FFormItem>
    </FForm>
    <FTree
        :data="data"
        :cascade="cascade"
        :checkable="true"
        :selectable="false"
    />
</template>

<script setup>
import { ref } from 'vue';

const createLabel = (level) => {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
};

const createData = (level = 1, baseKey = '') => {
    if (!level) {
        return undefined;
    }
    return new Array(3).fill(null).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key),
        };
    });
};

const data = ref(createData(4));
const cascade = ref(true);
</script>
```

#### 展开部分 + 默认选中 + 默认勾选

通过 `expandedKeys` 配置默认展开节点，通过 `selectedKeys` 配置默认选择节点，通过 `checkedKeys` 配置默认勾选节点。

```vue
<template>
    <FTree
        v-model:selectedKeys="selectedKeys"
        v-model:expandedKeys="expandedKeys"
        v-model:checkedKeys="checkedKeys"
        :data="data"
        checkable
    />
</template>

<script>
import { h, reactive, ref } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix, suffix) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        const selectedKeys = ref(['40']);
        const expandedKeys = ref(['40']);
        const checkedKeys = ref(['40']);

        return {
            data,
            selectedKeys,
            expandedKeys,
            checkedKeys,
        };
    },
};
</script>
```

#### 禁用节点

无法被选中和点击。

```vue
<template>
    <FTree :data="data" checkable />
</template>

<script>
import { h, reactive } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix, suffix) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const originData = createData(4);
        originData.forEach((item) => {
            item.disabled = true;
        });
        const data = reactive(originData);
        return {
            data,
        };
    },
};
</script>
```

### 异步加载

点击展开节点时加载子选项。

```vue
<template>
    <FTree :data="data" :loadData="loadData" checkable cascade remote />
</template>

<script>
import { h, reactive } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix, suffix) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(1));
        const loadData = (node) => {
            return new Promise((resolve) => {
                setTimeout(() => {
                    node.children = [
                        {
                            label: '1',
                            value: `${node.value}1`,
                        },
                        {
                            label: '2',
                            value: `${node.value}2`,
                        },
                    ];
                    resolve();
                }, 2000);
            });
        };

        return {
            loadData,
            data,
        };
    },
};
</script>
```

### 搜索

通过关键字过滤树节点。

```vue
<template>
    <FInput v-model="filterText" placeholder="请输入" />
    <FTree ref="refTree" :data="data" checkable :filterMethod="filterMethod" />
</template>

<script>
import { h, reactive, ref, watch } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix, suffix) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4));
        const filterText = ref('');
        const refTree = ref(null);
        watch(filterText, () => {
            refTree.value.filter(filterText.value);
        });
        const filterMethod = (value, node) => {
            return node.label.indexOf(value) !== -1;
        };
        return {
            data,
            filterText,
            refTree,
            filterMethod,
        };
    },
};
</script>
```

### 前缀与后缀

放一些操作。

```vue
<template>
    <FTree :data="data" checkable />
</template>

<script>
import { h, reactive } from 'vue';
import { AppstoreOutlined, PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix, suffix) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: () => h('div', {
                class: 'fix-node-content',
            }, [
                createLabel(level),
                h(AppstoreOutlined),
            ]),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? (data) => (data.isHovered ? h(PlusCircleOutlined) : null) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

export default {
    setup() {
        const data = reactive(createData(4, '', true, true));

        return {
            data,
        };
    },
};
</script>

<style>
.fix-node-content {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: start;
    gap: 5px;
}
</style>
```

### 拖拽

```vue
<template>
    <FTree :data="data" draggable @drop="onDrop" />
</template>

<script>
import { h, reactive } from 'vue';
import { PictureOutlined, PlusCircleOutlined } from '@fesjs/fes-design/icon';

function createData(level = 1, baseKey = '', prefix, suffix) {
    if (!level) {
        return undefined;
    }
    return Array.apply(null, { length: 2 }).map((_, index) => {
        const key = `${baseKey}${level}${index}`;
        return {
            label: key + createLabel(level),
            value: key,
            children: createData(level - 1, key, prefix, suffix),
            prefix: prefix ? () => h(PictureOutlined) : null,
            suffix: suffix ? () => h(PlusCircleOutlined) : null,
        };
    });
}

function createLabel(level) {
    if (level === 4) {
        return '道生一';
    }
    if (level === 3) {
        return '一生二';
    }
    if (level === 2) {
        return '二生三';
    }
    if (level === 1) {
        return '三生万物';
    }
}

function findSiblingsAndIndex(node, nodes) {
    if (!nodes) {
        return [null, null];
    }
    for (let i = 0; i < nodes.length; ++i) {
        const siblingNode = nodes[i];
        if (siblingNode.value === node.value) {
            return [nodes, i];
        }
        const [siblings, index] = findSiblingsAndIndex(
            node,
            siblingNode.children,
        );
        if (siblings && index !== null) {
            return [siblings, index];
        }
    }
    return [null, null];
}

export default {
    setup() {
        const data = reactive(createData(4));

        const onDrop = ({ originNode, originDragNode, position }) => {
            const [dragNodeSiblings, dragNodeIndex] = findSiblingsAndIndex(
                originDragNode,
                data,
            );
            if (dragNodeSiblings === null || dragNodeIndex === null) {
                return;
            }
            dragNodeSiblings.splice(dragNodeIndex, 1);
            if (position === 'before') {
                const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(
                    originNode,
                    data,
                );
                if (nodeSiblings === null || nodeIndex === null) {
                    return;
                }
                nodeSiblings.splice(nodeIndex, 0, originDragNode);
            } else if (position === 'after') {
                const [nodeSiblings, nodeIndex] = findSiblingsAndIndex(
                    originNode,
                    data,
                );
                if (nodeSiblings === null || nodeIndex === null) {
                    return;
                }
                nodeSiblings.splice(nodeIndex + 1, 0, originDragNode);
            } else if (position === 'inside') {
                if (!originNode.children) {
                    originNode.children = [];
                }
                originNode.children.splice(0, 0, originDragNode);
            }
        };

        return {
            data,
            onDrop,
        };
    },
};
</script>
```


<script setup>
import Async from '../../.vitepress/.temp/components/tree/async.vue'
import Checkable from '../../.vitepress/.temp/components/tree/checkable.vue'
import Common from '../../.vitepress/.temp/components/tree/common.vue'
import Default from '../../.vitepress/.temp/components/tree/default.vue'
import Drag from '../../.vitepress/.temp/components/tree/drag.vue'
import Filter from '../../.vitepress/.temp/components/tree/filter.vue'
import Fix from '../../.vitepress/.temp/components/tree/fix.vue'
import Inline from '../../.vitepress/.temp/components/tree/inline.vue'
import ItemDisabled from '../../.vitepress/.temp/components/tree/itemDisabled.vue'
import Multiple from '../../.vitepress/.temp/components/tree/multiple.vue'
import VirtualList from '../../.vitepress/.temp/components/tree/virtualList.vue'
</script>


## Tree Props

| 属性                  | 说明                                                                                                                                                        | 类型                                      | 默认值     |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ---------- |
| data                  | 展示数据                                                                                                                                                    | Array\<TreeOption\>                       | `[]`       |
| defaultExpandAll      | 是否默认展开所有节点，当有 `expandedKeys` 时，`defaultExpandAll` 将失效                                                                                     | boolean                                   | `false`    |
| expandedKeys(v-model) | 展开的节点的 key 的数组                                                                                                                                     | Array<string \| number>                   | `[]`       |
| selectable            | 是否可选中节点                                                                                                                                              | boolean                                   | `true`     |
| selectedKeys(v-model) | 设置选中的节点                                                                                                                                              | Array<string \| number>                   | `[]`       |
| multiple              | 是否能选中多个节点                                                                                                                                          | boolean                                   | `false`    |
| cancelable            | 选中后是否可以再次点击取消选中                                                                                                                              | boolean                                   | `true`     |
| checkable             | 是否显示 `Checkbox` 选择框                                                                                                                                  | boolean                                   | `false`    |
| cascade               | 当勾选选择框时，父子节点的选择框勾选状态是否关联，相互影响                                                                                                  | boolean                                   | `false`    |
| checkStrictly         | 设置勾选策略来计算`checkedKeys`，`all`为全部选中节点；`parent` 为全部选中节点中的父节点（当父节点下所有子节点都选中时）；`child` 为全部选中节点中的叶子节点 | string                                    | `all`      |
| checkedKeys(v-model)  | 勾选节点 key 的数组                                                                                                                                         | Array<string \| number>                   | `[]`       |
| childrenField         | 替代 `TreeOption` 中的 `children` 字段名                                                                                                                    | string                                    | `children` |
| valueField            | 替代 `TreeOption` 中的 `value` 字段名                                                                                                                       | string                                    | `value`    |
| labelField            | 替代 `TreeOption` 中的 `label` 字段名                                                                                                                       | string                                    | `label`    |
| remote                | 是否异步获取选项，和 `loadData` 配合                                                                                                                        | boolean                                   | `false`    |
| loadData              | 异步加载数据的回调函数                                                                                                                                      | (node: TreeOption) => Promise\<void\>     | `null`     |
| inline                | 底层节点是否横向排列                                                                                                                                        | boolean                                   | `false`    |
| filterMethod          | 类似 Array.filter 函数，筛选树节点（高亮）                                                                                                                  | (filterText, node: TreeOption) => Boolean | `null`     |
| virtualList           | 是否使用虚拟滚动，需要设置 tree 的高度，并且 inline 需要为 false                                                                                            | boolean                                   | `false`    |
| accordion             | 手风琴模式，是否保持同级节点中只有一个节点展开                                                                                                              | boolean                                   | `false`    |
| draggable             | 是否拖拽，`inline`需要为 false                                                                                                                              | boolean                                   | `false`    |

## Tree Events

| 事件名称  | 说明                     | 回调参数                                                                                                                          |
| --------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| check     | 点击节点中的选择框时触发 | ({ checkedKeys, node, event, checked }) => void                                                                                   |
| expand    | 展开、收起节点时触发     | ({ expandedKeys, node, event, expanded }) => void                                                                                 |
| select    | 点击节点内容时触发       | ({ selectedKeys, node, event, selected }) => void                                                                                 |
| dragstart | 开始拖拽时调用           | ({ node: TreeOption, event: DragEvent }) => void                                                                                  |
| dragend   | dragend 时触发时调用     | ({ node: TreeOption, event: DragEvent }) => void                                                                                  |
| dragenter | dragenter 时触发时调用   | ({ node: TreeOption, event: DragEvent }) => void                                                                                  |
| dragleave | dragleave 时触发时调用   | ({ node: TreeOption, event: DragEvent }) => void                                                                                  |
| dragover  | dragover 时触发时调用    | ({ node: TreeOption, event: DragEvent }) => void                                                                                  |
| drop      | drop 时触发时调用        | ({ node: TreeOption, dragNode: TreeOption, originNode, originDragNode, position: 'before' \| 'inside' \| 'after', event: DragEvent }) => void |

## Tree Methods

| 方法名称   | 说明           | 参数         |
| ---------- | -------------- | ------------ |
| filter     | 过滤 Tree 节点 | (filterText) |
| selectNode | 选中节点       | (value)      |
| expandNode | 展开树节点     | (value)      |
| checkNode  | check 节点     | (value)      |

## Tree Slots

| 名称    | 说明                           |
| ------- | ------------------------------ |
| label | treeOption 优先级比这个高             |

## TreeOption props

| 属性        | 说明                                                 | 类型                        | 默认值  |
|-------------|----------------------------------------------------|-----------------------------|---------|
| value       | 节点的 `key`，需要唯一，可使用 `valueField` 修改字段名 | string / number             | `-`     |
| label       | 节点的内容，可使用 `labelField` 修改字段名            | string / (() => VNodeChild) | `-`     |
| children?   | 节点的子节点                                         | TreeOption[]                | `[]`    |
| disabled?   | 是否禁用节点                                         | boolean                     | `-`     |
| selectable? | 是否禁用选中节点，默认为 `Tree` 组件的 `selectable`   | boolean                     | `-`     |
| checkable?  | 是否显示勾选节点，默认为 `Tree` 组件的 `checkable`    | boolean                     | `-`     |
| draggable?  | 节点是否能被拖拽，默认为 true                         | boolean                     | `true`  |
| isLeaf?     | 节点是否是叶节点，在 remote 模式下是必须的            | boolean                     | `false` |
| prefix?     | 节点的前缀                                           | string / (() => VNodeChild) | `null`  |
| suffix?     | 节点的后缀                                           | string / (() => VNodeChild) | `null`  |
