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
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>
|