You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

175 lines
4.1 KiB
Vue

<script setup lang="ts" name="LayoutMenu">
import type { MenuInst } from 'naive-ui'
import { NIcon } from 'naive-ui'
import { RouterLink } from 'vue-router'
import type { RouteLocationNormalizedLoaded, RouteRecordRaw } from 'vue-router'
import Logo from '../logo/index.vue'
import FooterTrigger from '../trigger/footer-trigger.vue'
import { createNamespace, listenerRouteChange, mapTree } from '~/utils'
import { REDIRECT_NAME } from '~/constants'
const props = withDefaults(defineProps<{
mode?: 'vertical' | 'horizontal'
split?: boolean
}>(), {
mode: 'vertical',
split: false,
})
const title = import.meta.env.VITE_APP_TITLE
const {
menu,
isMixSidebar,
getCollapsedShowTitle,
sidebar,
isSidebar,
} = useAppConfig()
const { getTopMenuAlign, getShowFooterTrigger } = useMenuSetting()
const showSidebarLogo = computed(() => {
return unref(isSidebar) || unref(isMixSidebar)
})
const { bem } = createNamespace('layout-menu')
const { t } = useI18n()
const { currentRoute } = useRouter()
const menuRef = $ref<MenuInst | null>(null)
let menuListRef = $ref<any[]>([])
let activeKey = $ref<any>()
const getMenuCollapsed = computed(() => {
if (unref(isMixSidebar)) {
return true
}
return unref(sidebar).collapsed
})
// 定位菜单选择 与 当前路由匹配
function showOption() {
nextTick(() => {
if (!menuRef) {
return
}
menuRef.showOption()
})
}
const { menuList, generateRoutes } = useMenu()
// TODO 静态路由 待实现
onMounted(async () => {
await generateRoutes()
menuListRef = mapTree<any>(unref(menuList), { conversion: menu => routeToOption(menu) })
showOption()
})
listenerRouteChange((route: RouteLocationNormalizedLoaded) => {
if (route.name === REDIRECT_NAME) {
return
}
const currentActiveMenu = route.meta?.currentActiveMenu as string
handleMenuChange(route)
if (currentActiveMenu) {
activeKey = currentActiveMenu
}
showOption()
})
async function handleMenuChange(route?: RouteLocationNormalizedLoaded) {
const menu = route || unref(currentRoute)
activeKey = menu.name
}
// 路由格式转换
function routeToOption(item: RouteRecordRaw) {
const { name, children, meta: metaRef, component } = item
const meta = unref(metaRef)
const title = meta?.title ? t(meta.title) : ''
const icon = `i-${meta?.icon}`
return {
label: () => {
if (!component) {
return title
}
if (children && children.length > 0) {
return title
}
return h(
RouterLink,
{
to: {
name,
},
},
{ default: () => title },
)
},
key: name,
icon: () => {
if (!meta?.icon)
return true
return h(NIcon, null, { default: () => h('div', { [icon]: '', class: icon }) })
},
}
}
// function clickMenu(key: any) {
// if (unref(isTopMenu) && menu.value.split && !props.split) {
// // 通过emit将子路由传递出去
// }
// }
</script>
<template>
<div :class="bem()">
<Logo v-if="showSidebarLogo" :class="bem('logo')" :title="title" :show-title="getCollapsedShowTitle" />
<NScrollbar :class="bem('scrollbar')">
<NMenu
ref="menuRef" v-model:value="activeKey" class="w-full" :class="bem('menu')" :style="{
justifyContent: getTopMenuAlign === 'center' ? 'center' : `flex-${getTopMenuAlign}`,
}" :options="menuListRef" :collapsed="getMenuCollapsed" :collapsed-width="48"
:collapsed-icon-size="22" :indent="18" :root-indent="18" :mode="props.mode" :accordion="menu.accordion"
/>
</NScrollbar>
<FooterTrigger v-if="getShowFooterTrigger" />
</div>
</template>
<style lang="less" scoped>
.layout-menu {
display: flex;
flex-direction: column;
height: 100%;
&__logo {
flex-shrink: 0;
}
&__scrollbar {
flex: 1;
flex-basis: auto;
}
&__menu:not(.n-menu--collapsed) {
.n-menu-item-content {
&::before {
left: 5px;
right: 5px;
}
&.n-menu-item-content--selected,
&:hover {
&::before {
border-left: 4px solid var(--primary-color);
}
}
}
}
}
</style>