# 布局组件
**本文引用的文件**
- [DefaultLayout.vue](file://fms-vue/src/layouts/DefaultLayout.vue)
- [splitter.vue](file://fms-vue/src/components/ui/splitter/splitter.vue)
- [panel.vue](file://fms-vue/src/components/ui/splitter/panel.vue)
- [index.scss(分割面板)](file://fms-vue/src/components/ui/splitter/index.scss)
- [row.vue](file://fms-vue/src/components/ui/grid/row.vue)
- [col.vue](file://fms-vue/src/components/ui/grid/col.vue)
- [index.scss(栅格)](file://fms-vue/src/components/ui/grid/index.scss)
- [AppSidebar.vue](file://fms-vue/src/layouts/components/AppSidebar.vue)
- [AppTopbar.vue](file://fms-vue/src/layouts/components/AppTopbar.vue)
- [tokens.css](file://fms-vue/src/theme/tokens.css)
- [app.js](file://fms-vue/src/stores/app.js)
## 目录
1. [简介](#简介)
2. [项目结构](#项目结构)
3. [核心组件](#核心组件)
4. [架构总览](#架构总览)
5. [详细组件分析](#详细组件分析)
6. [依赖关系分析](#依赖关系分析)
7. [性能考量](#性能考量)
8. [故障排查指南](#故障排查指南)
9. [结论](#结论)
10. [附录](#附录)
## 简介
本文件面向 FMS 前端工程中的页面布局体系,围绕“分割面板”和“栅格系统”两大基础布局能力进行系统化说明。文档覆盖设计理念、响应式行为与自适应算法、复杂界面布局方案(嵌套与动态调整)、性能优化技巧、浏览器兼容性处理,以及与主题系统的集成方式和自定义扩展方法。目标是帮助开发者快速理解并高效使用布局组件,构建稳定、可维护且高性能的后台界面。
## 项目结构
FMS 的布局由“应用级布局容器 + 分割面板 + 栅格系统 + 侧栏/顶栏 + 主题/状态管理”共同构成:
- 应用级布局容器:DefaultLayout 负责整体骨架、侧栏与主区分割、页签与内容区域组织。
- 分割面板:Splitter/SplitterPanel 提供水平/垂直方向的可拖拽分割,支持最小/最大尺寸、百分比/像素尺寸、键盘操作与无障碍属性。
- 栅格系统:Row/Col 提供 24 栅格布局、间距控制、对齐与排序等常用排版能力。
- 侧栏/顶栏:AppSidebar 与 AppTopbar 提供导航、团队切换、搜索、主题色与深浅色切换、全屏等功能。
- 主题/状态:通过 tokens.css 定义设计令牌,app store 管理深色模式、主色、侧栏折叠态与页签缓存等全局状态。
```mermaid
graph TB
subgraph "应用布局"
DL["DefaultLayout.vue"]
SB["AppSidebar.vue"]
TB["AppTopbar.vue"]
end
subgraph "分割面板"
SP["Splitter.vue"]
PNL["SplitterPanel.vue"]
SCS["splitter/index.scss"]
end
subgraph "栅格系统"
ROW["Grid Row.vue"]
COL["Grid Col.vue"]
GCS["grid/index.scss"]
end
subgraph "主题与状态"
TOK["theme/tokens.css"]
APP["stores/app.js"]
end
DL --> SP
SP --> PNL
DL --> SB
DL --> TB
DL --> ROW
ROW --> COL
DL --> TOK
DL --> APP
SB --> APP
TB --> APP
```
图表来源
- [DefaultLayout.vue:1-138](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L138)
- [splitter.vue:1-298](file://fms-vue/src/components/ui/splitter/splitter.vue#L1-L298)
- [panel.vue:1-38](file://fms-vue/src/components/ui/splitter/panel.vue#L1-L38)
- [index.scss(分割面板):1-144](file://fms-vue/src/components/ui/splitter/index.scss#L1-L144)
- [row.vue:1-61](file://fms-vue/src/components/ui/grid/row.vue#L1-L61)
- [col.vue:1-67](file://fms-vue/src/components/ui/grid/col.vue#L1-L67)
- [index.scss(栅格):1-131](file://fms-vue/src/components/ui/grid/index.scss#L1-L131)
- [tokens.css:1-152](file://fms-vue/src/theme/tokens.css#L1-L152)
- [app.js:1-123](file://fms-vue/src/stores/app.js#L1-L123)
章节来源
- [DefaultLayout.vue:1-138](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L138)
- [splitter.vue:1-298](file://fms-vue/src/components/ui/splitter/splitter.vue#L1-L298)
- [panel.vue:1-38](file://fms-vue/src/components/ui/splitter/panel.vue#L1-L38)
- [row.vue:1-61](file://fms-vue/src/components/ui/grid/row.vue#L1-L61)
- [col.vue:1-67](file://fms-vue/src/components/ui/grid/col.vue#L1-L67)
- [tokens.css:1-152](file://fms-vue/src/theme/tokens.css#L1-L152)
- [app.js:1-123](file://fms-vue/src/stores/app.js#L1-L123)
## 核心组件
- 分割面板 Splitter/SplitterPanel
- 功能:水平/垂直分割、拖拽调整、键盘调整、最小/最大尺寸约束、百分比/像素尺寸、hover 显示分隔条、无障碍 ARIA 值。
- 关键实现:容器主轴尺寸测量、拖拽 rAF 节流、两侧面板尺寸联动计算、释放后自动面板恢复。
- 栅格 Row/Col
- 功能:24 栅格、gutter 间距、对齐方式、排序、push/pull、flex 模式兼容。
- 关键实现:CSS 变量传递 gutter、负 margin 抵消、百分比宽度生成、flex 时跳过 span 宽度以避免冲突。
- 应用布局 DefaultLayout
- 功能:侧栏与主区分割、侧栏折叠态、页签缓存与过渡、内容滚动区域、主题与语言切换入口。
- 关键实现:Splitter 尺寸同步、keep-alive 缓存策略、路由刷新 key、折叠态样式适配。
- 侧栏/顶栏 AppSidebar/AppTopbar
- 功能:菜单树渲染、团队切换、搜索框、主题色板、深浅色切换、全屏切换、用户下拉。
- 关键实现:Pinia 状态读写、主题 token 注入、全屏 API 监听。
章节来源
- [splitter.vue:1-298](file://fms-vue/src/components/ui/splitter/splitter.vue#L1-L298)
- [panel.vue:1-38](file://fms-vue/src/components/ui/splitter/panel.vue#L1-L38)
- [row.vue:1-61](file://fms-vue/src/components/ui/grid/row.vue#L1-L61)
- [col.vue:1-67](file://fms-vue/src/components/ui/grid/col.vue#L1-L67)
- [DefaultLayout.vue:1-138](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L138)
- [AppSidebar.vue:1-363](file://fms-vue/src/layouts/components/AppSidebar.vue#L1-L363)
- [AppTopbar.vue:1-471](file://fms-vue/src/layouts/components/AppTopbar.vue#L1-L471)
## 架构总览
下图展示布局组件在应用中的协作关系:DefaultLayout 作为外壳,内部通过 Splitter 将侧栏与主区分割;主区内包含顶栏、页签与内容区;内容区可使用 Row/Col 进行栅格排版;主题与状态通过 app store 与 tokens.css 驱动。
```mermaid
sequenceDiagram
participant U as "用户"
participant DL as "DefaultLayout"
participant SP as "Splitter"
participant SB as "AppSidebar"
participant TB as "AppTopbar"
participant ST as "app store"
participant TK as "tokens.css"
U->>DL : 打开页面
DL->>SP : 初始化分割方向/事件
SP-->>DL : resize-start / resize / resize-end
DL->>ST : 读取/更新 sidebarCollapsed、主题、语言
ST-->>TK : 写入 --fms-primary / .dark 类
DL->>SB : 渲染侧栏菜单
DL->>TB : 渲染顶栏按钮
U->>SP : 拖拽分隔条
SP-->>DL : 回调 sizes,更新侧栏宽度
DL->>ST : 持久化/同步状态
```
图表来源
- [DefaultLayout.vue:60-138](file://fms-vue/src/layouts/DefaultLayout.vue#L60-L138)
- [splitter.vue:169-224](file://fms-vue/src/components/ui/splitter/splitter.vue#L169-L224)
- [app.js:30-123](file://fms-vue/src/stores/app.js#L30-L123)
- [tokens.css:1-152](file://fms-vue/src/theme/tokens.css#L1-L152)
## 详细组件分析
### 分割面板 Splitter/SplitterPanel
- 设计要点
- 非受控与受控双模式:未传 size/defaultSize 时由内部维护 innerSizes;传入 size 则由父级受控。
- 尺寸解析:支持 px、百分比字符串,按容器主轴尺寸换算。
- 拖拽体验:rAF 合并高频指针事件,避免频繁重排;拖拽中禁用 flex-basis 过渡保证跟手。
- 约束与回退:min/max 限制两侧面板范围;拖拽结束释放未显式声明尺寸的面板为自动分配。
- 无障碍:ARIA orientation、valueNow/min/max,键盘方向键调整。
- 数据流与复杂度
- 拖拽计算 O(1),每帧仅对当前分隔条两侧面板做 clamp 与赋值。
- 容器尺寸变化时重新测量,resize 事件监听窗口大小。
- 性能与兼容性
- 使用 requestAnimationFrame 降低抖动;pointer 事件统一鼠标/触摸;prefers-reduced-motion 禁用动画。
- CSS 变量控制触发区、视觉条、手柄尺寸,便于主题定制。
```mermaid
flowchart TD
Start(["拖拽开始"]) --> Measure["测量容器与面板初始尺寸"]
Measure --> Move{"指针移动?"}
Move --> |是| Delta["计算 delta"]
Delta --> Clamp["按 min/max 限制两侧面板范围"]
Clamp --> Apply["写入 innerSizes[index] 与 [index+1]"]
Apply --> Emit["emit('resize', sizes)"]
Emit --> Move
Move --> |否| End(["拖拽结束"])
End --> Release["释放未受控面板为自动分配"]
Release --> EmitEnd["emit('resize-end', finalSizes)"]
```
图表来源
- [splitter.vue:132-224](file://fms-vue/src/components/ui/splitter/splitter.vue#L132-L224)
- [panel.vue:18-26](file://fms-vue/src/components/ui/splitter/panel.vue#L18-L26)
- [index.scss(分割面板):27-144](file://fms-vue/src/components/ui/splitter/index.scss#L27-L144)
章节来源
- [splitter.vue:1-298](file://fms-vue/src/components/ui/splitter/splitter.vue#L1-L298)
- [panel.vue:1-38](file://fms-vue/src/components/ui/splitter/panel.vue#L1-L38)
- [index.scss(分割面板):1-144](file://fms-vue/src/components/ui/splitter/index.scss#L1-L144)
### 栅格系统 Row/Col
- 设计要点
- 24 栅格:通过 SCSS 循环生成 0-24 的宽度类,配合 flex 布局。
- 间距:Row 通过 CSS 变量下传 gutter,Col 以对称 padding 实现水平间距,row-gap 实现垂直间距。
- 偏移/排序:offset/order/push/pull 提供灵活排列能力。
- Flex 兼容:当设置 flex 时,不再套用基于 span 的百分比宽度,避免冲突。
- 响应式行为
- 当前实现不含断点,适合在业务层组合或扩展媒体查询。
- 可通过外层容器宽度与 Col 的 flex 属性实现自适应。
- 性能与兼容性
- 纯 CSS 计算,无运行时 JS 开销;box-sizing 与 min-width 防止内容溢出。
```mermaid
classDiagram
class FmsRow {
+Number|Array gutter
+String align
+String justify
+Boolean wrap
}
class FmsCol {
+Number|String span
+Number|String offset
+Number|String order
+Number|String push
+Number|String pull
+Number|String flex
}
FmsRow --> FmsCol : "包含多个"
```
图表来源
- [row.vue:1-61](file://fms-vue/src/components/ui/grid/row.vue#L1-L61)
- [col.vue:1-67](file://fms-vue/src/components/ui/grid/col.vue#L1-L67)
- [index.scss(栅格):1-131](file://fms-vue/src/components/ui/grid/index.scss#L1-L131)
章节来源
- [row.vue:1-61](file://fms-vue/src/components/ui/grid/row.vue#L1-L61)
- [col.vue:1-67](file://fms-vue/src/components/ui/grid/col.vue#L1-L67)
- [index.scss(栅格):1-131](file://fms-vue/src/components/ui/grid/index.scss#L1-L131)
### 应用布局 DefaultLayout
- 布局骨架
- 使用 Splitter 将侧栏与主区水平分割;侧栏支持折叠态固定宽度与不可调整。
- 主区包含顶栏与白卡容器,白卡内放置页签与内容区,内容区独立滚动。
- 响应式与自适应
- 折叠态隐藏分隔条,主区左侧内边距归零,避免过窄时两侧留白不对称。
- 拖拽中禁用过渡,提升跟手感;窗口缩放时重新测量容器尺寸。
- 页签缓存与过渡
- keep-alive 的 include 列表由 visitedTabs 派生,关闭页签即销毁实例。
- 详情页采用命名包装组件,按 fullPath 生成稳定 cacheKey,避免刷新泄漏。
- 页面切换使用 out-in 过渡,低内容页面撑满剩余高度,高内容页面自然滚动。
```mermaid
sequenceDiagram
participant R as "路由"
participant DL as "DefaultLayout"
participant KA as "keep-alive"
participant DP as "DetailPage"
participant AS as "app store"
R-->>DL : route.fullPath 变化
DL->>AS : 读取 cachedViews / visitedTabs
DL->>KA : include = cachedViews, max=8
alt 详情页
DL->>DP : 渲染 DetailPage(id)
else 普通页
DL->>KA : 渲染对应页面组件
end
Note over DL,KA : 外层 : key=fullPath 稳定槽位
内层 : key=fullPath : refreshKey 控制重挂载
```
图表来源
- [DefaultLayout.vue:16-58](file://fms-vue/src/layouts/DefaultLayout.vue#L16-L58)
- [DefaultLayout.vue:112-131](file://fms-vue/src/layouts/DefaultLayout.vue#L112-L131)
- [app.js:20-28](file://fms-vue/src/stores/app.js#L20-L28)
章节来源
- [DefaultLayout.vue:1-138](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L138)
- [app.js:1-123](file://fms-vue/src/stores/app.js#L1-L123)
### 侧栏与顶栏
- 侧栏 AppSidebar
- 动态菜单:从权限模块构建树,过滤目录/页面类型,保留父子关系一致的节点。
- 团队切换:下拉选择当前团队,折叠态仅显示图标。
- 滚动区域:菜单项过多时纵向滚动,滚动条样式与主题一致。
- 顶栏 AppTopbar
- 功能:折叠侧栏、搜索框、语言切换、主题色板、深浅色切换、全屏切换、通知占位、用户下拉。
- 主题集成:直接调用 app store 切换深色模式与主色,影响 tokens.css 变量。
章节来源
- [AppSidebar.vue:1-363](file://fms-vue/src/layouts/components/AppSidebar.vue#L1-L363)
- [AppTopbar.vue:1-471](file://fms-vue/src/layouts/components/AppTopbar.vue#L1-L471)
## 依赖关系分析
- 组件耦合
- DefaultLayout 依赖 Splitter/SplitterPanel 完成主布局;依赖 app store 获取/更新全局状态;依赖路由与 keep-alive 管理页面缓存。
- Splitter 依赖 panel 渲染子面板,并通过 index.scss 提供样式;不直接依赖业务组件,具备良好复用性。
- Row/Col 为纯展示组件,依赖 CSS 变量与 SCSS 生成的类名,无外部状态依赖。
- 外部依赖
- 主题系统:tokens.css 提供设计令牌,app store 通过 watch 将状态同步到 DOM(如 .dark 类、--fms-primary)。
- 全屏 API:AppTopbar 使用 document.requestFullscreen/exitFullscreen 实现全屏切换。
- 潜在循环依赖
- 当前布局组件之间单向依赖,未发现循环引用;store 与主题函数在初始化阶段绑定,避免启动期死锁。
```mermaid
graph LR
DL["DefaultLayout.vue"] --> SP["Splitter.vue"]
SP --> PNL["SplitterPanel.vue"]
DL --> SB["AppSidebar.vue"]
DL --> TB["AppTopbar.vue"]
DL --> ROW["Grid Row.vue"]
ROW --> COL["Grid Col.vue"]
DL --> TOK["tokens.css"]
DL --> APP["stores/app.js"]
TB --> APP
SB --> APP
```
图表来源
- [DefaultLayout.vue:1-138](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L138)
- [splitter.vue:1-298](file://fms-vue/src/components/ui/splitter/splitter.vue#L1-L298)
- [panel.vue:1-38](file://fms-vue/src/components/ui/splitter/panel.vue#L1-L38)
- [row.vue:1-61](file://fms-vue/src/components/ui/grid/row.vue#L1-L61)
- [col.vue:1-67](file://fms-vue/src/components/ui/grid/col.vue#L1-L67)
- [tokens.css:1-152](file://fms-vue/src/theme/tokens.css#L1-L152)
- [app.js:1-123](file://fms-vue/src/stores/app.js#L1-L123)
章节来源
- [DefaultLayout.vue:1-138](file://fms-vue/src/layouts/DefaultLayout.vue#L1-L138)
- [splitter.vue:1-298](file://fms-vue/src/components/ui/splitter/splitter.vue#L1-L298)
- [app.js:1-123](file://fms-vue/src/stores/app.js#L1-L123)
## 性能考量
- 拖拽性能
- 使用 requestAnimationFrame 合并高频 pointermove 事件,减少重排重绘。
- 拖拽中禁用 flex-basis 过渡,避免动画导致的卡顿。
- 布局与滚动
- 内容区独立滚动,避免整页滚动带来的重排;主区 overflow visible 允许卡片阴影外扩。
- 折叠态隐藏分隔条,减少不必要的交互元素。
- 缓存与渲染
- keep-alive 的 include 列表随页签集合变化,关闭页签即销毁实例,避免内存泄漏。
- 详情页包装组件按 fullPath 生成稳定 cacheKey,刷新仅影响内层 key,外层缓存槽位不变。
- 主题与样式
- 通过 CSS 变量集中管理尺寸与颜色,切换主题时无需重建组件。
- prefers-reduced-motion 媒体查询禁用动画,满足无障碍需求。
[本节为通用性能建议,无需特定文件来源]
## 故障排查指南
- 拖拽不生效或卡顿
- 检查 Splitter 是否被外层容器裁剪(overflow hidden),确保容器可测量宽高。
- 确认分隔条未被其他元素遮挡;hover 模式下分隔条平时隐藏,需 hover/focus/active 才显示。
- 查看控制台是否有 pointer 事件被阻止;必要时移除全局 user-select 干扰。
- 侧栏折叠后分隔条仍可见
- 确认布局容器添加了 is-collapsed 类,且样式规则命中了分隔条隐藏。
- 注意选择器限定为布局的直接子级分隔条,避免误伤嵌套 Splitter。
- 栅格间距异常
- 检查 Row 是否正确传入 gutter,Col 是否被外层容器压缩(min-width 0)。
- 若使用 flex 模式,确认未同时设置 span 导致宽度冲突。
- 主题切换无效
- 确认 app store 的 watch 已绑定,并在初始化时立即执行一次。
- 检查 tokens.css 中变量是否被正确覆盖(如 .dark 类是否存在)。
章节来源
- [splitter.vue:169-224](file://fms-vue/src/components/ui/splitter/splitter.vue#L169-L224)
- [DefaultLayout.vue:164-184](file://fms-vue/src/layouts/DefaultLayout.vue#L164-L184)
- [index.scss(分割面板):104-144](file://fms-vue/src/components/ui/splitter/index.scss#L104-L144)
- [index.scss(栅格):7-77](file://fms-vue/src/components/ui/grid/index.scss#L7-L77)
- [app.js:104-123](file://fms-vue/src/stores/app.js#L104-L123)
## 结论
FMS 布局体系以 Splitter 与 Grid 为核心,结合应用级布局容器与主题/状态管理,提供了稳定、可扩展且高性能的页面布局能力。分割面板支持丰富的交互与约束,栅格系统提供灵活的排版方案;DefaultLayout 将二者有机整合,并通过 keep-alive 与路由机制保障页面缓存与过渡体验。通过 CSS 变量与 app store 的解耦设计,主题与状态切换高效可靠。建议在复杂界面中优先使用 Splitter 进行大块区域划分,再在内容区使用 Grid 进行精细排版,以获得最佳的可维护性与性能表现。
[本节为总结性内容,无需特定文件来源]
## 附录
- 主题系统集成
- 通过 app store 的 watch 将 darkMode 与 primaryColor 同步到 DOM:添加/移除 .dark 类、写入 --fms-primary。
- tokens.css 集中定义所有设计令牌,组件样式通过 CSS 变量引用,便于统一替换与扩展。
- 自定义扩展方法
- 新增分割面板行为:在 Splitter 的 emit 事件中接入业务逻辑(如记录用户偏好、持久化尺寸)。
- 扩展栅格断点:在 grid/index.scss 中添加媒体查询,或在业务层组合 Row/Col 实现响应式。
- 自定义分隔条样式:通过修改 splitter/index.scss 中的 CSS 变量或类名,调整触发区、视觉条与手柄外观。
- 最佳实践
- 在 DefaultLayout 中使用 Splitter 管理侧栏与主区,避免在业务页面重复实现分割逻辑。
- 内容区优先使用 Row/Col 进行栅格布局,保持间距一致性与可读性。
- 合理使用 keep-alive 的 include 与 max,控制缓存数量,避免内存占用过高。
章节来源
- [app.js:104-123](file://fms-vue/src/stores/app.js#L104-L123)
- [tokens.css:1-152](file://fms-vue/src/theme/tokens.css#L1-L152)
- [index.scss(分割面板):100-144](file://fms-vue/src/components/ui/splitter/index.scss#L100-L144)
- [index.scss(栅格):1-131](file://fms-vue/src/components/ui/grid/index.scss#L1-L131)