Переглянути джерело

chore: auto bump version and changelog [skip ci]

iwt 5 днів тому
батько
коміт
d8836e40b8

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-bcc6f1a8085418cf97f3b2b729cb3146532b54e2
+672b21ea5b110dd327f27be685af80835bc27ea8

+ 2 - 2
cfc-web/package-lock.json

@@ -1,12 +1,12 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1681",
+  "version": "1.0.1682",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "cfc-web",
-      "version": "1.0.1681",
+      "version": "1.0.1682",
       "dependencies": {
         "@wangeditor/editor": "^5.1.23",
         "@wangeditor/editor-for-vue": "^1.0.2",

+ 1 - 1
cfc-web/package.json

@@ -1,6 +1,6 @@
 {
   "name": "cfc-web",
-  "version": "1.0.1682",
+  "version": "1.0.1683",
   "private": true,
   "scripts": {
     "dev": "vue-cli-service serve",

+ 13 - 0
cfc-web/public/CHANGELOG-v1.0.md

@@ -4,6 +4,19 @@
 
 ---
 
+## v1.0.1683 (2026-10-19)
+
+### Bug 修复
+- 知识库列表支持 tagId/dimensionId 过滤与状态容错,新增自动回填接口
+
+### 其他
+- - status 参数改为类型容错解析(前端传字符串 "1"/"0" 不再 ClassCastException 500)
+- - 新增 /api/admin/knowledge-base/backfill-tags:基于标题/内容关键词自动回填标签与维度绑定
+- - 单元测试 KnowledgeBaseBackfillTest 7 项(标签 2字仅标题/3字+正文、组合标签拆分、维度身智行心映射、关联过滤)
+- 备注:并行 rebase 中本地未推送的 5856eb277 fix(activity) 因改的是旧架构已删除的 isAdmin/isActivityProvider(已被 HEAD 的 visibleTabs/canManageActivity 取代),三方合并无净变化,rebase --skip 跳过。注意 HEAD 的 cfc-frontend/utils/activity-role.js isApprovedActivityProvider 仍用 vendorType+vendorStatus 判定,与 5856eb277 主张的仅凭 activityProviderStatus=approved 不一致,建议 activity 领域后续复核。
+- 
+
+
 ## v1.0.1682 (2026-10-19)
 
 ### Bug 修复

+ 14 - 1
cfc-web/public/CHANGELOG.md

@@ -1,6 +1,6 @@
 # 更新日志
 
-> 当前版本: v1.0.1682
+> 当前版本: v1.0.1683
 
 ## 历史版本
 
@@ -8,6 +8,19 @@
 
 ---
 
+## v1.0.1683 (2026-10-19)
+
+### Bug 修复
+- 知识库列表支持 tagId/dimensionId 过滤与状态容错,新增自动回填接口
+
+### 其他
+- - status 参数改为类型容错解析(前端传字符串 "1"/"0" 不再 ClassCastException 500)
+- - 新增 /api/admin/knowledge-base/backfill-tags:基于标题/内容关键词自动回填标签与维度绑定
+- - 单元测试 KnowledgeBaseBackfillTest 7 项(标签 2字仅标题/3字+正文、组合标签拆分、维度身智行心映射、关联过滤)
+- 备注:并行 rebase 中本地未推送的 5856eb277 fix(activity) 因改的是旧架构已删除的 isAdmin/isActivityProvider(已被 HEAD 的 visibleTabs/canManageActivity 取代),三方合并无净变化,rebase --skip 跳过。注意 HEAD 的 cfc-frontend/utils/activity-role.js isApprovedActivityProvider 仍用 vendorType+vendorStatus 判定,与 5856eb277 主张的仅凭 activityProviderStatus=approved 不一致,建议 activity 领域后续复核。
+- 
+
+
 ## v1.0.1682 (2026-10-19)
 
 ### Bug 修复

+ 534 - 0
docs/superpowers/plans/2026-10-13-homepage-family-task-count.md

@@ -0,0 +1,534 @@
+# 首页与任务列表家庭任务展示修正 实现计划
+
+> **面向 AI 代理的工作者:** 必需子技能:使用 superpowers:subagent-driven-development(推荐)或 superpowers:executing-plans 逐任务实现此计划。步骤使用复选框(`- [ ]`)语法来跟踪进度。
+
+**目标:** 修正首页「我的任务」仅显示本人任务、引导条显示全家今日待办数、任务列表成员标签显示待完成数。
+
+**架构:** 复用现有 `/api/tasks/today` 的 `familyWide` 参数,单次请求派生三类数据(本人任务、家庭任务总数、各成员待完成数)。后端对齐查询口径,前端派生展示逻辑。
+
+**技术栈:** Java 8 + Spring Boot 2.7.18, MyBatis-Plus, uni-app Vue 2
+
+---
+
+## 文件变更清单
+
+### 后端
+- `cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java`
+  - 修改 `getTodayTasksByFamily` 方法(第 389-411 行)
+  - 补全状态过滤(含 `in_progress`)
+  - 补全条件(含 `isDailyProgress` 任务)
+
+### 前端
+- `cfc-frontend/pages/index-home/index.vue`
+  - 修改 `loadTodayTasks` 方法
+  - 新增 `countFamilyPendingInProgress` 方法
+  - 修改 `nextBestAction` 计算属性
+  - 更新数据字段(`familyTasks`, `familyTodoCount`)
+
+- `cfc-frontend/pages/tasks/tasks.vue`
+  - 新增 `memberPendingCounts` 数据字段
+  - 修改 `loadTodayTasks` 方法
+  - 新增 `countTasksByMember` 方法
+  - 传递 props 给 FamilyMemberStrip
+
+- `cfc-frontend/components/FamilyMemberStrip.vue`
+  - 新增 `pendingCounts` prop
+  - 透传 `pendingCount` 给 FamilyMemberCard
+
+- `cfc-frontend/components/FamilyMemberCard.vue`
+  - 新增 `pendingCount` prop
+  - 添加角标模板
+  - 添加角标样式
+
+---
+
+## 任务分解
+
+### 任务 1:后端对齐 `getTodayTasksByFamily` 查询口径
+
+**文件:**
+- 修改:`cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java:389-411`
+
+- [ ] **步骤 1:定位并理解当前代码**
+
+读取 `TaskService.java` 第 389-411 行,确认 `getTodayTasksByFamily` 方法当前实现:
+```java
+public List<Task> getTodayTasksByFamily(Long familyId) {
+    if (familyId == null) {
+        return new ArrayList<>();
+    }
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.HOUR_OF_DAY, 0);
+    calendar.set(Calendar.MINUTE, 0);
+    calendar.set(Calendar.SECOND, 0);
+    calendar.set(Calendar.MILLISECOND, 0);
+    Date startOfDay = calendar.getTime();
+
+    calendar.add(Calendar.DAY_OF_MONTH, 1);
+    Date endOfDay = calendar.getTime();
+
+    List<Task> all = taskMapper.selectList(new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, familyId)
+            .ge(Task::getDeadline, startOfDay)
+            .lt(Task::getDeadline, endOfDay)
+            .in(Task::getStatus, Arrays.asList("pending", "completed", "locked", "pending_review"))
+            .orderByAsc(Task::getDeadline)
+            .orderByAsc(Task::getId));
+    return filterLockedChildren(all);
+}
+```
+
+- [ ] **步骤 2:修改查询条件**
+
+修改第 389-411 行的 `getTodayTasksByFamily` 方法,补全状态和条件:
+
+```java
+public List<Task> getTodayTasksByFamily(Long familyId) {
+    if (familyId == null) {
+        return new ArrayList<>();
+    }
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.HOUR_OF_DAY, 0);
+    calendar.set(Calendar.MINUTE, 0);
+    calendar.set(Calendar.SECOND, 0);
+    calendar.set(Calendar.MILLISECOND, 0);
+    Date startOfDay = calendar.getTime();
+
+    calendar.add(Calendar.DAY_OF_MONTH, 1);
+    Date endOfDay = calendar.getTime();
+
+    List<Task> all = taskMapper.selectList(new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, familyId)
+            .in(Task::getStatus, Arrays.asList("pending", "completed", "locked", "pending_review", "in_progress"))
+            .and(w -> w
+                .and(w2 -> w2.isNotNull(Task::getDeadline)
+                        .ge(Task::getDeadline, startOfDay)
+                        .lt(Task::getDeadline, endOfDay))
+                .or(w3 -> w3.eq(Task::getIsDailyProgress, 1)
+                        .ge(Task::getCreatedAt, startOfDay)
+                        .lt(Task::getCreatedAt, endOfDay)))
+            )
+            .orderByAsc(Task::getDeadline)
+            .orderByAsc(Task::getId));
+    return filterLockedChildren(all);
+}
+```
+
+关键改动:
+- 第 407 行:状态列表新增 `"in_progress"`
+- 第 408-409 行:从简单的 `.ge()` + `.lt()` 改为 `.and()` + `.or()` 复合条件,包含 `isDailyProgress` 任务
+
+- [ ] **步骤 3:编译验证**
+
+运行:
+```bash
+cd /sc-data/cfc/cfc-backend
+mvn clean compile -q
+```
+
+预期输出:BUILD SUCCESS(或无错误)
+
+- [ ] **步骤 4:Commit**
+
+```bash
+cd /sc-data/cfc
+git add cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java
+git commit -m "fix: align family task query with personal query - include in_progress and isDailyProgress tasks"
+```
+
+---
+
+### 任务 2:首页统一使用 familyWide 派生数据
+
+**文件:**
+- 修改:`cfc-frontend/pages/index-home/index.vue`
+
+- [ ] **步骤 1:定位当前 `loadTodayTasks` 方法**
+
+读取 `index-home/index.vue` 第 1084-1113 行,确认当前实现:
+```js
+loadTodayTasks: function() {
+  var self = this
+  var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
+  // 家长:家庭维度拉取(方案任务可能挂在任意成员,不只是当前孩子)
+  if (role === 'parent') {
+    getTodayTasks(null, { familyWide: true }).then(function(res) {
+      if (res.code === 200 && Array.isArray(res.data)) {
+        self.pendingTasks = res.data.filter(function(t) {
+          return t.status === 'pending'
+        })
+      }
+    }).catch(function() {
+      self.pendingTasks = []
+    })
+    return
+  }
+  if (!self.currentChildId) {
+    self.pendingTasks = []
+    return
+  }
+  getTodayTasks(self.currentChildId).then(function(res) {
+    if (res.code === 200 && Array.isArray(res.data)) {
+      self.pendingTasks = res.data.filter(function(t) {
+        return t.status === 'pending'
+      })
+    }
+  }).catch(function() {
+    self.pendingTasks = []
+  })
+},
+```
+
+- [ ] **步骤 2:修改 `loadTodayTasks` 方法**
+
+替换第 1084-1113 行的方法体:
+
+```js
+loadTodayTasks: function() {
+  var self = this
+  var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
+  
+  // 统一使用 familyWide 请求,一次获取全家任务
+  getTodayTasks(null, { familyWide: true }).then(function(res) {
+    if (res.code === 200 && Array.isArray(res.data)) {
+      // 存全家任务列表,用于派生
+      self.familyTasks = res.data
+      
+      // 计算家庭待办总数(pending + in_progress)
+      self.familyTodoCount = self.countFamilyPendingInProgress(res.data)
+      
+      // 派生:我的任务(仅 pending,按成员过滤)
+      if (role === 'parent') {
+        // 家长:取登录用户自己(isSelf 标记的 member)
+        var ownMemberId = null
+        if (self.familyMembersVisible && self.familyMembersVisible.length > 0) {
+          for (var i = 0; i < self.familyMembersVisible.length; i++) {
+            if (self.familyMembersVisible[i] && self.familyMembersVisible[i].isSelf) {
+              ownMemberId = self.familyMembersVisible[i].id
+              break
+            }
+          }
+        }
+        self.pendingTasks = res.data.filter(function(t) {
+          return t.familyMemberId === ownMemberId && t.status === 'pending'
+        })
+      } else if (self.currentChildId) {
+        // 孩子视角:按当前孩子过滤
+        self.pendingTasks = res.data.filter(function(t) {
+          return t.familyMemberId === self.currentChildId && t.status === 'pending'
+        })
+      }
+    }
+  }).catch(function() {
+    self.pendingTasks = []
+    self.familyTasks = []
+    self.familyTodoCount = 0
+  })
+},
+```
+
+- [ ] **步骤 3:新增辅助方法**
+
+在 `loadTodayTasks` 方法后面(约 1114 行之后)新增:
+
+```js
+// 统计 pending + in_progress 数量
+countFamilyPendingInProgress: function(tasks) {
+  var count = 0
+  for (var i = 0; i < tasks.length; i++) {
+    var t = tasks[i]
+    if (t && (t.status === 'pending' || t.status === 'in_progress')) {
+      count += 1
+    }
+  }
+  return count
+},
+```
+
+- [ ] **步骤 4:修改 `nextBestAction` 计算属性**
+
+读取第 648-658 行,找到待办任务分支:
+
+```js
+// 4) 待办任务 — 用户已完成自检后的执行路径
+if (this.pendingTasks && this.pendingTasks.length > 0) {
+  return {
+    type: 'task',
+    icon: '🎯',
+    title: '今日有 ' + this.pendingTasks.length + ' 项待办',
+    sub: '完成可累积成长分',
+    btn: '去完成',
+    color: 'green'
+  }
+}
+```
+
+修改为:
+
+```js
+// 4) 家庭待办任务
+if (this.familyTodoCount > 0) {
+  return {
+    type: 'task',
+    icon: '🎯',
+    title: '今天有 ' + this.familyTodoCount + ' 项家庭任务',
+    sub: '完成可累积成长分',
+    btn: '去完成',
+    color: 'green'
+  }
+}
+```
+
+- [ ] **步骤 5:在 data 中新增字段**
+
+读取 `data()` 函数(约第 300 行附近),在 `pendingTasks` 定义后新增:
+
+```js
+familyTasks: [],           // 全家今日任务列表(raw data)
+familyTodoCount: 0,        // 全家今日待办总数(pending + in_progress)
+```
+
+- [ ] **步骤 6:语法校验**
+
+运行:
+```bash
+cd /sc-data/cfc/cfc-frontend
+node --check pages/index-home/index.vue
+```
+
+> 注意:`.vue` 文件不能直接 node check,只检查 script 块:
+> ```bash
+> grep -A 9999 '<script>' pages/index-home/index.vue | head -n 500 | node --check /dev/stdin
+> ```
+
+- [ ] **步骤 7:Commit**
+
+```bash
+cd /sc-data/cfc
+git add cfc-frontend/pages/index-home/index.vue
+git commit -m "feat: fix homepage task display - own tasks only + family todo count"
+```
+
+---
+
+### 任务 3:任务列表成员标签显示待完成数
+
+**文件:**
+- 修改:`cfc-frontend/pages/tasks/tasks.vue`
+- 修改:`cfc-frontend/components/FamilyMemberStrip.vue`
+- 修改:`cfc-frontend/components/FamilyMemberCard.vue`
+
+- [ ] **步骤 1:查看 tasks.vue 数据结构**
+
+读取 `tasks.vue` 的 `data()` 函数,找到相关字段位置(约第 50-150 行),确认 `pendingTasks`、`familyMembersVisible` 等字段定义。
+
+- [ ] **步骤 2:在 data 中新增字段**
+
+```js
+memberPendingCounts: {},  // 各成员待完成数 { memberId: count }
+```
+
+- [ ] **步骤 3:定位并修改 loadTodayTasks**
+
+读取 `tasks.vue` 的 `loadTodayTasks` 方法(找到 `getTodayTasks` 调用位置),在成功回调中新增成员计数计算:
+
+```js
+// 在 getTodayTasks 成功的 .then() 末尾新增:
+if (res.code === 200 && Array.isArray(res.data)) {
+  // ... 现有处理 ...
+  
+  // 计算各成员待完成数
+  self.memberPendingCounts = self.countTasksByMember(res.data)
+}
+```
+
+- [ ] **步骤 4:新增辅助方法**
+
+在 `methods` 中添加:
+
+```js
+// 按成员统计待完成数(pending + in_progress)
+countTasksByMember: function(tasks) {
+  var counts = {}
+  for (var i = 0; i < tasks.length; i++) {
+    var task = tasks[i]
+    if (!task || !task.familyMemberId) continue
+    
+    var memberId = task.familyMemberId
+    if (!counts[memberId]) {
+      counts[memberId] = 0
+    }
+    if (task.status === 'pending' || task.status === 'in_progress') {
+      counts[memberId] += 1
+    }
+  }
+  return counts
+},
+```
+
+- [ ] **步骤 5:传递 props 给 FamilyMemberStrip**
+
+在模板中找到 `<FamilyMemberStrip>` 组件调用,新增 `:pendingCounts` prop:
+
+```vue
+<FamilyMemberStrip
+  :members="familyMembersVisible"
+  :selectedMemberId="currentMemberId"
+  :isParent="isParent"
+  :pendingCounts="memberPendingCounts"
+  @select="onMemberSelect"
+/>
+```
+
+- [ ] **步骤 6:修改 FamilyMemberStrip.vue**
+
+读取组件,找到渲染 `FamilyMemberCard` 的位置,新增 prop 定义和透传:
+
+```vue
+<script>
+export default {
+  props: {
+    // ... 现有 props ...
+    pendingCounts: {
+      type: Object,
+      default: function() { return {} }
+    }
+  }
+}
+</script>
+
+<template>
+  <!-- 透传 pendingCount 给子组件 -->
+  <FamilyMemberCard
+    v-for="member in members"
+    :key="member.id"
+    :member="member"
+    :isSelected="selectedMemberId === member.id"
+    :energyScore="member.energyScore"
+    :pendingCount="pendingCounts[member.id] || 0"
+    @click="selectMember(member)"
+  />
+</template>
+```
+
+- [ ] **步骤 7:修改 FamilyMemberCard.vue**
+
+在组件中添加 prop、模板角标和样式:
+
+```vue
+<script>
+export default {
+  props: {
+    // ... 现有 props ...
+    pendingCount: {
+      type: Number,
+      default: 0
+    }
+  }
+}
+</script>
+
+<template>
+  <view class="family-member-card" :class="{ 'is-selected': isSelected }">
+    <view class="avatar-wrapper">
+      <image class="avatar" :src="member.avatar || defaultAvatar" mode="aspectFill" />
+      <!-- 待办角标 -->
+      <view v-if="pendingCount > 0" class="pending-badge">
+        {{ pendingCount }}
+      </view>
+    </view>
+    <!-- ... 其余内容保持不变 ... -->
+  </view>
+</template>
+
+<style scoped>
+.pending-badge {
+  position: absolute;
+  top: -4rpx;
+  right: -4rpx;
+  min-width: 32rpx;
+  height: 32rpx;
+  padding: 0 8rpx;
+  background: #FF6B6B;
+  color: #fff;
+  font-size: 20rpx;
+  border-radius: 16rpx;
+  text-align: center;
+  line-height: 32rpx;
+  font-weight: 500;
+}
+</style>
+```
+
+- [ ] **步骤 8:语法校验**
+
+```bash
+cd /sc-data/cfc/cfc-frontend
+node --check pages/tasks/tasks.vue 2>/dev/null || echo "vue 文件无法直接 node check,跳过"
+node --check components/FamilyMemberStrip.vue 2>/dev/null || echo "vue 文件无法直接 node check,跳过"
+node --check components/FamilyMemberCard.vue 2>/dev/null || echo "vue 文件无法直接 node check,跳过"
+```
+
+- [ ] **步骤 9:API 调用审计**
+
+```bash
+cd /sc-data/cfc/cfc-frontend
+node scripts/audit-duplicate-api-calls.js
+```
+
+预期:exit code 0(无重复请求)
+
+- [ ] **步骤 10:Commit**
+
+```bash
+cd /sc-data/cfc
+git add cfc-frontend/pages/tasks/tasks.vue \
+        cfc-frontend/components/FamilyMemberStrip.vue \
+        cfc-frontend/components/FamilyMemberCard.vue
+git commit -m "feat: add task count badge to family member cards"
+```
+
+---
+
+### 任务 4:集成验证
+
+- [ ] **步骤 1:运行前端审计**
+
+```bash
+cd /sc-data/cfc/cfc-frontend
+node scripts/audit-duplicate-api-calls.js
+```
+
+预期:PASS(exit code 0)
+
+- [ ] **步骤 2:检查 git 状态**
+
+```bash
+cd /sc-data/cfc
+git log --oneline -5
+git diff --stat HEAD~3
+```
+
+确认所有改动已提交。
+
+- [ ] **步骤 3:准备推送**
+
+```bash
+git push origin HEAD
+```
+
+---
+
+## 自检清单
+
+- [x] 规格覆盖度:三个需求均有关联任务
+- [x] 占位符扫描:无"待定"、"TODO"等占位符
+- [x] 类型一致性:`memberPendingCounts`、`pendingCount` 在三个文件中保持一致
+- [x] 后端对齐:`getTodayTasksByFamily` 与 `getTodayTasks` 口径一致
+- [x] 前端规范:单次 familyWide 请求,派生三类数据,无 N+1
+
+---
+
+**计划版本**: v1.0  
+**创建时间**: 2026-10-13  
+**批准状态**: ✅ 用户已批准方案 A

+ 394 - 0
docs/superpowers/specs/2026-10-13-homepage-family-task-count-design.md

@@ -0,0 +1,394 @@
+# 首页与任务列表家庭任务展示修正设计
+
+**日期**: 2026-10-13  
+**状态**: ✅ 已批准  
+**关联需求**: 
+- 首页「我的任务」只显示登录用户本人任务
+- 首页文案「今日有 X 项待办」→「今天有 X 项家庭任务」
+- 任务列表成员标签显示该成员「待完成(pending + in_progress)」数量
+
+---
+
+## 一、现状与问题诊断
+
+### 1.1 问题表现
+
+| 位置 | 现状 | 预期 |
+|------|------|------|
+| 首页「我的任务」 | 家长角色调用 `getTodayTasks(null, {familyWide:true})` → 返回全家任务 | 仅显示登录用户本人(家长自己)的任务 |
+| 首页引导条文案 | "今日有 X 项待办"(X = pendingTasks.length,即个人 pending 数) | "今天有 X 项家庭任务"(X = 全家 pending+in_progress 数) |
+| 任务列表成员角标 | 无待办数量显示 | 显示该成员待完成(pending + in_progress)任务数 |
+
+### 1.2 代码落点
+
+- `pages/index-home/index.vue:1084-1113` — `loadTodayTasks` 方法
+- `pages/index-home/index.vue:649-657` — `nextBestAction` 计算属性第4分支
+- `pages/index-home/index.vue:145-163` — 「我的任务」区块
+- `pages/tasks/tasks.vue` — 任务列表页面(含 FamilyMemberStrip 组件)
+- `components/FamilyMemberStrip.vue` — 成员横条组件
+- `components/FamilyMemberCard.vue` — 成员卡片组件
+- `cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java:389-411` — `getTodayTasksByFamily` 方法
+
+### 1.3 后端查询缺陷
+
+`getTodayTasksByFamily` 当前实现存在两个问题:
+1. **不含 `in_progress` 状态** → 进行中任务无法被统计
+2. **不含 `isDailyProgress` 任务** → 进度型任务(deadline=NULL)无法被统计
+
+个人查询 `getTodayTasks(memberId)` 已包含上述逻辑(第 360-369 行),但家庭查询未同步。
+
+---
+
+## 二、设计方案
+
+### 2.1 方案选择
+
+采用 **方案 A:复用 familyWide 请求 + 后端对齐**(已获批准)
+
+| 对比项 | 方案 A(推荐) | 方案 B(备选) |
+|--------|--------------|--------------|
+| 数据源 | 复用 `/api/tasks/today` 的 `familyWide=true` | 新增轻量统计接口 |
+| 请求次数 | 1次(满足首页 + 列表) | 2次(家庭数 + 成员分布) |
+| 后端改动 | 修改 `getTodayTasksByFamily` | 新增 Controller + Service 方法 |
+| 风险 | 低(现有方法补全逻辑) | 中(新增接口需审批) |
+| 维护成本 | 低 | 高 |
+
+### 2.2 核心设计原则
+
+- **单次请求派生多类数据**(符合项目规则 4)
+- **统一口径**:pending + in_progress(不含 completed/locked/pending_review)
+- **后端对齐**:家庭查询与个人查询保持一致(状态过滤 + 进度型任务支持)
+
+---
+
+## 三、详细设计
+
+### 3.1 后端改动
+
+#### 文件:`cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java`
+
+**改动 1:`getTodayTasksByFamily` 方法(第 389-411 行)**
+
+```java
+public List<Task> getTodayTasksByFamily(Long familyId) {
+    if (familyId == null) {
+        return new ArrayList<>();
+    }
+    Calendar calendar = Calendar.getInstance();
+    calendar.set(Calendar.HOUR_OF_DAY, 0);
+    calendar.set(Calendar.MINUTE, 0);
+    calendar.set(Calendar.SECOND, 0);
+    calendar.set(Calendar.MILLISECOND, 0);
+    Date startOfDay = calendar.getTime();
+
+    calendar.add(Calendar.DAY_OF_MONTH, 1);
+    Date endOfDay = calendar.getTime();
+
+    // ✅ 补全:状态包含 in_progress,条件包含 isDailyProgress
+    List<Task> all = taskMapper.selectList(new LambdaQueryWrapper<Task>()
+            .eq(Task::getFamilyId, familyId)
+            .in(Task::getStatus, Arrays.asList("pending", "completed", "locked", "pending_review", "in_progress"))
+            .and(w -> w
+                .and(w2 -> w2.isNotNull(Task::getDeadline)
+                        .ge(Task::getDeadline, startOfDay)
+                        .lt(Task::getDeadline, endOfDay))
+                .or(w3 -> w3.eq(Task::getIsDailyProgress, 1)
+                        .ge(Task::getCreatedAt, startOfDay)
+                        .lt(Task::getCreatedAt, endOfDay)))
+            )
+            .orderByAsc(Task::getDeadline)
+            .orderByAsc(Task::getId));
+    
+    return filterLockedChildren(all);
+}
+```
+
+**改动 2(可选):`getTodayTasks` 方法(第 347-384 行)**
+
+- 状态列表补全 `in_progress`(与家庭查询保持一致)
+- ⚠️ 需评估影响范围(仅影响今日任务展示,不影响统计)
+
+---
+
+### 3.2 前端改动
+
+#### 文件 1:`pages/index-home/index.vue`
+
+**改动 1:`loadTodayTasks` 方法(第 1084-1113 行)**
+
+```js
+loadTodayTasks: function() {
+  var self = this
+  var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || 'parent'
+  
+  // 统一使用 familyWide 请求,一次获取全家任务
+  getTodayTasks(null, { familyWide: true }).then(function(res) {
+    if (res.code === 200 && Array.isArray(res.data)) {
+      // 派生:全家今日任务数(pending + in_progress)
+      self.familyTasks = res.data
+      self.familyTodoCount = self.countFamilyPendingInProgress(res.data)
+      
+      // 派生:我的任务(登录用户本人 pending)
+      if (role === 'parent') {
+        var ownMemberId = uni.getStorageSync('memberId') // 或从 familyMembersVisible 取 isSelf
+        self.pendingTasks = res.data.filter(function(t) {
+          return t.familyMemberId === ownMemberId && t.status === 'pending'
+        })
+      } else if (self.currentChildId) {
+        // 孩子视角:按当前孩子过滤
+        self.pendingTasks = res.data.filter(function(t) {
+          return t.familyMemberId === self.currentChildId && t.status === 'pending'
+        })
+      }
+    }
+  }).catch(function() {
+    self.pendingTasks = []
+    self.familyTasks = []
+    self.familyTodoCount = 0
+  })
+},
+
+// 新增方法:统计 pending + in_progress
+countFamilyPendingInProgress: function(tasks) {
+  return tasks.filter(function(t) {
+    return t.status === 'pending' || t.status === 'in_progress'
+  }).length
+}
+```
+
+**改动 2:`nextBestAction` 计算属性(第 649-657 行)**
+
+```js
+// 4) 家庭待办任务
+if (this.familyTodoCount > 0) {
+  return {
+    type: 'task',
+    icon: '🎯',
+    title: '今天有 ' + this.familyTodoCount + ' 项家庭任务', // ✅ 新文案
+    sub: '完成可累积成长分',
+    btn: '去完成',
+    color: 'green'
+  }
+}
+```
+
+**改动 3:模板更新(第 145-163 行)**
+
+- 「我的任务」标题保持不变(已符合语义)
+- 展示条件改为:`v-if="pendingTasks.length > 0"`
+
+---
+
+#### 文件 2:`pages/tasks/tasks.vue`
+
+**改动 1:新增 `memberPendingCounts` 数据字段**
+
+```js
+data() {
+  return {
+    // ... 现有字段
+    memberPendingCounts: {} // 新增:{ memberId: pendingCount }
+  }
+}
+```
+
+**改动 2:`loadTodayTasks` 方法增强**
+
+```js
+loadTodayTasks: function() {
+  // ... 现有逻辑
+  
+  getTodayTasks(null, { familyWide: true }).then(function(res) {
+    // ... 现有处理
+    
+    // 新增:计算各成员待完成数
+    if (res.code === 200 && Array.isArray(res.data)) {
+      self.memberPendingCounts = self.countTasksByMember(res.data)
+    }
+  })
+},
+
+// 新增方法:按成员统计待完成数
+countTasksByMember: function(tasks) {
+  var counts = {}
+  tasks.forEach(function(task) {
+    var memberId = task.familyMemberId
+    if (!counts[memberId]) {
+      counts[memberId] = 0
+    }
+    if (task.status === 'pending' || task.status === 'in_progress') {
+      counts[memberId] += 1
+    }
+  })
+  return counts
+}
+```
+
+**改动 3:传递 props 给 FamilyMemberStrip**
+
+```vue
+<!-- 现有 -->
+<FamilyMemberStrip
+  :members="familyMembersVisible"
+  :selectedMemberId="currentMemberId"
+  :isParent="isParent"
+  @select="onMemberSelect"
+/>
+
+<!-- 新增 -->
+<FamilyMemberStrip
+  :members="familyMembersVisible"
+  :selectedMemberId="currentMemberId"
+  :isParent="isParent"
+  :pendingCounts="memberPendingCounts"
+  @select="onMemberSelect"
+/>
+```
+
+---
+
+#### 文件 3:`components/FamilyMemberStrip.vue`
+
+**改动:透传 pendingCounts 给 FamilyMemberCard**
+
+```vue
+<!-- 现有 -->
+<FamilyMemberCard
+  v-for="member in members"
+  :key="member.id"
+  :member="member"
+  :isSelected="selectedMemberId === member.id"
+  :energyScore="member.energyScore"
+  @click="selectMember(member)"
+/>
+
+<!-- 新增 pendingCounts prop -->
+<FamilyMemberStrip
+  <!-- ... 现有 props ... -->
+  :pendingCounts="pendingCounts"  // 新增
+  <!-- ... -->
+>
+
+<!-- 透传给子组件 -->
+<FamilyMemberCard
+  v-for="member in members"
+  :key="member.id"
+  :member="member"
+  :isSelected="selectedMemberId === member.id"
+  :energyScore="member.energyScore"
+  :pendingCount="pendingCounts[member.id] || 0"  // 新增
+  @click="selectMember(member)"
+/>
+```
+
+---
+
+#### 文件 4:`components/FamilyMemberCard.vue`
+
+**改动:展示待办角标**
+
+```vue
+<!-- 新增 prop -->
+<script>
+export default {
+  props: {
+    // ... 现有 props
+    pendingCount: {
+      type: Number,
+      default: 0
+    }
+  }
+}
+</script>
+
+<!-- 模板新增角标 -->
+<template>
+  <view class="family-member-card" :class="{ 'is-selected': isSelected }">
+    <view class="avatar-wrapper">
+      <image class="avatar" :src="member.avatar || defaultAvatar" mode="aspectFill" />
+      <!-- 新增角标 -->
+      <view v-if="pendingCount > 0" class="pending-badge">
+        {{ pendingCount }}
+      </view>
+    </view>
+    <!-- ... 现有内容 ... -->
+  </view>
+</template>
+
+<!-- 新增样式 -->
+<style scoped>
+.pending-badge {
+  position: absolute;
+  top: -4rpx;
+  right: -4rpx;
+  min-width: 32rpx;
+  height: 32rpx;
+  padding: 0 8rpx;
+  background: #FF6B6B;
+  color: #fff;
+  font-size: 20rpx;
+  border-radius: 16rpx;
+  text-align: center;
+  line-height: 32rpx;
+  font-weight: 500;
+}
+</style>
+```
+
+---
+
+## 四、验证计划
+
+### 4.1 单元测试(后端)
+
+```java
+// 测试 getTodayTasksByFamily 包含 in_progress 和 isDailyProgress 任务
+@Test
+public void testGetTodayTasksByFamilyIncludesInProgress() {
+    // 构造 pending + in_progress + isDailyProgress 任务
+    // 验证查询结果包含这些任务
+}
+```
+
+### 4.2 集成测试(前端)
+
+| 场景 | 预期行为 |
+|------|----------|
+| 家长打开首页 | 「我的任务」仅显示家长自己的 pending 任务 |
+| 家长打开首页 | 「今天有 X 项家庭任务」X = 全家 pending+in_progress 数 |
+| 家长切换孩子视角 | 「我的任务」显示该孩子的任务 |
+| 打开任务列表 | 成员标签显示该成员待完成数量角标 |
+| 任务状态为 in_progress | 角标正确计数,引导条文案正确 |
+
+### 4.3 审计脚本
+
+```bash
+cd cfc-frontend
+node scripts/audit-duplicate-api-calls.js
+# 预期:exit code 0(无重复请求)
+```
+
+---
+
+## 五、风险评估
+
+| 风险点 | 影响 | 缓解措施 |
+|--------|------|----------|
+| `DailyOverviewService.todoToday` 口径变化 | 数字可能增加(新增 in_progress + 进度型任务) | 属合理修正,影响正面 |
+| `getTodayTasks`(个人)未同步更新 | 个人今日任务不含 in_progress | 本轮不修改,后续迭代考虑 |
+| familyMembersVisible 加载时序 | pendingTasks 可能短暂为空 | 使用 computed 属性延迟派生 |
+
+---
+
+## 六、下一步行动
+
+1. ✅ 设计文档已创建
+2. 📋 调用 `writing-plans` 技能生成实现计划
+3. 🛠️ 按计划执行改动
+4. ✅ 验证通过后提交 PR
+
+---
+
+**文档版本**: v1.0  
+**创建时间**: 2026-10-13  
+**最后更新**: 2026-10-13