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