VirtTree
Basic
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
</script>
<template>
<div class="demo-tree">
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="20"
expandOnClickNode
>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
Selectable
selectable 模式下只能点击图标进行展开/折叠
ts
type Props = {
selectable: boolean; // 开启 展开/折叠
selectMultiple: boolean; // 开启多选
};
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
disableSelect?: boolean;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
list.value[1].disableSelect = true;
});
const virtTreeRef = ref<typeof VirtTree>();
// 可传可不传
const selectedKeys = ref<(number | string)[]>(['0']);
function onSelect(keys: number[]) {
console.log('keys', keys);
}
let selectAll = false;
function onToggleSelectAll() {
selectAll = !selectAll;
virtTreeRef.value?.selectAll(selectAll);
}
let selectKey1 = true;
function onToggleSelectNode() {
selectKey1 = !selectKey1;
virtTreeRef.value?.selectNode('0', selectKey1);
}
// setTimeout(() => {
// selectedKeys.value = [];
// }, 2000);
// setTimeout(() => {
// selectedKeys.value = [];
// }, 4000);
</script>
<template>
<div class="demo-tree">
<div style="height: 40px; display: flex">
<div>选中keys:</div>
<div style="flex: 1; overflow: auto">[{{ selectedKeys.join(', ') }}]</div>
</div>
<div style="margin-bottom: 4px">
<span class="demo-btn" @click="onToggleSelectAll">ToggleSelectAll</span>
<span class="demo-btn" @click="onToggleSelectNode"
>ToggleSelectNode(key: 0)</span
>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="20"
defaultExpandAll
v-model:selectedKeys="selectedKeys"
selectable
selectMultiple
@select="onSelect"
>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
Focus
Focus 状态切换完全交由外部处理,内部仅给Node节点加上.is-focused
类名
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef, triggerRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
disableSelect?: boolean;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const focusedKeys = ref<(number | string)[]>(['0']);
const selectedKeys = ref<(number | string)[]>(['1']);
function onSelect(keys: number[]) {
console.log('keys', keys);
}
function onChangeFocus(node: any) {
focusedKeys.value = [node.data.id];
// focusedKeys.value.splice(0, 1, node.key);
console.log('onChangeFocus', focusedKeys.value);
virtTreeRef.value?.forceUpdate();
}
// setTimeout(() => {
// focusedKeys.value = [];
// }, 2000);
</script>
<template>
<div class="demo-tree">
<div>
<span>选中keys:</span>
<span>[{{ focusedKeys.join(', ') }}]</span>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
selectable
defaultExpandAll
:list="list"
:fieldNames="customFieldNames"
:indent="20"
:focusedKeys="focusedKeys"
:selectedKeys="selectedKeys"
@select="onSelect"
>
<template #content="{ node }">
<div class="content">
<div>
<span>level: {{ node.level }}; </span>
<span>title: {{ node.data.name }}</span>
</div>
<div class="more" @click.stop="onChangeFocus(node)">
<svg
t="1720683384262"
class="icon"
viewBox="0 0 1024 1024"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
p-id="5388"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%"
height="100%"
>
<path
d="M288 456.864A63.264 63.264 0 0 0 256 448a64 64 0 1 0 0 128c11.712 0 22.56-3.392 32-8.896 19.04-11.072 32-31.488 32-55.104 0-23.648-12.96-44.064-32-55.136M544 456.864A63.264 63.264 0 0 0 512 448c-11.712 0-22.56 3.36-32 8.864-19.04 11.072-32 31.488-32 55.136 0 23.616 12.96 44.032 32 55.104 9.44 5.504 20.288 8.896 32 8.896s22.56-3.392 32-8.896c19.04-11.072 32-31.488 32-55.104 0-23.648-12.96-44.064-32-55.136M768 448c-11.712 0-22.56 3.392-32 8.864-19.04 11.104-32 31.52-32 55.136 0 23.616 12.96 44.032 32 55.136 9.44 5.472 20.288 8.864 32 8.864a64 64 0 1 0 0-128"
fill="#757575"
p-id="5389"
></path>
</svg>
</div>
</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
.content {
width: 100%;
display: flex;
justify-content: space-between;
&:hover {
.more {
display: block;
}
}
.more {
display: none;
width: 22px;
height: 22px;
border-radius: 4px;
color: #757575;
background-color: red;
}
}
}
</style>
<style lang="scss">
.demo-tree {
.virt-tree-node.is-focused:not(.is-selected) {
background-color: #4c88ff26;
}
.virt-tree-node.is-focused {
.more {
display: block;
}
}
}
</style>
Expand
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
disableCheckbox: indexChild % 2 === 0,
children:
indexChild % 2 !== 0
? []
: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}-${indexChild}`,
})),
})),
})),
}));
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<string>('0-0');
const onExpandAll = () => {
virtTreeRef.value?.expandAll(true);
};
const onCollapseAll = () => {
virtTreeRef.value?.expandAll(false);
};
const expandNode = () => {
virtTreeRef.value?.expandNode(key.value, true);
};
const collapseNode = () => {
virtTreeRef.value?.expandNode(key.value, false);
};
const expandedKeys = ref<(number | string)[]>(['0-0']);
// setTimeout(() => {
// expandedKeys.value = [];
// }, 2000);
const onExpand = (data: Data, expandedInfo: any) => {
console.warn('onExpand', data, expandedInfo);
};
</script>
<template>
<div class="demo-tree">
<div class="tree-btn-container">
<div class="input-container">
<div class="input-label">操作指定节点:</div>
<input v-model="key" />
<div class="btn-item" @click="expandNode">展开</div>
<div class="btn-item" @click="collapseNode">折叠</div>
</div>
<div style="display: flex; gap: 8px">
<div class="btn-item" @click="onCollapseAll">折叠所有</div>
<div class="btn-item" @click="onExpandAll">展开所有</div>
</div>
</div>
<div style="height: 40px; display: flex">
<div>expandedKeys:</div>
<div style="flex: 1; overflow: auto">{{ expandedKeys }}</div>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
v-model:expandedKeys="expandedKeys"
@expand="onExpand"
>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
.tree-btn-container {
display: flex;
flex: 1;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
Checkbox
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
disableCheckbox: indexChild % 2 === 0,
children:
indexChild % 2 !== 0
? []
: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}-${indexChild}`,
})),
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<string>('0');
const onExpandAll = () => {
virtTreeRef.value?.expandAll(true);
};
const onCollapseAll = () => {
virtTreeRef.value?.expandAll(false);
};
const expandNode = () => {
virtTreeRef.value?.expandNode(key.value, true);
};
const collapseNode = () => {
virtTreeRef.value?.expandNode(key.value, false);
};
const checkedKeys = ref<(number | string)[]>(['0']);
const onCheck = (data: Data, checkedInfo: any) => {
console.warn('data', data, checkedInfo);
};
const clearCheck = (check: boolean) => {
virtTreeRef.value?.checkAll(check);
};
</script>
<template>
<div class="demo-tree">
<div class="tree-btn-container">
<div style="display: flex; gap: 8px">
<div class="btn-item" @click="onCollapseAll">折叠所有</div>
<div class="btn-item" @click="onExpandAll">展开所有</div>
<div class="btn-item" @click="clearCheck(false)">清空 check</div>
<div class="btn-item" @click="clearCheck(true)">check所有</div>
</div>
<div class="input-container">
<div class="input-label">操作指定节点:</div>
<input v-model="key" />
<div class="btn-item" @click="expandNode">展开</div>
<div class="btn-item" @click="collapseNode">折叠</div>
</div>
</div>
<div>{{ checkedKeys }}</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="20"
checkable
checkOnClickNode
v-model:checkedKeys="checkedKeys"
@check="onCheck"
defaultExpandAll
>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
Filter
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<string>('Node-0');
const filterMethod = (query: string, node: any) => {
return node.title.includes(query);
};
const onFilter = () => {
virtTreeRef.value?.filter(key.value);
};
</script>
<template>
<div class="demo-tree">
<div class="tree-btn-container">
<div class="input-container">
<input v-model="key" />
<div class="btn-item" @click="onFilter">filter</div>
</div>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="20"
:filter-method="filterMethod"
>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
ICON Slot
提供展开状态下的图标
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<number>(0);
const filterMethod = (query: string, node: any) => {
return node.title.includes(query);
};
</script>
<template>
<div class="demo-tree">
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="20"
>
<template #icon>
<div style="height: 16px; width: 16px">
<svg
viewBox="0 0 48 48"
fill="none"
xmlns="http://www.w3.org/2000/svg"
stroke="currentColor"
class="arco-icon arco-icon-down"
stroke-width="4"
stroke-linecap="butt"
stroke-linejoin="miter"
>
<path d="M39.6 17.443 24.043 33 8.487 17.443"></path>
</svg>
</div>
</template>
<!-- 或者使用作用域插槽,注意:折叠状态下面是被旋转的 -->
<!-- <template #icon="{ node }">
<div v-if="node.isExpanded">1</div>
<div v-else>2</div>
</template> -->
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
Content Slot
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<number>(0);
const filterMethod = (query: string, node: any) => {
return node.title.includes(query);
};
</script>
<template>
<div class="demo-tree">
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="20"
:filter-method="filterMethod"
>
<template #content="{ node }">
<div>
<span>level: {{ node.level }}; </span>
<span>title: {{ node.data.name }}</span>
</div>
</template>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
Default Slot
自定义整个node节点
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<number>(0);
const onExpandAll = () => {
virtTreeRef.value?.expandAll(true);
};
const onCollapseAll = () => {
virtTreeRef.value?.expandAll(false);
};
const expandNode = () => {
virtTreeRef.value?.expandNode(key.value, true);
};
const collapseNode = () => {
virtTreeRef.value?.expandNode(key.value, false);
};
</script>
<template>
<div class="demo-tree">
<div class="tree-btn-container">
<div style="display: flex; gap: 8px">
<div class="btn-item" @click="onCollapseAll">折叠所有</div>
<div class="btn-item" @click="onExpandAll">展开所有</div>
</div>
<div class="input-container">
<div class="input-label">操作指定节点:</div>
<input v-model="key" />
<div class="btn-item" @click="expandNode">展开</div>
<div class="btn-item" @click="collapseNode">折叠</div>
</div>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:expandedKeys="['4']"
:indent="20"
selectable
defaultExpandAll
stickyHeaderStyle="text-align: center; height: 40px; background: #42b983;"
headerStyle="text-align: center; height: 40px; background: cyan"
footerStyle="text-align: center; height: 40px; background: cyan"
stickyFooterStyle="text-align: center; height: 40px; background: #42b983;"
>
<template #default="{ node }">
<div
style="
height: 40px;
display: flex;
align-items: center;
border-bottom: 1px solid red;
"
>
<div>level: {{ node.level }};</div>
<div>--</div>
<div>title: {{ node.data.name }}</div>
</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
showLine
展示节点连接线
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
// list.value[0].children[0].title =
// '所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入';
// list.value[0].children[0].title =
// '1Sm4srxpVaGczlsAPRv-F - Synagoga quae eligendi est arx alveus pauper ager. Canonicus verbera auditor utrum vociferor taceo. Paens volo peior.';
list.value[0].children[0].title =
'abvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagacabvfgzgagagac';
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<number>(0);
const filterMethod = (query: string, node: any) => {
return node.title.includes(query);
};
const showLine = ref(true);
const changeLine = () => {
showLine.value = !showLine.value;
};
</script>
<template>
<div class="demo-tree">
<div class="tree-btn-container">
<div class="btn-item" @click="changeLine">
{{ showLine ? '隐藏' : '显示' }}连接线
</div>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="28"
:iconSize="14"
:filter-method="filterMethod"
:showLine="showLine"
defaultExpandAll
:itemGap="4"
fixed
>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
Operations
对 tree 的各种操作方式
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<number>(0);
const onExpandAll = () => {
virtTreeRef.value?.expandAll(true);
};
const onCollapseAll = () => {
virtTreeRef.value?.expandAll(false);
};
const expandNode = () => {
virtTreeRef.value?.expandNode(key.value, true);
};
const collapseNode = () => {
virtTreeRef.value?.expandNode(key.value, false);
};
const targetOffset = ref(0);
const targetKey = ref(0);
const scrollToOffset = () => {
if (targetOffset.value >= 0)
virtTreeRef.value?.scrollTo({
offset: targetOffset.value,
});
};
const scrollToTarget = (isTop: boolean) => {
if (isTop) {
virtTreeRef.value?.scrollTo({
key: targetKey.value,
align: 'top',
});
} else {
virtTreeRef.value?.scrollTo({
key: targetKey.value,
});
}
};
</script>
<template>
<div class="demo-tree">
<div class="tree-btn-container">
<div style="display: flex; gap: 8px">
<div class="btn-item" @click="onCollapseAll">折叠所有</div>
<div class="btn-item" @click="onExpandAll">展开所有</div>
</div>
<div class="input-container">
<div class="input-label">操作指定节点:</div>
<input v-model="key" />
<div class="btn-item" @click="expandNode">展开</div>
<div class="btn-item" @click="collapseNode">折叠</div>
</div>
</div>
<div class="tree-btn-container">
<div style="display: flex; gap: 8px">
<div class="btn-item" @click="scrollToOffset">滚动到指定位置</div>
<div class="btn-item" @click="scrollToTarget(true)">
滚动到指定节点(顶部)
</div>
<div class="btn-item" @click="scrollToTarget(false)">
滚动到指定节点(可视区)
</div>
</div>
<div class="input-container">
<div class="input-label">目标 key:</div>
<input v-model="targetKey" />
<div class="input-label">目标距离:</div>
<input v-model="targetOffset" />
</div>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:indent="20"
>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
width: 120px;
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
header&footer Slot
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree } from 'vue-virt-list';
import 'vue-virt-list/lib/assets/tree.css';
type Data = {
id: string | number;
title: string;
children?: Data;
}[];
const customFieldNames = {
key: 'id',
};
const list = shallowRef<Data>([]);
onMounted(() => {
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: String(i),
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: `${i}-${index}`,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: `${i}-${index}-${indexChild}`,
title: `Node-${i}-${index}-${indexChild}`,
})),
})),
}));
});
const virtTreeRef = ref<typeof VirtTree>();
const key = ref<number>(0);
const onExpandAll = () => {
virtTreeRef.value?.expandAll(true);
};
const onCollapseAll = () => {
virtTreeRef.value?.expandAll(false);
};
const expandNode = () => {
virtTreeRef.value?.expandNode(key.value, true);
};
const collapseNode = () => {
virtTreeRef.value?.expandNode(key.value, false);
};
</script>
<template>
<div class="demo-tree">
<div class="tree-btn-container">
<div style="display: flex; gap: 8px">
<div class="btn-item" @click="onCollapseAll">折叠所有</div>
<div class="btn-item" @click="onExpandAll">展开所有</div>
</div>
<div class="input-container">
<div class="input-label">操作指定节点:</div>
<input v-model="key" />
<div class="btn-item" @click="expandNode">展开</div>
<div class="btn-item" @click="collapseNode">折叠</div>
</div>
</div>
<div class="virt-tree-wrapper">
<VirtTree
ref="virtTreeRef"
:list="list"
:fieldNames="customFieldNames"
:expandedKeys="['4']"
:indent="20"
selectable
defaultExpandAll
stickyHeaderStyle="text-align: center; height: 40px; background: #42b983;"
headerStyle="text-align: center; height: 40px; background: cyan"
footerStyle="text-align: center; height: 40px; background: cyan"
stickyFooterStyle="text-align: center; height: 40px; background: #42b983;"
>
<template #stickyHeader>
<div>悬浮header</div>
</template>
<template #header>
<div>header</div>
</template>
<template #footer>
<div>footer</div>
</template>
<template #stickyFooter>
<div>悬浮footer</div>
</template>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
draggable
源码
vue
<script lang="ts" setup>
import { onMounted, ref, shallowRef } from 'vue';
import { VirtTree, type TreeNode } from 'vue-virt-list';
type ItemData = {
id: string | number;
title: string;
children?: ItemData[];
// 禁止拖入
disableDragIn?: boolean;
// 禁止托出
disableDragOut?: boolean;
};
const customFieldNames = {
key: 'id',
};
const list = ref<ItemData[]>([]);
list.value = Array.from({ length: 40 }).map((_, i) => ({
id: i + 1,
title: `Node-${i}`,
children: Array.from({ length: 3 }).map((_, index) => ({
id: (i + 1) * 100 + index,
title: `Node-${i}-${index}`,
children: Array.from({ length: 2 }).map((_, indexChild) => ({
id: (i + 1) * 1000 + (index + 1) * 10 + indexChild,
title: `Node-${i}-${index}-${indexChild} (禁止拖入-disableDragIn)`,
// 所有叶子节点禁用拖入
disableDragIn: true,
})),
})),
}));
// setTimeout(() => {
// console.log(list.value.length);
// list.value.splice(0, 1);
// console.log(list.value.length);
// }, 1000);
// TODO 模拟数据
// list.value[0].title =
// '所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入所有叶子节点禁用拖入';
list.value[1].disableDragOut = true;
list.value[1].title = `${list.value[1].title} (禁止拖出-disableDragOut)`;
const virtTreeRef = ref<typeof VirtTree>();
// const key = ref<number>(0);
const filterMethod = (query: string, node: any) => {
return node.title.includes(query);
};
function onDragstart() {
console.log('onDragstart');
}
function onDragEnd(data: any) {
if (data) {
console.log('drag success', data);
// const { node, prevNode, parentNode } = data;
// console.log('drag node', node);
// console.log('target prevNode', prevNode);
// console.log('target parentNode', parentNode);
} else {
console.warn('drag fail: Invalid');
}
}
const draggable = ref(true);
// setTimeout(() => {
// draggable.value = true;
// }, 1000);
// setTimeout(() => {
// draggable.value = false;
// }, 6000);
const expandedKeys = ref<number[]>([1, 100, 102]);
</script>
<template>
<div class="demo-tree">
<div class="virt-tree-wrapper">
<!--
:dragLineWidth="28"
:dragLineLeading="14"
dragSourceClass="drag-class"
dragGhostClass="drag-ghost-class" -->
<VirtTree
ref="virtTreeRef"
v-model:expandedKeys="expandedKeys"
:list="list"
:fieldNames="customFieldNames"
:indent="16"
:iconSize="14"
:filter-method="filterMethod"
:itemGap="4"
:draggable="draggable"
@dragstart="onDragstart"
@dragend="onDragEnd"
dragOnly
dragGhostClass="drag-ghost-class"
dragClass="drag-class"
expandOnClickNode
default-expand-all
>
<template #empty>
<div style="padding: 16px">暂无数据</div>
</template>
</VirtTree>
</div>
</div>
</template>
<style scoped lang="scss">
.demo-tree {
width: 100%;
display: flex;
flex-direction: column;
overflow: hidden;
.tree-btn-container {
display: flex;
flex: 1;
flex-direction: row-reverse;
justify-content: space-between;
padding: 12px 8px;
gap: 8px;
.input-label {
font-size: 14px;
}
.btn-item {
padding: 4px 12px;
cursor: pointer;
border: 1px solid #ececec;
border-radius: 4px;
font-size: 14px;
}
.input-container {
display: flex;
gap: 8px;
align-items: center;
input {
height: 100%;
border: 1px solid #ececec;
border-radius: 4px;
padding: 0 8px;
}
}
}
}
</style>
css variable
css
.virt-tree-item {
/* drag line */
--virt-tree-color-drag-line: #4c88ff;
--virt-tree-color-drag-box: rgb(76, 136, 255, 0.1);
--virt-tree-color-drag-line-disabled: rgb(76, 136, 255, 0.4);
/* text */
--virt-tree-color-text: #1f2329;
--virt-tree-color-text-disabled: #a8abb2;
--virt-tree-color-text-selected: #1456f0;
/* node */
--virt-tree-color-node-bg: #fff;
--virt-tree-color-node-bg-hover: #1f232914;
--virt-tree-color-node-bg-disabled: transparent;
--virt-tree-color-node-bg-selected: #f0f4ff;
/* icon */
--virt-tree-color-icon: #2b2f36;
--virt-tree-color-icon-bg-hover: #1f23291a;
/* line */
--virt-tree-line-color: #cacdd1;
/* checkbox */
--virt-tree-color-checkbox-bg: #fff;
--virt-tree-color-checkbox-bg-indeterminate: #1890ff;
--virt-tree-color-checkbox-bg-checked: #1890ff;
--virt-tree-color-checkbox-bg-disabled: rgba(255, 255, 255, 0.3);
--virt-tree-color-checkbox-border: rgb(190, 192, 198);
--virt-tree-color-checkbox-border-checked: #1890ff;
--virt-tree-color-checkbox-border-indeterminate: #1890ff;
/* 生效于图标的margin和拖拽线的左边距离 */
--virt-tree-switcher-icon-margin-right: 4px;
--virt-tree-drag-line-gap: 4px;
}
html.dark .virt-tree-item {
/* drag line */
--virt-tree-color-drag-line: #4c88ff;
--virt-tree-color-drag-box: rgb(76, 136, 255, 0.1);
--virt-tree-color-drag-line-disabled: rgb(76, 136, 255, 0.4);
/* text */
--virt-tree-color-text: #f9f9f9;
--virt-tree-color-text-disabled: rgba(255, 255, 255, 0.3);
--virt-tree-color-text-selected: #4c88ff;
/* node */
--virt-tree-color-node-bg: #1b1b1f;
--virt-tree-color-node-bg-hover: #2e3238;
--virt-tree-color-node-bg-disabled: transparent;
--virt-tree-color-node-bg-selected: #152340;
/* icon */
--virt-tree-color-icon: #f9f9f9;
--virt-tree-color-icon-bg-hover: #ebebeb1a;
/* line */
--virt-tree-line-color: #35393f;
/* checkbox */
--virt-tree-color-checkbox-bg: #fff;
--virt-tree-color-checkbox-bg-indeterminate: #1890ff;
--virt-tree-color-checkbox-bg-checked: #1890ff;
--virt-tree-color-checkbox-bg-disabled: rgba(255, 255, 255, 0.3);
--virt-tree-color-checkbox-border: rgb(190, 192, 198);
--virt-tree-color-checkbox-border-checked: #1890ff;
--virt-tree-color-checkbox-border-indeterminate: #1890ff;
/* 生效于图标的margin和拖拽线的左边距离 */
--virt-tree-switcher-icon-margin-right: 4px;
--virt-tree-drag-line-gap: 4px;
}