70 lines
1.8 KiB
Vue
70 lines
1.8 KiB
Vue
<template>
|
|
<nav v-if="items.length > 1" class="breadcrumbs" aria-label="面包屑导航">
|
|
<a-breadcrumb>
|
|
<a-breadcrumb-item v-for="(it, idx) in items" :key="`${it.key}::${idx}`">
|
|
<NuxtLink v-if="it.to && idx < items.length - 1" :to="it.to">
|
|
{{ it.label }}
|
|
</NuxtLink>
|
|
<span v-else>{{ it.label }}</span>
|
|
</a-breadcrumb-item>
|
|
</a-breadcrumb>
|
|
</nav>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { mainNav, type NavItem } from '@/config/nav'
|
|
|
|
type Crumb = { key: string; label: string; to?: string }
|
|
|
|
const route = useRoute()
|
|
const currentPath = computed(() => route.path || '/')
|
|
|
|
function bestTrail(items: NavItem[], path: string): NavItem[] {
|
|
let best: NavItem[] = []
|
|
|
|
const walk = (list: NavItem[], ancestors: NavItem[]) => {
|
|
for (const item of list) {
|
|
const nextAncestors = [...ancestors, item]
|
|
const to = item.to || ''
|
|
const isPrefix = to && to !== '/' && (path === to || path.startsWith(`${to}/`))
|
|
const isExact = to && path === to
|
|
|
|
if (isExact || isPrefix) {
|
|
if (nextAncestors.length > best.length) best = nextAncestors
|
|
}
|
|
|
|
if (item.children?.length) walk(item.children, nextAncestors)
|
|
}
|
|
}
|
|
|
|
walk(items, [])
|
|
return best
|
|
}
|
|
|
|
const items = computed<Crumb[]>(() => {
|
|
const home: Crumb = { key: 'home', label: '首页', to: '/' }
|
|
if (currentPath.value === '/') return [home]
|
|
|
|
const trail = bestTrail(mainNav, currentPath.value)
|
|
if (!trail.length) {
|
|
return [home, { key: currentPath.value, label: currentPath.value.replace(/^\//, '') || '当前页面' }]
|
|
}
|
|
|
|
return [
|
|
home,
|
|
...trail.map((it) => ({ key: it.key, label: it.label, to: it.to }))
|
|
]
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.breadcrumbs {
|
|
padding: 10px 0;
|
|
color: var(--text-secondary);
|
|
}
|
|
|
|
.breadcrumbs :deep(.ant-breadcrumb) {
|
|
font-size: 13px;
|
|
}
|
|
</style>
|