97 lines
2.2 KiB
Vue
97 lines
2.2 KiB
Vue
<template>
|
|
<n-menu
|
|
:collapsed="authStore.layoutCollapsed"
|
|
:collapsed-width="72"
|
|
:collapsed-icon-size="18"
|
|
:options="menuOptions"
|
|
:value="activePath"
|
|
:indent="18"
|
|
@update:value="handleSelect"
|
|
/>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, h, type Component } from 'vue'
|
|
import { NIcon, type MenuOption } from 'naive-ui'
|
|
import * as LucideIcons from 'lucide-vue-next'
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
import { useAuthStore } from '@/stores/auth'
|
|
import type { MenuNode } from '@/types/auth'
|
|
|
|
const route = useRoute()
|
|
const router = useRouter()
|
|
const authStore = useAuthStore()
|
|
|
|
function renderIcon(icon?: string | null) {
|
|
if (!icon) return undefined
|
|
const IconComp = LucideIcons[icon as keyof typeof LucideIcons] as unknown as Component | undefined
|
|
if (!IconComp) return undefined
|
|
return () => h(NIcon, null, { default: () => h(IconComp) })
|
|
}
|
|
|
|
function toOption(menu: MenuNode): MenuOption | null {
|
|
if (!menu.visible) return null
|
|
if (menu.type === 'BUTTON') return null
|
|
|
|
const children = (menu.children ?? [])
|
|
.map((item) => toOption(item))
|
|
.filter((item): item is MenuOption => Boolean(item))
|
|
|
|
const key = menu.path || `catalog-${menu.id}`
|
|
return {
|
|
key,
|
|
label: menu.title,
|
|
icon: renderIcon(menu.icon),
|
|
children: children.length > 0 ? children : undefined
|
|
}
|
|
}
|
|
|
|
const menuOptions = computed(() =>
|
|
authStore.menus.map((item) => toOption(item)).filter((item): item is MenuOption => Boolean(item))
|
|
)
|
|
|
|
const activePath = computed(() => route.path)
|
|
|
|
function handleSelect(key: string) {
|
|
if (key.startsWith('catalog-')) return
|
|
router.push(key)
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
:deep(.n-menu) {
|
|
overflow-x: hidden;
|
|
font-size: 13px;
|
|
}
|
|
|
|
:deep(.n-menu-item) {
|
|
margin: 2px 0;
|
|
}
|
|
|
|
:deep(.n-menu-item-content) {
|
|
color: #64748b;
|
|
transition:
|
|
background 0.18s ease,
|
|
color 0.18s ease;
|
|
}
|
|
|
|
:deep(.n-menu-item-content-header),
|
|
:deep(.n-menu-item-content__arrow) {
|
|
min-width: 0;
|
|
}
|
|
|
|
:deep(.n-menu-item-content-header) {
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
:deep(.n-menu-item-content--selected::before) {
|
|
background: #eff6ff;
|
|
}
|
|
|
|
:deep(.n-menu-item-content--selected) {
|
|
font-weight: 600;
|
|
}
|
|
</style>
|