|
|
<template>
|
|
|
<aside class="sidebar" :class="{ collapse: isCollapse }">
|
|
|
<el-menu
|
|
|
:default-active="activeMenu"
|
|
|
:default-openeds="defaultOpeneds"
|
|
|
class="nav-menu"
|
|
|
router
|
|
|
:collapse="isCollapse"
|
|
|
:collapse-transition="false"
|
|
|
>
|
|
|
<sidebar-item
|
|
|
v-for="route in permissionRoutes"
|
|
|
:key="route.key || route.path || route.name || String(route.id || Math.random())"
|
|
|
:item="route"
|
|
|
:base-path="''"
|
|
|
:is-collapse="isCollapse"
|
|
|
/>
|
|
|
</el-menu>
|
|
|
|
|
|
<div class="sidebar-toggle" @click="toggleCollapse">
|
|
|
<el-icon><Fold v-if="!isCollapse" /><Expand v-else /></el-icon>
|
|
|
</div>
|
|
|
</aside>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
import { ref, computed } from 'vue'
|
|
|
import { useRoute, useRouter } from 'vue-router'
|
|
|
import { usePermissionStore } from '@/store/permission'
|
|
|
import { Fold, Expand } from '@element-plus/icons-vue'
|
|
|
import SidebarItem from './SidebarItem.vue'
|
|
|
|
|
|
const route = useRoute()
|
|
|
const router = useRouter()
|
|
|
const permissionStore = usePermissionStore()
|
|
|
|
|
|
const isCollapse = ref(false)
|
|
|
|
|
|
// 默认不展开任何菜单
|
|
|
const defaultOpeneds = computed(() => {
|
|
|
return []
|
|
|
})
|
|
|
|
|
|
// 获取所有路由(包括常量路由和动态路由)
|
|
|
// 参考项目:直接使用 permission_routes,它包含完整的一级菜单结构(包括 children)
|
|
|
const permissionRoutes = computed(() => {
|
|
|
// 获取动态路由(包含完整的一级菜单结构)
|
|
|
// 注意:permissionStore.permissionRoutes 已经过滤掉了 404 路由,只包含用于菜单显示的路由
|
|
|
const dynamicRoutes = permissionStore.permissionRoutes.filter(route => !route.hidden && !route.meta?.hidden)
|
|
|
|
|
|
// 从 router 获取主布局(Layout)的子路由(常量路由,如 dashboard)
|
|
|
const layoutRoute = router.getRoutes().find(r => r.name === 'Layout')
|
|
|
const constantChildren = (layoutRoute?.children || []).filter(route => !route.hidden && !route.meta?.hidden)
|
|
|
|
|
|
// 合并常量路由和动态路由
|
|
|
// 注意:动态路由应该包含一级菜单(path 为空字符串,有 children),用于菜单显示
|
|
|
const allRoutes = [...constantChildren, ...dynamicRoutes]
|
|
|
|
|
|
// 调试日志
|
|
|
console.log('[Sidebar] 动态路由数量:', dynamicRoutes.length)
|
|
|
console.log('[Sidebar] 动态路由详情:', dynamicRoutes)
|
|
|
console.log('[Sidebar] 常量路由数量:', constantChildren.length)
|
|
|
console.log('[Sidebar] 最终菜单路由数量:', allRoutes.length)
|
|
|
|
|
|
return allRoutes
|
|
|
})
|
|
|
|
|
|
// 计算当前激活的菜单
|
|
|
const activeMenu = computed(() => {
|
|
|
const { meta, path } = route
|
|
|
// 如果设置了 activeMenu,使用它来高亮
|
|
|
if (meta?.activeMenu) {
|
|
|
return meta.activeMenu
|
|
|
}
|
|
|
return path
|
|
|
})
|
|
|
|
|
|
// 切换侧边栏折叠
|
|
|
const toggleCollapse = () => {
|
|
|
isCollapse.value = !isCollapse.value
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
.sidebar {
|
|
|
width: 260px;
|
|
|
background: white;
|
|
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.05);
|
|
|
border-right: 1px solid #f5f7fa;
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
transition: width 0.3s;
|
|
|
height: 100%;
|
|
|
overflow: hidden;
|
|
|
}
|
|
|
|
|
|
.sidebar.collapse {
|
|
|
width: 64px;
|
|
|
}
|
|
|
|
|
|
.nav-menu {
|
|
|
flex: 1;
|
|
|
border-right: none;
|
|
|
overflow-y: auto;
|
|
|
overflow-x: hidden;
|
|
|
}
|
|
|
|
|
|
/* 移除子菜单标题的下划线 */
|
|
|
.nav-menu :deep(.el-sub-menu__title) {
|
|
|
text-decoration: none !important;
|
|
|
}
|
|
|
|
|
|
.nav-menu :deep(.el-sub-menu__title:hover) {
|
|
|
text-decoration: none !important;
|
|
|
}
|
|
|
|
|
|
/* 移除 el-menu 下所有 a 标签的下划线 */
|
|
|
.nav-menu :deep(a) {
|
|
|
text-decoration: none !important;
|
|
|
}
|
|
|
|
|
|
.nav-menu :deep(a:hover) {
|
|
|
text-decoration: none !important;
|
|
|
}
|
|
|
|
|
|
/* 自定义滚动条样式 */
|
|
|
.nav-menu::-webkit-scrollbar {
|
|
|
width: 6px;
|
|
|
}
|
|
|
|
|
|
.nav-menu::-webkit-scrollbar-track {
|
|
|
background: #f5f5f5;
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
|
|
|
.nav-menu::-webkit-scrollbar-thumb {
|
|
|
background: #c0c4cc;
|
|
|
border-radius: 3px;
|
|
|
}
|
|
|
|
|
|
.nav-menu::-webkit-scrollbar-thumb:hover {
|
|
|
background: #a0a4ac;
|
|
|
}
|
|
|
|
|
|
.sidebar-toggle {
|
|
|
height: 48px;
|
|
|
display: flex;
|
|
|
align-items: center;
|
|
|
justify-content: center;
|
|
|
cursor: pointer;
|
|
|
border-top: 1px solid #e4e7ed;
|
|
|
transition: background-color 0.3s;
|
|
|
}
|
|
|
|
|
|
.sidebar-toggle:hover {
|
|
|
background-color: #f5f7fa;
|
|
|
}
|
|
|
|
|
|
/* 响应式 */
|
|
|
@media (max-width: 768px) {
|
|
|
.sidebar {
|
|
|
position: fixed;
|
|
|
left: -260px;
|
|
|
z-index: 1000;
|
|
|
height: calc(100vh - 60px);
|
|
|
}
|
|
|
|
|
|
.sidebar:not(.collapse) {
|
|
|
left: 0;
|
|
|
}
|
|
|
}
|
|
|
</style>
|
|
|
|