2026-06-24-weapp-capsule-safearea-design.md 5.4 KB


comet_change: weapp-capsule-safearea role: technical-design canonical_spec: openspec archived-with: 2026-06-24-weapp-capsule-safearea

status: final

微信小程序胶囊安全区适配设计

背景

清道夫微信小程序所有页面均使用 navigationStyle: custom 自定义导航栏。微信小程序右上角存在系统胶囊按钮(菜单 + 关闭),在真机上会遮挡页面右上角的操作按钮(通知铃铛、新增/发布、今天、新增排班等)。

本次 change 引入全局统一的胶囊安全区组件,让 TopBar 右侧操作区自动避让胶囊。

目标

  • 提供可在任何页面/组件复用的胶囊安全区方案。
  • 改造 TopBar.vue,使其右侧插槽内容自动避让胶囊。
  • 修复首页、任务页、排班页的胶囊遮挡问题。
  • 保持 H5/App 等非微信环境下的正常显示(graceful fallback)。

非目标

  • 不调整底部 tabBar 结构。
  • 不改页面整体布局风格。
  • 不处理胶囊以外的系统 UI(如灵动岛、底部横条)。
  • 不做横屏/旋转后的实时安全区重算(后续可单独优化)。

组件设计

CapsuleSafeArea.vue

新增 src/components/common/CapsuleSafeArea.vue

职责

计算微信小程序胶囊按钮所需的最小右侧安全宽度,并以 wrapper 形式给默认 slot 内容添加左侧间距。

算法

safeWidth = screenWidth - menuButton.left + 8px
  • screenWidth 来自 uni.getSystemInfoSync().screenWidth
  • menuButton.left 来自 uni.getMenuButtonBoundingClientRect().left
  • 8px 为额外缓冲,避免按钮紧贴胶囊

平台回退

  • 微信小程序:使用上述动态计算值。
  • H5 / App / 其他平台:返回 0,布局保持不变。
  • API 调用失败:使用基于 screenWidth 的保守估算值兜底(约为屏幕宽度的 28%,覆盖常见 iPhone/Android 胶囊宽度)。

API

<template>
  <CapsuleSafeArea>
    <!-- 右侧操作按钮 -->
  </CapsuleSafeArea>
</template>

组件仅提供默认 slot,不暴露 props。内部使用 :style="{ marginLeft:${safeWidth}px}" 作用在包裹层上。

TopBar.vue 改造

修改 src/components/common/TopBar.vue

  • 右侧 <slot name="right" /> 外层自动包裹 <CapsuleSafeArea>
  • 页面只需正常填充 <template #right>,无需感知胶囊安全区。
  • 标题区使用 flex-1 min-w-0 truncate,避免左侧内容过长或右侧安全区挤压导致标题溢出。

页面改造

首页(DispatchHome.vue / SalesHome.vue / ConstructionHome.vue

当前三个首页使用自定义 header:左侧头像+欢迎语,右侧通知铃铛。

改造为使用 TopBar

  • 左侧头像和欢迎语放入 left slot
  • 右侧通知铃铛放入 right slot
  • 保持原有白底、下边框、内边距风格。
  • TopBartitle 可置空或使用页面标题(如「首页」),本次保持顶部以用户欢迎语为主,标题置空。

任务页(TaskListView.vue

当前顶部是自定义标题栏 + 筛选标签,右上角是「发布/新增」按钮。

改造:

  • 顶部标题栏改为 <TopBar :title="pageTitle">,右侧放入发布/新增按钮。
  • 筛选标签保留在 TopBar 下方,不受安全区影响。
  • 保留底部的悬浮「+」按钮(仅在非调度端角色显示)。

排班页(ScheduleView.vue

当前已使用 TopBar,但「今天」按钮在月份切换行右侧,「新增排班」在列表标题右侧。

改造:

  • TopBar 标题保持「排班管理」。
  • 右侧 slot 同时放入「今天」和「新增排班」两个操作按钮,中间保留 gap-2 间距。
  • 月份切换行只保留左侧月份导航,右侧清空。

数据流与状态

  • CapsuleSafeArea 为纯展示组件,无外部状态依赖。
  • 安全宽度在 onMounted 中计算一次,不监听窗口尺寸变化。
  • 计算结果通过响应式变量绑定到 style,无全局 store 或事件总线。

测试策略

  1. 类型检查npm run type-check,确保 TypeScript 无错误。
  2. 构建验证npm run build:mp-weixin,确保微信小程序构建通过。
  3. 真机/模拟器验证
    • 检查首页右上角通知铃铛未被胶囊遮挡。
    • 检查任务页右上角发布/新增按钮可点击。
    • 检查排班页「今天」「新增排班」均未被遮挡。
  4. H5 回归npm run build:h5,确认非微信环境下布局与之前一致。

风险与回退

风险 影响 缓解措施
uni.getMenuButtonBoundingClientRect() 获取失败 安全区宽度计算错误,可能仍遮挡 使用基于屏幕宽度的估算值兜底
不同机型胶囊尺寸差异大 固定估算值可能不足 优先使用动态 API,仅在失败时回退
顶部空间被压缩 标题可能显示不全 标题使用 truncate 截断
横屏/旋转 胶囊位置变化后安全区失效 本次不处理,后续单独优化

依赖

  • uni.getMenuButtonBoundingClientRect()
  • uni.getSystemInfoSync()
  • 无新增 npm 依赖,无后端接口变更。

受影响文件

  • 新增:src/components/common/CapsuleSafeArea.vue
  • 修改:src/components/common/TopBar.vue
  • 修改:src/components/home/DispatchHome.vue
  • 修改:src/components/home/SalesHome.vue
  • 修改:src/components/home/ConstructionHome.vue
  • 修改:src/components/task/TaskListView.vue
  • 修改:src/components/dispatch/ScheduleView.vue