Files
mversion-ui/src/components/standardMenu/index.vue
2026-01-13 18:32:56 +08:00

154 lines
5.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="mj-standard-menu">
<el-menu
:default-active="activeIndex"
:active-text-color="mode === 'horizontal' ? '#409EFF' : undefined"
class="mj-menu"
:mode="mode"
:collapse="isCollapse"
@select="handleMenuSelect"
router
>
<template v-for="item in menuList" :key="`${item.path}-${item.meta?.title}`">
<el-sub-menu v-if="item.children && item.children.length > 0">
<template #title>
<el-icon v-if="item.meta?.icon"><component :is="item.meta.icon" /></el-icon>
<span>{{ item.meta.title }}</span>
</template>
<el-menu-item :index="resolvePath(item.path, row.path)" v-for="(row,key) in item.children" :key="resolvePath(item.path, row.path)">
<el-icon v-if="row.meta?.icon"><component :is="row.meta.icon" /></el-icon>
<template #title>{{ row.meta.title }}</template>
</el-menu-item>
</el-sub-menu>
<el-menu-item v-else :index="getFirstChildPath(item)">
<el-icon v-if="item.meta?.icon"><component :is="item.meta.icon" /></el-icon>
<template #title>{{ item.meta.title }}</template>
</el-menu-item>
</template>
</el-menu>
</div>
</template>
<script setup lang="ts">
defineOptions({ name: "standardMenu" })
const route = useRoute();
const {mode="vertical",menuList,isCollapse,activeMenu} = defineProps<{
mode?: 'vertical' | 'horizontal'
menuList: any[]
isCollapse?:boolean
activeMenu?: string
}>()
const emit = defineEmits<{
(e: 'menu-select', path: string): void
}>()
const activeIndex = computed(() => {
// 如果传入了 activeMenu使用它否则使用默认值
return activeMenu || route.path
})
/**
* 智能拼接路径
* @param parentPath 父级路径
* @param childPath 子级路径
*/
const resolvePath = (parentPath: string, childPath: string) => {
// 1. 如果子路径是绝对路径(以 / 开头),直接返回子路径
if (childPath.startsWith('/')) {
return childPath;
}
// 2. 移除父路径末尾的斜杠
const parent = parentPath.endsWith('/') ? parentPath.slice(0, -1) : parentPath;
// 3. 移除子路径开头的斜杠(虽然第一步已经过滤了,但为了健壮性保留)
const child = childPath.startsWith('/') ? childPath.slice(1) : childPath;
// 4. 返回拼接后的路径
return `${parent}/${child}`;
};
/**
* 获取菜单项的跳转路径
* 如果是顶级菜单且有子项,点击应跳转到第一个子项
*/
const getFirstChildPath = (item: any) => {
// 如果是顶级菜单且有子菜单
if (item.children && item.children.length > 0) {
return resolvePath(item.path, item.children[0].path);
}
// 如果没有子菜单,直接返回 item.path但要确保它是以 / 开头
return item.path.startsWith('/') ? item.path : `/${item.path}`;
};
// 处理菜单选中事件
const handleMenuSelect = (index: string) => {
// 如果是水平模式(顶部菜单),触发选中事件
if (mode === 'horizontal') {
emit('menu-select', index)
}
}
</script>
<style lang="scss" scoped>
.mj-standard-menu {
height: 100%;
overflow: hidden;
:deep(.el-menu) {
--el-menu-border-color: transparent;
border-right: none;
transition: width 0.3s;
}
// 菜单项和子菜单标题的基础样式
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
transition: padding 0.3s;
overflow: hidden;
position: relative;
// 文字容器样式 - 使用 flex 布局避免压缩
span {
display: inline-block;
white-space: nowrap;
width: auto;
max-width: 1000px; // 设置一个足够大的值
overflow: hidden;
transition: max-width 0.25s, opacity 0.25s, width 0.25s;
opacity: 1;
vertical-align: middle;
}
}
// 收缩时隐藏文字 - 先隐藏文字,再改变宽度,避免压缩感
:deep(.el-menu--collapse) {
.el-menu-item span,
.el-sub-menu__title span {
max-width: 0;
width: 0;
opacity: 0;
transition: opacity 0.15s, max-width 0.2s 0.1s, width 0.2s 0.1s;
padding: 0;
margin: 0;
}
}
// 展开时显示文字 - 先改变宽度,再显示文字,避免压缩感
:deep(.el-menu:not(.el-menu--collapse)) {
.el-menu-item span,
.el-sub-menu__title span {
max-width: 1000px;
width: auto;
opacity: 1;
transition: max-width 0.3s 0.15s, width 0.3s 0.15s, opacity 0.3s 0.2s;
}
}
// 确保图标不受影响
:deep(.el-menu-item .el-icon),
:deep(.el-sub-menu__title .el-icon) {
flex-shrink: 0;
transition: none;
}
}
</style>