---
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
<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`。
- 保持原有白底、下边框、内边距风格。
- `TopBar` 的 `title` 可置空或使用页面标题（如「首页」），本次保持顶部以用户欢迎语为主，标题置空。

### 任务页（`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`
