Files
LokiStack/docs/web-ui-guide/05-布局系统与导航设计.md
T
logan f9c6c1d826 feat: opsx-loki 全面升级 — 前端重构/用户与LDAP/部署向导/日志查询
基于 WEB-UI-Development-Guide 标准的整体升级,并更名 LokiDeploy → opsx-loki。

前端(Gin+React+shadcn/ui):
- 按 02/07 章重构:页面域化拆分(无超 400 行文件)、PageContainer/ListQuery、
  反馈分级(成功静默/失败 toast/危险确认)、401 单飞刷新+队列重放
- 暗色模式(next-themes)+ 语义 Token(--primary 品牌色/success/warning/info)
- 部署向导(状态推导四步:环境→存储→Loki→采集,内嵌表单闭环)
- 日志查询页:LogQL 标签构建器/拖拽直方图/日志上下文/Live Tail/查询历史
- 按用户+标签策略的查询授权(服务端强制合并策略)

后端(Go+Gin+JSON DB):
- 简单用户管理(仅 admin,无 RBAC)+ TokenVersion 会话吊销 + 登录 IP 限流
- LDAP 接入:配置(AES 加密存储/hasPassword 脱敏)/测试连接/批量导入/登录
- 存储配置测试连接(SigV4 零依赖)+ 桶不存在自动创建
- Loki 三模式部署修复:SSH 密钥认证、3.x 配置模板、ruler 对象存储嵌套块、
  凭证解密、孤儿 deploying 状态恢复、Grafana 部署(可选默认关)+面板导入
- DaemonSet 采集纯 YAML(去 helm 依赖)+ DNS 自愈回退 ClusterIP + 集群名标签
- 日志查询代理:cluster-svc 经 kube-apiserver proxy/url 直连 + cursor 翻页
- 统一 log 命名空间;部署就绪探活;Grafana 访问地址自动记录

修复:run.sh 增量编译检测、Sanitized 脱敏补全、LokiDeployment Update 白名单、
健康检查 CreatedAt 零值等 10+ 存量 bug。

数据兼容:DB 沿用旧 lokideploy.json(新名优先);localStorage 一次性迁移;
HKDF 加密前缀保留(已加密凭证不受影响)。
2026-10-24 13:20:12 +08:00

12 KiB
Raw Blame History

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) │         <router-view>                      │
│          │         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 中 <keep-alive :include="cachedNames">
  → 刷新: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

  1. 超过 500 行的 Layout / 超过 400 行的视图页面列出,并给拆分建议(参照 07 章)
  2. keepAlive 链路是否完整(meta + defineOptions name + include)?
  3. 响应式三断点行为是否齐全?弹窗移动端宽度是否有适配?
  4. ECharts 是否按需引入?WebSocket 是否有卸载断开与重连退避?

P2

  1. 是否值得引入标签页(多工位场景)?若引入,keep-alive 联动方案确认
  2. 是否值得布局引擎化(出现第二种布局模式时)

下一篇:06-主题系统与视觉设计 —— 换肤只改变量,不引第二套样式。