若資料本身具有階層特性,則適合使用 Treeview 呈現,若要能支援無限階層,則必須在 Component 內 Recursive 使用自身 Component。
Version
Tailwind CSS 2.2.0
Treeview
- folder 以紅色表示
- 以
+
展開 tree,-
收縮 tree
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: 'Hello' },
]
},
{ name: 'Hello' },
{
name: 'Children',
isOpened: false,
children: [
{ name: 'Hello' },
]
}
]
}
]
})
let onSelect = x => console.log (x.name)
</script>
<template>
<ul>
<Treeview class="cursor-pointer" :item="tree" @select="onSelect"/>
</ul>
</template>
使用 <Treeview>
的 page。
37 行
<ul>
<Treeview class="cursor-pointer" :item="tree" @select="onSelect"/>
</ul>
<Treeview>
使用方式必須包在 <ul>
內:
class
:使<Treeview>
的 cursor 有 pointer:item
:傳入 data 供<Treeview>
data binding@select
:處理當 item 被選擇時所發出的select
event
第 4 行
let tree = $ref ({
name: 'My Treeview',
isOpened: true,
children: [
{ name: 'Hello' },
{
name: 'Children',
isOpened: true,
children: [
{
name: 'Children',
isOpened: true,
children: [
{ name: 'Hello' },
]
},
{ name: 'Hello' },
{
name: 'Children',
isOpened: false,
children: [
{ name: 'Hello' },
]
}
]
}
]
})
Folder 須提供以下 property:
name
:folder 顯示名稱isOpened
:是否展開 folder,folder 必須另外提供此 property
Item 須提供以下 property:
name
:item 顯示名稱
33 行
let onSelect = x => console.log (x.name)
當 item 被選擇時,<Treeview>
將發出 select
event,user 可獲得所選擇 item 名稱。
Treeview.vue
<script setup>
let props = defineProps ({ item: Object })
let emits = defineEmits (['select'])
let isFolder = $computed (_ => props.item.children && props.item.children.length)
let onToggle = x => {
if (isFolder) x.isOpened = !x.isOpened
}
let onSelect = x => emits ('select', x)
</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>
<div @click="onSelect (item)">{{ item.name }}</div>
</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" @select="emits ('select', $event)"/>
</ul>
</li>
</template>
<Treeview>
component 部分。
15 行
<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
16 行
<div :class="{ 'font-bold': isFolder, 'text-red-500': isFolder }">
若是 folder 則為紅色粗體。
17 行
<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
處理。
29 行
<div @click="onSelect (item)">{{ item.name }}</div>
顯示 folder 或 item,若選擇則呼叫 onSelect
。
32 行
<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" @select="emits ('select', $event)"/>
</ul>
- 顯示 folder,會再 recursive 以
v-for
對children
使用<Treeview>
,並將 data 傳入item
prop <Treeview>
的@select
也須 recursive 繼續使用emits
觸發
第 2 行
let props = defineProps ({ item: Object })
let emits = defineEmits (['select'])
- 使用
defineProps
宣告item
prop - 使用
defineEmits
宣告select
event
第 5 行
let isFolder = $computed (_ => props.item.children && props.item.children.length)
根據是否有 children
且非 Empty Array 定義 isFolder
computed。
第 7 行
let onToggle = x => {
if (isFolder) x.isOpened = !x.isOpened
}
當按下 +
或 -
時對 folder 的 isOpened
toggle。
11 行
let onSelect = x => emits ('select', x)
當選擇 folder 或 item 時發出 select
event。
Conclusion
- 雖然有很多 package 提供 treeview,但自行實作 treeview 可對其更深入客製化