# 布局组件 **本文引用的文件** - [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)