wip: 布局优化完成
This commit is contained in:
@@ -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
|
|
||||||
|
|
||||||
提供菜单相关功能:
|
|
||||||
- 获取菜单数据
|
|
||||||
- 处理菜单激活状态
|
|
||||||
- 混合布局下的菜单联动
|
|
||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
11
src/types/components.d.ts
vendored
11
src/types/components.d.ts
vendored
@@ -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 {
|
||||||
|
|||||||
Reference in New Issue
Block a user