使 menu UI 更加舒适

This commit is contained in:
TermiNexus committed 2026-08-02 15:36:49 +08:00
1 parent e8ceb5b46b
commit f062fb1e29
1 file changed
+100 -11
+100 -11
View File
@@ -8,7 +8,7 @@
* - TabBar:多标签页切换栏
* - 头像下拉菜单:用户信息头部 + 个人中心 + 系统设置 + 主题切换 + 退出登录
*/
import { computed, onMounted, ref } from 'vue'
import { computed, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { useAppStore } from '@/stores/app'
@@ -75,6 +75,38 @@ function handleMenuSelect(index: string): void {
router.push(index)
}
/** 折叠态下点击侧边栏任意位置(菜单项/子菜单标题),自动展开侧边栏 */
function handleSidebarClick(): void {
if (appStore.sidebarCollapsed) {
appStore.toggleSidebar()
}
}
// === 菜单折叠:折叠时收起所有展开的二级菜单(配合 CSS 过渡实现平滑收回)===
const menuRef = ref()
/** 收起所有已展开的二级菜单 */
function closeAllSubMenus(): void {
const menu = menuRef.value
if (!menu) return
for (const m of userStore.menus) {
if (m.Children?.length) {
try {
menu.close(m.Path || m.Title)
} catch {
// 未展开的 sub-menu close 可能抛错,忽略
}
}
}
}
watch(
() => appStore.sidebarCollapsed,
(collapsed) => {
if (collapsed) closeAllSubMenus()
},
)
/** 头像下拉菜单命令统一处理 */
function handleUserCommand(cmd: string): void {
switch (cmd) {
@@ -226,11 +258,11 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
<aside class="gerp-sidebar" :class="{ collapsed: appStore.sidebarCollapsed }">
<el-scrollbar>
<el-menu
ref="menuRef"
:default-active="activeMenu"
:collapse="appStore.sidebarCollapsed"
:collapse-transition="true"
router
@select="handleMenuSelect"
@click.capture="handleSidebarClick"
>
<template v-for="menu in userStore.menus" :key="menu.Path || menu.Title">
<!-- 含子菜单 -->
@@ -247,7 +279,7 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
>
<i v-if="child.Icon && isFaIcon(child.Icon)" :class="child.Icon" class="gerp-menu-icon" />
<el-icon v-else-if="child.Icon"><component :is="child.Icon" /></el-icon>
<template #title>{{ child.Title }}</template>
<template #title><span>{{ child.Title }}</span></template>
</el-menu-item>
</el-sub-menu>
@@ -255,7 +287,7 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
<el-menu-item v-else :index="menu.Path || ''">
<i v-if="menu.Icon && isFaIcon(menu.Icon)" :class="menu.Icon" class="gerp-menu-icon" />
<el-icon v-else-if="menu.Icon"><component :is="menu.Icon" /></el-icon>
<template #title>{{ menu.Title }}</template>
<template #title><span>{{ menu.Title }}</span></template>
</el-menu-item>
</template>
</el-menu>
@@ -394,6 +426,7 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
.gerp-sidebar {
width: 220px;
flex-shrink: 0;
background: #fff;
border-right: 1px solid var(--gerp-border-color);
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
@@ -401,10 +434,6 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
display: flex;
flex-direction: column;
&.collapsed {
width: 64px;
}
:deep(.el-scrollbar) {
flex: 1;
}
@@ -412,6 +441,64 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
:deep(.el-menu) {
border-right: none;
padding: 8px 0;
// 宽度跟随侧边栏平滑收缩
transition: width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
// 菜单文字:折叠/展开时平滑淡出/淡入 + max-width 收缩,icon 不受影响保持显示
// max-width + overflow hidden 让文字淡出时不占空间,避免 width 变小时文字换行叠到 icon 上
:deep(.el-menu-item span),
:deep(.el-sub-menu__title span) {
transition: opacity 0.25s ease, max-width 0.3s cubic-bezier(0.4, 0, 0.2, 1);
white-space: nowrap;
overflow: hidden;
max-width: 160px;
}
// sub-menu 箭头:折叠时随文字一起淡出,展开时淡入
:deep(.el-sub-menu__icon-arrow) {
transition: opacity 0.25s ease;
}
// 二级菜单容器:折叠时高度平滑收起,展开侧边栏后恢复
// min-width: 0 允许其跟随容器收缩(否则默认 min-width: 200px 会撑住 sub-menu 宽度)
:deep(.el-menu--inline) {
min-width: 0;
max-height: 1000px;
overflow: hidden;
transition: max-height 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
// 折叠态:宽度变小 + 文字淡出收缩 + 箭头隐藏 + 二级菜单收起
// 注意:icon 保持与展开态完全相同的位置(padding/margin 不变),仅宽度收缩 + 文字淡出
&.collapsed {
width: 64px;
// 隐藏左侧指示条
:deep(.el-menu-item),
:deep(.el-sub-menu__title) {
&::before {
display: none;
}
}
:deep(.el-menu-item span),
:deep(.el-sub-menu__title span) {
opacity: 0;
max-width: 0;
}
:deep(.el-sub-menu__icon-arrow) {
opacity: 0;
}
:deep(.el-menu--inline) {
max-height: 0;
}
// 折叠态隐藏版本号文字,设置按钮居中
.gerp-version {
display: none;
}
.gerp-sidebar-footer {
justify-content: center;
padding: 8px 0;
}
}
// 菜单项:对齐原版 GERP 的左侧指示条 + hover/active 效果
@@ -419,7 +506,7 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
position: relative;
margin: 2px 8px;
border-radius: 8px;
transition: all 0.2s ease;
transition: padding 0.4s cubic-bezier(0.25, 0.1, 0.25, 1), background-color 0.2s ease, color 0.2s ease;
// 左侧指示条(hover/active 时显示)
&::before {
@@ -459,7 +546,7 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
:deep(.el-sub-menu__title) {
margin: 2px 8px;
border-radius: 8px;
transition: all 0.2s ease;
transition: padding 0.4s cubic-bezier(0.25, 0.1, 0.25, 1), background-color 0.2s ease, color 0.2s ease;
&:hover {
background: var(--el-fill-color-light);
@@ -503,6 +590,8 @@ const isFaIcon = (icon: string | undefined | null): boolean => {
font-size: 18px;
text-align: center;
vertical-align: middle;
// margin 过渡让折叠/展开时图标右间距平滑变化(更平缓)
transition: margin 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);
}
// 折叠态:图标居中,去除右边距