侧边栏伸缩

This commit is contained in:
kazusa 2025-08-27 11:24:02 +08:00
parent d59f99da21
commit a5506ddcd6
2 changed files with 169 additions and 11 deletions

View File

@ -19,15 +19,11 @@ declare module 'vue' {
ElCheckbox: typeof import('element-plus/es')['ElCheckbox']
ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']
ElCol: typeof import('element-plus/es')['ElCol']
ElCollapse: typeof import('element-plus/es')['ElCollapse']
ElCollapseItem: typeof import('element-plus/es')['ElCollapseItem']
ElContainer: typeof import('element-plus/es')['ElContainer']
ElDialog: typeof import('element-plus/es')['ElDialog']
ElDivider: typeof import('element-plus/es')['ElDivider']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElEmpty: typeof import('element-plus/es')['ElEmpty']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElHeader: typeof import('element-plus/es')['ElHeader']

View File

@ -2,17 +2,24 @@
<div class="layout-container">
<el-container>
<!-- 侧边栏 -->
<el-aside :width="sidebarWidth">
<el-aside :width="sidebarWidth" v-show="!isHidden" :class="['sidebar-container', { resizing: isResizing }]">
<div class="sidebar">
<div class="logo">
<h3>Admin System</h3>
<template v-if="autoCollapse">
<el-icon size="24" style="color: white;">
<Setting />
</el-icon>
</template>
<template v-else>
<h3>Admin System</h3>
</template>
</div>
<el-menu
:default-active="activeMenu"
class="sidebar-menu"
router
:collapse="isCollapse"
:collapse="autoCollapse"
background-color="#304156"
text-color="#bfcbd9"
active-text-color="#409eff"
@ -41,6 +48,14 @@
</div>
</el-menu>
</div>
<!-- 拖拽调整手柄 -->
<div class="resize-handle" @mousedown="startResize"></div>
<!-- 拖拽时的宽度提示 -->
<div v-if="isResizing" class="resize-tooltip">
{{ sidebarWidthValue }}px
</div>
</el-aside>
<!-- 主内容区域 -->
@ -50,10 +65,19 @@
<div class="header-left">
<el-button
type="text"
class="sidebar-toggle-btn"
@click="toggleSidebar"
>
<el-icon size="20">
<component :is="isCollapse ? 'Expand' : 'Fold'" />
<template v-if="isHidden">
<Menu />
</template>
<template v-else-if="autoCollapse">
<Expand />
</template>
<template v-else>
<Fold />
</template>
</el-icon>
</el-button>
@ -122,9 +146,13 @@ import { useTabsStore } from '@/stores/tabs'
import { useUserStore } from '@/stores/user'
import {
ArrowDown,
Expand,
Fold,
House,
Loading,
Lock,
Menu,
Setting,
SwitchButton,
User
} from '@element-plus/icons-vue'
@ -149,9 +177,25 @@ const cachedViews = computed(() => {
//
const isCollapse = ref(false)
const isHidden = ref(false)
const sidebarWidthValue = ref(200) //
const isResizing = ref(false)
//
const MIN_WIDTH = 64
const MAX_WIDTH = 400
const COLLAPSE_THRESHOLD = 120 //
//
const sidebarWidth = computed(() => isCollapse.value ? '64px' : '200px')
const sidebarWidth = computed(() => {
if (isHidden.value) return '0px'
return `${sidebarWidthValue.value}px`
})
//
const autoCollapse = computed(() => {
return sidebarWidthValue.value < COLLAPSE_THRESHOLD
})
//
const activeMenu = computed(() => route.path)
@ -164,9 +208,52 @@ const breadcrumbs = computed(() => {
//
const toggleSidebar = () => {
isCollapse.value = !isCollapse.value
if (!isHidden.value && !autoCollapse.value) {
//
sidebarWidthValue.value = MIN_WIDTH
} else if (!isHidden.value && autoCollapse.value) {
//
isHidden.value = true
} else {
//
isHidden.value = false
sidebarWidthValue.value = 200
}
}
//
const startResize = (e: MouseEvent) => {
isResizing.value = true
document.addEventListener('mousemove', handleResize)
document.addEventListener('mouseup', stopResize)
e.preventDefault()
}
const handleResize = (e: MouseEvent) => {
if (!isResizing.value) return
const newWidth = e.clientX
if (newWidth >= MIN_WIDTH && newWidth <= MAX_WIDTH) {
sidebarWidthValue.value = newWidth
}
}
const stopResize = () => {
isResizing.value = false
document.removeEventListener('mousemove', handleResize)
document.removeEventListener('mouseup', stopResize)
//
if (sidebarWidthValue.value < MIN_WIDTH + 10) {
sidebarWidthValue.value = MIN_WIDTH
}
}
// localStorage
watch(sidebarWidthValue, (newWidth) => {
localStorage.setItem('sidebarWidth', newWidth.toString())
})
//
const handleProfile = () => {
ElMessageBox.alert('个人信息功能待开发', '提示', {
@ -196,8 +283,17 @@ const handleLogout = async () => {
}
}
//
//
onMounted(async () => {
//
const savedWidth = localStorage.getItem('sidebarWidth')
if (savedWidth) {
const width = parseInt(savedWidth)
if (width >= MIN_WIDTH && width <= MAX_WIDTH) {
sidebarWidthValue.value = width
}
}
//
if (userStore.token && !menuStore.isMenuLoaded) {
try {
@ -226,9 +322,60 @@ watch(
overflow: hidden;
}
.sidebar-container {
position: relative;
transition: width 0.3s ease;
}
.sidebar-container:not(.resizing) {
transition: width 0.3s ease;
}
.sidebar-container.resizing {
transition: none;
}
.sidebar {
height: 100vh;
background-color: #304156;
width: 100%;
}
/* 拖拽手柄样式 */
.resize-handle {
position: absolute;
top: 0;
right: -2px;
width: 4px;
height: 100%;
background-color: transparent;
cursor: col-resize;
z-index: 10;
transition: background-color 0.2s ease;
}
.resize-handle:hover {
background-color: #409eff;
}
.resize-handle:active {
background-color: #337ecc;
}
/* 拖拽提示样式 */
.resize-tooltip {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
background-color: rgba(0, 0, 0, 0.8);
color: white;
padding: 6px 10px;
border-radius: 4px;
font-size: 12px;
z-index: 100;
pointer-events: none;
white-space: nowrap;
}
.logo {
@ -239,12 +386,16 @@ watch(
background-color: #2c3e50;
color: white;
margin-bottom: 0;
overflow: hidden;
transition: all 0.3s ease;
}
.logo h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
white-space: nowrap;
transition: all 0.3s ease;
}
.sidebar-menu {
@ -268,6 +419,17 @@ watch(
gap: 20px;
}
.sidebar-toggle-btn {
color: #606266 !important;
padding: 8px !important;
border-radius: 4px !important;
}
.sidebar-toggle-btn:hover {
background-color: #f5f7fa !important;
color: #409eff !important;
}
.header-right {
display: flex;
align-items: center;