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.
375 lines
8.4 KiB
Vue
375 lines
8.4 KiB
Vue
<script setup lang="ts">
|
|
import type { CSSProperties } from 'vue'
|
|
import type { RouteLocationNormalized } from 'vue-router/auto'
|
|
import Logo from '../logo/index.vue'
|
|
import SiderTrigger from '../trigger/sider-trigger.vue'
|
|
import MixSubMenu from './mix-sub-menu.vue'
|
|
import { createNamespace, listenerRouteChange } from '~/utils'
|
|
import { MixSidebarTriggerEnum, NavBarModeEnum } from '~/constants'
|
|
|
|
const props = defineProps({
|
|
mixSidebarWidth: { type: Number, default: 40 },
|
|
})
|
|
|
|
const title = import.meta.env.VITE_APP_TITLE
|
|
|
|
const { t } = useI18n()
|
|
const { bem } = createNamespace('layout-mix-menu')
|
|
const { currentRoute } = useRouter()
|
|
const go = useGo()
|
|
let currentRouteRef = $ref<Nullable<RouteLocationNormalized>>(null)
|
|
let menuModulesRef = $ref<Menu[]>([])
|
|
let activePathRef = $ref<string>('')
|
|
let childrenMenus = $ref<Menu[]>([])
|
|
let openMenu = $ref(false)
|
|
let childrenTitle = $ref('')
|
|
const sideRef = ref<ElRef>(null)
|
|
|
|
const {
|
|
getIsFixed,
|
|
getIsMixSidebar,
|
|
getMixSideFixed,
|
|
getMixSideTrigger,
|
|
getCloseMixSidebarOnChange,
|
|
getMenuWidth,
|
|
getCollapsed,
|
|
getMenuType,
|
|
mixSideHasChildren,
|
|
setMenuSetting,
|
|
} = useMenuSetting()
|
|
|
|
const {
|
|
getChildrenMenus,
|
|
getCurrentParentPath,
|
|
getShallowMenus,
|
|
} = useMenu()
|
|
|
|
let oldIsFixed = unref(getIsFixed)
|
|
let pushpin = unref(getIsFixed)
|
|
|
|
onMounted(async () => {
|
|
menuModulesRef = await getShallowMenus()
|
|
activePathRef = await getCurrentParentPath(currentRoute.value.path)
|
|
const currentItem = menuModulesRef.find(item => item.path === activePathRef)
|
|
if (currentItem) {
|
|
handleModuleClick(currentItem.path, false, currentItem.meta?.title || '')
|
|
}
|
|
})
|
|
|
|
listenerRouteChange(async (route) => {
|
|
currentRouteRef = route
|
|
activePathRef = await getCurrentParentPath(route.path)
|
|
setActive(true)
|
|
if (unref(getCloseMixSidebarOnChange)) {
|
|
closeMenu()
|
|
}
|
|
})
|
|
|
|
// Set the currently active menu and submenu
|
|
async function setActive(setChildren = false) {
|
|
const path = currentRouteRef?.path
|
|
if (!path) {
|
|
return
|
|
}
|
|
// activePathRef = await getCurrentParentPath(path)
|
|
if (unref(getIsMixSidebar)) {
|
|
const activeMenu = unref(menuModulesRef).find(item => item.path === unref(activePathRef))
|
|
const p = activeMenu?.path
|
|
if (p) {
|
|
const children = await getChildrenMenus(p)
|
|
if (setChildren) {
|
|
childrenMenus = children
|
|
|
|
if (unref(getMixSideFixed)) {
|
|
openMenu = children.length > 0
|
|
}
|
|
}
|
|
if (children.length === 0) {
|
|
childrenMenus = []
|
|
}
|
|
}
|
|
}
|
|
mixSideHasChildren.value = childrenMenus.length > 0
|
|
}
|
|
|
|
function closeMenu() {
|
|
if (!unref(getIsFixed)) {
|
|
openMenu = false
|
|
setActive(false)
|
|
}
|
|
}
|
|
|
|
// Process module menu click
|
|
async function handleModuleClick(path: string, hover = false, title = '') {
|
|
const children = await getChildrenMenus(path)
|
|
childrenTitle = t(title)
|
|
if (unref(activePathRef) === path) {
|
|
if (!hover) {
|
|
if (!unref(openMenu)) {
|
|
openMenu = true
|
|
}
|
|
else {
|
|
closeMenu()
|
|
}
|
|
}
|
|
else {
|
|
if (!unref(openMenu)) {
|
|
openMenu = true
|
|
}
|
|
}
|
|
if (!unref(openMenu)) {
|
|
await setActive()
|
|
}
|
|
}
|
|
else {
|
|
openMenu = true
|
|
activePathRef = path
|
|
}
|
|
|
|
if (!children || children.length === 0) {
|
|
if (!hover) {
|
|
go(path)
|
|
}
|
|
childrenMenus = []
|
|
closeMenu()
|
|
return
|
|
}
|
|
childrenMenus = children
|
|
mixSideHasChildren.value = childrenMenus.length > 0
|
|
}
|
|
|
|
function getItemEvents(item: Menu) {
|
|
if (unref(getMixSideTrigger) === MixSidebarTriggerEnum.HOVER) {
|
|
return {
|
|
onMouseenter: () => {
|
|
return handleModuleClick(item.path, true, item.meta?.title || '')
|
|
},
|
|
onClick: async () => {
|
|
const children = await getChildrenMenus(item.path)
|
|
if (item.path && (!children || children.length === 0)) {
|
|
go(item.path)
|
|
}
|
|
},
|
|
}
|
|
}
|
|
return {
|
|
onClick: () => {
|
|
handleModuleClick(item.path, false, item.meta?.title || '')
|
|
},
|
|
}
|
|
}
|
|
|
|
const getMenuEventsRef = computed(() => {
|
|
return !unref(getMixSideFixed)
|
|
? {
|
|
onMouseleave: () => {
|
|
// 鼠标移出菜单不做操作
|
|
// if (!openMenu.value) {
|
|
// setActive(true)
|
|
// }
|
|
// 鼠标离开Menu 不触发关闭菜单面板
|
|
// closeMenu()
|
|
},
|
|
onMouseenter: () => {
|
|
},
|
|
}
|
|
: {}
|
|
})
|
|
|
|
function handleFixedMenu() {
|
|
setMenuSetting({
|
|
mixSideFixed: !unref(getMixSideFixed),
|
|
})
|
|
pushpin = !unref(getMixSideFixed)
|
|
}
|
|
|
|
const getMenuStyle = computed((): CSSProperties => {
|
|
if (unref(getIsFixed)) {
|
|
setActive(true)
|
|
}
|
|
else {
|
|
if (oldIsFixed !== unref(getIsFixed) && !pushpin) {
|
|
closeMenu()
|
|
}
|
|
else {
|
|
pushpin = false
|
|
}
|
|
}
|
|
oldIsFixed = unref(getIsFixed)
|
|
return {
|
|
width: unref(openMenu) ? `${unref(getMenuWidth)}px` : 0,
|
|
left: `${props.mixSidebarWidth}px`,
|
|
}
|
|
})
|
|
</script>
|
|
|
|
<template>
|
|
<div :class="bem()">
|
|
<Logo
|
|
v-if="getMenuType === NavBarModeEnum.MIX_SIDEBAR"
|
|
class="shadow"
|
|
:class="[bem('logo')]"
|
|
:style="{ '--un-shadow-color': 'var(--n-border-color)' }"
|
|
:show-title="false"
|
|
/>
|
|
<NScrollbar
|
|
:class="bem('scrollbar')"
|
|
v-bind="getMenuEventsRef"
|
|
>
|
|
<ul :class="bem('module')">
|
|
<li
|
|
v-for="item in menuModulesRef"
|
|
:key="item.path"
|
|
:class="[
|
|
bem('module__item'),
|
|
{
|
|
[bem('module__item--active') as string]: item.path === activePathRef,
|
|
},
|
|
]"
|
|
v-bind="getItemEvents(item)"
|
|
>
|
|
<LIcon
|
|
:class="bem('module__icon')"
|
|
:size="getCollapsed ? 16 : 20"
|
|
:icon="item.icon || (item.meta && item.meta.icon)"
|
|
/>
|
|
<p v-show="!getCollapsed" :class="bem('module__name')">
|
|
{{ $t(item.meta?.title || '') }}
|
|
</p>
|
|
</li>
|
|
</ul>
|
|
</NScrollbar>
|
|
<SiderTrigger />
|
|
<div
|
|
ref="sideRef"
|
|
class="shadow"
|
|
:class="[bem('menu-list')]"
|
|
:style="getMenuStyle"
|
|
@mouseleave="closeMenu"
|
|
>
|
|
<div
|
|
v-show="openMenu && childrenMenus.length > 0"
|
|
class="shadow"
|
|
:class="[
|
|
bem('menu-list__title'),
|
|
{
|
|
show: openMenu,
|
|
},
|
|
]"
|
|
>
|
|
<span class="text">{{ title }}</span>
|
|
<LIcon
|
|
:size="16"
|
|
:icon="getMixSideFixed ? 'ant-design:pushpin-filled' : 'ant-design:pushpin-outlined'"
|
|
class="pushpin hover:cursor-pointer"
|
|
@click="handleFixedMenu"
|
|
/>
|
|
</div>
|
|
<NH5
|
|
v-if="openMenu"
|
|
class="mb-2!"
|
|
:class="bem('menu-list__children-title')"
|
|
:style="{ '--n-text-color': 'var(--n-text-color)' }"
|
|
prefix="bar"
|
|
strong
|
|
>
|
|
{{ childrenTitle }}
|
|
</NH5>
|
|
<MixSubMenu :list="childrenMenus" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style lang="less" scoped>
|
|
.layout-mix-menu {
|
|
display: flex;
|
|
flex-direction: column;
|
|
height: 100%;
|
|
|
|
&__logo {
|
|
flex-shrink: 0;
|
|
justify-content: center;
|
|
}
|
|
|
|
&__scrollbar {
|
|
flex: 1;
|
|
flex-basis: auto;
|
|
}
|
|
&__module {
|
|
position: relative;
|
|
padding: 1px 0 40px 0;
|
|
margin: 0;
|
|
&__item {
|
|
position: relative;
|
|
padding: 12px 0;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
flex-direction: column;
|
|
cursor: pointer;
|
|
transition: all 0.3s ease;
|
|
|
|
&:hover,
|
|
&--active {
|
|
// font-weight: 700;
|
|
|
|
color: #18a058;
|
|
&::before {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 0;
|
|
width: 3px;
|
|
height: 100%;
|
|
background-color: #18a058;
|
|
content: '';
|
|
}
|
|
}
|
|
}
|
|
&__icon {
|
|
transition: all 0.3s;
|
|
}
|
|
|
|
&__name {
|
|
margin-bottom: 0;
|
|
margin-top: 5px;
|
|
font-size: 12px;
|
|
transition: all 0.3s;
|
|
}
|
|
}
|
|
&__menu-list {
|
|
position: fixed;
|
|
height: 100%;
|
|
top: 0;
|
|
width: 0px;
|
|
height: calc(100%);
|
|
background-color: var(--n-color);
|
|
transition: all 0.3s;
|
|
|
|
&__title {
|
|
display: flex;
|
|
height: 48px;
|
|
font-size: 18px;
|
|
opacity: 0%;
|
|
transition: unset;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
box-sizing: border-box;
|
|
padding-left: 20px;
|
|
|
|
&.show {
|
|
min-width: 130px;
|
|
opacity: 100%;
|
|
transition: all 0.5s ease;
|
|
}
|
|
.pushpin {
|
|
margin-right: 8px;
|
|
}
|
|
}
|
|
&__children-title {
|
|
padding: 6px 20px;
|
|
margin: 0;
|
|
}
|
|
}
|
|
}
|
|
</style>
|