fix-homepage-task-close.md 8.4 KB

首页每日任务卡未关闭 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。
  • 签名:

    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 失败不阻断返回)。

预期结果:完成任意一类联动打卡并返回首页后,对应任务卡片立即消失;未完成的联动任务卡片保持显示。