iFileProxyAdmin/src/layout/Layout.vue
2024-12-01 14:25:39 +08:00

336 lines
No EOL
7.5 KiB
Vue

<template>
<el-container class="layout-container">
<div
v-show="isMobile && !isCollapse"
class="mobile-mask"
@click="toggleSidebar">
</div>
<el-aside
:width="isCollapse ? '64px' : '220px'"
class="aside"
:class="{ 'mobile-aside': isMobile, 'mobile-collapsed': isMobile && isCollapse }">
<div class="logo">
<img src="../assets/logo.png" alt="logo" class="logo-img">
<span class="logo-text" v-show="!isCollapse">iFileProxy<br />数据管理系统</span>
</div>
<el-menu
router
:default-active="$route.path"
class="el-menu-vertical"
background-color="#001529"
text-color="rgba(255,255,255,0.65)"
active-text-color="#fff"
:collapse="isCollapse">
<el-menu-item :index="`${ADMIN_ROUTE_BASE}/dashboard`">
<el-icon><DataLine /></el-icon>
<span>概览面板</span>
</el-menu-item>
<template v-if="userRole === 'admin'">
<el-menu-item :index="`${ADMIN_ROUTE_BASE}/tasks`">
<el-icon><List /></el-icon>
<span>任务管理</span>
</el-menu-item>
<el-menu-item :index="`${ADMIN_ROUTE_BASE}/config`">
<el-icon><Setting /></el-icon>
<span>系统配置</span>
</el-menu-item>
<template v-if="userInfo.mask === 2">
<el-menu-item :index="`${ADMIN_ROUTE_BASE}/users`">
<el-icon><User /></el-icon>
<span>用户管理</span>
</el-menu-item>
</template>
<el-menu-item :index="`${ADMIN_ROUTE_BASE}/events`">
<el-icon><List /></el-icon>
<span>事件记录</span>
</el-menu-item>
</template>
</el-menu>
</el-aside>
<el-container>
<el-header class="header">
<div class="header-left">
<el-icon
class="toggle-icon"
@click="toggleSidebar">
<component :is="isCollapse ? 'Expand' : 'Fold'" />
</el-icon>
</div>
<div class="header-right">
<el-dropdown>
<span class="user-info">
<el-avatar size="small" src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png" />
<span class="username">你好,
{{ userInfo.nickname }}
</span>
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="router.push(`${ADMIN_ROUTE_BASE}/profile`)">
<el-icon><User /></el-icon>个人信息
</el-dropdown-item>
<el-dropdown-item divided @click="handleLogout">
<el-icon><SwitchButton /></el-icon>退出登录
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</el-header>
<el-main class="main-container">
<router-view></router-view>
</el-main>
</el-container>
</el-container>
</template>
<script setup>
import { ref, computed, onMounted, onUnmounted, watch } from 'vue'
import { useRouter, useRoute } from 'vue-router'
import { DataLine, List, Expand, Fold, Setting, User, SwitchButton } from '@element-plus/icons-vue'
import { ADMIN_ROUTE_BASE } from '@/config/api.config'
const router = useRouter()
const route = useRoute()
const userRole = computed(() => localStorage.getItem('userRole'))
const userInfo = computed(() => JSON.parse(localStorage.getItem('userInfo')))
const handleLogout = () => {
localStorage.removeItem('token')
localStorage.removeItem('isAuthenticated')
localStorage.removeItem('userRole')
router.push('/login')
}
const isCollapse = ref(localStorage.getItem('sidebarCollapsed') === 'true')
const toggleSidebar = () => {
isCollapse.value = !isCollapse.value
localStorage.setItem('sidebarCollapsed', isCollapse.value)
}
// 添加移动端检测
const isMobile = computed(() => {
return window.innerWidth <= 768
})
// 监听窗口大小变化
const resizeHandler = () => {
isMobile.value = window.innerWidth <= 768
}
onMounted(() => {
window.addEventListener('resize', resizeHandler)
})
onUnmounted(() => {
window.removeEventListener('resize', resizeHandler)
})
// 监听路由变化,在移动端自动关闭侧边栏
watch(
() => route.path,
() => {
if (isMobile.value && !isCollapse.value) {
isCollapse.value = true
localStorage.setItem('sidebarCollapsed', 'true')
}
}
)
</script>
<style scoped>
.layout-container {
height: 100vh;
}
.aside {
background-color: #001529;
transition: all 0.3s;
overflow: hidden;
position: relative;
z-index: 10;
}
.logo {
height: 60px;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
background: #002140;
overflow: hidden;
transition: all 0.3s;
}
.logo-img {
width: 32px;
height: 32px;
margin-right: v-bind('isCollapse ? "0" : "12px"');
}
.logo-text {
color: white;
font-size: 16px;
font-weight: 600;
white-space: nowrap;
transition: opacity 0.3s;
}
.header {
background-color: #fff;
border-bottom: 1px solid #f0f0f0;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 20px;
box-shadow: 0 1px 4px rgba(0,21,41,.08);
}
.header-left {
display: flex;
align-items: center;
}
.toggle-icon {
font-size: 20px;
cursor: pointer;
color: #666;
transition: transform 0.3s;
}
.header-right {
display: flex;
align-items: center;
}
.user-info {
display: flex;
align-items: center;
cursor: pointer;
}
.username {
margin-left: 8px;
font-size: 14px;
color: #666;
}
.main-container {
background-color: #f0f2f5;
padding: 20px;
}
.el-menu {
border-right: none;
}
.el-menu-vertical {
height: calc(100% - 60px);
}
:deep(.el-menu-item) {
height: 50px;
line-height: 50px;
}
:deep(.el-menu-item.is-active) {
background-color: #1890ff !important;
}
:deep(.el-menu-item:hover) {
background-color: #001f3d !important;
}
:deep(.el-dropdown-menu__item) {
display: flex;
align-items: center;
gap: 8px;
}
:deep(.el-dropdown-menu__item .el-icon) {
margin: 0;
}
/* 添加菜单折叠样式 */
:deep(.el-menu--collapse) {
width: 64px;
}
:deep(.el-menu--collapse .el-menu-item) {
padding: 0 20px !important;
}
/* 遮罩层过渡动画 */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
/* 移动端样式 */
@media screen and (max-width: 768px) {
.mobile-mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(255, 255, 255, 0.705);
z-index: 9;
backdrop-filter: blur(8px);
opacity: 1;
transition: opacity 0.3s ease;
}
.mobile-mask[style*="display: none"] {
opacity: 0;
}
.mobile-aside {
position: fixed;
left: 0;
top: 0;
bottom: 0;
z-index: 10;
box-shadow: 2px 0 8px rgba(0,0,0,0.15);
transform: translateX(0);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.mobile-collapsed {
transform: translateX(-100%);
}
/* 调整主容器在侧边栏隐藏时的样式 */
.el-container:has(.mobile-aside) + .el-container {
margin-left: 0;
}
/* 调整头部样式 */
.header {
position: sticky;
top: 0;
z-index: 8;
}
/* 调整主容器样式 */
.main-container {
padding: 10px;
min-height: calc(100vh - 60px); /* 60px 是头部高度 */
}
}
/* 平板端样式 */
@media screen and (min-width: 769px) and (max-width: 1024px) {
.aside {
position: relative;
}
}
</style>