diff --git a/admin-frontend/components.d.ts b/admin-frontend/components.d.ts index c632f90..c4151de 100644 --- a/admin-frontend/components.d.ts +++ b/admin-frontend/components.d.ts @@ -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'] diff --git a/admin-frontend/src/layout/LayoutView.vue b/admin-frontend/src/layout/LayoutView.vue index 2a6f2e0..91b0ebd 100644 --- a/admin-frontend/src/layout/LayoutView.vue +++ b/admin-frontend/src/layout/LayoutView.vue @@ -2,17 +2,24 @@
- + + + +
+ + +
+ {{ sidebarWidthValue }}px +
@@ -50,10 +65,19 @@
- + + + @@ -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;