Menu 若使用 Treeview,通常都是 URL 即可,若要能支援無限階層,則必須在 Component 內 Recursive 使用自身 Component。
Version
Tailwind CSS 2.2.0
Treeview
- folder 以紅色表示
- 以
+
展開 tree,-
收縮 tree Google
與Apple
都只是<a>
而已
App.vue
<script setup>
import Treeview from '/src/components/Treeview.vue'
let tree = $ref ({
name: 'My Treeview',
isOpened: true,
children: [
{ name: 'Hello' },
{
name: 'Children',
isOpened: true,
children: [
{
name: 'Children',
isOpened: true,
children: [
{
name: 'Google',
href: 'https://www.google.com'
},
]
},
{ name: 'Hello' },
{
name: 'Children',
isOpened: false,
children: [
{
name: 'Apple',
href: 'https://www.apple.com'
},
]
}
]
}
]
})
</script>
<template>
<ul>
<Treeview class="cursor-pointer" :item="tree"/>
</ul>
</template>
使用 <Treeview>
的 page。
41 行
<ul>
<Treeview class="cursor-pointer" :item="tree"/>
</ul>
<Treeview>
使用方式必須包在 <ul>
內:
class
:使<Treeview>
的 cursor 有 pointer:item
:傳入 data 供<Treeview>
data binding
第 4 行
let tree = $ref ({
name: 'My Treeview',
isOpened: true,
children: [
{ name: 'Hello' },
{
name: 'Children',
isOpened: true,
children: [
{
name: 'Children',
isOpened: true,
children: [
{
name: 'Google',
href: 'https://www.google.com'
},
]
},
{ name: 'Hello' },
{
name: 'Children',
isOpened: false,
children: [
{
name: 'Apple',
href: 'https://www.apple.com'
},
]
}
]
}
]
})
Folder 須提供以下 property:
name
:folder 顯示名稱isOpened
:是否展開 folder,folder 必須另外提供此 property
Item 須提供以下 property:
name
:item 顯示名稱href
:item 所對應 URL
Treeview.vue
<script setup>
let props = defineProps ({ item: Object })
let isFolder = $computed (_ => props.item.children && props.item.children.length)
let onToggle = x => {
if (isFolder) x.isOpened = !x.isOpened
}
</script>
<template>
<li>
<div :class="{ 'font-bold': isFolder, 'text-red-500': isFolder }" class="flex">
<div v-if="isFolder" class="mr-1">
<div v-if="!item.isOpened" @click="onToggle (item)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
<div v-else @click="onToggle (item)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 12H6" />
</svg>
</div>
</div>
<a :href="item.href">{{ item.name }}</a>
</div>
<ul v-show="item.isOpened" v-if="isFolder" class="pl-4 leading-6">
<Treeview class="item" v-for="(x, i) in item.children" :key="i" :item="x"/>
</ul>
</li>
</template>
<Treeview>
component 部分。
12 行
<li>
<span>{{ item.name }}</span>
<ul>
<TreeItem v-for="(x, i) in item.children" :key="i" :item="x"/>
</ul>
</li>
<Treeview>
本質是 <li>
,包含兩部分:
<span>
:顯示 item<ul>
:顯示 folder,會再 recursive 以v-for
對children
使用<Treeview>
,並將 data 傳入item
prop
13 行
<div :class="{ 'font-bold': isFolder, 'text-red-500': isFolder }">
若是 folder 則為紅色粗體。
14 行
<div v-if="isFolder" class="mr-1">
<div v-if="!item.isOpened" @click="onToggle (item)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 6v6m0 0v6m0-6h6m-6 0H6" />
</svg>
</div>
<div v-else @click="onToggle (item)">
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M18 12H6" />
</svg>
</div>
</div>
若 item 為 folder 則顯示 +
展開 folder 或 -
收縮 folder,並都呼叫 onToggle
處理。
26 行
<a :href="item.href">{{ item.name }}</a>
改用 <a>
顯示 folder 或 item,若有提供 href
則會綁定。
29 行
<ul v-show="item.isOpened" v-if="isFolder" class="pl-4 leading-6">
<Treeview class="item" v-for="(x, i) in item.children" :key="i" :item="x"/>
</ul>
顯示 folder,會再 recursive 以 v-for
對 children
使用 <Treeview>
,並將 data 傳入 item
prop。
第 2 行
let props = defineProps ({ item: Object })
使用 defineProps
宣告 item
prop。
第 4 行
let isFolder = $computed (_ => props.item.children && props.item.children.length)
根據是否有 children
且非 Empty Array 定義 isFolder
computed。
第 6 行
let onToggle = x => {
if (isFolder) x.isOpened = !x.isOpened
}
當按下 +
或 -
時對 folder 的 isOpened
toggle。
Conclusion
- 雖然有很多 package 提供 treeview,但自行實作 treeview 可對其更深入客製化