Files
ApiServer-Web-admin_dashboa…/src/components/layout/AdminLayout.vue
T
lin 1e79005440
Build and Deploy Vue3 / build (push) Successful in 9m50s
Build and Deploy Vue3 / deploy (push) Successful in 1m31s
fix:修改侧边栏样式
2026-01-20 18:25:38 +08:00

777 lines
17 KiB
Vue

<template>
<div class="admin-layout" :class="{ 'sidebar-collapsed': isCollapsed, 'mobile-open': isMobileMenuOpen }">
<!-- 移动端遮罩层 -->
<div class="mobile-overlay" v-if="isMobileMenuOpen" @click="closeMobileMenu"></div>
<!-- 侧边栏 -->
<div class="sidebar" :class="{ 'collapsed': isCollapsed }">
<div class="logo-container">
<img src="@/assets/logo.png" alt="Logo" class="logo-img" v-show="!isCollapsed" />
<img src="@/assets/logo.svg" alt="Logo" class="logo-img-mini" v-show="isCollapsed" />
</div>
<el-scrollbar class="sidebar-scrollbar">
<el-menu
:default-active="activeMenu"
class="sidebar-menu"
background-color="transparent"
text-color="#34495e"
active-text-color="#2c3e50"
:unique-opened="true"
:collapse="isCollapsed"
:collapse-transition="false"
router
>
<sidebar-menu-item v-for="menu in menus" :key="menu.path" :menu="menu" />
</el-menu>
</el-scrollbar>
<!-- 收缩按钮 -->
<div class="collapse-btn" @click="toggleCollapse">
<el-icon :size="18">
<Fold v-if="!isCollapsed" />
<Expand v-else />
</el-icon>
</div>
</div>
<!-- 主区域 -->
<div class="main-container">
<!-- 顶部导航 -->
<div class="navbar">
<div class="navbar-left">
<!-- 移动端菜单按钮 -->
<el-button type="text" class="mobile-menu-btn" @click="toggleMobileMenu">
<el-icon :size="22"><Menu /></el-icon>
</el-button>
<breadcrumb />
</div>
<div class="navbar-right">
<div class="navbar-item hidden-mobile">
<el-tooltip content="全屏" placement="bottom">
<el-button type="text" class="header-btn" @click="toggleFullScreen">
<el-icon :size="18"><full-screen /></el-icon>
</el-button>
</el-tooltip>
</div>
<div class="navbar-item">
<el-dropdown trigger="click">
<div class="avatar-container">
<el-avatar :size="32" :src="userStore.getUserAvatar() || 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png'" />
<span class="username hidden-mobile">{{ userStore.userInfo.user_name }}</span>
<el-icon class="el-icon--right hidden-mobile"><arrow-down /></el-icon>
</div>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="router.push('/profile')">
<el-icon><user /></el-icon>个人信息
</el-dropdown-item>
<el-dropdown-item @click="router.push('/change-password')">
<el-icon><key /></el-icon>修改密码
</el-dropdown-item>
<el-dropdown-item divided @click="handleLogout">
<el-icon><switch-button /></el-icon>退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</div>
<!-- 标签页 -->
<tags-view />
<!-- 内容区域 -->
<div class="content-container">
<el-config-provider :locale="zhCn">
<router-view v-slot="{ Component }">
<transition name="fade" mode="out-in">
<keep-alive>
<component :is="Component" />
</keep-alive>
</transition>
</router-view>
</el-config-provider>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import SidebarMenuItem from './SidebarMenuItem.vue'
import Breadcrumb from './Breadcrumb.vue'
import TagsView from './TagsView.vue'
import { menus as menuConfig } from '@/config/menus'
import {
FullScreen,
ArrowDown,
User,
Key,
SwitchButton,
Fold,
Expand,
Menu
} from '@element-plus/icons-vue'
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import { ElMessageBox } from 'element-plus'
import {useUserStore} from "@/store/userStore.js";
const userStore = useUserStore()
const route = useRoute()
const router = useRouter()
// 侧边栏菜单数据
const menus = ref(menuConfig)
// 侧边栏收缩状态
const isCollapsed = ref(false)
// 移动端菜单状态
const isMobileMenuOpen = ref(false)
// 检测是否是移动端
const isMobile = ref(false)
const checkMobile = () => {
isMobile.value = window.innerWidth <= 768
// 移动端默认收起侧边栏
if (isMobile.value) {
isCollapsed.value = false
isMobileMenuOpen.value = false
}
}
// 获取当前激活的菜单项
const activeMenu = computed(() => {
return route.path
})
// 切换侧边栏收缩
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value
// 保存状态到localStorage
localStorage.setItem('sidebarCollapsed', isCollapsed.value)
}
// 切换移动端菜单
const toggleMobileMenu = () => {
isMobileMenuOpen.value = !isMobileMenuOpen.value
}
// 关闭移动端菜单
const closeMobileMenu = () => {
isMobileMenuOpen.value = false
}
// 切换全屏
const toggleFullScreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen()
} else {
if (document.exitFullscreen) {
document.exitFullscreen()
}
}
}
// 退出登录
const handleLogout = () => {
ElMessageBox.confirm('确定要退出登录吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
localStorage.removeItem('token')
localStorage.removeItem('tokenExpire')
localStorage.removeItem('userInfo')
userStore.clearUserInfo()
router.push('/login')
}).catch(() => {})
}
// 监听路由变化,移动端自动关闭菜单
router.afterEach(() => {
if (isMobile.value) {
closeMobileMenu()
}
})
onMounted(() => {
// 恢复侧边栏状态
const savedState = localStorage.getItem('sidebarCollapsed')
if (savedState !== null) {
isCollapsed.value = savedState === 'true'
}
// 检测设备类型
checkMobile()
window.addEventListener('resize', checkMobile)
})
onUnmounted(() => {
window.removeEventListener('resize', checkMobile)
})
</script>
<style scoped>
.admin-layout {
display: flex;
height: 100vh;
width: 100%;
}
/* 移动端遮罩层 */
.mobile-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
z-index: 998;
}
/* 侧边栏样式 */
.sidebar {
width: 260px;
height: 100%;
background-color: #ffffff;
border-right: 1px solid #e1e8ed;
overflow: hidden;
z-index: 999;
transition: width 0.3s ease;
display: flex;
flex-direction: column;
position: relative;
}
.sidebar.collapsed {
width: 64px;
}
.logo-container {
height: 70px;
display: flex;
align-items: center;
justify-content: center;
padding: 0 20px;
background-color: #ffffff;
border-bottom: 1px solid #e1e8ed;
flex-shrink: 0;
}
.logo-img {
height: 50px;
width: auto;
object-fit: contain;
}
.logo-img-mini {
height: 32px;
width: 32px;
object-fit: contain;
}
.sidebar-scrollbar {
flex: 1;
overflow: hidden;
}
.sidebar-menu {
border-right: none;
min-height: 100%;
background-color: transparent !important;
padding: 0;
}
/* 收缩按钮 */
.collapse-btn {
height: 48px;
display: flex;
align-items: center;
justify-content: center;
border-top: 1px solid #e1e8ed;
cursor: pointer;
color: #7f8c8d;
transition: all 0.2s ease;
flex-shrink: 0;
}
.collapse-btn:hover {
color: #2c3e50;
background-color: #f8f9fa;
}
/* 移动端菜单按钮 */
.mobile-menu-btn {
display: none;
margin-right: 12px;
padding: 8px;
color: #34495e;
}
/* 主容器样式 */
.main-container {
flex: 1;
display: flex;
flex-direction: column;
background-color: #f0f2f5;
overflow: hidden;
min-width: 0;
}
/* 顶部导航栏样式 */
.navbar {
height: 60px;
padding: 0 20px;
background-color: #ffffff;
border-bottom: 1px solid #e1e8ed;
display: flex;
align-items: center;
justify-content: space-between;
z-index: 10;
flex-shrink: 0;
}
.navbar-left {
display: flex;
align-items: center;
flex: 1;
min-width: 0;
}
.navbar-right {
display: flex;
align-items: center;
gap: 8px;
flex-shrink: 0;
}
.navbar-item {
height: 60px;
display: flex;
align-items: center;
}
.header-btn {
height: 36px;
width: 36px;
display: flex;
align-items: center;
justify-content: center;
color: #34495e;
transition: all 0.2s ease;
border-radius: 0;
}
.header-btn:hover {
background-color: #f8f9fa;
color: #2c3e50;
}
.avatar-container {
display: flex;
align-items: center;
cursor: pointer;
padding: 0 12px;
height: 60px;
gap: 8px;
transition: all 0.2s ease;
border-radius: 0;
}
.avatar-container:hover {
background-color: #f8f9fa;
}
.username {
margin: 0;
font-size: 14px;
font-weight: 500;
color: #2c3e50;
}
:deep(.avatar-container .el-icon--right) {
color: #7f8c8d;
font-size: 12px;
margin-left: 4px;
transition: color 0.2s ease;
}
.avatar-container:hover :deep(.el-icon--right) {
color: #34495e;
}
/* 内容区域样式 */
.content-container {
flex: 1;
padding: 16px;
overflow-y: auto;
background-color: #f0f2f5;
position: relative;
}
/* 过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 移动端隐藏元素 */
.hidden-mobile {
display: flex;
}
/* 移动端响应式 */
@media (max-width: 768px) {
.mobile-overlay {
display: block;
}
.sidebar {
position: fixed;
left: -260px;
top: 0;
bottom: 0;
transition: left 0.3s ease;
}
.sidebar.collapsed {
width: 260px;
left: -260px;
}
.admin-layout.mobile-open .sidebar {
left: 0;
}
.admin-layout.mobile-open .sidebar.collapsed {
left: 0;
}
.collapse-btn {
display: none;
}
.mobile-menu-btn {
display: flex;
}
.hidden-mobile {
display: none !important;
}
.navbar {
padding: 0 12px;
}
.content-container {
padding: 12px;
}
.main-container {
width: 100%;
}
}
:deep(.el-dropdown-menu) {
border-radius: 0;
border: 1px solid #e1e8ed;
background-color: #ffffff;
box-shadow: 0 2px 8px rgba(44, 62, 80, 0.1);
padding: 4px 0;
}
:deep(.el-dropdown-menu__item) {
display: flex;
align-items: center;
color: #34495e;
transition: all 0.2s ease;
padding: 8px 16px;
}
:deep(.el-dropdown-menu__item i) {
margin-right: 8px;
color: #7f8c8d;
}
:deep(.el-dropdown-menu__item:hover) {
background-color: #f8f9fa;
color: #2c3e50;
}
:deep(.el-dropdown-menu__item:hover i) {
color: #2c3e50;
}
:deep(.el-dropdown-menu__item.is-divided) {
border-top: 1px solid #e1e8ed;
}
/* 侧边栏滚动条样式优化 */
:deep(.sidebar-scrollbar .el-scrollbar__wrap) {
overflow-x: hidden;
}
:deep(.sidebar-scrollbar .el-scrollbar__view) {
height: 100%;
}
/* 自定义滚动条样式 */
:deep(.sidebar-scrollbar .el-scrollbar__bar) {
opacity: 0.3;
}
:deep(.sidebar-scrollbar .el-scrollbar__thumb) {
background-color: rgba(255, 255, 255, 0.2);
transition: background-color 0.2s;
}
:deep(.sidebar-scrollbar .el-scrollbar__thumb:hover) {
background-color: rgba(255, 255, 255, 0.35);
}
/* Element Plus 菜单项样式优化 */
:deep(.el-menu) {
border-right: none;
padding: 8px 0;
}
/* 一级菜单标题(有子菜单) */
:deep(.el-sub-menu__title) {
height: 48px;
line-height: 48px;
margin: 2px 8px;
padding: 0 16px !important;
border-radius: 6px;
transition: all 0.2s ease;
color: #34495e !important;
font-weight: 500;
font-size: 14px;
}
:deep(.el-sub-menu__title:hover) {
background-color: #f5f7fa !important;
color: #2c3e50 !important;
}
/* 一级菜单项(无子菜单) */
:deep(.sidebar-menu > .el-menu-item) {
height: 48px;
line-height: 48px;
margin: 2px 8px;
padding: 0 16px !important;
border-radius: 6px;
transition: all 0.2s ease;
color: #34495e !important;
font-weight: 500;
font-size: 14px;
}
:deep(.sidebar-menu > .el-menu-item:hover) {
background-color: #f5f7fa !important;
color: #2c3e50 !important;
}
:deep(.sidebar-menu > .el-menu-item.is-active) {
background-color: rgba(44, 62, 80, 0.08) !important;
color: #2c3e50 !important;
font-weight: 600;
}
:deep(.sidebar-menu > .el-menu-item.is-active::before) {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 24px;
background-color: #2c3e50;
border-radius: 0 2px 2px 0;
}
/* 展开的一级菜单标题 */
:deep(.el-sub-menu.is-opened > .el-sub-menu__title) {
color: #2c3e50 !important;
font-weight: 600;
background-color: #f5f7fa !important;
}
/* 二级菜单容器 */
:deep(.sidebar-menu > .el-sub-menu > .el-menu) {
background-color: transparent !important;
padding: 4px 0 8px 0;
}
/* 二级菜单项 */
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-menu-item) {
height: 40px;
line-height: 40px;
margin: 2px 8px 2px 16px;
padding: 0 16px 0 28px !important;
border-radius: 6px;
font-size: 13px;
color: #606266 !important;
background-color: transparent !important;
position: relative;
font-weight: 400;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-menu-item::before) {
content: '';
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
background-color: #c0c4cc;
border-radius: 50%;
transition: all 0.2s ease;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-menu-item:hover) {
background-color: #f5f7fa !important;
color: #2c3e50 !important;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-menu-item:hover::before) {
background-color: #7f8c8d;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-menu-item.is-active) {
background-color: rgba(44, 62, 80, 0.08) !important;
color: #2c3e50 !important;
font-weight: 500;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-menu-item.is-active::before) {
width: 6px;
height: 6px;
background-color: #2c3e50;
}
/* 二级菜单中的子菜单标题(三级菜单父级) */
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-sub-menu__title) {
height: 40px;
line-height: 40px;
margin: 2px 8px 2px 16px;
padding: 0 16px 0 28px !important;
border-radius: 6px;
font-size: 13px;
color: #606266 !important;
font-weight: 400;
position: relative;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-sub-menu__title::before) {
content: '';
position: absolute;
left: 12px;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 4px;
background-color: #c0c4cc;
border-radius: 50%;
transition: all 0.2s ease;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-sub-menu__title:hover) {
background-color: #f5f7fa !important;
color: #2c3e50 !important;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-sub-menu__title:hover::before) {
background-color: #7f8c8d;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu.is-opened > .el-sub-menu__title) {
color: #2c3e50 !important;
font-weight: 500;
background-color: #f5f7fa !important;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu.is-opened > .el-sub-menu__title::before) {
width: 6px;
height: 6px;
background-color: #2c3e50;
}
/* 三级菜单容器 */
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-menu) {
background-color: transparent !important;
padding: 4px 0;
}
/* 三级菜单项 */
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-menu > .el-menu-item) {
height: 36px;
line-height: 36px;
margin: 2px 8px 2px 28px;
padding: 0 16px 0 24px !important;
border-radius: 6px;
font-size: 13px;
color: #909399 !important;
background-color: transparent !important;
position: relative;
font-weight: 400;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-menu > .el-menu-item::before) {
content: '-';
position: absolute;
left: 10px;
top: 50%;
transform: translateY(-50%);
color: #c0c4cc;
font-size: 12px;
transition: all 0.2s ease;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-menu > .el-menu-item:hover) {
background-color: #f5f7fa !important;
color: #606266 !important;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-menu > .el-menu-item:hover::before) {
color: #7f8c8d;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-menu > .el-menu-item.is-active) {
background-color: rgba(44, 62, 80, 0.08) !important;
color: #2c3e50 !important;
font-weight: 500;
}
:deep(.sidebar-menu > .el-sub-menu > .el-menu > .el-sub-menu > .el-menu > .el-menu-item.is-active::before) {
content: '•';
color: #2c3e50;
font-size: 14px;
}
/* 子菜单箭头图标 */
:deep(.el-sub-menu__icon-arrow) {
color: #909399 !important;
transition: all 0.2s ease;
font-size: 12px;
}
:deep(.el-sub-menu:hover > .el-sub-menu__title .el-sub-menu__icon-arrow) {
color: #7f8c8d !important;
}
:deep(.el-sub-menu.is-opened > .el-sub-menu__title .el-sub-menu__icon-arrow) {
transform: rotate(180deg);
color: #2c3e50 !important;
}
</style>