|
|
@@ -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?**
|