comet_change: weapp-capsule-safearea role: technical-design canonical_spec: openspec archived-with: 2026-06-24-weapp-capsule-safearea
清道夫微信小程序所有页面均使用 navigationStyle: custom 自定义导航栏。微信小程序右上角存在系统胶囊按钮(菜单 + 关闭),在真机上会遮挡页面右上角的操作按钮(通知铃铛、新增/发布、今天、新增排班等)。
本次 change 引入全局统一的胶囊安全区组件,让 TopBar 右侧操作区自动避让胶囊。
TopBar.vue,使其右侧插槽内容自动避让胶囊。CapsuleSafeArea.vue新增 src/components/common/CapsuleSafeArea.vue。
计算微信小程序胶囊按钮所需的最小右侧安全宽度,并以 wrapper 形式给默认 slot 内容添加左侧间距。
safeWidth = screenWidth - menuButton.left + 8px
screenWidth 来自 uni.getSystemInfoSync().screenWidthmenuButton.left 来自 uni.getMenuButtonBoundingClientRect().left8px 为额外缓冲,避免按钮紧贴胶囊0,布局保持不变。screenWidth 的保守估算值兜底(约为屏幕宽度的 28%,覆盖常见 iPhone/Android 胶囊宽度)。<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">,右侧放入发布/新增按钮。ScheduleView.vue)当前已使用 TopBar,但「今天」按钮在月份切换行右侧,「新增排班」在列表标题右侧。
改造:
slot 同时放入「今天」和「新增排班」两个操作按钮,中间保留 gap-2 间距。CapsuleSafeArea 为纯展示组件,无外部状态依赖。onMounted 中计算一次,不监听窗口尺寸变化。npm run type-check,确保 TypeScript 无错误。npm run build:mp-weixin,确保微信小程序构建通过。npm run build:h5,确认非微信环境下布局与之前一致。| 风险 | 影响 | 缓解措施 |
|---|---|---|
uni.getMenuButtonBoundingClientRect() 获取失败 |
安全区宽度计算错误,可能仍遮挡 | 使用基于屏幕宽度的估算值兜底 |
| 不同机型胶囊尺寸差异大 | 固定估算值可能不足 | 优先使用动态 API,仅在失败时回退 |
| 顶部空间被压缩 | 标题可能显示不全 | 标题使用 truncate 截断 |
| 横屏/旋转 | 胶囊位置变化后安全区失效 | 本次不处理,后续单独优化 |
uni.getMenuButtonBoundingClientRect()uni.getSystemInfoSync()src/components/common/CapsuleSafeArea.vuesrc/components/common/TopBar.vuesrc/components/home/DispatchHome.vuesrc/components/home/SalesHome.vuesrc/components/home/ConstructionHome.vuesrc/components/task/TaskListView.vuesrc/components/dispatch/ScheduleView.vue