# 05 — 布局系统与导航设计 > 参考:vue-vben-admin(三层布局架构)、soybean-admin(layouts/modules 按功能拆分)、ant-design-pro(ProLayout 运行时配置)、vue-pure-admin(布局三模式);补充:gin-vue-admin(侧栏模式/标签页/水印) --- ## 一、布局的分层架构 ### 1.1 vben 三层模型(理解布局复杂度的标尺) ``` ┌─ 内核层 @core/ui-kit/layout-ui ──────────────────────┐ │ headless 布局引擎:40 个 props + defineModel │ │ (布局模式/头部模式/侧栏宽度折叠悬停),不认识 store │ ├─ 业务层 effects/layouts ─────────────────────────────┤ │ 把 preferences 逐项映射为内核 props; │ │ 组装 15 个可插拔部件(面包屑/主题切换/搜索/锁屏…) │ ├─ App 层 layouts/basic.vue ───────────────────────────┤ │ 只用插槽填内容(#user-dropdown #notification …) │ └──────────────────────────────────────────────────────┘ ``` soybean 的同构做法:布局引擎抽成 `packages/materials`(AdminLayout/PageTab/SimpleScrollbar),`src/layouts/base-layout/` 只是配置消费者,`layouts/modules/global-*` 按功能拆分(header/sider/tab/breadcrumb/search/footer/theme-drawer)。pure 提供 vertical/horizontal/mix 三种布局模式运行时可切。 **设计要义**:布局引擎与业务解耦(内核不依赖状态库),布局模式是**运行时偏好**而非构建期选择。 ### 1.2 单文件 Layout 的取舍(中小工程) 300 行左右的单文件 Layout(el-container 侧栏 + 顶栏 + main)对单布局产品是**正确选择**——不要为了架构美引入三层。分层触发条件: - 出现第 2 种布局模式(如「无侧栏全屏监控大屏」) - 布局配置需要用户可调(折叠宽度/头部固定/水印) - 布局代码超过 500 行开始挤压可读性 无论哪种形态:**业务页挂在内容区(页面容器组件协议),禁止重写布局层**。 --- ## 二、布局骨架与尺寸规范 ### 2.1 管理端标准骨架 ``` ┌──────────────────────────────────────────────────────┐ │ Topbar 48px │ 页标题 · 面包屑 │ 暗色切换 · 用户下拉 │ ├──────────┬───────────────────────────────────────────┤ │ Sidebar │ │ │ 220px │ main │ │ (折叠64) │ │ │ │ PageContainer │ │ 分组: │ │ │ 按层定序 │ 页头(标题/描述/操作按钮) │ │ │ 内容卡片(24px 内边距) │ └──────────┴───────────────────────────────────────────┘ ``` ### 2.2 尺寸规范表(推荐值) ``` 侧栏宽度 220px(折叠 64px,<1200px 自动折叠) 顶栏高度 48-56px 页面内边距 24px(紧凑模式 16px) 卡片间距 16px 卡片圆角 8px(--el-border-radius-base 级) 按钮/输入高度 32px(default size) 图标 16px 主图标 / 12px 辅助 正文字号 14px;辅助 12px;页面标题 18-20px 移动端断点 <768px 侧栏变覆盖式抽屉 + backdrop ``` ### 2.3 卡片构成规范(视觉一致性的最小单位) 页面 = 卡片的组合,卡片写法全站统一: ``` 构成 背景 bg-card + 描边 border-base 或 阴影 shadow-1(全站二选一,禁止混用) 内边距 20-24px;卡片内区块间距 16px;卡片之间间距 16px 标题区 卡片标题 16px/600 + 右侧操作区(同一基线对齐) 页头 页面标题 18-20px/600 + 副标题 13px 次要色;页头与内容区间距 16px 指标卡 数字 24-28px/600 + tabular-nums + 环比箭头(涨跌语义色,方向全站统一) ``` --- ## 三、侧栏与导航 ### 3.1 侧栏必备能力清单 | 能力 | 实现要点 | 出处 | |------|----------|------| | 折叠 | 图标态切换;`<1200px` 自动折叠;移动端强制抽屉 | gva/pure | | 高亮 | 路由激活态;外链/iframe 用 `meta.activePath` 指回 | vben `activePath` | | 分组 | 前端按 layer 分组 + 常量定序,服务端菜单为准 | 常见底座实现 | | 图标 | icon 名是数据(菜单表字段),前端映射渲染 | gva/pure/soybean | | 失败重试 | 菜单接口失败显示重试入口,不留空白 | 通行要求 | | 外链 | `window.open` 新窗;不注册路由 | gva `link` | ### 3.2 可选演进:标签页(tabs) 功能完整参照(gva 280 行 / pure multiTags / vben tabs-ui):sessionStorage 持久化、tab key = `name + query + params`(同路由不同参数多开)、右键菜单(关左/关右/关其他)、中键关闭、首页不可关、`meta.closeTab` 不留痕;vben 再加 affixTab 固定、maxNumOfOpenTab 上限、拖拽排序。 **引入判断**:运维/排障类平台(多工位切换频繁)收益大;配置型后台收益小。做了标签页**必须**解决 keep-alive 联动(否则切 tab 丢状态)。 --- ## 四、Keep-Alive 链路(做对了很隐形,做错了很诡异) ``` 路由 meta.keepAlive: true → 页面组件 defineOptions({ name: 'UserList' }) ← 强约定! → layout 中 → 刷新:keepAlive 页销毁重建 toggle;非缓存页跳隐藏 Reload 路由(gva 双轨) ``` **为什么必须 defineOptions name**:keep-alive 的 include 匹配的是**组件名**而非路由名。script setup 组件默认匿名,不写 name 缓存必然失效。gva/pure 以 Vite 插件扫描生成「路径→组件名」映射做校验——**推荐**「组件 name 与路由 name 同名」并用 lint 守住(`vue/name` 规则)。 pure 的补充经验:**三级以上路由拍平成二级**再注册(keep-alive 只对二级稳定生效,社区已知问题)。 --- ## 五、页面模板(新页面从这里复制起步) ### 5.1 列表页(最高频模板) ``` ┌─────────────────────────────────────────────────┐ │ PageContainer #header │ │ 批量删除 · 导入 · [+ 新增] ← 按钮级权限 │ ├─────────────────────────────────────────────────┤ │ ListPage │ │ ├ filters slot:关键字/状态/日期筛选 [查询][重置] │ │ ├ el-table(操作列 fixed right,状态列 tag 化) │ │ └ el-pagination(10/30/50/100) │ ├─────────────────────────────────────────────────┤ │ 对话框子组件:XxxFormDialog / ResetDialog / … │ └─────────────────────────────────────────────────┘ ``` 范式要点: 1. 页面只做「编排」:容器 + 列表组件 + 子组件组合,本身 ≤400 行 2. 数据获取走列表组件的 fetcher(内嵌「取消 + 版本号」双重防护,见 03 章) 3. 表单弹窗独立成 `components/XxxFormDialog.vue`,通过 props/emit 通信 4. 操作按钮逐个挂权限控制 ### 5.2 详情/编辑页 ``` ┌─────────────────────────────────────────────┐ │ ← 返回 编辑用户 [保存] │ ├─────────────────────────────────────────────┤ │ ┌─ 基本信息 ────────────────────────────┐ │ │ │ 分组卡片:descriptions 只读态 │ │ │ └───────────────────────────────────────┘ │ │ ┌─ 高级设置 ────────────────────────────┐ │ │ │ 表单区:危险操作独立分区(红色警示卡) │ │ │ └───────────────────────────────────────┘ │ └─────────────────────────────────────────────┘ ``` ### 5.3 仪表盘页 ``` ┌─────────────────────────────────────────────┐ │ 4-6 张指标卡(数字 + 环比箭头) │ ├──────────────────┬──────────────────────────┤ │ 折线图(趋势) │ 饼图/柱状(分布) │ ├──────────────────┴──────────────────────────┤ │ 最近活动列表 / 快捷入口 │ └─────────────────────────────────────────────┘ ``` 图表规范:ECharts **按需引入**(`echarts/core` + 按需 register,**禁止**全量 import echarts);图表区懒加载 + loading 占位;WebSocket 实时图必须处理「组件卸载断开连接 + 重连退避」;图表色板从主题变量读取(见 06 章)。 ### 5.4 空态页 图标 + 一句话 + 引导动作(如「模块未启用 → 查看启用步骤」)是「功能未开启」的统一形态。 --- ## 六、响应式策略 | 断点 | 行为 | |------|------| | ≥1200px | 完整布局 | | 768-1200px | 侧栏自动折叠为图标态 | | <768px | 侧栏变覆盖式抽屉 + backdrop;表格横向滚动;弹窗宽度 90% | 实现参照(gva `hooks/responsive.js`、soybean 断点写回 store):resize 监听 + **100ms debounce** 写入全局 device 状态(`desktop | mobile`),布局组件只读状态不各自监听。弹窗移动端宽度走全局配置统一控制。 --- ## 七、最佳实践清单 ✅ **要做** 1. 业务页挂内容区(页面容器协议),**禁止重写 layout/** 2. 页面标题来自 `route.meta.title`,不硬编码 3. 可访问性:折叠钮/菜单项带 aria-label 4. 布局状态(折叠)持久化,key 纳入全局命名空间(见 08 章双端一致) 5. 路由切换动画统一一种(fade 150ms),`transition` 包 `router-view` ❌ **不要** 1. 不要在业务页写 fixed 定位的伪头部(与 Topbar 层级打架) 2. 不要侧栏菜单硬编码(数据源走菜单 store) 3. 不要绕过页面容器自造页面容器(内边距/面包屑/页头规范会失控) 4. 不要在移动端保留 hover-only 交互(用点击展开兜底) 5. 不要保留空目录和过期布局文档 --- ## 八、AI 检查清单(对比目标工程逐项核对) **P0** 1. 业务页面是否有绕过页面容器、直接改写布局层的代码? 2. 页面标题是否单源(meta.title)?硬编码标题清单列出 **P1** 3. 超过 500 行的 Layout / 超过 400 行的视图页面列出,并给拆分建议(参照 07 章) 4. keepAlive 链路是否完整(meta + defineOptions name + include)? 5. 响应式三断点行为是否齐全?弹窗移动端宽度是否有适配? 6. ECharts 是否按需引入?WebSocket 是否有卸载断开与重连退避? **P2** 7. 是否值得引入标签页(多工位场景)?若引入,keep-alive 联动方案确认 8. 是否值得布局引擎化(出现第二种布局模式时) --- > 下一篇:[06-主题系统与视觉设计](06-主题系统与视觉设计.md) —— 换肤只改变量,不引第二套样式。