120 lines
2.7 KiB
Vue
120 lines
2.7 KiB
Vue
<script setup lang="ts">
|
|
import { mdiClose } from '@mdi/js'
|
|
import { ref, watch } from 'vue'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import type { LayoutMenuItem } from '@/stores/menu'
|
|
|
|
const props = defineProps<{
|
|
menuItems?: LayoutMenuItem[]
|
|
showTabs?: boolean
|
|
}>()
|
|
|
|
const emit = defineEmits<{
|
|
(e: 'close', path: string): void
|
|
}>()
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
|
|
const tabs = ref<Array<{ title: string; path: string }>>([])
|
|
const activeTab = ref<string | null>(null)
|
|
|
|
function findTitle(path: string, items?: LayoutMenuItem[]): string | null {
|
|
const searchIn = items || []
|
|
for (const item of searchIn) {
|
|
if (item.path === path) return item.title
|
|
if (item.subItems?.length) {
|
|
const found = findTitle(path, item.subItems)
|
|
if (found) return found
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
function resolveTitle(path: string): string {
|
|
const fromProps = findTitle(path, props.menuItems)
|
|
if (fromProps) return fromProps
|
|
|
|
if (path === '/') return '首頁'
|
|
return path
|
|
}
|
|
|
|
watch(
|
|
() => route.path,
|
|
(newPath) => {
|
|
if (!props.showTabs) return
|
|
|
|
const existingTab = tabs.value.find((t) => t.path === newPath)
|
|
if (!existingTab) {
|
|
const title = resolveTitle(newPath)
|
|
tabs.value.push({ title, path: newPath })
|
|
}
|
|
activeTab.value = newPath
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
|
|
function closeTab(path: string) {
|
|
if (tabs.value.length <= 1) return
|
|
|
|
const index = tabs.value.findIndex((t) => t.path === path)
|
|
if (index === -1) return
|
|
|
|
tabs.value.splice(index, 1)
|
|
|
|
if (route.path === path) {
|
|
const nextTab = tabs.value[index] || tabs.value[index - 1]
|
|
if (nextTab) {
|
|
router.push(nextTab.path)
|
|
} else {
|
|
router.push('/')
|
|
}
|
|
}
|
|
|
|
emit('close', path)
|
|
}
|
|
|
|
function clearTabs() {
|
|
tabs.value = []
|
|
activeTab.value = null
|
|
}
|
|
|
|
defineExpose({ tabs, activeTab, closeTab, clearTabs })
|
|
</script>
|
|
|
|
<template>
|
|
<div v-if="showTabs" class="d-flex flex-column h-100">
|
|
<v-tabs
|
|
v-model="activeTab"
|
|
bg-color="background"
|
|
color="primary"
|
|
density="compact"
|
|
show-arrows
|
|
style="flex-shrink: 0"
|
|
>
|
|
<v-tab v-for="tab in tabs" :key="tab.path" border="sm" :to="tab.path" :value="tab.path">
|
|
{{ tab.title }}
|
|
<v-btn
|
|
aria-label="關閉頁籤"
|
|
class="pl-2"
|
|
color="grey"
|
|
density="compact"
|
|
:disabled="tabs.length <= 1"
|
|
icon
|
|
size="x-small"
|
|
variant="text"
|
|
@click.prevent.stop="closeTab(tab.path)"
|
|
>
|
|
<v-icon :icon="mdiClose" />
|
|
</v-btn>
|
|
</v-tab>
|
|
</v-tabs>
|
|
|
|
<div class="flex-grow-1 overflow-auto" style="min-height: 0" tabindex="0">
|
|
<slot />
|
|
</div>
|
|
</div>
|
|
|
|
<slot v-else />
|
|
</template>
|