Files
skt-vuetify-templates/src/shell/AppTabs.vue
T
2026-05-19 16:38:08 +08:00

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>