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

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<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>