2026-09-12-miniprogram-header-capsule-safe-area-design.md 7.2 KB

小程序自定义顶部栏 · 微信胶囊安全区适配(方案 B)

日期:2026-09-12
范围:miniprogram 中 navigationStyle: custom 的页面
状态:待用户审阅后进入实现计划

1. 背景与问题

体验版中,从工作台进入地磅「仪表盘」等自定义导航页时,业务顶部栏(返回、标题、右侧用户名/操作)与微信小程序原生顶栏(状态栏 + 右上角胶囊「··· / 关闭」)重叠,尤其右侧关闭按钮区域会挡住业务内容。

根因:

  1. 多数业务顶栏仅用固定 padding(如 .db-header { padding: 12px 16px }),未计入 statusBarHeight。
  2. 仓库已有 utils/safeArea.js、components/app-safe-header.vue 与 CSS 变量约定(--sq-safe-top 等),但:
    • applySafeAreaCssVars 仅写入 H5 的 document.documentElement,微信小程序端不生效;
    • app-safe-header 未被任何页面引用;
    • 地磅 .db-header、报销 .topbar、壳层 .mp-header 未统一消费同一套量纲。

用户确认采用 方案 B:顶部加高并对齐胶囊垂直中心,右侧永久预留胶囊宽度,业务可点按钮不得放入胶囊区。

2. 目标

  • 所有自定义导航页的顶栏内容(标题、返回、次要文案)落在胶囊下方/左侧安全区内,不被遮挡。
  • 量纲由运行时 API 计算,适配不同机型(刘海、胶囊位置差异)。
  • 视觉风格不变:白底列表顶栏与绿色渐变大头仍分别沿用现有 Token;只调整几何与占位。
  • 最小改动:复用已有 safeArea 基建,统一样式入口,避免每页手写高度。

3. 非目标

  • 不改系统默认导航栏页面(如协议/隐私等非 custom 页)。
  • 不调整底部 TabBar / safe-area-inset-bottom(除非顺带发现明显缺陷且与本次无关则不做)。
  • 不做顶栏视觉重设计(颜色、圆角、字号大改)。
  • 不在右侧胶囊区放置业务「关闭」「更多」按钮。

4. 量纲与 CSS 变量

沿用并落实 miniprogram/utils/safeArea.js 的计算(uni.getSystemInfoSync + uni.getMenuButtonBoundingClientRect):

变量 含义 典型算法
--sq-safe-top 状态栏高度 statusBarHeight
--sq-nav-height 导航内容行高(与胶囊垂直居中) (capsule.top - statusBarHeight) * 2 + capsule.height,下限 44
--sq-header-height 顶栏总高 safe-top + nav-height
--sq-capsule-right-safe 右侧预留宽 capsule.width + (windowWidth - capsule.right) + 水平余量,无胶囊时为 0

启动时 App.onLaunch 已调用 initGlobalSafeArea();实现时须保证指标写入 小程序可消费的样式(见 §5),不能仅依赖 H5 document。

5. 布局规则(方案 B)

5.1 白底顶栏(地磅 / 报销)

结构:

┌─────────────────────────────────────────────┐
│  状态栏占位 (--sq-safe-top)                   │
├──────────┬──────────────────┬───────────────┤
│ 返回+标题 │ 次要信息(可选)   │ 胶囊右侧空白  │
│          │                  │ (不可放按钮)  │
└──────────┴──────────────────┴───────────────┘
  ←────────── --sq-nav-height ───────────────→

约束:

  1. 总高度 = --sq-header-height;内容行垂直居中对齐胶囊。
  2. 网格或等价布局:auto | minmax(0,1fr) | var(--sq-capsule-right-safe)。
  3. 标题省略号截断;右侧列为空占位。

5.2 渐变大头(工作台 / 首页 / 待办等 .mp-header)

  • padding-top: calc(var(--sq-safe-top) + 内容上边距)(替换仅 env(safe-area-inset-top) 或写死像素)。
  • padding-right: calc(水平内边距 + var(--sq-capsule-right-safe)),保证副文案/用户信息不进入胶囊区。
  • 若该页在头部右侧放置「退出」等按钮:须落在胶囊左侧列,不得占用 --sq-capsule-right-safe 区域(工作台现有右侧用户栈需按此收紧)。

5.3 变量下发(微信端)— 既定路径

采用 「SCSS 统一吃变量 + 顶栏根节点内联注入」,不强制先改完所有页面 DOM:

  1. 新增或复用 composable / 工具函数:基于 getGlobalSafeArea() 返回四变量的 style 对象(px 字符串)。
  2. .db-header / .bx-page .topbar / .mp-header(及必要变体)在 SCSS 中改用 --sq-*;各页顶栏根节点绑定 :style="safeAreaStyle"(或等价)。
  3. app-safe-header:补齐样式与插槽后,新页优先使用;存量地磅/报销可先改 SCSS + 内联变量,不必一次性替换 DOM。
  4. applySafeAreaCssVars:保留 H5 写入 document;微信端以顶栏内联为准(不依赖 page 全局 CSS 变量穿透)。

禁止:仅增大固定 padding-top、不预留右侧(已否决的方案 C)。

6. 影响面

样式/组件 路径 动作
safeArea 工具 miniprogram/utils/safeArea.js 确保微信端可消费;必要时补小程序注入
公共顶栏 miniprogram/components/app-safe-header.vue 完善后供白底页复用,或抽出等效 SCSS
地磅顶栏 miniprogram/styles/db-theme.scss + 各地磅页 .db-header 改为方案 B 几何
报销顶栏 miniprogram/styles/bx-theme.scss .topbar 对齐同一变量(替换仅 env(safe-area-inset-top) + 写死 58px)
壳层大头 miniprogram/styles/mp-shell.scss(及 mp-biz 变体) .mp-header* 使用 safe-top + capsule-right
既有大背景头 miniprogram/styles/index.scss .sq-hero-header 核对已引用变量是否在微信端生效;无效则一并修通
UI 规范 docs/miniprogram-ui-spec-sanqiang.md §3.2 补充「自定义 Header 须适配胶囊」SHALL

页面枚举以 pages.json 中 navigationStyle: custom 为准,至少覆盖:工作台、首页、待办、地磅全链路、报销全链路、占位页。

7. 验收标准

  1. iPhone(刘海)与 Android 体验版:仪表盘返回/标题/用户名均不被胶囊遮挡。
  2. 地磅列表/详情、报销列表/表单顶栏同样不被遮挡。
  3. 工作台渐变头标题与右侧用户/退出不与胶囊重叠。
  4. H5 预览不出现异常巨大右侧空白或顶栏塌陷(无胶囊时 --sq-capsule-right-safe = 0)。
  5. 不引入新的业务按钮挤进胶囊区。

8. 风险与注意

  • getMenuButtonBoundingClientRect 在部分端或过早调用可能为空:已有默认值(safe-top≈20、nav=44、右侧 0/96);实现时保持兜底。
  • 工作台当前 mp-header--workbench 含额外 40px 偏移,改造时应用变量替换魔法数,避免双重叠加导致头部过高。
  • 规范文档与 Demo HTML 可后置同步,但代码合入前至少更新 miniprogram-ui-spec-sanqiang.md 中 Header 一小节。

9. 决策记录

  • 方案 B(加高 + 右侧预留)— 用户确认 2026-09-12。
  • 否决方案 C(只加高不预留右侧)。
  • 视觉 Token 不换色;几何统一到 --sq-* 变量。