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

fix(frontend): 修复首页每日任务打卡后卡片不消失

- sleep/exercise/diet 三个打卡页:打卡成功分支在 navigateBack 之前补调 completeTaskApi(taskId, memberId, ''),把关联每日任务显式置为 completed(后端 /api/tasks/{id}/complete 对每日进度型任务仅累计进度不保证完成)
- index-home:loadFamilyData 的 catch 路径补调 loadTodayTasks(),即使获取家庭成员列表失败,从打卡页返回后也能刷新任务列表,确保已完成任务卡片消失
- 统一从 ../../utils/api.js 引入 completeTask as completeTaskApi(与 index-home 别名一致)
- 新增修复报告 docs/superpowers/plans/verify/fix-homepage-task-close.md
zxyj-web 2 недель назад
Родитель
Сommit
15e64792ac

+ 6 - 1
cfc-frontend/pages/growth/diet-checkin.vue

@@ -159,7 +159,7 @@
 <script>
 import config from '../../config'
 import { parseDate } from '../../utils/format.js'
-import { sttTranscribe, recognizeDietFood } from '../../utils/api.js'
+import { sttTranscribe, recognizeDietFood, getAllQuotaStatus, completeTask as completeTaskApi } from '../../utils/api.js'
 import shareMixin from '../../components/share-mixin.js'
 import { pickImage } from '../../utils/image-picker.js'
 import TagChip from '../../components/TagChip.vue'
@@ -476,6 +476,11 @@ success: function(res) {
               self.submitting = false
               if (res.data && res.data.code === 200) {
                 uni.showToast({ title: self.taskId ? '打卡成功,任务已完成' : '打卡成功', icon: 'success' })
+                // 联动任务(来自首页「我的任务」?taskId=):显式将对应每日任务标记完成,
+                // 后端按 taskId 置为 completed,返回首页后卡片才会消失
+                if (self.taskId) {
+                  completeTaskApi(self.taskId, self.memberId || null, '').catch(function () {})
+                }
                 setTimeout(function() {
                   uni.navigateBack()
                 }, 800)

+ 6 - 1
cfc-frontend/pages/growth/exercise-checkin.vue

@@ -151,7 +151,7 @@
 <script>
 import config from '../../config'
 import { parseDate } from '../../utils/format.js'
-import { sttTranscribe } from '../../utils/api.js'
+import { sttTranscribe, completeTask as completeTaskApi } from '../../utils/api.js'
 import { pickImage } from '../../utils/image-picker.js'
 import TagChip from '../../components/TagChip.vue'
 import fontScaleMixin from '@/mixins/font-scale.js'
@@ -474,6 +474,11 @@ export default {
             if (res.data && res.data.code === 200) {
               uni.showToast({ title: self.taskId ? '打卡成功,任务已完成' : '打卡成功', icon: 'success' })
               self.syncedStepText = ''
+              // 联动任务(来自首页「我的任务」?taskId=):显式将对应每日任务标记完成,
+              // 后端按 taskId 置为 completed,返回首页后卡片才会消失
+              if (self.taskId) {
+                completeTaskApi(self.taskId, self.memberId || null, '').catch(function () {})
+              }
               setTimeout(function() {
                 uni.navigateBack()
               }, 800)

+ 6 - 0
cfc-frontend/pages/growth/sleep-checkin.vue

@@ -160,6 +160,7 @@
 import config from '../../config'
 import { formatDate } from '../../utils/format.js'
 import { BrandColors } from '../../utils/brand-colors.js'
+import { completeTask as completeTaskApi } from '../../utils/api.js'
 import fontScaleMixin from '@/mixins/font-scale.js'
 
 export default {
@@ -443,6 +444,11 @@ export default {
               list.unshift(created)
               self.history = list.slice(0, 5)
             }
+            // 联动任务(来自首页「我的任务」?taskId=):显式将对应每日任务标记完成,
+            // 后端按 taskId 置为 completed,返回首页后卡片才会消失
+            if (self.taskId) {
+              completeTaskApi(self.taskId, self.memberId || null, '').catch(function () {})
+            }
             setTimeout(function () {
               uni.navigateBack()
             }, 800)

+ 2 - 0
cfc-frontend/pages/index-home/index.vue

@@ -1010,6 +1010,8 @@ title: '添加家庭成员',
         self.children = []
         self.childrenChecked = true
         self.membersChecked = true
+        // 即使获取成员列表失败,仍刷新今日任务,确保从打卡页返回后已完成的任务卡片能消失
+        self.loadTodayTasks()
       })
     },
     // loadChildren 已废弃:loadFamilyData 统一调用 getFamilyMemberList 一次,

+ 73 - 0
docs/superpowers/plans/verify/fix-homepage-task-close.md

@@ -0,0 +1,73 @@
+# 首页每日任务卡未关闭 Bug 修复报告
+
+## 1. 根因确认
+
+| 根因项 | 确认结果 | 说明 |
+|---|---|---|
+| **A. 首页仅跳转,不标记完成** | 确认 | `pages/index-home/index.vue` `quickCompleteTask()` 对联动任务(actionType 含 diet/sleep/exercise)仅调用 `navigateByLinkedAction(task)` 通过 `?taskId=` 跳转打卡页,未在跳转前后调用 `completeTaskApi` 将每日任务标记完成。 |
+| **B. 打卡页成功分支未调 completeTaskApi** | 确认 | `sleep-checkin.vue` / `exercise-checkin.vue` / `diet-checkin.vue` 的打卡成功分支仅 `showToast` + `uni.navigateBack()`,未调用 `completeTaskApi(taskId, memberId, '')` 把对应每日任务标记完成。 |
+| **C. 后端任务回写对“每日进度型”任务不足以完成** | 独立核实 | `HealthSleepController.create` / `HealthExerciseController.create` / `HealthMealController.create` 的任务回写挂钩:若关联任务为 `isDailyProgress=1`(每日进度型,taskKey=SLEEP/DIET/EXERCISE)仅调用 `taskService.updateProgress(userId, memberId, taskKey, 1)` 累计进度,**若目标进度>1 不会在一次打卡后直接置 completed**;非进度型才会直接 `completeTask`。故前端必须显式调 `completeTaskApi` 保证每日任务卡片能被关闭。 |
+| **D. 后端完成接口可用** | 独立核实 | `TaskController.completeTask` (`POST /api/tasks/{id}/complete`),接收 `memberId`(缺省则取 JWT 的 `currentMemberId`),内部 `taskService.completeTask(id, memberId, photoUrl, content, contentType)` 将任务置为 completed。能按 taskId 正确完成任务,无需改后端。 |
+| **E. 首页返回刷新** | 部分已有/已补强 | `index-home` `onShow` → `_refreshOnShow()` → `loadFamilyData()` → `.then()` 内 `loadTodayTasks()` 可在返回时刷新任务列表;但 `loadFamilyData` 的 `.catch()` 路径**不**调用 `loadTodayTasks()`,若获取成员列表失败则任务不刷新、卡片残留。已补强:catch 路径亦调用 `loadTodayTasks()`。 |
+
+## 2. 改动文件清单与每处改动说明
+
+### 2.1 `cfc-frontend/pages/index-home/index.vue`
+- **位置**:`loadFamilyData()` 方法的 `.catch()` 分支(获取家庭成员列表失败路径)。
+- **改动**:在 catch 块末追加 `self.loadTodayTasks()`。
+- **说明**:确保即使获取成员列表失败,从打卡页通过 `navigateBack` 返回首页后仍能刷新今日任务,已完成的任务卡片能消失。正常 `.then()` 路径已通过 `loadFamilyData` → `loadTodayTasks()` 刷新,未重复请求(catch 与 then 互斥,不构成重复调用)。
+
+### 2.2 `cfc-frontend/pages/growth/sleep-checkin.vue`
+- **导入**:新增 `import { completeTask as completeTaskApi } from '../../utils/api.js'`(与 `index-home` 保持一致的别名)。
+- **改动**:在 `doCheckin()` 成功分支(`res.data.code === 200`)中,`uni.navigateBack()` **之前** 新增:若 `self.taskId` 存在,则调用 `completeTaskApi(self.taskId, self.memberId || null, '')` 并 `.catch()` 吞异常(不阻断返回)。
+- **说明**:睡眠打卡完成后显式将关联每日任务置 completed。
+
+### 2.3 `cfc-frontend/pages/growth/exercise-checkin.vue`
+- **导入**:`import { sttTranscribe } from '../../utils/api.js'` 改为 `import { sttTranscribe, completeTask as completeTaskApi } from '../../utils/api.js'`。
+- **改动**:在 `submitCheckin()` 成功分支(`res.data.code === 200`)中,`uni.navigateBack()` 之前 新增 `if (self.taskId) completeTaskApi(self.taskId, self.memberId || null, '').catch(...)`。
+- **说明**:运动打卡完成后显式将关联每日任务置 completed。
+
+### 2.4 `cfc-frontend/pages/growth/diet-checkin.vue`
+- **导入**:`import { sttTranscribe, recognizeDietFood, getAllQuotaStatus } from '../../utils/api.js'` 改为追加 `completeTask as completeTaskApi`。
+- **改动**:在 `submitCheckin()` 成功分支(`res.data.code === 200`)中,`uni.navigateBack()` 之前 新增 `if (self.taskId) completeTaskApi(self.taskId, self.memberId || null, '').catch(...)`。
+- **说明**:饮食打卡完成后显式将关联每日任务置 completed。(该文件已有的 BASIC 会员配额提示等改动为预先存在的未提交工作,本次未动。)
+
+> memberId 语义:取页面已有的 `this.memberId`(打卡所属成员,优先 currentChildId/currentMemberId/userId),若为 null 则传 `null`,由后端 `TaskController` 按登录用户 `currentMemberId` 处理,与 `index-home` 的 `completeTaskApi(task.id, task.familyMemberId || null, '')` 用法一致。
+
+## 3. completeTaskApi 来源与签名
+
+- **来源**:`cfc-frontend/utils/api.js`,导出函数 `completeTask`。
+- **签名**:
+  ```js
+  export const completeTask = (taskId, memberId, photoUrl, content, contentType) => {
+    return request(`/api/tasks/${taskId}/complete`, 'POST', { memberId, photoUrl, content, contentType })
+  }
+  ```
+- **index-home 已有用法**:`import { ... completeTask as completeTaskApi ... } from '../../utils/api.js'`,调用 `completeTaskApi(task.id, memberId, '')`。
+- **三个打卡页**:统一采用相同导入路径 `../../utils/api.js` 及别名 `completeTaskApi`,调用形态 `completeTaskApi(taskId, memberId || null, '')`。
+- **后端接口**:`TaskController.completeTask`,`POST /api/tasks/{id}/complete`,`memberId` 缺省取 JWT `currentMemberId`;内部 `taskService.completeTask(...)` 置任务为 completed。已核验能按 taskId 正确完成任务,**未改动后端**。
+
+## 4. 是否改动后端
+
+**未改动后端。** 修改仅限 `cfc-frontend` 下 4 个 `.vue` 文件。后端 `TaskController.completeTask` (`/api/tasks/{id}/complete`) 已具备按 `taskId` 将任务置为 completed 的能力,且健康打卡三 controller 的任务回写对“每日进度型”任务仅做进度累计不一定直接完成,故前端显式调 `completeTaskApi` 即可修复,无需后端改动。未触碰 BASIC 会员相关后端文件,未手改 `dist/unpackage`。
+
+## 5. 代码自审结论
+
+- **语法/编译**:`node scripts/verify-vue-compile.js` 对 4 个变更文件全部 PASS(模板/样式/脚本均通过)。
+- **重复 API 调用审计**:`node scripts/audit-duplicate-api-calls.js` 退出码 0,0 个 ERROR,仅项目预存在的 WARN。本次改动未引入新的 LIFECYCLE_DOUBLE(未在 onLoad/onShow 直接重复调用 `loadTodayTasks`,仅在 `loadFamilyData` 的互斥 catch 路径补强)亦未引入新的 DUPLICATE_WRAPPER(复用已有 `completeTask` 导出,未新增封装)。
+- **小程序反模式**:未使用可选链 `?.`、未在 `:key` 使用表达式、未使用 `new Date(string)`/toLocale*String、未使用 `uni.getSystemInfoSync()`,符合 `cfc-frontend/AGENTS.md` 与 `AGENTS.md` 规范。
+- **Vue 2 Options API**:所有新增逻辑均写在 `methods` 内,使用 `var`/function 表达式,与现有风格一致。
+- **导航逻辑**:`completeTaskApi` 调用置于打卡成功分支、`uni.navigateBack()` 之前,异常通过 `.catch()` 吞掉,不阻断页面返回;若页面无 `taskId`(非首页联动入口打卡),则不调用,行为与之前一致。
+
+## 6. 建议的回归验证步骤(HBuilderX 构建后手测)
+
+在 HBuilderX 中重新打包 `cfc-frontend`,用微信开发者工具运行,登录后按以下步骤验证:
+
+1. **首页「我的任务」**:确认快捷列表中出现睡眠/运动/饮食三类联动任务卡片(“去完成”按钮)。
+2. **睡眠打卡**:点睡眠卡片“去完成” → 进入 `pages/growth/sleep-checkin.vue` → 填写入睡/醒来/质量 → 点“完成打卡” → 返回首页 → **睡眠任务卡片应消失**。
+3. **运动打卡**:点运动卡片“去完成” → 进入 `pages/growth/exercise-checkin.vue` → 填写类型/时长等 → 点“✓ 完成打卡” → 返回首页 → **运动任务卡片应消失**。
+4. **饮食打卡**:点饮食卡片“去完成” → 进入 `pages/growth/diet-checkin.vue` → 选择餐次并填内容 → 点“✓ 完成打卡” → 返回首页 → **饮食任务卡片应消失**。
+5. **非联动打卡兜底**:直接在打卡页(不带 `?taskId=`)完成打卡并返回,应正常跳转、不报错、不影响其他页面。
+6. **异常路径**:断网或后端 `completeTask` 返回非 200 时,打卡页仍应正常 `showToast` 并 `navigateBack` 返回首页(`completeTaskApi` 失败不阻断返回)。
+
+预期结果:完成任意一类联动打卡并返回首页后,对应任务卡片立即消失;未完成的联动任务卡片保持显示。