Pārlūkot izejas kodu

feat(layout): 实现侧边栏折叠功能并优化主题样式

- 添加 Pinia store 管理侧边栏状态
- 重构侧边栏组件支持折叠功能
- 调整主题颜色和按钮样式
- 优化布局响应式效果
piks 1 nedēļu atpakaļ
vecāks
revīzija
798dbfc308

+ 2 - 3
src/components/ThemeToggle/index.vue

@@ -57,9 +57,8 @@ function onToggle() {
   display: flex;
   align-items: center;
   justify-content: center;
-  border: 1px solid #F5E4FF;
-  background: linear-gradient(180deg, rgba(239, 64, 255, 0.50) 0%, rgba(133, 47, 255, 0.50) 100%);
-  box-shadow: 0 11.392px 22.336px 0 rgba(130, 53, 185, 0.09), 0 -2px 1px 0 rgba(200, 151, 255, 0.40) inset;
+  // border: 1px solid #F5E4FF;
+  background: linear-gradient(91deg, #5F53FF -40.99%, #8D53FF 88.34%);
   backdrop-filter: blur(2px);
   color: #fff;
 }

+ 23 - 2
src/layout/components/Header.vue

@@ -1,7 +1,10 @@
 <script setup lang="ts">
 import { useUserStore } from '@/store/modules/user'
+import { useAppStore } from '@/store/modules/app'
 import ThemeToggle from '@/components/ThemeToggle/index.vue'
+
 const userStore = useUserStore()
+const appStore = useAppStore()
 
 function logout() {
   userStore.logout()
@@ -11,10 +14,12 @@ function logout() {
 <template>
   <header class="header">
     <div class="header-left">
-      <h1 class="title">后台管理系统</h1>
-      <ThemeToggle />
+      <el-icon class="collapse-btn" @click="appStore.toggleSidebar()">
+        <Operation />
+      </el-icon>
     </div>
     <div class="header-right">
+      <ThemeToggle />
       <el-dropdown>
         <div class="user-info">
           <el-icon>
@@ -38,6 +43,7 @@ function logout() {
   display: flex;
   justify-content: space-between;
   align-items: center;
+  box-sizing: border-box;
   height: 100%;
   padding: 0 20px;
   color: var(--admin-text-primary);
@@ -55,6 +61,21 @@ function logout() {
     gap: 16px;
   }
 
+  .collapse-btn {
+    cursor: pointer;
+    font-size: 20px;
+    color: var(--admin-text-secondary);
+
+    &:hover {
+      color: var(--admin-text-primary);
+    }
+  }
+
+  .header-right {
+    display: flex;
+    gap: 40px;
+  }
+
   .user-info {
     display: flex;
     align-items: center;

+ 5 - 3
src/layout/components/Sidebar/SidebarItem.vue

@@ -39,7 +39,7 @@ function resolveBasePath(path: string) {
 </script>
 
 <template>
-  <div v-if="!item.meta?.hidden" class="menu-item">
+  <template v-if="!item.meta?.hidden" class="menu-item">
     <template v-if="hasChildren">
       <el-sub-menu :index="item.path">
         <template #title>
@@ -61,10 +61,12 @@ function resolveBasePath(path: string) {
         <el-icon v-if="item.meta?.icon">
           <component :is="item.meta.icon" />
         </el-icon>
-        <span>{{ item.meta?.title }}</span>
+        <template #title>
+          <span>{{ item.meta?.title }}</span>
+        </template>
       </el-menu-item>
     </template>
-  </div>
+  </template>
 </template>
 
 <style scoped>

+ 18 - 2
src/layout/components/Sidebar/SidebarLogo.vue

@@ -1,13 +1,17 @@
 <template>
-  <div class="sidebar-logo">
+  <div class="sidebar-logo" :class="{ 'is-collapsed': collapsed }">
     <div class="logo-icon">
       <img :src="Logo" alt="游戏盾" />
     </div>
-    <span class="logo-text">游戏盾SDK</span>
+    <span v-if="!collapsed" class="logo-text">游戏盾SDK</span>
   </div>
 </template>
 <script setup lang="ts">
 import Logo from '@/assets/logo/logo.png'
+
+defineProps<{
+  collapsed?: boolean
+}>()
 </script>
 
 <style scoped lang="scss">
@@ -18,11 +22,23 @@ import Logo from '@/assets/logo/logo.png'
   justify-content: start;
   gap: 12px;
   padding: 0 20px;
+  overflow: hidden;
+
+  &.is-collapsed {
+    justify-content: center;
+    padding: 0;
+  }
 
   .logo-icon {
     width: 34px;
     height: 34px;
     flex-shrink: 0;
+
+    img {
+      width: 100%;
+      height: 100%;
+      object-fit: contain;
+    }
   }
 
   .logo-text {

+ 8 - 27
src/layout/components/Sidebar/index.vue

@@ -4,9 +4,11 @@ import { useRoute } from 'vue-router'
 import SidebarItem from './SidebarItem.vue'
 import SidebarLogo from './SidebarLogo.vue'
 import { usePermissionStore } from '@/store/modules/permission'
+import { useAppStore } from '@/store/modules/app'
 
 const route = useRoute()
 const permissionStore = usePermissionStore()
+const appStore = useAppStore()
 
 // 当前激活的菜单
 const activeMenu = computed(() => {
@@ -21,20 +23,11 @@ const menuList = computed(() => permissionStore.menuList)
 
 <template>
   <div class="sidebar">
-    <SidebarLogo />
-
+    <SidebarLogo :collapsed="appStore.sidebarCollapsed" />
     <el-scrollbar class="sidebar-scroll">
-      <el-menu
-        :default-active="activeMenu"
-        :unique-opened="true"
-        mode="vertical"
-        router
-      >
-        <SidebarItem
-          v-for="item in menuList"
-          :key="item.path"
-          :item="item"
-        />
+      <el-menu :default-active="activeMenu" :unique-opened="true" :collapse="appStore.sidebarCollapsed" mode="vertical"
+        router>
+        <SidebarItem v-for="item in menuList" :key="item.path" :item="item" />
       </el-menu>
     </el-scrollbar>
   </div>
@@ -55,21 +48,9 @@ const menuList = computed(() => permissionStore.menuList)
 
 :deep(.el-menu) {
   border-right: none;
-  background-color: transparent !important;
-
-  .el-menu-item,
-  .el-sub-menu__title {
-    color: var(--admin-text-secondary) !important;
-    background-color: transparent !important;
-
-    &:hover {
-      background-color: var(--admin-border-color) !important;
-    }
-  }
 
-  .el-menu-item.is-active {
-    color: var(--el-color-primary) !important;
-    background-color: var(--admin-border-color) !important;
+  &:not(.el-menu--collapse) {
+    width: 100%;
   }
 }
 </style>

+ 15 - 3
src/layout/index.vue

@@ -1,11 +1,17 @@
 <script setup lang="ts">
+import { computed } from 'vue'
 import SiderBar from './components/Sidebar/index.vue'
 import Header from './components/Header.vue'
 import AppMain from './components/AppMain.vue'
+import { useAppStore } from '@/store/modules/app'
+
+const appStore = useAppStore()
+
+const sidebarWidth = computed(() => appStore.sidebarCollapsed ? '64px' : '300px')
 </script>
 
 <template>
-  <div class="app-wrapper">
+  <div class="app-wrapper" :class="{ 'is-collapsed': appStore.sidebarCollapsed }">
     <SiderBar class="sidebar-container" />
     <div class="main-wrapper">
       <Header class="header-container" />
@@ -16,20 +22,26 @@ import AppMain from './components/AppMain.vue'
 
 <style lang="scss" scoped>
 .app-wrapper {
+  --sidebar-width: 300px;
   display: grid;
   width: 100vw;
   height: 100vh;
-  grid-template-columns: 300px 1fr;
+  grid-template-columns: var(--sidebar-width) 1fr;
   grid-template-rows: 88px 1fr;
   grid-template-areas:
     "sidebar header"
     "sidebar main";
   overflow: hidden;
+  transition: grid-template-columns 0.3s ease;
+
+  &.is-collapsed {
+    --sidebar-width: 64px;
+  }
 }
 
 .sidebar-container {
   grid-area: sidebar;
-  background-color: var(--admin-sidebar-bg);
+  border-right: 1px solid var(--admin-border-color);
 }
 
 .main-wrapper {

+ 17 - 0
src/store/modules/app.ts

@@ -0,0 +1,17 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useAppStore = defineStore('app', () => {
+  // sidebar 是否收起
+  const sidebarCollapsed = ref(false)
+
+  // 切换 sidebar 状态
+  function toggleSidebar() {
+    sidebarCollapsed.value = !sidebarCollapsed.value
+  }
+
+  return {
+    sidebarCollapsed,
+    toggleSidebar
+  }
+})

+ 3 - 3
src/styles/theme.scss

@@ -35,9 +35,9 @@ html.dark {
   --btn-primary-shadow: rgba(90, 111, 224, 0.3);
 
   // 页面级自定义变量
-  --admin-bg: #0a0a0a;
-  --admin-sidebar-bg: #141414;
-  --admin-header-bg: #1d1e1f;
+  --admin-bg: #050505;
+  --admin-sidebar-bg: #050505;
+  --admin-header-bg: #050505;
   --admin-card-bg: #1d1e1f;
   --admin-text-primary: #e5eaf3;
   --admin-text-secondary: #a3a6ad;