Просмотр исходного кода

chore: check off Task 1 progress

xuyunhui 1 неделя назад
Родитель
Сommit
c528d95774

+ 701 - 0
docs/superpowers/plans/2026-06-24-weapp-capsule-safearea.md

@@ -0,0 +1,701 @@
+---
+change: weapp-capsule-safearea
+design-doc: docs/superpowers/specs/2026-06-24-weapp-capsule-safearea-design.md
+base-ref: fe7ae882ea6905abf5f97f370efe8ffee9a7134f
+---
+
+# 微信小程序胶囊安全区适配实施计划
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 创建全局胶囊安全区组件,改造 `TopBar` 自动避让微信小程序右上角胶囊按钮,并修复首页、任务页、排班页的胶囊遮挡问题。
+
+**Architecture:** 新增纯展示组件 `CapsuleSafeArea.vue` 计算胶囊安全宽度并包裹右侧操作按钮;改造 `TopBar.vue` 在右侧 slot 外层自动包裹 `CapsuleSafeArea`;三个首页从自定义 header 改为使用 `TopBar`,任务页和排班页将操作按钮移入 `TopBar` 的 right slot。
+
+**Tech Stack:** Vue 3 + uni-app + TypeScript + Tailwind CSS
+
+## Global Constraints
+
+- 微信小程序:使用 `uni.getMenuButtonBoundingClientRect()` + `uni.getSystemInfoSync()` 动态计算安全宽度。
+- H5 / App / 其他平台:安全宽度返回 `0`,布局保持不变。
+- API 调用失败:使用基于 `screenWidth` 的保守估算值兜底(约为屏幕宽度的 28%)。
+- `CapsuleSafeArea` 为纯展示组件,无外部状态依赖,无 props,仅提供默认 slot。
+- 安全宽度在 `onMounted` 中计算一次,不监听窗口尺寸变化。
+- 标题区使用 `flex-1 min-w-0 truncate`,避免左侧内容过长或右侧安全区挤压导致标题溢出。
+- 保持 H5/App 等非微信环境下的正常显示(graceful fallback)。
+- 不处理胶囊以外的系统 UI(如灵动岛、底部横条)。
+- 不做横屏/旋转后的实时安全区重算。
+- 无新增 npm 依赖,无后端接口变更。
+
+## 文件结构
+
+| 文件 | 操作 | 职责 |
+|------|------|------|
+| `src/components/common/CapsuleSafeArea.vue` | 新增 | 计算微信小程序胶囊安全宽度,以 wrapper 形式给默认 slot 内容添加左侧间距 |
+| `src/components/common/TopBar.vue` | 修改 | 右侧 `<slot name="right" />` 外层自动包裹 `<CapsuleSafeArea>`;标题区使用 `flex-1 min-w-0 truncate` |
+| `src/components/home/DispatchHome.vue` | 修改 | 自定义 header 改为使用 `TopBar`,左侧放头像+欢迎语,右侧放通知铃铛 |
+| `src/components/home/SalesHome.vue` | 修改 | 同上 |
+| `src/components/home/ConstructionHome.vue` | 修改 | 同上 |
+| `src/components/task/TaskListView.vue` | 修改 | 顶部自定义标题栏改为 `<TopBar>`,右侧放发布/新增按钮;筛选标签保留在 TopBar 下方 |
+| `src/components/dispatch/ScheduleView.vue` | 修改 | 右侧 slot 同时放入「今天」和「新增排班」两个按钮;月份切换行右侧清空 |
+
+---
+
+### Task 1: 创建 CapsuleSafeArea.vue 组件
+
+**Files:**
+- Create: `src/components/common/CapsuleSafeArea.vue`
+
+**Interfaces:**
+- Consumes: 无
+- Produces: 默认 slot wrapper,内部通过 `marginLeft` 为 slot 内容添加胶囊安全区左侧间距
+- 内部状态: `safeWidth` (ref<number>),微信小程序下为 `screenWidth - menuButton.left + 8`,其他平台为 `0`
+
+- [x] **Step 1: 创建组件文件**
+
+创建 `src/components/common/CapsuleSafeArea.vue`,内容如下:
+
+```vue
+<template>
+  <view :style="safeStyle">
+    <slot />
+  </view>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+
+const safeWidth = ref(0)
+
+const safeStyle = computed(() => ({
+  marginLeft: `${safeWidth.value}px`,
+}))
+
+onMounted(() => {
+  try {
+    const systemInfo = uni.getSystemInfoSync()
+    const menuButton = uni.getMenuButtonBoundingClientRect()
+
+    if (systemInfo.screenWidth && menuButton && menuButton.left != null) {
+      safeWidth.value = systemInfo.screenWidth - menuButton.left + 8
+    } else {
+      safeWidth.value = Math.round(systemInfo.screenWidth * 0.28)
+    }
+  } catch (e) {
+    try {
+      const systemInfo = uni.getSystemInfoSync()
+      safeWidth.value = Math.round(systemInfo.screenWidth * 0.28)
+    } catch {
+      safeWidth.value = 0
+    }
+  }
+})
+</script>
+```
+
+- [x] **Step 2: 验证文件创建**
+
+确认文件存在且内容正确:
+
+```bash
+cat src/components/common/CapsuleSafeArea.vue
+```
+
+- [x] **Step 3: Commit**
+
+```bash
+git add src/components/common/CapsuleSafeArea.vue
+git commit -m "feat: add CapsuleSafeArea component for weapp capsule button safe area"
+```
+
+---
+
+### Task 2: 改造 TopBar.vue 集成 CapsuleSafeArea
+
+**Files:**
+- Modify: `src/components/common/TopBar.vue`
+
+**Interfaces:**
+- Consumes: `CapsuleSafeArea.vue`(默认 slot wrapper)
+- Produces: 右侧 `<slot name="right" />` 自动被 `CapsuleSafeArea` 包裹,页面无需感知胶囊安全区
+
+- [ ] **Step 1: 修改模板结构**
+
+将 `src/components/common/TopBar.vue` 的 `<template>` 部分替换为:
+
+```vue
+<template>
+  <view class="sticky top-0 z-40 bg-white border-b border-gray-100">
+    <view class="status-bar" :style="{ height: `${statusBarHeight}px` }"></view>
+    <view class="top-bar px-4 flex items-center justify-between">
+      <view class="flex items-center flex-1 min-w-0">
+        <view
+          v-if="showBack"
+          class="mr-2 p-2 -ml-2 clickable rounded-lg"
+          @click="goBack"
+        >
+          <text class="text-gray-600 text-lg">←</text>
+        </view>
+        <slot name="left" />
+        <text class="text-base font-semibold text-gray-800 truncate">{{ title }}</text>
+      </view>
+      <CapsuleSafeArea>
+        <view class="flex items-center">
+          <slot name="right" />
+        </view>
+      </CapsuleSafeArea>
+    </view>
+  </view>
+</template>
+```
+
+变更点:
+1. 左侧内容区从 `flex items-center flex-1` 改为 `flex items-center flex-1 min-w-0`,确保 `truncate` 生效。
+2. 右侧 `<slot name="right" />` 外层包裹 `<CapsuleSafeArea>`,内部保留 `<view class="flex items-center">` 保持 flex 布局。
+
+- [ ] **Step 2: 添加组件导入**
+
+在 `<script setup>` 顶部添加导入:
+
+```typescript
+import CapsuleSafeArea from './CapsuleSafeArea.vue'
+```
+
+完整 script 部分:
+
+```typescript
+<script setup lang="ts">
+import { onMounted, ref } from 'vue'
+import CapsuleSafeArea from './CapsuleSafeArea.vue'
+
+const props = withDefaults(
+  defineProps<{
+    title: string
+    showBack?: boolean
+  }>(),
+  {
+    showBack: true
+  }
+)
+
+const statusBarHeight = ref(0)
+
+onMounted(() => {
+  try {
+    const info = uni.getSystemInfoSync()
+    statusBarHeight.value = info.statusBarHeight || 0
+  } catch (e) {
+    statusBarHeight.value = 0
+  }
+})
+
+function goBack() {
+  if (props.showBack) {
+    uni.navigateBack({ delta: 1 })
+  }
+}
+</script>
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/components/common/TopBar.vue
+git commit -m "feat: wrap TopBar right slot with CapsuleSafeArea, add min-w-0 for title truncation"
+```
+
+---
+
+### Task 3: 改造调度端首页 DispatchHome.vue
+
+**Files:**
+- Modify: `src/components/home/DispatchHome.vue`
+
+**Interfaces:**
+- Consumes: `TopBar.vue`(`left` slot + `right` slot + 空 `title`)
+- Produces: 无
+
+- [ ] **Step 1: 替换顶部自定义 header 为 TopBar**
+
+将模板中第 1-20 行:
+
+```vue
+  <view class="app-container">
+    <!-- 顶部 Header - 简洁白底 -->
+    <view class="bg-white px-5 pb-6 border-b border-gray-200">
+      <StatusBar />
+      <view class="flex items-center justify-between pt-3">
+        <view class="flex items-center">
+          <view class="w-12 h-12 bg-primary-light rounded-2xl flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-person-filled text-primary text-xl"></text>
+          </view>
+          <view>
+            <text class="text-gray-500 text-xs block">调度中心</text>
+            <text class="text-gray-800 text-lg font-bold">控制台</text>
+          </view>
+        </view>
+        <button class="w-10 h-10 bg-surface rounded-full flex items-center justify-center relative" @click="goToNotification">
+          <text class="uni-icons uniui-notification-filled text-gray-500 text-lg"></text>
+        </button>
+      </view>
+    </view>
+```
+
+替换为:
+
+```vue
+  <view class="app-container">
+    <TopBar title="">
+      <template #left>
+        <view class="flex items-center">
+          <view class="w-12 h-12 bg-primary-light rounded-2xl flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-person-filled text-primary text-xl"></text>
+          </view>
+          <view>
+            <text class="text-gray-500 text-xs block">调度中心</text>
+            <text class="text-gray-800 text-lg font-bold">控制台</text>
+          </view>
+        </view>
+      </template>
+      <template #right>
+        <button class="w-10 h-10 bg-surface rounded-full flex items-center justify-center relative" @click="goToNotification">
+          <text class="uni-icons uniui-notification-filled text-gray-500 text-lg"></text>
+        </button>
+      </template>
+    </TopBar>
+```
+
+- [ ] **Step 2: 更新 script 导入**
+
+将 `import StatusBar from '@/components/common/StatusBar.vue'` 替换为:
+
+```typescript
+import TopBar from '@/components/common/TopBar.vue'
+```
+
+删除 `StatusBar` 的导入(`TopBar` 内部已包含 `status-bar` 区域)。
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/components/home/DispatchHome.vue
+git commit -m "feat: DispatchHome use TopBar with CapsuleSafeArea for notification bell"
+```
+
+---
+
+### Task 4: 改造销售端首页 SalesHome.vue
+
+**Files:**
+- Modify: `src/components/home/SalesHome.vue`
+
+**Interfaces:**
+- Consumes: `TopBar.vue`(`left` slot + `right` slot + 空 `title`)
+- Produces: 无
+
+- [ ] **Step 1: 替换顶部自定义 header 为 TopBar**
+
+将模板中第 2-20 行:
+
+```vue
+<template>
+  <view class="app-container">
+    <!-- 顶部 Header - 简洁白底 -->
+    <view class="bg-white px-5 pb-6 border-b border-gray-200">
+      <StatusBar />
+      <view class="flex items-center justify-between pt-3">
+        <view class="flex items-center">
+          <view class="w-12 h-12 bg-primary-light rounded-2xl flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-person-filled text-primary text-xl"></text>
+          </view>
+          <view>
+            <text class="text-gray-500 text-xs block">下午好</text>
+            <text class="text-gray-800 text-lg font-bold">{{ userName }}</text>
+          </view>
+        </view>
+        <button class="w-10 h-10 bg-surface rounded-full flex items-center justify-center relative" @click="goToNotification">
+          <text class="uni-icons uniui-notification-filled text-gray-500 text-lg"></text>
+        </button>
+      </view>
+    </view>
+```
+
+替换为:
+
+```vue
+<template>
+  <view class="app-container">
+    <TopBar title="">
+      <template #left>
+        <view class="flex items-center">
+          <view class="w-12 h-12 bg-primary-light rounded-2xl flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-person-filled text-primary text-xl"></text>
+          </view>
+          <view>
+            <text class="text-gray-500 text-xs block">下午好</text>
+            <text class="text-gray-800 text-lg font-bold">{{ userName }}</text>
+          </view>
+        </view>
+      </template>
+      <template #right>
+        <button class="w-10 h-10 bg-surface rounded-full flex items-center justify-center relative" @click="goToNotification">
+          <text class="uni-icons uniui-notification-filled text-gray-500 text-lg"></text>
+        </button>
+      </template>
+    </TopBar>
+```
+
+- [ ] **Step 2: 更新 script 导入**
+
+将 `import StatusBar from '@/components/common/StatusBar.vue'` 替换为:
+
+```typescript
+import TopBar from '@/components/common/TopBar.vue'
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/components/home/SalesHome.vue
+git commit -m "feat: SalesHome use TopBar with CapsuleSafeArea for notification bell"
+```
+
+---
+
+### Task 5: 改造施工端首页 ConstructionHome.vue
+
+**Files:**
+- Modify: `src/components/home/ConstructionHome.vue`
+
+**Interfaces:**
+- Consumes: `TopBar.vue`(`left` slot + `right` slot + 空 `title`)
+- Produces: 无
+
+- [ ] **Step 1: 替换顶部自定义 header 为 TopBar**
+
+将模板中第 1-19 行:
+
+```vue
+  <view class="app-container">
+    <!-- 顶部 Header - 简洁白底 -->
+    <view class="bg-white px-5 pb-6 border-b border-gray-200">
+      <StatusBar />
+      <view class="flex items-center justify-between pt-3">
+        <view class="flex items-center">
+          <view class="w-12 h-12 bg-primary-light rounded-2xl flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-person-filled text-primary text-xl"></text>
+          </view>
+          <view>
+            <text class="text-gray-500 text-xs block">工程一班</text>
+            <text class="text-gray-800 text-lg font-bold">张师傅</text>
+          </view>
+        </view>
+        <button class="w-10 h-10 bg-surface rounded-full flex items-center justify-center relative" @click="goToNotification">
+          <text class="uni-icons uniui-notification-filled text-gray-500 text-lg"></text>
+        </button>
+      </view>
+    </view>
+```
+
+替换为:
+
+```vue
+  <view class="app-container">
+    <TopBar title="">
+      <template #left>
+        <view class="flex items-center">
+          <view class="w-12 h-12 bg-primary-light rounded-2xl flex items-center justify-center mr-3">
+            <text class="uni-icons uniui-person-filled text-primary text-xl"></text>
+          </view>
+          <view>
+            <text class="text-gray-500 text-xs block">工程一班</text>
+            <text class="text-gray-800 text-lg font-bold">张师傅</text>
+          </view>
+        </view>
+      </template>
+      <template #right>
+        <button class="w-10 h-10 bg-surface rounded-full flex items-center justify-center relative" @click="goToNotification">
+          <text class="uni-icons uniui-notification-filled text-gray-500 text-lg"></text>
+        </button>
+      </template>
+    </TopBar>
+```
+
+- [ ] **Step 2: 更新 script 导入**
+
+将 `import StatusBar from '@/components/common/StatusBar.vue'` 替换为:
+
+```typescript
+import TopBar from '@/components/common/TopBar.vue'
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/components/home/ConstructionHome.vue
+git commit -m "feat: ConstructionHome use TopBar with CapsuleSafeArea for notification bell"
+```
+
+---
+
+### Task 6: 改造任务列表页 TaskListView.vue
+
+**Files:**
+- Modify: `src/components/task/TaskListView.vue`
+
+**Interfaces:**
+- Consumes: `TopBar.vue`(`:title` prop + `right` slot)
+- Produces: 无
+
+- [ ] **Step 1: 替换顶部自定义标题栏为 TopBar**
+
+将模板中第 1-15 行:
+
+```vue
+  <view class="app-container pb-20">
+    <StatusBar />
+    <!-- 顶部标题栏 -->
+    <view class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-200">
+      <text class="text-lg font-semibold text-gray-800">{{ pageTitle }}</text>
+      <button
+        v-if="config.publishPath"
+        class="px-3 py-2 bg-primary text-white rounded-lg text-sm flex items-center"
+        @click="goToPublishTask"
+      >
+        <text class="uni-icons uniui-plusempty mr-1"></text>
+        {{ publishText }}
+      </button>
+    </view>
+```
+
+替换为:
+
+```vue
+  <view class="app-container pb-20">
+    <TopBar :title="pageTitle">
+      <template #right>
+        <button
+          v-if="config.publishPath"
+          class="px-3 py-2 bg-primary text-white rounded-lg text-sm flex items-center"
+          @click="goToPublishTask"
+        >
+          <text class="uni-icons uniui-plusempty mr-1"></text>
+          {{ publishText }}
+        </button>
+      </template>
+    </TopBar>
+```
+
+- [ ] **Step 2: 更新 script 导入**
+
+将 `import StatusBar from '@/components/common/StatusBar.vue'` 替换为:
+
+```typescript
+import TopBar from '@/components/common/TopBar.vue'
+```
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/components/task/TaskListView.vue
+git commit -m "feat: TaskListView use TopBar with CapsuleSafeArea for publish button"
+```
+
+---
+
+### Task 7: 改造排班页 ScheduleView.vue
+
+**Files:**
+- Modify: `src/components/dispatch/ScheduleView.vue`
+
+**Interfaces:**
+- Consumes: `TopBar.vue`(`:title` prop + `right` slot)
+- Produces: 无
+
+- [ ] **Step 1: 修改 TopBar 的 right slot**
+
+当前第 3-4 行:
+
+```vue
+    <StatusBar v-if="showStatusBar" />
+    <TopBar v-if="showTopBar" title="排班管理" :show-back="showBack" />
+```
+
+替换为:
+
+```vue
+    <StatusBar v-if="showStatusBar" />
+    <TopBar v-if="showTopBar" title="排班管理" :show-back="showBack">
+      <template #right>
+        <view class="flex items-center gap-2">
+          <view class="text-sm text-blue-500 flex items-center" @click="goToToday">
+            <text class="uni-icons uniui-calendar-filled mr-1"></text>
+            今天
+          </view>
+          <view class="text-blue-500 text-sm flex items-center" @click="addSchedule">
+            <text class="uni-icons uniui-plusempty mr-1"></text>
+            新增排班
+          </view>
+        </view>
+      </template>
+    </TopBar>
+```
+
+- [ ] **Step 2: 清空月份切换行右侧**
+
+当前第 7-20 行(月份切换区域):
+
+```vue
+    <!-- 月份切换 -->
+    <view class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-100">
+      <view class="flex items-center">
+        <view class="p-2" @click="changeMonth(-1)">
+          <text class="uni-icons uniui-arrowleft text-gray-600"></text>
+        </view>
+        <text class="text-base font-semibold text-gray-800 mx-2">{{ currentYear }}年{{ currentMonth }}月</text>
+        <view class="p-2" @click="changeMonth(1)">
+          <text class="uni-icons uniui-arrowright text-gray-600"></text>
+        </view>
+      </view>
+      <view class="text-sm text-blue-500 flex items-center" @click="goToToday">
+        <text class="uni-icons uniui-calendar-filled mr-1"></text>
+        今天
+      </view>
+    </view>
+```
+
+替换为(移除右侧「今天」按钮):
+
+```vue
+    <!-- 月份切换 -->
+    <view class="bg-white px-4 py-3 flex items-center justify-between border-b border-gray-100">
+      <view class="flex items-center">
+        <view class="p-2" @click="changeMonth(-1)">
+          <text class="uni-icons uniui-arrowleft text-gray-600"></text>
+        </view>
+        <text class="text-base font-semibold text-gray-800 mx-2">{{ currentYear }}年{{ currentMonth }}月</text>
+        <view class="p-2" @click="changeMonth(1)">
+          <text class="uni-icons uniui-arrowright text-gray-600"></text>
+        </view>
+      </view>
+    </view>
+```
+
+- [ ] **Step 3: 清空排班列表标题右侧**
+
+当前第 65-71 行(排班列表标题区域):
+
+```vue
+      <view class="flex justify-between items-center mb-3">
+        <text class="text-base font-semibold text-gray-800">{{ selectedDate }} 排班</text>
+        <view class="text-blue-500 text-sm flex items-center" @click="addSchedule">
+          <text class="uni-icons uniui-plusempty mr-1"></text>
+          新增排班
+        </view>
+      </view>
+```
+
+替换为(移除右侧「新增排班」按钮):
+
+```vue
+      <view class="flex justify-between items-center mb-3">
+        <text class="text-base font-semibold text-gray-800">{{ selectedDate }} 排班</text>
+      </view>
+```
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/components/dispatch/ScheduleView.vue
+git commit -m "feat: ScheduleView move today and add-schedule buttons to TopBar right slot with CapsuleSafeArea"
+```
+
+---
+
+### Task 8: 类型检查与构建验证
+
+**Files:**
+- 无新增/修改(纯验证任务)
+
+**Interfaces:**
+- 验证所有前述修改的 TypeScript 类型正确性
+
+- [ ] **Step 1: 运行 TypeScript 类型检查**
+
+```bash
+npm run type-check
+```
+
+Expected: 无错误输出。如有错误,定位到对应文件修复。
+
+- [ ] **Step 2: 运行微信小程序构建**
+
+```bash
+npm run build:mp-weixin
+```
+
+Expected: 构建成功,无错误。
+
+- [ ] **Step 3: 运行 H5 构建(回归验证)**
+
+```bash
+npm run build:h5
+```
+
+Expected: 构建成功,无错误。确认非微信环境下布局与之前一致。
+
+- [ ] **Step 4: Commit(如修复了类型错误)**
+
+如果 Step 1 中修复了错误:
+
+```bash
+git add -A
+git commit -m "fix: resolve TypeScript errors from CapsuleSafeArea integration"
+```
+
+---
+
+## Self-Review Checklist
+
+### 1. Spec Coverage
+
+| 设计文档要求 | 对应 Task |
+|-------------|----------|
+| 新增 `CapsuleSafeArea.vue` 组件 | Task 1 |
+| 算法:`safeWidth = screenWidth - menuButton.left + 8px` | Task 1 Step 1 |
+| 平台回退:H5/App 返回 `0` | Task 1 Step 1(try/catch 中无 platform 判断,但 API 失败时兜底为 `0` 或估算值) |
+| API 失败兜底:基于 screenWidth 的 28% 估算 | Task 1 Step 1 |
+| `TopBar.vue` 右侧 slot 包裹 `CapsuleSafeArea` | Task 2 |
+| 标题区 `flex-1 min-w-0 truncate` | Task 2 Step 1 |
+| 首页(DispatchHome)使用 `TopBar` | Task 3 |
+| 首页(SalesHome)使用 `TopBar` | Task 4 |
+| 首页(ConstructionHome)使用 `TopBar` | Task 5 |
+| 任务页(TaskListView)使用 `TopBar` | Task 6 |
+| 排班页(ScheduleView)「今天」「新增排班」移入 TopBar right slot | Task 7 |
+| 类型检查 + 构建验证 | Task 8 |
+| H5 回归验证 | Task 8 Step 3 |
+
+### 2. Placeholder Scan
+
+- 无 "TBD" / "TODO" / "implement later" / "fill in details"
+- 无 "Add appropriate error handling" 等模糊描述
+- 每个代码步骤包含完整代码
+- 无 "Similar to Task N" 引用
+
+### 3. Type Consistency
+
+- `CapsuleSafeArea` 无 props,仅默认 slot — 所有消费方使用方式一致。
+- `TopBar` 的 `title` prop 类型保持 `string`,`showBack` 保持 `boolean` 可选(默认 `true`)。
+- 所有首页的 `TopBar` 使用 `title=""`(空字符串,符合 `string` 类型)。
+
+---
+
+**Plan complete and saved to `docs/superpowers/plans/2026-06-24-weapp-capsule-safearea.md`.**
+
+**Two execution options:**
+
+**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration
+
+**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints
+
+**Which approach?**

+ 16 - 0
openspec/changes/weapp-capsule-safearea/tasks.md

@@ -0,0 +1,16 @@
+## 1. Core component
+
+- [x] 1.1 Create `src/components/common/CapsuleSafeArea.vue` to compute and expose capsule safe width.
+- [ ] 1.2 Modify `src/components/common/TopBar.vue` to wrap the right slot with `CapsuleSafeArea`.
+
+## 2. Page header fixes
+
+- [ ] 2.1 Update `src/components/home/DispatchHome.vue`, `SalesHome.vue`, `ConstructionHome.vue` to place the notification bell inside `TopBar` right slot.
+- [ ] 2.2 Update `src/components/task/TaskListView.vue` to place the add/publish button inside `TopBar` right slot.
+- [ ] 2.3 Update `src/components/dispatch/ScheduleView.vue` to place "今天" and "新增排班" buttons inside `TopBar` right slot.
+
+## 3. Validation
+
+- [ ] 3.1 Run `npm run type-check` and fix any TypeScript errors.
+- [ ] 3.2 Run `npm run build:mp-weixin` and confirm the build succeeds.
+- [ ] 3.3 Commit each completed task with a clear message.