You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

174 lines
4.2 KiB

6 months ago
<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>