Tree 树
对于文件夹、分类目录、组织架构等层级较多的内容,树可以清楚显示他们的层级关系,并具有展开、收起、选择等交互功能。
基础用法
为每个节点赋予全局唯一的 key(必填项),title 为该节点显示的内容。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-tree :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
disabled: true,
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
<template>
<tu-tree :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
disabled: true,
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
节点占一行
节点占据一整行。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-tree blockNode :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
<template>
<tu-tree blockNode :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
多选
Tree 设置 multiple 属性为true,可以启用多选。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-tree multiple :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
<template>
<tu-tree multiple :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
带复选框的树
为 Tree 添加 checkable 属性即可使树具有复选框功能,可以用 defaultCheckedKeys 指定复选框默认选中的节点。
父子关联
叶子 1-1
叶子 1-2-1
叶子 1-2-2
叶子 2-1-1
叶子 2-1-2
叶子 2-2
不关联
叶子 1-1
叶子 1-2-1
叶子 1-2-2
叶子 2-1-1
叶子 2-1-2
叶子 2-2
<template>
<tu-row :gutter="20">
<tu-col :span="12">
<p>父子关联</p>
<tu-tree checkable :data="treeData" />
</tu-col>
<tu-col :span="12">
<p>不关联</p>
<tu-tree checkable check-strictly :data="treeData" />
</tu-col>
</tu-row>
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '叶子 1-1',
key: '1-1'
},
{
title: '分支 1-2',
key: '1-2',
disabled: true,
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
},
{
title: '叶子 1-2-2',
key: '1-2-2',
disableCheckbox: true
}
]
}
]
},
{
title: '主干 2',
key: '2',
children: [
{
title: '分支 2-1',
key: '2-1',
children: [
{
title: '叶子 2-1-1',
key: '2-1-1'
},
{
title: '叶子 2-1-2',
key: '2-1-2'
}
]
},
{
title: '叶子 2-2',
key: '2-2'
}
]
}
];
</script>
<template>
<tu-row :gutter="20">
<tu-col :span="12">
<p>父子关联</p>
<tu-tree checkable :data="treeData" />
</tu-col>
<tu-col :span="12">
<p>不关联</p>
<tu-tree checkable check-strictly :data="treeData" />
</tu-col>
</tu-row>
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '叶子 1-1',
key: '1-1'
},
{
title: '分支 1-2',
key: '1-2',
disabled: true,
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
},
{
title: '叶子 1-2-2',
key: '1-2-2',
disableCheckbox: true
}
]
}
]
},
{
title: '主干 2',
key: '2',
children: [
{
title: '分支 2-1',
key: '2-1',
children: [
{
title: '叶子 2-1-1',
key: '2-1-1'
},
{
title: '叶子 2-1-2',
key: '2-1-2'
}
]
},
{
title: '叶子 2-2',
key: '2-2'
}
]
}
];
</script>
双向绑定
selectedKeys 、 checkedKeys 、 expandedKeys 属性均可受控,不仅支持 v-model ,还可以在对应的 select / check / expand 事件中自行控制如何更新属性值。
叶子 1-1
叶子 1-2-1
叶子 2-1
叶子 2-2
<template>
<tu-button-group class="mb-2">
<tu-button type="primary" @click="toggleChecked">
{{ checkedKeys?.length ? '取消选择' : '全部选择' }}
</tu-button>
<tu-button type="primary" @click="toggleExpanded">
{{ expandedKeys?.length ? '折叠全部' : '展开全部' }}
</tu-button>
</tu-button-group>
<tu-tree
:data="treeData"
:checkable="true"
v-model:selected-keys="selectedKeys"
v-model:checked-keys="checkedKeys"
v-model:expanded-keys="expandedKeys"
@select="onSelect"
@check="onCheck"
@expand="onExpand"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '叶子 1-1',
key: '1-1'
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
},
{
title: '主干 2',
key: '2',
children: [
{
title: '叶子 2-1',
key: '2-1'
},
{
title: '叶子 2-2',
key: '2-2'
}
]
}
];
const allCheckedKeys = ['1', '1-1', '1-2', '1-2-1', '2', '2-1', '2-2'];
const allExpandedKeys = ['1', '1-2', '2'];
const selectedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>(allExpandedKeys);
const toggleChecked = () => {
checkedKeys.value = checkedKeys?.value.length ? [] : allCheckedKeys;
};
const toggleExpanded = () => {
expandedKeys.value = expandedKeys?.value.length ? [] : allExpandedKeys;
};
const onSelect = (newSelectedKeys: string[], event: any) => {
console.log('select: ', newSelectedKeys, event);
};
const onCheck = (newCheckedKeys: string[], event: any) => {
console.log('check: ', newCheckedKeys, event);
};
const onExpand = (newExpandedKeys: string[], event: any) => {
console.log('expand: ', newExpandedKeys, event);
};
</script>
<template>
<tu-button-group class="mb-2">
<tu-button type="primary" @click="toggleChecked">
{{ checkedKeys?.length ? '取消选择' : '全部选择' }}
</tu-button>
<tu-button type="primary" @click="toggleExpanded">
{{ expandedKeys?.length ? '折叠全部' : '展开全部' }}
</tu-button>
</tu-button-group>
<tu-tree
:data="treeData"
:checkable="true"
v-model:selected-keys="selectedKeys"
v-model:checked-keys="checkedKeys"
v-model:expanded-keys="expandedKeys"
@select="onSelect"
@check="onCheck"
@expand="onExpand"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '叶子 1-1',
key: '1-1'
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
},
{
title: '主干 2',
key: '2',
children: [
{
title: '叶子 2-1',
key: '2-1'
},
{
title: '叶子 2-2',
key: '2-2'
}
]
}
];
const allCheckedKeys = ['1', '1-1', '1-2', '1-2-1', '2', '2-1', '2-2'];
const allExpandedKeys = ['1', '1-2', '2'];
const selectedKeys = ref<string[]>([]);
const checkedKeys = ref<string[]>([]);
const expandedKeys = ref<string[]>(allExpandedKeys);
const toggleChecked = () => {
checkedKeys.value = checkedKeys?.value.length ? [] : allCheckedKeys;
};
const toggleExpanded = () => {
expandedKeys.value = expandedKeys?.value.length ? [] : allExpandedKeys;
};
const onSelect = (newSelectedKeys: string[], event: any) => {
console.log('select: ', newSelectedKeys, event);
};
const onCheck = (newCheckedKeys: string[], event: any) => {
console.log('check: ', newCheckedKeys, event);
};
const onExpand = (newExpandedKeys: string[], event: any) => {
console.log('expand: ', newExpandedKeys, event);
};
</script>
动态加载
动态加载节点。
<template>
<tu-tree :data="treeData" :load-more="loadMore" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
{
title: '主干 1',
key: '1'
},
{
title: '主干 2',
key: '2',
children: [
{
title: '分支 2-1',
key: '2-1'
}
]
}
]);
const loadMore = (nodeData) => {
return new Promise((resolve) => {
setTimeout(() => {
nodeData.children = [
{
title: `叶子 ${nodeData.key}-1`,
key: `${nodeData.key}-1`,
isLeaf: true
}
];
resolve();
}, 1000);
});
};
</script>
<template>
<tu-tree :data="treeData" :load-more="loadMore" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
{
title: '主干 1',
key: '1'
},
{
title: '主干 2',
key: '2',
children: [
{
title: '分支 2-1',
key: '2-1'
}
]
}
]);
const loadMore = (nodeData) => {
return new Promise((resolve) => {
setTimeout(() => {
nodeData.children = [
{
title: `叶子 ${nodeData.key}-1`,
key: `${nodeData.key}-1`,
isLeaf: true
}
];
resolve();
}, 1000);
});
};
</script>
拖拽
可拖拽的树节点。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-tree draggable blockNode :data="treeData" @drop="onDrop" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
]);
const onDrop = ({ dragNode, dropNode, dropPosition }) => {
const data = treeData.value;
const loop = (data, key, callback) => {
data.some((item, index, arr) => {
if (item.key === key) {
callback(item, index, arr);
return true;
}
if (item.children) {
return loop(item.children, key, callback);
}
return false;
});
};
loop(data, dragNode.key, (_, index, arr) => {
arr.splice(index, 1);
});
if (dropPosition === 0) {
loop(data, dropNode.key, (item) => {
item.children = item.children || [];
item.children.push(dragNode);
});
} else {
loop(data, dropNode.key, (_, index, arr) => {
arr.splice(dropPosition < 0 ? index : index + 1, 0, dragNode);
});
}
};
</script>
<template>
<tu-tree draggable blockNode :data="treeData" @drop="onDrop" />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = ref([
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
]);
const onDrop = ({ dragNode, dropNode, dropPosition }) => {
const data = treeData.value;
const loop = (data, key, callback) => {
data.some((item, index, arr) => {
if (item.key === key) {
callback(item, index, arr);
return true;
}
if (item.children) {
return loop(item.children, key, callback);
}
return false;
});
};
loop(data, dragNode.key, (_, index, arr) => {
arr.splice(index, 1);
});
if (dropPosition === 0) {
loop(data, dropNode.key, (item) => {
item.children = item.children || [];
item.children.push(dragNode);
});
} else {
loop(data, dropNode.key, (_, index, arr) => {
arr.splice(dropPosition < 0 ? index : index + 1, 0, dragNode);
});
}
};
</script>
设置回填方式
为 Tree 添加 checkedStrategy 可以设置选中时的回填方式。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
当前选择节点:
<template>
<tu-radio-group
class="mb-2"
type="button"
v-model="checkedStrategy"
@change="
() => {
checkedKeys = [];
}
"
>
<tu-radio label="all">所有节点</tu-radio>
<tu-radio label="parent">父节点</tu-radio>
<tu-radio label="child">子节点</tu-radio>
</tu-radio-group>
<tu-tree
checkable
v-model:checked-keys="checkedKeys"
:data="treeData"
:checked-strategy="checkedStrategy"
/>
<p>当前选择节点:{{ checkedKeys }}</p>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
const checkedKeys = ref();
const checkedStrategy = ref('all');
</script>
<template>
<tu-radio-group
class="mb-2"
type="button"
v-model="checkedStrategy"
@change="
() => {
checkedKeys = [];
}
"
>
<tu-radio label="all">所有节点</tu-radio>
<tu-radio label="parent">父节点</tu-radio>
<tu-radio label="child">子节点</tu-radio>
</tu-radio-group>
<tu-tree
checkable
v-model:checked-keys="checkedKeys"
:data="treeData"
:checked-strategy="checkedStrategy"
/>
<p>当前选择节点:{{ checkedKeys }}</p>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
const checkedKeys = ref();
const checkedStrategy = ref('all');
</script>
显示连接线
为 Tree 添加 showLine 属性即可使树具有连接线。
叶子 1-1-1
叶子 1-1-2-1
叶子 1-1-3
分支 1-2
叶子 1-3-1
叶子 1-3-2
主干 2
叶子 3-1-1
叶子 3-1-2
<template>
<tu-tree show-line :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{ title: '叶子 1-1-1', key: '1-1-1' },
{
title: '叶子 1-1-2',
key: '1-1-2',
children: [{ title: '叶子 1-1-2-1', key: '1-1-2-1' }]
},
{ title: '叶子 1-1-3', key: '1-1-3' }
]
},
{
title: '分支 1-2',
key: '1-2'
},
{
title: '分支 1-3',
key: '1-3',
children: [
{ title: '叶子 1-3-1', key: '1-3-1' },
{
title: '叶子 1-3-2',
key: '1-3-2'
}
]
}
]
},
{
title: '主干 2',
key: '2'
},
{
title: '主干 3',
key: '3',
children: [
{
title: '分支 3-1',
key: '3-1',
children: [
{ title: '叶子 3-1-1', key: '3-1-1' },
{ title: '叶子 3-1-2', key: '3-1-2' }
]
}
]
}
];
</script>
<template>
<tu-tree show-line :data="treeData" />
</template>
<script lang="ts" setup>
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{ title: '叶子 1-1-1', key: '1-1-1' },
{
title: '叶子 1-1-2',
key: '1-1-2',
children: [{ title: '叶子 1-1-2-1', key: '1-1-2-1' }]
},
{ title: '叶子 1-1-3', key: '1-1-3' }
]
},
{
title: '分支 1-2',
key: '1-2'
},
{
title: '分支 1-3',
key: '1-3',
children: [
{ title: '叶子 1-3-1', key: '1-3-1' },
{
title: '叶子 1-3-2',
key: '1-3-2'
}
]
}
]
},
{
title: '主干 2',
key: '2'
},
{
title: '主干 3',
key: '3',
children: [
{
title: '分支 3-1',
key: '3-1',
children: [
{ title: '叶子 3-1-1', key: '3-1-1' },
{ title: '叶子 3-1-2', key: '3-1-2' }
]
}
]
}
];
</script>
定制节点图标
节点图标可以通过 tree 的 icon 插槽全局指定,也可以通过节点的 icon 属性单独指定。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-tree :data="treeData">
<template #icon>
<Sunny />
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Sunny, Pouring } from '@tu-view-plus/icons-vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1',
icon: () => h(Pouring)
},
{
title: '叶子 1-1-2',
key: '1-1-2',
icon: () => h(Pouring)
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
<template>
<tu-tree :data="treeData">
<template #icon>
<Sunny />
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Sunny, Pouring } from '@tu-view-plus/icons-vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1',
icon: () => h(Pouring)
},
{
title: '叶子 1-1-2',
key: '1-1-2',
icon: () => h(Pouring)
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
定制组件图标
节点的图标 loadingIcon, switcherIcon,同时支持在 tree 和 node 两个纬度上定制,其中 node 的优先级较高。
主干 1
分支 1-1
叶子 1-1-1
叶子 1-1-2
分支 1-2
叶子 1-2-1
<template>
<tu-tree :data="treeData">
<template #switcher-icon="node, { isLeaf }">
<ArrowDown v-if="!isLeaf" />
<Star v-if="isLeaf" />
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { Star, ArrowDown } from '@tu-view-plus/icons-vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
disabled: true,
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
<template>
<tu-tree :data="treeData">
<template #switcher-icon="node, { isLeaf }">
<ArrowDown v-if="!isLeaf" />
<Star v-if="isLeaf" />
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { Star, ArrowDown } from '@tu-view-plus/icons-vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
disabled: true,
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
</script>
定制额外节点
Tree 提供了名为 extra 的 Slot, 可以在节点上定制额外的内容。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-tree block-node :data="treeData">
<template #extra="nodeData">
<tu-button class="ml-2" size="mini" @click="() => handleAdd(nodeData)">
<template #icon><Plus /></template>
</tu-button>
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Plus } from '@tu-view-plus/icons-vue';
const treeData = ref([
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
disabled: true,
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
]);
const handleAdd = (nodeData) => {
const children = nodeData.children || [];
children.push({
title: '新节点',
key: `${nodeData.key}-${children.length + 1}`
});
nodeData.children = children;
treeData.value = [...treeData.value];
};
</script>
<template>
<tu-tree block-node :data="treeData">
<template #extra="nodeData">
<tu-button class="ml-2" size="mini" @click="() => handleAdd(nodeData)">
<template #icon><Plus /></template>
</tu-button>
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Plus } from '@tu-view-plus/icons-vue';
const treeData = ref([
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
disabled: true,
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
]);
const handleAdd = (nodeData) => {
const children = nodeData.children || [];
children.push({
title: '新节点',
key: `${nodeData.key}-${children.length + 1}`
});
nodeData.children = children;
treeData.value = [...treeData.value];
};
</script>
虚拟列表
通过指定 virtualListProps 来开启虚拟列表,在大量数据时获得高性能表现。
1-1-1-1
1-1-1-2
1-1-1-3
1-1-1-4
1-1-1-5
1-1-1-6
1-1-1-7
1-1-1-8
1-1-1-9
1-1-1-10
1-1-2-1
1-1-2-2
1-1-2-3
1-1-2-4
1-1-2-5
1-1-2-6
1-1-2-7
1-1-2-8
1-1-2-9
1-1-2-10
1-1-3-1
1-1-3-2
1-1-3-3
1-1-3-4
1-1-3-5
1-1-3-6
<template>
<tu-button class="mb-2" @click="scrollIntoView">滚动到 1-6-6-6</tu-button>
<tu-tree
ref="treeRef"
blockNode
checkable
:data="treeData"
:virtualListProps="{
height: 300
}"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const loop = (path = '1', level = 2) => {
const list = [];
for (let i = 0; i < 10; i += 1) {
const key = `${path}-${i + 1}`;
const treeNode = {
title: key,
key
};
if (level > 0) {
treeNode.children = loop(key, level - 1);
}
list.push(treeNode);
}
return list;
};
const treeRef = ref();
const treeData = loop();
const scrollIntoView = () => {
treeRef.value && treeRef.value.scrollIntoView({ key: '1-6-6-6' });
};
</script>
<template>
<tu-button class="mb-2" @click="scrollIntoView">滚动到 1-6-6-6</tu-button>
<tu-tree
ref="treeRef"
blockNode
checkable
:data="treeData"
:virtualListProps="{
height: 300
}"
/>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const loop = (path = '1', level = 2) => {
const list = [];
for (let i = 0; i < 10; i += 1) {
const key = `${path}-${i + 1}`;
const treeNode = {
title: key,
key
};
if (level > 0) {
treeNode.children = loop(key, level - 1);
}
list.push(treeNode);
}
return list;
};
const treeRef = ref();
const treeData = loop();
const scrollIntoView = () => {
treeRef.value && treeRef.value.scrollIntoView({ key: '1-6-6-6' });
};
</script>
搜索树
展示如何实现搜索树的效果。
<template>
<tu-input
class="mb-2"
placeholder="请输入关键词搜索"
allow-clear
v-model="searchKey"
>
</tu-input>
<tu-tree :data="treeData">
<template #title="nodeData">
<template v-if="((index = getMatchIndex(nodeData?.title)), index < 0)">
{{ nodeData?.title }}
</template>
<template v-else>
<span> {{ nodeData?.title?.substr(0, index) }}</span>
<span style="color: #5e7ce0">
{{ nodeData?.title?.substr(index, searchKey.length) }}
</span>
<span> {{ nodeData?.title?.substr(index + searchKey.length) }}</span>
</template>
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
const originTreeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
const searchKey = ref('');
const treeData = computed(() => {
if (!searchKey.value) return originTreeData;
return handleSearch(searchKey.value);
});
const handleSearch = (keyword) => {
const loop = (data) => {
const result = [];
data.forEach((item) => {
if (item.title.toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
result.push({ ...item });
} else if (item.children) {
const filterData = loop(item.children);
if (filterData.length) {
result.push({
...item,
children: filterData
});
}
}
});
return result;
};
return loop(originTreeData);
};
const getMatchIndex = (title) => {
if (!searchKey.value) return -1;
return title.toLowerCase().indexOf(searchKey.value.toLowerCase());
};
</script>
<template>
<tu-input
class="mb-2"
placeholder="请输入关键词搜索"
allow-clear
v-model="searchKey"
>
</tu-input>
<tu-tree :data="treeData">
<template #title="nodeData">
<template v-if="((index = getMatchIndex(nodeData?.title)), index < 0)">
{{ nodeData?.title }}
</template>
<template v-else>
<span> {{ nodeData?.title?.substr(0, index) }}</span>
<span style="color: #5e7ce0">
{{ nodeData?.title?.substr(index, searchKey.length) }}
</span>
<span> {{ nodeData?.title?.substr(index + searchKey.length) }}</span>
</template>
</template>
</tu-tree>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
const originTreeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
const searchKey = ref('');
const treeData = computed(() => {
if (!searchKey.value) return originTreeData;
return handleSearch(searchKey.value);
});
const handleSearch = (keyword) => {
const loop = (data) => {
const result = [];
data.forEach((item) => {
if (item.title.toLowerCase().indexOf(keyword.toLowerCase()) > -1) {
result.push({ ...item });
} else if (item.children) {
const filterData = loop(item.children);
if (filterData.length) {
result.push({
...item,
children: filterData
});
}
}
});
return result;
};
return loop(originTreeData);
};
const getMatchIndex = (title) => {
if (!searchKey.value) return -1;
return title.toLowerCase().indexOf(searchKey.value.toLowerCase());
};
</script>
自定义 data 的字段名称
通过 fieldNames 字段可以自定义 data 的字段名。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-tree
:data="treeData"
:fieldNames="{
key: 'value',
title: 'label',
children: 'options',
icon: 'customIcon'
}"
/>
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Star } from '@tu-view-plus/icons-vue';
const treeData = [
{
label: '主干 1',
value: '1',
options: [
{
label: '分支 1-1',
value: '1-1',
customIcon: () => h(Star),
options: [
{
label: '叶子 1-1-1',
value: '1-1-1'
},
{
label: '叶子 1-1-2',
value: '1-1-2'
}
]
},
{
label: '分支 1-2',
value: '1-2',
options: [
{
label: '叶子 1-2-1',
value: '1-2-1'
}
]
}
]
}
];
</script>
<template>
<tu-tree
:data="treeData"
:fieldNames="{
key: 'value',
title: 'label',
children: 'options',
icon: 'customIcon'
}"
/>
</template>
<script lang="ts" setup>
import { h } from 'vue';
import { Star } from '@tu-view-plus/icons-vue';
const treeData = [
{
label: '主干 1',
value: '1',
options: [
{
label: '分支 1-1',
value: '1-1',
customIcon: () => h(Star),
options: [
{
label: '叶子 1-1-1',
value: '1-1-1'
},
{
label: '叶子 1-1-2',
value: '1-1-2'
}
]
},
{
label: '分支 1-2',
value: '1-2',
options: [
{
label: '叶子 1-2-1',
value: '1-2-1'
}
]
}
]
}
];
</script>
不同尺寸
不同尺寸的树。
叶子 1-1-1
叶子 1-1-2
叶子 1-2-1
<template>
<tu-radio-group class="mb-2" v-model="size" type="button">
<tu-radio label="mini">超小</tu-radio>
<tu-radio label="small">较小</tu-radio>
<tu-radio label="medium">中等</tu-radio>
<tu-radio label="large">较大</tu-radio>
</tu-radio-group>
<tu-tree :data="treeData" :size="size" checkable />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
const size = ref('medium');
</script>
<template>
<tu-radio-group class="mb-2" v-model="size" type="button">
<tu-radio label="mini">超小</tu-radio>
<tu-radio label="small">较小</tu-radio>
<tu-radio label="medium">中等</tu-radio>
<tu-radio label="large">较大</tu-radio>
</tu-radio-group>
<tu-tree :data="treeData" :size="size" checkable />
</template>
<script lang="ts" setup>
import { ref } from 'vue';
const treeData = [
{
title: '主干 1',
key: '1',
children: [
{
title: '分支 1-1',
key: '1-1',
children: [
{
title: '叶子 1-1-1',
key: '1-1-1'
},
{
title: '叶子 1-1-2',
key: '1-1-2'
}
]
},
{
title: '分支 1-2',
key: '1-2',
children: [
{
title: '叶子 1-2-1',
key: '1-2-1'
}
]
}
]
}
];
const size = ref('medium');
</script>
Tree API
Tree Attributes
参数名 | 描述 | 类型 | 默认值 |
---|---|---|---|
size | 尺寸 | String | medium |
block-node | 节点是否占据一行 | Boolean | false |
default-expand-all | 是否默认展开父节点 | Boolean | true |
multiple | 是否支持多选 | Boolean | false |
checkable | 是否在节点前添加复选框 | Boolean | false |
selectable | 是否支持选择 | Boolean | true |
check-strictly | 是否取消父子节点关联 | Boolean | false |
checked-strategy | 定制回填方式 all: 返回所有选中的节点 parent: 父子节点都选中时只返回父节点 child: 只返回子节点 | String | all |
default-selected-keys | 默认选中的树节点 | Array | - |
selected-keys / v-model | 选中的树节点 | Array | - |
default-checked-keys | 默认选中复选框的树节点 | Array | - |
checked-keys / v-model | 选中复选框的树节点 | Array | - |
default-expanded-keys | 默认展开的节点 | Array | - |
expanded-keys / v-model | 展开的节点 | Array | - |
data | 传入 data, 生成对应的树结构 | Array | [] |
field-names | 指定节点数据中的字段名 | Object | - |
show-line | 是否展示连接线 | Boolean | false |
load-more | 异步加载数据的回调,返回一个 Promise | Function | - |
draggable | 是否可以拖拽 | Boolean | false |
allow-drop | 拖拽时是否允许在某节点上释放 | Function | - |
virtual-list-props | 传递虚拟列表属性,传入此参数以开启虚拟滚动,VirtualListProps | Object | - |
default-expand-selected | 是否默认展开已选中节点的父节点 | Boolean | false |
default-expand-checked | 是否默认展开已选中复选框节点的父节点 | Boolean | false |
auto-expand-parent | 是否自动展开已展开节点的父节点 | Boolean | true |
half-checked-keys / v-model | 半选状态的节点.仅在 checkable 且 checkStrictly 时生效 | Array | - |
only-check-leaf | 开启后 checkedKeys 只处理叶子节点,父节点状态由子节点决定(仅在 checkable 且 checkStrictly 为 false 时生效) | Boolean | false |
animation | 是否开启展开时的过渡动效 | Boolean | true |
action-on-node-click | 点击节点的时候触发的动作 | String | - |
Tree Events
事件名 | 描述 | 参数 |
---|---|---|
select | 点击树节点时触发 | Function |
check | 点击树节点复选框时触发。 | Function |
expand | 展开/关闭 | Function |
drag-start | 节点开始拖拽 | - |
drag-end | 节点结束拖拽 | Function |
drag-over | 节点被拖拽至可释放目标 | Function |
drag-leave | 节点离开可释放目标 | Function |
drop | 节点在可释放目标上释放 | Function |
Tree Methods
方法名 | 描述 | 参数 | 返回值 |
---|---|---|---|
scrollIntoView | 虚拟列表滚动某个元素 | Function | - |
getSelectedNodes | 获取选中的节点 | - | TreeNodeData[] |
getCheckedNodes | 获取选中复选框的节点。支持传入 checkedStrategy,没有传则取组件的配置。 | Function | TreeNodeData[] |
getHalfCheckedNodes | 获取复选框半选的节点 | - | TreeNodeData[] |
getExpandedNodes | 获取展开的节点 | - | TreeNodeData[] |
checkAll | 设置全部节点的复选框状态 | Function | - |
checkNode | 设置指定节点的复选框状态 | Function | - |
selectAll | 设置全部节点的选中状态 | Function | - |
selectNode | 设置指定节点的选中状态 | Function | - |
expandAll | 设置全部节点的展开状态 | Function | - |
expandNode | 设置指定节点的展开状态 | Function | - |
Tree Slots
参数名 | 描述 | 参数 |
---|---|---|
title | 标题 | - |
extra | 渲染额外的节点内容 | - |
drag-icon | 定制 drag 图标 | node |
loading-icon | 定制 loading 图标 | - |
switcher-icon | 定制 switcher 图标 | - |
icon | 定制节点图标 | node |
TreeNodeData
参数名 | 描述 | 类型 | 默认值 |
---|---|---|---|
key | 唯一标示 | String Number | - |
title | 该节点显示的标题 | String | - |
selectable | 是否允许选中 | Boolean | false |
disabled | 是否禁用节点 | Boolean | false |
disableCheckbox | 是否禁用复选框 | Boolean | false |
checkable | 是否显示多选框 | Boolean | false |
draggable | 是否可以拖拽 | Boolean | false |
isLeaf | 是否是叶子节点。动态加载时有效 | Boolean | false |
icon | 节点的图标 | Function | - |
switcherIcon | 定制 switcher 图标,优先级大于 tree | Function | - |
loadingIcon | 定制 loading 图标,优先级大于 tree | Function | - |
dragIcon | 定制 drag 图标,优先级大于 tree | Function | - |
children | 子节点 | Array | - |
TreeFieldNames
参数名 | 描述 | 类型 | 默认值 |
---|---|---|---|
key | 指定 key 在 TreeNodeData 中的字段名 | String | key |
title | 指定 title 在 TreeNodeData 中的字段名 | String | title |
disabled | 指定 disabled 在 TreeNodeData 中的字段名 | String | disabled |
children | 指定 children 在 TreeNodeData 中的字段名 | String | children |
isLeaf | 指定 isLeaf 在 TreeNodeData 中的字段名 | String | isLeaf |
disableCheckbox | 指定 disableCheckbox 在 TreeNodeData 中的字段名 | String | disableCheckbox |
checkable | 指定 checkable 在 TreeNodeData 中的字段名 | String | checkable |
icon | 指定 icon 在 TreeNodeData 中的字段名 | String | checkable |
VirtualListProps
参数名 | 描述 | 类型 | 默认值 |
---|---|---|---|
height | 可视区域高度 | Number String | - |
threshold | 开启虚拟滚动的元素数量阈值,当数据数量小于阈值时不会开启虚拟滚动。 | Number | - |
isStaticItemHeight | (已废除)元素高度是否是固定的。 | Boolean | false |
fixedSize | 元素高度是否是固定的。 | Boolean | false |
estimatedSize | 元素高度不固定时的预估高度。 | Number | - |
buffer | 视口边界外提前挂载的元素数量。 | Number | 10 |