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

fix(growth-main): 今日成长打卡入口补传 taskId,打通任务→打卡→任务完成联动(C1)

缺口:pages/growth-main/index.vue 的 handleProgressClick 对 diet/exercise/sleep 跳转时不带 taskId,后端只能走 updateProgress(taskKey) 兜底,仅命中每日进度型任务(isDailyProgress=1 + 标题前缀 [KEY]);一次性健康任务经今日成长入口打卡不会完成。

改动(唯一文件 cfc-frontend/pages/growth-main/index.vue):
1. loadProgressItems 阶段新增 fetchTodayTasks(),一次请求取当日当前成员任务,按 actionType 索引挂到 progressItems 的 taskId/taskMemberId 上;
2. findTaskByAction 只取 status 为 pending/in_progress 的任务(同 actionType 取首个);
3. handleProgressClick 命中 diet/exercise/sleep 且有 taskId 时拼 ?taskId=X&memberId=Y(memberId 取任务归属 familyMemberId||executorId||this.memberId,与 tasks.vue navigateByAction 同口径);无任务时保持纯跳转,退化为纯记录打卡(后端 updateProgress 兜底,不报错);
4. loadRecentTasks 复用 loadProgressItems 已拉取的今日任务缓存,避免同页重复调用 /api/tasks/today(原先传参为 {memberId} 对象,与 getTodayTasks(memberId) 签名不符,实际未按成员过滤)。

范围纪律:不改打卡页(diet/sleep/exercise-checkin.vue)、不改后端(updateCheckinTaskProgress/completeTask/updateProgress 三条路径均已存在且幂等)、不改任务生成器、不改完成度判定(仍基于记录查询)、不改 onShow 刷新逻辑。

验收:node --check PASS;scripts/verify-vue-compile.js 模板/样式/脚本 1/1/1 PASS;audit-mp-antipatterns ERROR 0;audit-duplicate-api-calls 0 error;逻辑级验证 27 项断言全通过(含无任务退化、接口异常静默降级、done=true 仍挂 taskId)。
Agnes Agent 2 недель назад
Родитель
Сommit
a25b49f309
1 измененных файлов с 45 добавлено и 5 удалено
  1. 45 5
      cfc-frontend/pages/growth-main/index.vue

+ 45 - 5
cfc-frontend/pages/growth-main/index.vue

@@ -294,7 +294,7 @@ import shareMixin from '../../components/share-mixin.js'
 export default {
   mixins: [fontScaleMixin, shareMixin],
   components: { LoginGuideCard, DimensionActivities, DimensionProducts, ArticleBookshelf },
-  data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], recentTasks: [], streakDays: 0, activeChallenges: 0, completeness: 0, cautionCount: 0,
+  data() { return { memberId: '', todayDate: '', hasIncomplete: false, incompleteCount: 0, progressItems: [], recentTasks: [], todayTaskList: [], streakDays: 0, activeChallenges: 0, completeness: 0, cautionCount: 0,
       healthArticles: [],
       growthArticles: [],
       isLoggedIn: false } },
@@ -330,6 +330,14 @@ export default {
       var url = m[item.key]
       // 情绪打卡已合并至 mind-detail 情绪打卡页,成长上下文需带上 memberId
       if (url && item.key === 'emotion' && this.memberId) url = url + '?memberId=' + this.memberId
+      // 健康打卡类(饮食/运动/睡眠):有当日未完成任务则补传 taskId+memberId,
+      // 打卡提交后后端据 taskId 回写任务进度/直接完成;无任务时退化为纯记录打卡
+      if (url && item.taskId && (item.key === 'diet' || item.key === 'exercise' || item.key === 'sleep')) {
+        var qs = 'taskId=' + item.taskId
+        var mid = item.taskMemberId || this.memberId
+        if (mid) qs = qs + '&memberId=' + mid
+        url = url + '?' + qs
+      }
       if (url) uni.navigateTo({ url: url })
     },
     getTaskKey: function(task, idx) { return (task && task.id) ? ('t' + task.id) : ('tk' + idx) },
@@ -339,13 +347,29 @@ export default {
       if (!d) return ''
       return (d.getMonth() + 1) + '/' + d.getDate() + ' ' + this.pad(d.getHours()) + ':' + this.pad(d.getMinutes())
     },
+    /** 当日任务统一取数入口:一次请求供"今日成长打卡联动 taskId"与"今日任务预览"共用,避免同页重复调用 /api/tasks/today */
+    async fetchTodayTasks() {
+      var self = this
+      try {
+        var res = await getTodayTasks(self.memberId)
+        self.todayTaskList = (res && res.code === 200 && res.data) ? (res.data || []) : []
+      } catch(e) { self.todayTaskList = [] }
+    },
+    /** 按 actionType 取当日未完成任务(pending / in_progress),无则返回 null */
+    findTaskByAction: function(actionType) {
+      var list = this.todayTaskList || []
+      for (var i = 0; i < list.length; i++) {
+        var t = list[i]
+        if (!t || !t.id || t.actionType !== actionType) continue
+        if (t.status === 'pending' || t.status === 'in_progress') return t
+      }
+      return null
+    },
     async loadRecentTasks() {
       var self = this
       try {
-        var res = await getTodayTasks(self.memberId ? { memberId: self.memberId } : {})
-        if (res && res.code === 200 && res.data) {
-          self.recentTasks = (res.data || []).slice(0, 3)
-        }
+        if (!self.todayTaskList) await self.fetchTodayTasks()
+        self.recentTasks = (self.todayTaskList || []).slice(0, 3)
       } catch(e) {}
     },
     goActivityDetail: function(act) { uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id }) },
@@ -399,6 +423,8 @@ export default {
     async loadProgressItems() {
       var self = this
       var items = []
+      // 取当日未完成任务(pending / in_progress),供下方健康打卡项挂载 taskId
+      await self.fetchTodayTasks()
       var dietDone = false
       try {
         var dParams = self.memberId ? { memberId: self.memberId } : {}
@@ -461,6 +487,20 @@ export default {
         }
       } catch(e) {}
       items.push({ key: 'checkin', icon: '💰', label: '记账', done: checkinDone, actionText: '去打卡' })
+      // 健康打卡类:挂载当日对应类型的未完成任务(actionType 同口径),
+      // 完成度判定仍以记录查询为准(上方 dietDone/exerciseDone/sleepDone),此处仅用于跳转联动
+      var actionTypeMap = { diet: 'diet', exercise: 'exercise', sleep: 'sleep' }
+      for (var ki = 0; ki < items.length; ki++) {
+        var it = items[ki]
+        if (!it) continue
+        var at = actionTypeMap[it.key]
+        if (!at) continue
+        var tk = self.findTaskByAction(at)
+        if (tk) {
+          it.taskId = tk.id
+          it.taskMemberId = tk.familyMemberId || tk.executorId || self.memberId
+        }
+      }
       self.progressItems = items.filter(function(it) { return it && it.key })
       self.incompleteCount = 0
       for (var pi = 0; pi < items.length; pi++) if (items[pi] && !items[pi].done) self.incompleteCount++