Files
workspace/code/fms/.qoder/repowiki/zh/content/UI组件库/布局组件.md
T
2026-08-23 21:02:41 +08:00

19 KiB
Raw Blame History

布局组件

**本文引用的文件** - [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 管理深色模式、主色、侧栏折叠态与页签缓存等全局状态。
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

图表来源

章节来源

核心组件

  • 分割面板 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 监听。

章节来源

架构总览

下图展示布局组件在应用中的协作关系:DefaultLayout 作为外壳,内部通过 Splitter 将侧栏与主区分割;主区内包含顶栏、页签与内容区;内容区可使用 Row/Col 进行栅格排版;主题与状态通过 app store 与 tokens.css 驱动。

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 : 持久化/同步状态

图表来源

详细组件分析

分割面板 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 变量控制触发区、视觉条、手柄尺寸,便于主题定制。
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)"]

图表来源

章节来源

栅格系统 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 防止内容溢出。
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 : "包含多个"

图表来源

章节来源

应用布局 DefaultLayout

  • 布局骨架
    • 使用 Splitter 将侧栏与主区水平分割;侧栏支持折叠态固定宽度与不可调整。
    • 主区包含顶栏与白卡容器,白卡内放置页签与内容区,内容区独立滚动。
  • 响应式与自适应
    • 折叠态隐藏分隔条,主区左侧内边距归零,避免过窄时两侧留白不对称。
    • 拖拽中禁用过渡,提升跟手感;窗口缩放时重新测量容器尺寸。
  • 页签缓存与过渡
    • keep-alive 的 include 列表由 visitedTabs 派生,关闭页签即销毁实例。
    • 详情页采用命名包装组件,按 fullPath 生成稳定 cacheKey,避免刷新泄漏。
    • 页面切换使用 out-in 过渡,低内容页面撑满剩余高度,高内容页面自然滚动。
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 稳定槽位<br/>内层 : key=fullPath : refreshKey 控制重挂载

图表来源

章节来源

侧栏与顶栏

  • 侧栏 AppSidebar
    • 动态菜单:从权限模块构建树,过滤目录/页面类型,保留父子关系一致的节点。
    • 团队切换:下拉选择当前团队,折叠态仅显示图标。
    • 滚动区域:菜单项过多时纵向滚动,滚动条样式与主题一致。
  • 顶栏 AppTopbar
    • 功能:折叠侧栏、搜索框、语言切换、主题色板、深浅色切换、全屏切换、通知占位、用户下拉。
    • 主题集成:直接调用 app store 切换深色模式与主色,影响 tokens.css 变量。

章节来源

依赖关系分析

  • 组件耦合
    • 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 与主题函数在初始化阶段绑定,避免启动期死锁。
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

图表来源

章节来源

性能考量

  • 拖拽性能
    • 使用 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 类是否存在)。

章节来源

结论

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,控制缓存数量,避免内存占用过高。

章节来源