wip: 布局优化完成

This commit is contained in:
Ray.Hao
2025-05-23 15:26:37 +08:00
parent 51f9b298c6
commit cfe041d7d2
12 changed files with 125 additions and 133 deletions

View File

@@ -1,96 +0,0 @@
# 布局系统
## 目录结构
```
layouts/
├── index.vue # 布局系统入口,根据设置动态加载布局
├── BaseLayout.vue # 基础布局容器,提供通用功能
├── LeftLayout.vue # 左侧菜单布局
├── TopLayout.vue # 顶部菜单布局
├── MixLayout.vue # 混合布局(顶部+左侧)
├── components/ # 布局相关组件
│ ├── AppMain.vue # 主内容区域
│ ├── NavBar.vue # 导航栏
│ ├── NavbarActions.vue # 导航栏右侧操作区
│ ├── TagsView.vue # 标签页视图
│ ├── LayoutMenu.vue # 菜单组件
│ ├── Sidebar/ # 侧边栏相关组件
│ │ ├── SidebarLogo.vue # Logo 组件
│ │ ├── SidebarMenu.vue # 菜单主体(未使用)
│ │ ├── SidebarMenuItem.vue # 菜单项
│ │ ├── SidebarMenuItemTitle.vue # 菜单项标题
│ │ └── SidebarMixTopMenu.vue # 混合布局顶部菜单
│ ├── Settings/ # 设置面板
│ │ └── index.vue # 设置面板主组件(包含布局选择)
│ └── common/ # 通用组件
│ └── LayoutSidebar.vue # 侧边栏容器
└── composables/ # 组合式函数
├── useLayout.ts # 布局相关逻辑
├── useLayoutMenu.ts # 菜单相关逻辑
└── useLayoutResponsive.ts # 响应式处理
```
## 布局说明
### 1. LeftLayout左侧布局
- 传统的左侧固定菜单布局
- 支持菜单折叠/展开
- 适合大多数管理系统
### 2. TopLayout顶部布局
- 菜单位于顶部横向排列
- 适合一级菜单较少的系统
- 节省横向空间
### 3. MixLayout混合布局
- 一级菜单在顶部,二级菜单在左侧
- 适合菜单层级较多的大型系统
- 提供更好的菜单组织方式
## 使用方式
布局系统会根据 `settings store` 中的 `layout` 配置自动切换:
```typescript
// 在设置面板中切换布局
// 或通过代码:
settingsStore.layout = LayoutMode.LEFT; // 'left' | 'top' | 'mix'
```
## 自定义布局
如需添加新布局:
1.`layouts/` 目录下创建新的布局组件(如 `CustomLayout.vue`
2.`index.vue` 中导入并添加到切换逻辑
3.`enums/settings/layout.enum.ts` 中添加新的布局类型
## 主要功能
1. **响应式适配**: 自动适配桌面端和移动端,移动端下自动收起侧边栏
2. **多种布局模式**: 支持左侧菜单、顶部菜单、混合菜单三种模式
3. **主题切换**: 支持明亮/暗黑主题
4. **标签页**: 支持多标签页功能,可通过设置开启/关闭
## 可组合式API
### useLayout
提供布局相关的基础功能:
- 侧边栏展开/收起控制
- 布局模式获取
- 布局样式类计算
### useLayoutResponsive
提供响应式布局功能:
- 根据屏幕尺寸自动调整设备类型
- 根据设备类型自动调整侧边栏状态
### useLayoutMenu
提供菜单相关功能:
- 获取菜单数据
- 处理菜单激活状态
- 混合布局下的菜单联动

View File

@@ -138,15 +138,44 @@ function logout() {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
min-width: 40px; min-width: 36px;
height: $navbar-height; height: 100%;
padding: 0 8px;
text-align: center; text-align: center;
cursor: pointer; cursor: pointer;
transition: all 0.3s; transition: all 0.3s;
// 确保直接子元素也居中
> * {
display: flex;
align-items: center;
justify-content: center;
}
// 确保 el-dropdown 也居中
:deep(.el-dropdown) {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
// 修复可能的 SVG 图标容器
:deep(.icon-container),
:deep(.action-icon) {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
}
// 默认图标样式(明亮模式 + 左侧布局) // 默认图标样式(明亮模式 + 左侧布局)
:deep([class^="i-svg:"]) { :deep([class^="i-svg:"]) {
display: inline-flex;
align-items: center;
justify-content: center;
font-size: 18px; font-size: 18px;
line-height: 1;
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
transition: color 0.3s; transition: color 0.3s;
} }
@@ -165,18 +194,20 @@ function logout() {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
height: 100%; height: 100%;
padding: 0 13px; padding: 0 8px;
&__avatar { &__avatar {
width: 32px; width: 28px;
height: 32px; height: 28px;
border-radius: 50%; border-radius: 50%;
flex-shrink: 0;
} }
&__name { &__name {
margin-left: 10px; margin-left: 8px;
color: var(--el-text-color-regular); color: var(--el-text-color-regular);
transition: color 0.3s; transition: color 0.3s;
white-space: nowrap;
} }
} }
} }

View File

@@ -37,12 +37,16 @@ function toggleSideBar() {
.navbar { .navbar {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center;
height: $navbar-height; height: $navbar-height;
padding: 0 20px;
background: var(--el-bg-color); background: var(--el-bg-color);
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
&__actions { &__actions {
display: flex; display: flex;
align-items: center; align-items: center;
height: 100%;
} }
} }
</style> </style>

View File

@@ -30,7 +30,7 @@ defineProps({
background-color: $sidebar-logo-background; background-color: $sidebar-logo-background;
.title { .title {
flex-shrink: 0; /* 防止容器在空间不足时缩小 */ flex-shrink: 0;
margin-left: 10px; margin-left: 10px;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;

View File

@@ -19,7 +19,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useLayout } from "../composables/useLayout"; import { useLayout } from "../composables/useLayout";
import { useLayoutResponsive } from "../composables/useLayoutResponsive"; import { useLayoutResponsive } from "../composables/useLayoutResponsive";
import Settings from "../components/Settings.vue"; import Settings from "../components/Settings/index.vue";
// 布局相关 // 布局相关
const { layoutClass, isShowSettings, isSidebarOpen, closeSidebar } = useLayout(); const { layoutClass, isShowSettings, isSidebarOpen, closeSidebar } = useLayout();

View File

@@ -29,10 +29,10 @@ import { useLayout } from "../composables/useLayout";
import { useLayoutMenu } from "../composables/useLayoutMenu"; import { useLayoutMenu } from "../composables/useLayoutMenu";
import BaseLayout from "./BaseLayout.vue"; import BaseLayout from "./BaseLayout.vue";
import Sidebar from "../components/Sidebar/index.vue"; import Sidebar from "../components/Sidebar/index.vue";
import NavBar from "../components/navbar/index.vue"; import NavBar from "../components/Navbar/index.vue";
import TagsView from "../components/TagsView.vue"; import TagsView from "../components/TagsView/index.vue";
import AppMain from "../components/AppMain.vue"; import AppMain from "../components/AppMain/index.vue";
import Menu from "../components/menu/index.vue"; import Menu from "../components/Menu/index.vue";
// 布局相关参数 // 布局相关参数
const { isShowTagsView, isShowLogo, isSidebarOpen, isMobile } = useLayout(); const { isShowTagsView, isShowLogo, isSidebarOpen, isMobile } = useLayout();

View File

@@ -64,11 +64,11 @@ import { useLayout } from "../composables/useLayout";
import { useLayoutMenu } from "../composables/useLayoutMenu"; import { useLayoutMenu } from "../composables/useLayoutMenu";
import BaseLayout from "./BaseLayout.vue"; import BaseLayout from "./BaseLayout.vue";
import SidebarLogo from "../components/Sidebar/components/SidebarLogo.vue"; import SidebarLogo from "../components/Sidebar/components/SidebarLogo.vue";
import MixTopMenu from "../components/menu/components/MixTopMenu.vue"; import MixTopMenu from "../components/Menu/components/MixTopMenu.vue";
import NavbarActions from "../components/navbar/components/NavbarActions.vue"; import NavbarActions from "../components/Navbar/components/NavbarActions.vue";
import TagsView from "../components/TagsView.vue"; import TagsView from "../components/TagsView/index.vue";
import AppMain from "../components/AppMain.vue"; import AppMain from "../components/AppMain/index.vue";
import MenuItem from "../components/menu/components/MenuItem.vue"; import MenuItem from "../components/Menu/components/MenuItem.vue";
import Hamburger from "@/components/Hamburger/index.vue"; import Hamburger from "@/components/Hamburger/index.vue";
import variables from "@/styles/variables.module.scss"; import variables from "@/styles/variables.module.scss";

View File

@@ -2,10 +2,16 @@
<BaseLayout> <BaseLayout>
<!-- 顶部菜单栏 --> <!-- 顶部菜单栏 -->
<div class="layout__header"> <div class="layout__header">
<Sidebar :show-logo="isShowLogo" :is-collapsed="false"> <div class="layout__header-left">
<!-- Logo -->
<SidebarLogo v-if="isShowLogo" :collapse="false" />
<!-- 菜单 -->
<Menu :data="routes" menu-mode="horizontal" base-path="" /> <Menu :data="routes" menu-mode="horizontal" base-path="" />
</div>
<!-- 操作按钮 -->
<div class="layout__header-right">
<NavbarActions /> <NavbarActions />
</Sidebar> </div>
</div> </div>
<!-- 主内容区 --> <!-- 主内容区 -->
@@ -20,11 +26,11 @@
import { useLayout } from "../composables/useLayout"; import { useLayout } from "../composables/useLayout";
import { useLayoutMenu } from "../composables/useLayoutMenu"; import { useLayoutMenu } from "../composables/useLayoutMenu";
import BaseLayout from "./BaseLayout.vue"; import BaseLayout from "./BaseLayout.vue";
import Sidebar from "../components/Sidebar/index.vue"; import SidebarLogo from "../components/Sidebar/components/SidebarLogo.vue";
import Menu from "../components/menu/index.vue"; import Menu from "../components/Menu/index.vue";
import NavbarActions from "../components/navbar/components/NavbarActions.vue"; import NavbarActions from "../components/Navbar/components/NavbarActions.vue";
import TagsView from "../components/TagsView.vue"; import TagsView from "../components/TagsView/index.vue";
import AppMain from "../components/AppMain.vue"; import AppMain from "../components/AppMain/index.vue";
// 布局相关参数 // 布局相关参数
const { isShowTagsView, isShowLogo } = useLayout(); const { isShowTagsView, isShowLogo } = useLayout();
@@ -39,14 +45,72 @@ const { routes } = useLayoutMenu();
position: sticky; position: sticky;
top: 0; top: 0;
z-index: 999; z-index: 999;
display: flex;
justify-content: space-between;
align-items: center;
width: 100%; width: 100%;
height: $navbar-height; height: $navbar-height;
padding: 0 20px;
background-color: $menu-background; background-color: $menu-background;
box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
:deep(.layout-sidebar) { &-left {
display: flex; display: flex;
width: 100% !important; align-items: center;
height: 100%;
flex: 1;
// Logo 样式
:deep(.sidebar-logo) {
width: 200px;
height: $navbar-height;
padding: 0;
margin-right: 20px;
background: transparent;
.sidebar-logo__link {
height: 100%;
}
.logo {
width: 100%;
background-color: transparent;
}
}
}
&-right {
display: flex;
align-items: center;
height: 100%;
}
// 限制菜单高度
:deep(.el-menu--horizontal) {
flex: 1;
height: $navbar-height; height: $navbar-height;
line-height: $navbar-height;
border: none;
background-color: transparent;
.el-menu-item {
height: $navbar-height;
line-height: $navbar-height;
padding: 0 20px;
}
.el-sub-menu {
.el-sub-menu__title {
height: $navbar-height;
line-height: $navbar-height;
padding: 0 20px;
}
}
// 修复子菜单弹出位置
.el-menu--popup {
min-width: 160px;
}
} }
} }

View File

@@ -7,7 +7,6 @@ export {}
declare module "vue" { declare module "vue" {
export interface GlobalComponents { export interface GlobalComponents {
AppLink: (typeof import("./../components/AppLink/index.vue"))["default"]; AppLink: (typeof import("./../components/AppLink/index.vue"))["default"];
AppMain: (typeof import("./../layout/components/AppMain/index.vue"))["default"];
Breadcrumb: (typeof import("./../components/Breadcrumb/index.vue"))["default"]; Breadcrumb: (typeof import("./../components/Breadcrumb/index.vue"))["default"];
CopyButton: (typeof import("./../components/CopyButton/index.vue"))["default"]; CopyButton: (typeof import("./../components/CopyButton/index.vue"))["default"];
CURD: (typeof import("./../components/CURD/index.vue"))["default"]; CURD: (typeof import("./../components/CURD/index.vue"))["default"];
@@ -71,10 +70,8 @@ declare module "vue" {
Hamburger: (typeof import("./../components/Hamburger/index.vue"))["default"]; Hamburger: (typeof import("./../components/Hamburger/index.vue"))["default"];
IconSelect: (typeof import("./../components/IconSelect/index.vue"))["default"]; IconSelect: (typeof import("./../components/IconSelect/index.vue"))["default"];
LangSelect: (typeof import("./../components/LangSelect/index.vue"))["default"]; LangSelect: (typeof import("./../components/LangSelect/index.vue"))["default"];
LayoutSelect: (typeof import("./../layout/components/Settings/components/LayoutSelect.vue"))["default"];
MenuSearch: (typeof import("./../components/MenuSearch/index.vue"))["default"]; MenuSearch: (typeof import("./../components/MenuSearch/index.vue"))["default"];
MultiImageUpload: (typeof import("./../components/Upload/MultiImageUpload.vue"))["default"]; MultiImageUpload: (typeof import("./../components/Upload/MultiImageUpload.vue"))["default"];
NavbarActions: (typeof import("./../layout/components/NavBar/components/NavbarActions.vue"))["default"];
Notification: (typeof import("./../components/Notification/index.vue"))["default"]; Notification: (typeof import("./../components/Notification/index.vue"))["default"];
PageContent: (typeof import("./../components/CURD/PageContent.vue"))["default"]; PageContent: (typeof import("./../components/CURD/PageContent.vue"))["default"];
PageModal: (typeof import("./../components/CURD/PageModal.vue"))["default"]; PageModal: (typeof import("./../components/CURD/PageModal.vue"))["default"];
@@ -82,17 +79,9 @@ declare module "vue" {
Pagination: (typeof import("./../components/Pagination/index.vue"))["default"]; Pagination: (typeof import("./../components/Pagination/index.vue"))["default"];
RouterLink: (typeof import("vue-router"))["RouterLink"]; RouterLink: (typeof import("vue-router"))["RouterLink"];
RouterView: (typeof import("vue-router"))["RouterView"]; RouterView: (typeof import("vue-router"))["RouterView"];
Settings: (typeof import("./../layout/components/Settings/index.vue"))["default"];
Sidebar: (typeof import("./../layout/components/Sidebar/index.vue"))["default"];
SidebarLogo: (typeof import("./../layout/components/Sidebar/components/SidebarLogo.vue"))["default"];
SidebarMenu: (typeof import("./../layout/components/Sidebar/components/SidebarMenu.vue"))["default"];
SidebarMenuItem: (typeof import("./../layout/components/Sidebar/components/SidebarMenuItem.vue"))["default"];
SidebarMenuItemTitle: (typeof import("./../layout/components/Sidebar/components/SidebarMenuItemTitle.vue"))["default"];
SidebarMixTopMenu: (typeof import("./../layout/components/Sidebar/components/SidebarMixTopMenu.vue"))["default"];
SingleImageUpload: (typeof import("./../components/Upload/SingleImageUpload.vue"))["default"]; SingleImageUpload: (typeof import("./../components/Upload/SingleImageUpload.vue"))["default"];
SizeSelect: (typeof import("./../components/SizeSelect/index.vue"))["default"]; SizeSelect: (typeof import("./../components/SizeSelect/index.vue"))["default"];
TableSelect: (typeof import("./../components/TableSelect/index.vue"))["default"]; TableSelect: (typeof import("./../components/TableSelect/index.vue"))["default"];
TagsView: (typeof import("./../layout/components/TagsView/index.vue"))["default"];
WangEditor: (typeof import("./../components/WangEditor/index.vue"))["default"]; WangEditor: (typeof import("./../components/WangEditor/index.vue"))["default"];
} }
export interface ComponentCustomProperties { export interface ComponentCustomProperties {