用法
使用“树”组件可以显示项的层次结构。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree :items="items" />
</template>项目
使用items prop作为具有以下属性的对象数组:
icon?: stringlabel?: stringtrailingIcon?: stringdefaultExpanded?: booleandisabled?: booleanslot?: stringchildren?: TreeItem[]onToggle?: (e: TreeItemToggleEvent<TreeItem>) => voidonSelect?: (e: TreeItemSelectEvent<TreeItem>) => voidclass?: anyui?: { item?: ClassNameValue, itemWithChildren?: ClassNameValue, link?: ClassNameValue, linkLeadingIcon?: ClassNameValue, linkLabel?: ClassNameValue, linkTrailing?: ClassNameValue, linkTrailingIcon?: ClassNameValue, listWithChildren?: ClassNameValue }
get-key,组件将使用label prop作为标识符。理想情况下,您应该提供get-key函数prop以返回唯一标识符。或者,您可以使用labelKey prop指定哪个属性用作唯一标识符。<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree :items="items" />
</template>多个
使用multiple属性允许选择多个项目。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree multiple :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree multiple :items="items" />
</template>嵌套:badge{label="4.1+" class="align-text-top"}
使用nested属性来控制树是以嵌套结构还是以平面列表的形式呈现。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree :nested="false" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree :nested="false" :items="items" />
</template>Color
使用color道具来改变树的颜色。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree color="neutral" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree color="neutral" :items="items" />
</template>Size
使用size属性来改变树的大小。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree size="xl" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree size="xl" :items="items" />
</template>拖尾图标
使用trailing-icon prop将父节点的尾随Icon自定义为i-lucide-chevron-down。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
trailingIcon: 'i-lucide-chevron-down',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree trailing-icon="i-lucide-arrow-down" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
trailingIcon: 'i-lucide-chevron-down',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree trailing-icon="i-lucide-arrow-down" :items="items" />
</template>扩展图标
使用expanded-icon和collapsed-icon属性可以自定义父节点在展开或折叠时的图标。请分别将其设置为i-lucide-folder-open和i-lucide-folder。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree expanded-icon="i-lucide-book-open" collapsed-icon="i-lucide-book" :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree expanded-icon="i-lucide-book-open" collapsed-icon="i-lucide-book" :items="items" />
</template>已禁用
使用disabled prop防止用户与树进行任何交互。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app',
icon: 'i-lucide-folder',
defaultExpanded: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree disabled :items="items" />
</template><script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = ref<TreeItem[]>([
{
label: 'app',
icon: 'i-lucide-folder',
defaultExpanded: true,
children: [
{
label: 'composables',
icon: 'i-lucide-folder',
children: [
{
label: 'useAuth.ts',
icon: 'i-vscode-icons-file-type-typescript'
},
{
label: 'useUser.ts',
icon: 'i-vscode-icons-file-type-typescript'
}
]
},
{
label: 'components',
icon: 'i-lucide-folder',
children: [
{
label: 'Home',
icon: 'i-lucide-folder',
children: [
{
label: 'Card.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'Button.vue',
icon: 'i-vscode-icons-file-type-vue'
}
]
}
]
}
]
},
{
label: 'app.vue',
icon: 'i-vscode-icons-file-type-vue'
},
{
label: 'nuxt.config.ts',
icon: 'i-vscode-icons-file-type-nuxt'
}
])
</script>
<template>
<UTree disabled :items="items" />
</template>item.disabled禁用个别项目。示例
控制选定项目
您可以使用default-value prop或v-model指令控制所选项目。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
const value = ref()
</script>
<template>
<UTree v-model="value" :items="items" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
const value = ref()
</script>
<template>
<UTree v-model="value" :items="items" />
</template>
v-model或default-value时,使用get-key属性更改用于从每个项目获取唯一密钥的函数。如果要阻止选中某个项目,可以使用item.onSelect()属性或全局select事件:
<script setup lang="ts">
import type { TreeItemSelectEvent } from 'reka-ui'
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
onSelect: (e: Event) => {
e.preventDefault()
},
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
function onSelect(e: TreeItemSelectEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'click') {
e.preventDefault()
}
}
</script>
<template>
<UTree :items="items" @select="onSelect" />
</template>
Control扩展项
您可以使用default-expanded prop或v-model指令控制展开的项目。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = [
{
label: 'app/',
id: 'app',
children: [
{
label: 'composables/',
id: 'app/composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
id: 'app/components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', id: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', id: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
] satisfies TreeItem[]
const expanded = ref(['app', 'app/composables'])
</script>
<template>
<UTree v-model:expanded="expanded" :items="items" :get-key="i => i.id" />
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TreeItem } from '@nuxt/ui'
const items = [
{
label: 'app/',
id: 'app',
children: [
{
label: 'composables/',
id: 'app/composables',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
id: 'app/components',
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', id: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', id: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
] satisfies TreeItem[]
const expanded = ref(['app', 'app/composables'])
</script>
<template>
<UTree v-model:expanded="expanded" :items="items" :get-key="i => i.id" />
</template>
如果你想阻止一个项目被展开,你可以使用item.onToggle()属性或全局toggle事件:
<script setup lang="ts">
import type { TreeItemToggleEvent } from 'reka-ui'
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
onToggle: (e: Event) => {
e.preventDefault()
},
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
]
function onToggle(e: TreeItemToggleEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'keydown') {
e.preventDefault()
}
}
</script>
<template>
<UTree :items="items" @toggle="onToggle" />
</template>
在项目中带有复选框:badgexp438x
您可以使用item-leading插槽为项目添加一个Checkbox。使用multiple、propagate-select和bubble-select道具可以实现具有父子关系的多选,使用select和toggle事件可以控制项目的选中和展开状态。
- app/
- composables/
- components/
- Card.vue
- Button.vue
- app.vue
- nuxt.config.ts
<script setup lang="ts">
import type { TreeItemSelectEvent } from 'reka-ui'
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts' },
{ label: 'useUser.ts' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue' },
{ label: 'Button.vue' }
]
}
]
},
{ label: 'app.vue' },
{ label: 'nuxt.config.ts' }
]
const value = ref<(typeof items)>([])
function onSelect(e: TreeItemSelectEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'click') {
e.preventDefault()
}
}
</script>
<template>
<UTree
v-model="value"
:as="{ link: 'div' }"
:items="items"
multiple
propagate-select
bubble-select
@select="onSelect"
>
<template #item-leading="{ selected, indeterminate, handleSelect }">
<UCheckbox
:model-value="indeterminate ? 'indeterminate' : selected"
tabindex="-1"
@change="handleSelect"
@click.stop
/>
</template>
</UTree>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import type { TreeItemSelectEvent } from 'reka-ui'
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = [
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts' },
{ label: 'useUser.ts' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue' },
{ label: 'Button.vue' }
]
}
]
},
{ label: 'app.vue' },
{ label: 'nuxt.config.ts' }
]
const value = ref<(typeof items)>([])
function onSelect(e: TreeItemSelectEvent<TreeItem>) {
if (e.detail.originalEvent.type === 'click') {
e.preventDefault()
}
}
</script>
<template>
<UTree
v-model="value"
:as="{ link: 'div' }"
:items="items"
multiple
propagate-select
bubble-select
@select="onSelect"
>
<template #item-leading="{ selected, indeterminate, handleSelect }">
<UCheckbox
:model-value="indeterminate ? 'indeterminate' : selected"
tabindex="-1"
@change="handleSelect"
@click.stop
/>
</template>
</UTree>
</template>
通过拖放:badge{label="4.1+" class="align-text-top"}
使用@vueuse/integrations中的useSortable组合可在树上启用拖放功能。此集成包装Sortable.js以提供无缝拖放体验。
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'
const items = shallowRef<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
])
function flatten(
items: TreeItem[],
parent = items
): { item: TreeItem; parent: TreeItem[]; index: number }[] {
return items.flatMap((item, index) => [
{ item, parent, index },
...(item.children?.length && item.defaultExpanded ? flatten(item.children, item.children) : [])
])
}
function moveItem(oldIndex: number, newIndex: number) {
if (oldIndex === newIndex) return
const flat = flatten(items.value)
const source = flat[oldIndex]
const target = flat[newIndex]
if (!source || !target) return
const [moved] = source.parent.splice(source.index, 1)
if (!moved) return
const updatedFlat = flatten(items.value)
const updatedTarget = updatedFlat.find(({ item }) => item === target.item)
if (!updatedTarget) return
const insertIndex = oldIndex < newIndex ? updatedTarget.index + 1 : updatedTarget.index
updatedTarget.parent.splice(insertIndex, 0, moved)
}
const tree = useTemplateRef<HTMLElement>('tree')
useSortable(tree, items, {
animation: 150,
ghostClass: 'opacity-50',
onUpdate: (e: any) => moveItem(e.oldIndex, e.newIndex)
})
</script>
<template>
<UTree ref="tree" :nested="false" :unmount-on-hide="false" :items="items" />
</template><script setup lang="ts">
import { shallowRef, useTemplateRef } from 'vue'
import type { TreeItem } from '@nuxt/ui'
import { useSortable } from '@vueuse/integrations/useSortable'
const items = shallowRef<TreeItem[]>([
{
label: 'app/',
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
])
function flatten(
items: TreeItem[],
parent = items
): { item: TreeItem; parent: TreeItem[]; index: number }[] {
return items.flatMap((item, index) => [
{ item, parent, index },
...(item.children?.length && item.defaultExpanded ? flatten(item.children, item.children) : [])
])
}
function moveItem(oldIndex: number, newIndex: number) {
if (oldIndex === newIndex) return
const flat = flatten(items.value)
const source = flat[oldIndex]
const target = flat[newIndex]
if (!source || !target) return
const [moved] = source.parent.splice(source.index, 1)
if (!moved) return
const updatedFlat = flatten(items.value)
const updatedTarget = updatedFlat.find(({ item }) => item === target.item)
if (!updatedTarget) return
const insertIndex = oldIndex < newIndex ? updatedTarget.index + 1 : updatedTarget.index
updatedTarget.parent.splice(insertIndex, 0, moved)
}
const tree = useTemplateRef<HTMLElement>('tree')
useSortable(tree, items, {
animation: 150,
ghostClass: 'opacity-50',
onUpdate: (e: any) => moveItem(e.oldIndex, e.newIndex)
})
</script>
<template>
<UTree ref="tree" :nested="false" :unmount-on-hide="false" :items="items" />
</template>nested属性设置为false,以获得一个平面项列表,以便可以拖放这些项。虚拟化:badge{label="4.1+" class="align-text-top"}
使用virtualize prop将大型列表虚拟化为布尔值或带有{ estimateSize: 32, overscan: 12 }等选项的对象。
nested属性设置为false。<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items: TreeItem[] = Array(1000)
.fill(0)
.map((_, i) => ({
label: `Item ${i + 1}`,
children: [
{ label: `Child ${i + 1}-1`, icon: 'i-lucide-file' },
{ label: `Child ${i + 1}-2`, icon: 'i-lucide-file' }
]
}))
</script>
<template>
<UTree virtualize :items="items" class="h-80" />
</template>带自定义插槽
使用slot属性可自定义特定项。
您将可以访问以下插槽:
#{{ item.slot }}-wrapper#{{ item.slot }}#{{ item.slot }}-leading#{{ item.slot }}-label#{{ item.slot }}-trailing
<script setup lang="ts">
import type { TreeItem } from '@nuxt/ui'
const items = [
{
label: 'app/',
slot: 'app' as const,
defaultExpanded: true,
children: [
{
label: 'composables/',
children: [
{ label: 'useAuth.ts', icon: 'i-vscode-icons-file-type-typescript' },
{ label: 'useUser.ts', icon: 'i-vscode-icons-file-type-typescript' }
]
},
{
label: 'components/',
defaultExpanded: true,
children: [
{ label: 'Card.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'Button.vue', icon: 'i-vscode-icons-file-type-vue' }
]
}
]
},
{ label: 'app.vue', icon: 'i-vscode-icons-file-type-vue' },
{ label: 'nuxt.config.ts', icon: 'i-vscode-icons-file-type-nuxt' }
] satisfies TreeItem[]
</script>
<template>
<UTree :items="items">
<template #app="{ item }">
<p class="italic font-bold">
{{ item.label }}
</p>
</template>
</UTree>
</template>
API
Props
| Prop | Default | Type |
|---|---|---|
as | 'ul' | anyThe element or component this component should render as. |
color | 'primary' | "primary" | "secondary" | "success" | "info" | "warning" | "error" | "neutral" |
size | 'md' | "md" | "xs" | "sm" | "lg" | "xl" |
getKey | (val: T[number]): stringThis function is passed the index of each item and should return a unique key for that item | |
labelKey | 'label' | keyof Extract<NestedItem<T>, object> & string | DotPathKeys<Extract<NestedItem<T>, object>>The key used to get the label from the item. |
trailingIcon | appConfig.ui.icons.chevronDown | anyThe icon displayed on the right side of a parent node. |
expandedIcon | appConfig.ui.icons.folderOpen | anyThe icon displayed when a parent node is expanded. |
collapsedIcon | appConfig.ui.icons.folder | anyThe icon displayed when a parent node is collapsed. |
items | T | |
modelValue | M extends true ? T[number][] : T[number]The controlled value of the Tree. Can be bind as | |
defaultValue | M extends true ? T[number][] : T[number]The value of the Tree when initially rendered. Use when you do not need to control the state of the Tree. | |
multiple | MWhether multiple options can be selected or not. | |
nested | true | boolean Use nested DOM structure (children inside parents) vs flattened structure (all items at same level).
When |
virtualize | false | boolean | { overscan?: number ; estimateSize?: number | ((index: number) => number) | undefined; } | undefinedEnable virtualization for large lists.
Note: when enabled, the tree structure is flattened like if |
onSelect | (e: SelectEvent<T[number]>, item: T[number]): void | |
onToggle | (e: ToggleEvent<T[number]>, item: T[number]): void | |
expanded | string[]The controlled value of the expanded item. Can be binded with | |
defaultExpanded | string[]The value of the expanded tree when initially rendered. Use when you do not need to control the state of the expanded tree | |
selectionBehavior | "replace" | "toggle"How multiple selection should behave in the collection. | |
propagateSelect | boolean When | |
disabled | boolean When | |
bubbleSelect | boolean When | |
ui | { root?: SlotClass; item?: SlotClass; listWithChildren?: SlotClass; itemWithChildren?: SlotClass; link?: SlotClass; linkLeadingIcon?: SlotClass; linkLabel?: SlotClass; linkTrailing?: SlotClass; linkTrailingIcon?: SlotClass; } |
Slots
| Slot | Type |
|---|---|
item-wrapper | { item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean | undefined; handleSelect: () => void; handleToggle: () => void; ui: object; } |
item | { item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean | undefined; handleSelect: () => void; handleToggle: () => void; ui: object; } |
item-leading | { item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean | undefined; handleSelect: () => void; handleToggle: () => void; ui: object; } |
item-label | { item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean | undefined; handleSelect: () => void; handleToggle: () => void; ui: object; } |
item-trailing | { item: T[number]; index: number; level: number; expanded: boolean; selected: boolean; indeterminate: boolean | undefined; handleSelect: () => void; handleToggle: () => void; ui: object; } |
Emits
| Event | Type |
|---|---|
update:modelValue | [val: M extends true ? T[number][] : T[number]] |
update:expanded | [val: string[]] |
Theme
export default defineAppConfig({
ui: {
tree: {
slots: {
root: 'relative isolate',
item: 'w-full',
listWithChildren: 'border-s border-default',
itemWithChildren: 'ps-1.5 -ms-px',
link: 'relative group w-full flex items-center text-sm select-none before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:outline-3',
linkLeadingIcon: 'shrink-0 relative',
linkLabel: 'truncate',
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 ease-out motion-reduce:transition-none group-data-expanded:rotate-180'
},
variants: {
virtualize: {
true: {
root: 'overflow-y-auto'
}
},
color: {
primary: {
link: 'before:outline-primary/25'
},
secondary: {
link: 'before:outline-secondary/25'
},
success: {
link: 'before:outline-success/25'
},
info: {
link: 'before:outline-info/25'
},
warning: {
link: 'before:outline-warning/25'
},
error: {
link: 'before:outline-error/25'
},
neutral: {
link: 'before:outline-inverted/25'
}
},
size: {
xs: {
listWithChildren: 'ms-4',
link: 'px-2 py-1 text-xs gap-1',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
sm: {
listWithChildren: 'ms-4.5',
link: 'px-2.5 py-1.5 text-xs gap-1.5',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
md: {
listWithChildren: 'ms-5',
link: 'px-2.5 py-1.5 text-sm gap-1.5',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
lg: {
listWithChildren: 'ms-5.5',
link: 'px-3 py-2 text-sm gap-2',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
xl: {
listWithChildren: 'ms-6',
link: 'px-3 py-2 text-base gap-2',
linkLeadingIcon: 'size-6',
linkTrailingIcon: 'size-6'
}
},
selected: {
true: {
link: 'before:bg-elevated'
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [
{
color: 'primary',
selected: true,
class: {
link: 'text-primary'
}
},
{
color: 'neutral',
selected: true,
class: {
link: 'text-highlighted'
}
},
{
selected: false,
disabled: false,
class: {
link: [
'hover:text-highlighted hover:before:bg-elevated/50',
'transition-colors before:transition-colors'
]
}
}
],
defaultVariants: {
color: 'primary',
size: 'md'
}
}
}
})import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import ui from '@nuxt/ui/vite'
export default defineConfig({
plugins: [
vue(),
ui({
ui: {
tree: {
slots: {
root: 'relative isolate',
item: 'w-full',
listWithChildren: 'border-s border-default',
itemWithChildren: 'ps-1.5 -ms-px',
link: 'relative group w-full flex items-center text-sm select-none before:absolute before:inset-y-px before:inset-x-0 before:z-[-1] before:rounded-md focus:outline-none focus-visible:outline-none focus-visible:before:outline-3',
linkLeadingIcon: 'shrink-0 relative',
linkLabel: 'truncate',
linkTrailing: 'ms-auto inline-flex gap-1.5 items-center',
linkTrailingIcon: 'shrink-0 transform transition-transform duration-200 ease-out motion-reduce:transition-none group-data-expanded:rotate-180'
},
variants: {
virtualize: {
true: {
root: 'overflow-y-auto'
}
},
color: {
primary: {
link: 'before:outline-primary/25'
},
secondary: {
link: 'before:outline-secondary/25'
},
success: {
link: 'before:outline-success/25'
},
info: {
link: 'before:outline-info/25'
},
warning: {
link: 'before:outline-warning/25'
},
error: {
link: 'before:outline-error/25'
},
neutral: {
link: 'before:outline-inverted/25'
}
},
size: {
xs: {
listWithChildren: 'ms-4',
link: 'px-2 py-1 text-xs gap-1',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
sm: {
listWithChildren: 'ms-4.5',
link: 'px-2.5 py-1.5 text-xs gap-1.5',
linkLeadingIcon: 'size-4',
linkTrailingIcon: 'size-4'
},
md: {
listWithChildren: 'ms-5',
link: 'px-2.5 py-1.5 text-sm gap-1.5',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
lg: {
listWithChildren: 'ms-5.5',
link: 'px-3 py-2 text-sm gap-2',
linkLeadingIcon: 'size-5',
linkTrailingIcon: 'size-5'
},
xl: {
listWithChildren: 'ms-6',
link: 'px-3 py-2 text-base gap-2',
linkLeadingIcon: 'size-6',
linkTrailingIcon: 'size-6'
}
},
selected: {
true: {
link: 'before:bg-elevated'
}
},
disabled: {
true: {
link: 'cursor-not-allowed opacity-75'
}
}
},
compoundVariants: [
{
color: 'primary',
selected: true,
class: {
link: 'text-primary'
}
},
{
color: 'neutral',
selected: true,
class: {
link: 'text-highlighted'
}
},
{
selected: false,
disabled: false,
class: {
link: [
'hover:text-highlighted hover:before:bg-elevated/50',
'transition-colors before:transition-colors'
]
}
}
],
defaultVariants: {
color: 'primary',
size: 'md'
}
}
}
})
]
})