---
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
```vue
```
组件仅提供默认 slot,不暴露 props。内部使用 `:style="{ marginLeft: `${safeWidth}px` }"` 作用在包裹层上。
### `TopBar.vue` 改造
修改 `src/components/common/TopBar.vue`:
- 右侧 `` 外层自动包裹 ``。
- 页面只需正常填充 ``,无需感知胶囊安全区。
- 标题区使用 `flex-1 min-w-0 truncate`,避免左侧内容过长或右侧安全区挤压导致标题溢出。
## 页面改造
### 首页(`DispatchHome.vue` / `SalesHome.vue` / `ConstructionHome.vue`)
当前三个首页使用自定义 header:左侧头像+欢迎语,右侧通知铃铛。
改造为使用 `TopBar`:
- 左侧头像和欢迎语放入 `left slot`。
- 右侧通知铃铛放入 `right slot`。
- 保持原有白底、下边框、内边距风格。
- `TopBar` 的 `title` 可置空或使用页面标题(如「首页」),本次保持顶部以用户欢迎语为主,标题置空。
### 任务页(`TaskListView.vue`)
当前顶部是自定义标题栏 + 筛选标签,右上角是「发布/新增」按钮。
改造:
- 顶部标题栏改为 ``,右侧放入发布/新增按钮。
- 筛选标签保留在 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`