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

fix: 后端全面修复 - 实体字段、Controller参数、Service方法补齐 + DTO新增
\n- 修复字段类型不匹配(Long/Integer/String)\n- 补齐缺失的 import/方法/参数\n- 修改 @PostMapping 路由统一\n- 新增 Cart/CartItemDTO/SystemConfig 相关实体和Service\n- 重命名 ChildNutritionProfileService -> FamilyMemberNutritionProfileService\n- 修复测试用例

User 2 месяцев назад
Родитель
Сommit
577dc12f9b
88 измененных файлов с 1379 добавлено и 957 удалено
  1. 0 3
      cfc-backend/.classpath
  2. 1 1
      cfc-backend/.factorypath
  3. 1 1
      cfc-backend/pom.xml
  4. 8 0
      cfc-backend/src/main/java/com/etotem/cfc/common/Result.java
  5. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java
  6. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/controller/InviteCardController.java
  7. 12 12
      cfc-backend/src/main/java/com/etotem/cfc/controller/ReportStatsController.java
  8. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/controller/StreakController.java
  9. 9 9
      cfc-backend/src/main/java/com/etotem/cfc/controller/UserController.java
  10. 16 16
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java
  11. 32 26
      cfc-backend/src/main/java/com/etotem/cfc/controller/cart/CartController.java
  12. 17 17
      cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyUserController.java
  13. 22 22
      cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideFamilyTaskController.java
  14. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/controller/mind/EmotionAlertController.java
  15. 7 7
      cfc-backend/src/main/java/com/etotem/cfc/controller/parent/ParentAssessmentController.java
  16. 5 5
      cfc-backend/src/main/java/com/etotem/cfc/controller/stats/StatsController.java
  17. 3 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/AddFamilyMemberDTO.java
  18. 11 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/CreateProductOrderDTO.java
  19. 10 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/CreateWishDTO.java
  20. 5 2
      cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyMemberVO.java
  21. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/HealthDimensionVO.java
  22. 5 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/OrderItemVO.java
  23. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/dto/SwitchMemberVO.java
  24. 21 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/WishDTO.java
  25. 10 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/DanAssessmentResult.java
  26. 43 2
      cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMember.java
  27. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/GrowthPlan.java
  28. 4 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/GrowthRecord.java
  29. 15 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/MediaRecord.java
  30. 19 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/PackageOrder.java
  31. 1 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/PointsExchangeRecord.java
  32. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/PointsLog.java
  33. 20 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java
  34. 15 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/ProductOrder.java
  35. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/entity/RelationshipType.java
  36. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/Task.java
  37. 5 5
      cfc-backend/src/main/java/com/etotem/cfc/service/ActivityService.java
  38. 5 5
      cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java
  39. 92 91
      cfc-backend/src/main/java/com/etotem/cfc/service/CartService.java
  40. 0 200
      cfc-backend/src/main/java/com/etotem/cfc/service/ChildNutritionProfileService.java
  41. 26 12
      cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java
  42. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveTrainingConfigService.java
  43. 42 42
      cfc-backend/src/main/java/com/etotem/cfc/service/DataMigrationService.java
  44. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/service/DatabaseSyncService.java
  45. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/service/EmotionAlertService.java
  46. 6 6
      cfc-backend/src/main/java/com/etotem/cfc/service/EmotionCheckinService.java
  47. 25 17
      cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java
  48. 8 8
      cfc-backend/src/main/java/com/etotem/cfc/service/FamilyInvitationService.java
  49. 49 122
      cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java
  50. 5 11
      cfc-backend/src/main/java/com/etotem/cfc/service/FinanceCheckinService.java
  51. 6 5
      cfc-backend/src/main/java/com/etotem/cfc/service/GrowthTaskService.java
  52. 5 5
      cfc-backend/src/main/java/com/etotem/cfc/service/HealthCheckinService.java
  53. 23 29
      cfc-backend/src/main/java/com/etotem/cfc/service/HealthReportService.java
  54. 8 8
      cfc-backend/src/main/java/com/etotem/cfc/service/InviteCardService.java
  55. 6 6
      cfc-backend/src/main/java/com/etotem/cfc/service/InviteMilestoneService.java
  56. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/service/MentalHealthScreenService.java
  57. 5 5
      cfc-backend/src/main/java/com/etotem/cfc/service/MiniGameService.java
  58. 6 6
      cfc-backend/src/main/java/com/etotem/cfc/service/OnboardingService.java
  59. 11 11
      cfc-backend/src/main/java/com/etotem/cfc/service/PointsExchangeService.java
  60. 11 11
      cfc-backend/src/main/java/com/etotem/cfc/service/PointsService.java
  61. 11 11
      cfc-backend/src/main/java/com/etotem/cfc/service/RewardService.java
  62. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/service/ShareService.java
  63. 13 13
      cfc-backend/src/main/java/com/etotem/cfc/service/StreakService.java
  64. 43 8
      cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java
  65. 7 5
      cfc-backend/src/main/java/com/etotem/cfc/service/TaskStatsService.java
  66. 5 5
      cfc-backend/src/main/java/com/etotem/cfc/service/TeacherService.java
  67. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/service/TrainingPlanService.java
  68. 147 18
      cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java
  69. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/service/WechatAlertTemplateService.java
  70. 10 10
      cfc-backend/src/main/java/com/etotem/cfc/service/WishService.java
  71. 38 0
      cfc-backend/src/main/java/com/etotem/cfc/service/impl/DimensionScoreServiceImpl.java
  72. 118 31
      cfc-backend/src/main/resources/schema.sql
  73. 17 17
      cfc-backend/src/test/java/com/etotem/cfc/controller/GuideFamilyTaskControllerTest.java
  74. 9 11
      cfc-backend/src/test/java/com/etotem/cfc/controller/GuideRolePermissionTest.java
  75. 16 16
      cfc-backend/src/test/java/com/etotem/cfc/controller/PointsControllerTest.java
  76. 1 1
      cfc-backend/src/test/java/com/etotem/cfc/controller/StatsControllerTest.java
  77. 5 5
      cfc-backend/src/test/java/com/etotem/cfc/integration/DailyCheckinStreakFlowTest.java
  78. 2 2
      cfc-frontend/components/DimensionActivities.vue
  79. 4 6
      cfc-frontend/pages.json
  80. 2 2
      cfc-frontend/pages/article-center/index.vue
  81. 105 9
      cfc-frontend/pages/body/index.vue
  82. 32 1
      cfc-frontend/pages/discover-detail/product-detail/product-detail.vue
  83. 1 0
      cfc-frontend/pages/index/parent-index.vue
  84. 3 3
      cfc-frontend/pages/profile/family-members.vue
  85. 65 2
      cfc-frontend/pages/wisdom/index.vue
  86. 7 7
      cfc-frontend/store/index.js
  87. 6 0
      cfc-frontend/utils/api.js
  88. 6 6
      cfc-web/src/views/admin/RelationshipTypes.vue

+ 0 - 3
cfc-backend/.classpath

@@ -47,9 +47,6 @@
 	<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
 	<classpathentry kind="src" output="target/test-classes" path="target/generated-test-sources/test-annotations">
 		<attributes>
 		<attributes>
 			<attribute name="optional" value="true"/>
 			<attribute name="optional" value="true"/>
-			<attribute name="maven.pomderived" value="true"/>
-			<attribute name="ignore_optional_problems" value="true"/>
-			<attribute name="m2e-apt" value="true"/>
 			<attribute name="test" value="true"/>
 			<attribute name="test" value="true"/>
 		</attributes>
 		</attributes>
 	</classpathentry>
 	</classpathentry>

+ 1 - 1
cfc-backend/.factorypath

@@ -1,3 +1,3 @@
 <factorypath>
 <factorypath>
-    <factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.32/lombok-1.18.32.jar" enabled="true" runInBatchMode="false"/>
+    <factorypathentry kind="VARJAR" id="M2_REPO/org/projectlombok/lombok/1.18.30/lombok-1.18.30.jar" enabled="true" runInBatchMode="false"/>
 </factorypath>
 </factorypath>

+ 1 - 1
cfc-backend/pom.xml

@@ -166,7 +166,7 @@
                         <path>
                         <path>
                             <groupId>org.projectlombok</groupId>
                             <groupId>org.projectlombok</groupId>
                             <artifactId>lombok</artifactId>
                             <artifactId>lombok</artifactId>
-                            <version>1.18.32</version>
+                            <version>1.18.30</version>
                         </path>
                         </path>
                     </annotationProcessorPaths>
                     </annotationProcessorPaths>
                 </configuration>
                 </configuration>

+ 8 - 0
cfc-backend/src/main/java/com/etotem/cfc/common/Result.java

@@ -47,4 +47,12 @@ public class Result<T> implements Serializable {
         result.setMessage(message);
         result.setMessage(message);
         return result;
         return result;
     }
     }
+
+    // Explicit accessors (Lombok fallback)
+    public Integer getCode() { return code; }
+    public void setCode(Integer code) { this.code = code; }
+    public String getMessage() { return message; }
+    public void setMessage(String message) { this.message = message; }
+    public T getData() { return data; }
+    public void setData(T data) { this.data = data; }
 }
 }

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java

@@ -17,7 +17,7 @@ import com.etotem.cfc.entity.HealthReport;
 import com.etotem.cfc.entity.HealthReportDraft;
 import com.etotem.cfc.entity.HealthReportDraft;
 import com.etotem.cfc.entity.NutritionDeficiencyRecord;
 import com.etotem.cfc.entity.NutritionDeficiencyRecord;
 import com.etotem.cfc.entity.NutritionIndicatorMapping;
 import com.etotem.cfc.entity.NutritionIndicatorMapping;
-import com.etotem.cfc.service.ChildNutritionProfileService;
+import com.etotem.cfc.service.FamilyMemberNutritionProfileService;
 import com.etotem.cfc.service.DimensionScoreService;
 import com.etotem.cfc.service.DimensionScoreService;
 import com.etotem.cfc.service.FoodService;
 import com.etotem.cfc.service.FoodService;
 import com.etotem.cfc.service.HealthAnalysisService;
 import com.etotem.cfc.service.HealthAnalysisService;
@@ -65,7 +65,7 @@ public class HealthReportController {
     private NutritionDeficiencyService nutritionDeficiencyService;
     private NutritionDeficiencyService nutritionDeficiencyService;
 
 
     @Resource
     @Resource
-    private ChildNutritionProfileService childNutritionProfileService;
+    private FamilyMemberNutritionProfileService childNutritionProfileService;
 
 
     @Resource
     @Resource
     private HealthAnalysisService healthAnalysisService;
     private HealthAnalysisService healthAnalysisService;

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/controller/InviteCardController.java

@@ -1,11 +1,11 @@
 package com.etotem.cfc.controller;
 package com.etotem.cfc.controller;
 
 
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.InviteCard;
 import com.etotem.cfc.entity.InviteCard;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.InviteCardService;
 import com.etotem.cfc.service.InviteCardService;
@@ -31,7 +31,7 @@ public class InviteCardController {
     private InviteCardService inviteCardService;
     private InviteCardService inviteCardService;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private FamilyMapper familyMapper;
     private FamilyMapper familyMapper;
@@ -56,7 +56,7 @@ public class InviteCardController {
         }
         }
 
 
         if ("child".equals(type)) {
         if ("child".equals(type)) {
-            Child child = childMapper.selectById(refId);
+            FamilyMember child = familyMemberMapper.selectById(refId);
             if (child == null) {
             if (child == null) {
                 return Result.error("孩子不存在");
                 return Result.error("孩子不存在");
             }
             }

+ 12 - 12
cfc-backend/src/main/java/com/etotem/cfc/controller/ReportStatsController.java

@@ -2,11 +2,11 @@ package com.etotem.cfc.controller;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.GameRecord;
 import com.etotem.cfc.entity.GameRecord;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.service.GameRecordService;
 import com.etotem.cfc.service.GameRecordService;
 import com.etotem.cfc.service.TaskService;
 import com.etotem.cfc.service.TaskService;
@@ -25,7 +25,7 @@ import java.util.stream.Collectors;
 public class ReportStatsController {
 public class ReportStatsController {
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -43,7 +43,7 @@ public class ReportStatsController {
         Map<String, Object> result = new HashMap<>();
         Map<String, Object> result = new HashMap<>();
 
 
         // 基础信息
         // 基础信息
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         result.put("totalPoints", child != null ? child.getTotalPoints() : 0);
         result.put("totalPoints", child != null ? child.getTotalPoints() : 0);
 
 
         // 月活跃天数(当月有积分变动的天数)
         // 月活跃天数(当月有积分变动的天数)
@@ -149,15 +149,15 @@ public class ReportStatsController {
     @PostMapping("/family/summary")
     @PostMapping("/family/summary")
     public Result<Map<String, Object>> familySummary(@RequestBody Map<String, Object> body) {
     public Result<Map<String, Object>> familySummary(@RequestBody Map<String, Object> body) {
         Long familyId = Long.valueOf(body.get("familyId").toString());
         Long familyId = Long.valueOf(body.get("familyId").toString());
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>().eq(Child::getFamilyId, familyId));
+        List<FamilyMember> children = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getFamilyId, familyId));
 
 
         List<Map<String, Object>> childrenStats = new ArrayList<>();
         List<Map<String, Object>> childrenStats = new ArrayList<>();
         int familyTotalTasks = 0;
         int familyTotalTasks = 0;
         int familyCompletedTasks = 0;
         int familyCompletedTasks = 0;
         int familyTotalGames = 0;
         int familyTotalGames = 0;
 
 
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             Map<String, Object> childStat = new HashMap<>();
             Map<String, Object> childStat = new HashMap<>();
             childStat.put("childId", child.getId());
             childStat.put("childId", child.getId());
             childStat.put("childName", child.getNickname());
             childStat.put("childName", child.getNickname());
@@ -193,14 +193,14 @@ public class ReportStatsController {
     @PostMapping("/points/ranking")
     @PostMapping("/points/ranking")
     public Result<List<Map<String, Object>>> pointsRanking(@RequestBody Map<String, Object> body) {
     public Result<List<Map<String, Object>>> pointsRanking(@RequestBody Map<String, Object> body) {
         Long familyId = Long.valueOf(body.get("familyId").toString());
         Long familyId = Long.valueOf(body.get("familyId").toString());
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getFamilyId, familyId)
-                        .orderByDesc(Child::getTotalPoints));
+        List<FamilyMember> children = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId)
+                        .orderByDesc(FamilyMember::getTotalPoints));
 
 
         List<Map<String, Object>> ranking = new ArrayList<>();
         List<Map<String, Object>> ranking = new ArrayList<>();
         int rank = 1;
         int rank = 1;
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             Map<String, Object> item = new HashMap<>();
             Map<String, Object> item = new HashMap<>();
             item.put("rank", rank++);
             item.put("rank", rank++);
             item.put("childId", child.getId());
             item.put("childId", child.getId());

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/controller/StreakController.java

@@ -3,9 +3,9 @@ import javax.annotation.Resource;
 
 
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.service.StreakService;
 import com.etotem.cfc.service.StreakService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -25,7 +25,7 @@ public class StreakController {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     /**
     /**
      * 获取孩子的打卡进度信息
      * 获取孩子的打卡进度信息
@@ -43,7 +43,7 @@ public class StreakController {
                 return Result.error("用户不存在");
                 return Result.error("用户不存在");
             }
             }
 
 
-            Child child = childMapper.selectById(childId);
+            FamilyMember child = familyMemberMapper.selectById(childId);
             if (child == null) {
             if (child == null) {
                 return Result.error("孩子不存在");
                 return Result.error("孩子不存在");
             }
             }

+ 9 - 9
cfc-backend/src/main/java/com/etotem/cfc/controller/UserController.java

@@ -2,15 +2,15 @@ package com.etotem.cfc.controller;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
-import com.etotem.cfc.dto.ChildInfoDTO;
+import com.etotem.cfc.dto.FamilyMemberInfoDTO;
 import com.etotem.cfc.entity.AssessmentOrder;
 import com.etotem.cfc.entity.AssessmentOrder;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.PaymentOrder;
 import com.etotem.cfc.entity.PaymentOrder;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.AssessmentOrderMapper;
 import com.etotem.cfc.mapper.AssessmentOrderMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.PaymentOrderMapper;
 import com.etotem.cfc.mapper.PaymentOrderMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
@@ -41,7 +41,7 @@ public class UserController {
     private FamilyMapper familyMapper;
     private FamilyMapper familyMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PaymentOrderMapper paymentOrderMapper;
     private PaymentOrderMapper paymentOrderMapper;
@@ -57,9 +57,9 @@ public class UserController {
 
 
     @PostMapping("/children/list")
     @PostMapping("/children/list")
     @Operation(summary = "获取子女列表")
     @Operation(summary = "获取子女列表")
-    public Result<List<ChildInfoDTO>> getChildren(HttpServletRequest request) {
+    public Result<List<FamilyMemberInfoDTO>> getFamilyMemberren(HttpServletRequest request) {
         Long userId = (Long) request.getAttribute("userId");
         Long userId = (Long) request.getAttribute("userId");
-        List<ChildInfoDTO> children = userService.getChildren(userId);
+        List<FamilyMemberInfoDTO> children = userService.getFamilyMemberren(userId);
         return Result.success(children);
         return Result.success(children);
     }
     }
 
 
@@ -125,9 +125,9 @@ public class UserController {
                 .eq(User::getRole, "parent")
                 .eq(User::getRole, "parent")
         );
         );
 
 
-        List<Child> children = childMapper.selectList(
-            new LambdaQueryWrapper<Child>()
-                .eq(Child::getFamilyId, familyId)
+        List<FamilyMember> children = familyMemberMapper.selectList(
+            new LambdaQueryWrapper<FamilyMember>()
+                .eq(FamilyMember::getFamilyId, familyId)
         );
         );
 
 
         Map<String, Object> result = new HashMap<>();
         Map<String, Object> result = new HashMap<>();

+ 16 - 16
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java

@@ -27,7 +27,7 @@ public class AdminController {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
@@ -142,23 +142,23 @@ public class AdminController {
 
 
     // 孩子管理
     // 孩子管理
     @PostMapping("/children")
     @PostMapping("/children")
-    public Result<Page<Child>> getChildren(@RequestBody Map<String, Object> params) {
+    public Result<Page<FamilyMember>> getChildren(@RequestBody Map<String, Object> params) {
         int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
         int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
         int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 10;
         int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 10;
-        Page<Child> pageParam = new Page<>(page, size);
-        Page<Child> result = childMapper.selectPage(pageParam, null);
+        Page<FamilyMember> pageParam = new Page<>(page, size);
+        Page<FamilyMember> result = familyMemberMapper.selectPage(pageParam, null);
         return Result.success(result);
         return Result.success(result);
     }
     }
 
 
     @PostMapping("/children/{id}")
     @PostMapping("/children/{id}")
-    public Result<Child> getChild(@PathVariable Long id) {
-        return Result.success(childMapper.selectById(id));
+    public Result<FamilyMember> getChild(@PathVariable Long id) {
+        return Result.success(familyMemberMapper.selectById(id));
     }
     }
 
 
     @PutMapping("/children/{id}")
     @PutMapping("/children/{id}")
-    public Result<Boolean> updateChild(@PathVariable Long id, @RequestBody Child child) {
+    public Result<Boolean> updateChild(@PathVariable Long id, @RequestBody FamilyMember child) {
         child.setId(id);
         child.setId(id);
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
         return Result.success(true);
         return Result.success(true);
     }
     }
 
 
@@ -169,7 +169,7 @@ public class AdminController {
         Integer amount = params.get("amount") != null ? ((Number) params.get("amount")).intValue() : 0;
         Integer amount = params.get("amount") != null ? ((Number) params.get("amount")).intValue() : 0;
         String reason = (String) params.get("reason");
         String reason = (String) params.get("reason");
 
 
-        Child child = childMapper.selectById(id);
+        FamilyMember child = familyMemberMapper.selectById(id);
         if (child == null) {
         if (child == null) {
             return Result.error("孩子不存在");
             return Result.error("孩子不存在");
         }
         }
@@ -181,7 +181,7 @@ public class AdminController {
 
 
         child.setTotalPoints(newBalance);
         child.setTotalPoints(newBalance);
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
         // 记录积分流水
         // 记录积分流水
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();
@@ -330,10 +330,10 @@ public class AdminController {
             reward.setExchangedAt(new Date());
             reward.setExchangedAt(new Date());
         } else {
         } else {
             // 拒绝,返还积分
             // 拒绝,返还积分
-            Child child = childMapper.selectById(reward.getChildId());
+            FamilyMember child = familyMemberMapper.selectById(reward.getChildId());
             if (child != null) {
             if (child != null) {
                 child.setTotalPoints(child.getTotalPoints() + reward.getPointsRequired());
                 child.setTotalPoints(child.getTotalPoints() + reward.getPointsRequired());
-                childMapper.updateById(child);
+                familyMemberMapper.updateById(child);
 
 
                 PointsLog log = new PointsLog();
                 PointsLog log = new PointsLog();
                 log.setChildId(reward.getChildId());
                 log.setChildId(reward.getChildId());
@@ -408,7 +408,7 @@ public class AdminController {
     public Result<Map<String, Object>> getPointsStats() {
     public Result<Map<String, Object>> getPointsStats() {
         Map<String, Object> stats = new HashMap<>();
         Map<String, Object> stats = new HashMap<>();
 
 
-        Long totalChildren = childMapper.selectCount(null);
+        Long totalChildren = familyMemberMapper.selectCount(null);
 
 
         stats.put("totalChildren", totalChildren);
         stats.put("totalChildren", totalChildren);
 
 
@@ -450,9 +450,9 @@ public class AdminController {
         java.util.List<User> parents = userMapper.selectList(parentWrapper);
         java.util.List<User> parents = userMapper.selectList(parentWrapper);
 
 
         // 查询孩子
         // 查询孩子
-        LambdaQueryWrapper<Child> childWrapper = new LambdaQueryWrapper<>();
-        childWrapper.eq(Child::getFamilyId, id);
-        java.util.List<Child> children = childMapper.selectList(childWrapper);
+        LambdaQueryWrapper<FamilyMember> childWrapper = new LambdaQueryWrapper<>();
+        childWrapper.eq(FamilyMember::getFamilyId, id);
+        java.util.List<FamilyMember> children = familyMemberMapper.selectList(childWrapper);
 
 
         Map<String, Object> result = new HashMap<>();
         Map<String, Object> result = new HashMap<>();
         result.put("family", family);
         result.put("family", family);

+ 32 - 26
cfc-backend/src/main/java/com/etotem/cfc/controller/cart/CartController.java

@@ -1,10 +1,11 @@
 package com.etotem.cfc.controller.cart;
 package com.etotem.cfc.controller.cart;
 
 
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.CartItemDTO;
 import com.etotem.cfc.service.CartService;
 import com.etotem.cfc.service.CartService;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestAttribute;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.bind.annotation.RestController;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
@@ -12,43 +13,48 @@ import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
 @RestController
 @RestController
+@RequestMapping("/api/cart")
 public class CartController {
 public class CartController {
 
 
     @Resource
     @Resource
     private CartService cartService;
     private CartService cartService;
 
 
-    @PostMapping("/api/cart/list")
-    public Result<List<Map<String, Object>>> list(@RequestAttribute("userId") Long userId) {
-        return cartService.list(userId);
+    @PostMapping("/add")
+    public Result<CartItemDTO> add(@RequestBody Map<String, Object> params) {
+        Long userId = Long.valueOf(params.get("userId").toString());
+        Long productId = Long.valueOf(params.get("productId").toString());
+        Integer quantity = params.containsKey("quantity") ? Integer.valueOf(params.get("quantity").toString()) : 1;
+
+        CartItemDTO dto = cartService.addToCart(userId, productId, quantity);
+        if (dto == null) {
+            return Result.error("商品不存在");
+        }
+        return Result.success(dto);
     }
     }
 
 
-    @PostMapping("/api/cart/add")
-    public Result<String> add(@RequestAttribute("userId") Long userId, @RequestBody Map<String, Object> body) {
-        Long productId = Long.valueOf(body.get("productId").toString());
-        return cartService.add(userId, productId);
+    @PostMapping("/list")
+    public Result<List<CartItemDTO>> list(@RequestBody Map<String, Object> params) {
+        Long userId = Long.valueOf(params.get("userId").toString());
+        List<CartItemDTO> list = cartService.getCartList(userId);
+        return Result.success(list);
     }
     }
 
 
-    @PostMapping("/api/cart/update")
-    public Result<String> update(@RequestAttribute("userId") Long userId, @RequestBody Map<String, Object> body) {
-        Long productId = Long.valueOf(body.get("productId").toString());
-        Integer quantity = Integer.valueOf(body.get("quantity").toString());
-        return cartService.update(userId, productId, quantity);
-    }
+    @PostMapping("/update")
+    public Result<Void> update(@RequestBody Map<String, Object> params) {
+        Long userId = Long.valueOf(params.get("userId").toString());
+        Long productId = Long.valueOf(params.get("productId").toString());
+        Integer quantity = Integer.valueOf(params.get("quantity").toString());
 
 
-    @PostMapping("/api/cart/remove")
-    public Result<String> remove(@RequestAttribute("userId") Long userId, @RequestBody Map<String, Object> body) {
-        Long productId = Long.valueOf(body.get("productId").toString());
-        return cartService.remove(userId, productId);
+        cartService.updateQuantity(userId, productId, quantity);
+        return Result.success();
     }
     }
 
 
-    @PostMapping("/api/cart/toggle")
-    public Result<String> toggle(@RequestAttribute("userId") Long userId, @RequestBody Map<String, Object> body) {
-        Long productId = Long.valueOf(body.get("productId").toString());
-        return cartService.toggle(userId, productId);
-    }
+    @PostMapping("/remove")
+    public Result<Void> remove(@RequestBody Map<String, Object> params) {
+        Long userId = Long.valueOf(params.get("userId").toString());
+        Long productId = Long.valueOf(params.get("productId").toString());
 
 
-    @PostMapping("/api/cart/clearSelected")
-    public Result<String> clearSelected(@RequestAttribute("userId") Long userId) {
-        return cartService.clearSelected(userId);
+        cartService.removeItem(userId, productId);
+        return Result.success();
     }
     }
 }
 }

+ 17 - 17
cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyUserController.java

@@ -3,10 +3,10 @@ package com.etotem.cfc.controller.family;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.*;
 import com.etotem.cfc.dto.*;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.UserService;
 import com.etotem.cfc.service.UserService;
@@ -35,7 +35,7 @@ public class FamilyUserController {
     private FamilyMapper familyMapper;
     private FamilyMapper familyMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     private Long getUserId(javax.servlet.http.HttpServletRequest request) {
     private Long getUserId(javax.servlet.http.HttpServletRequest request) {
         // 从请求属性中获取用户ID(由拦截器设置)
         // 从请求属性中获取用户ID(由拦截器设置)
@@ -121,11 +121,11 @@ public class FamilyUserController {
 
 
     @Operation(summary = "家长切换到指定孩子身份")
     @Operation(summary = "家长切换到指定孩子身份")
     @PostMapping("/switch-to-child")
     @PostMapping("/switch-to-child")
-    public Result<Long> switchToChild(javax.servlet.http.HttpServletRequest request,
+    public Result<Long> switchToFamilyMember(javax.servlet.http.HttpServletRequest request,
                                        @RequestBody Map<String, Long> body) {
                                        @RequestBody Map<String, Long> body) {
         Long userId = getUserId(request);
         Long userId = getUserId(request);
         Long childId = body.get("childId");
         Long childId = body.get("childId");
-        Long result = userService.switchToChild(userId, childId);
+        Long result = userService.switchToFamilyMember(userId, childId);
         return Result.success(result);
         return Result.success(result);
     }
     }
 
 
@@ -139,28 +139,28 @@ public class FamilyUserController {
 
 
     @Operation(summary = "创建孩子")
     @Operation(summary = "创建孩子")
     @PostMapping("/children")
     @PostMapping("/children")
-    public Result<ChildInfoDTO> createChild(javax.servlet.http.HttpServletRequest request,
-                                            @RequestBody CreateChildDTO dto) {
+    public Result<FamilyMemberInfoDTO> createFamilyMember(javax.servlet.http.HttpServletRequest request,
+                                            @RequestBody CreateFamilyMemberDTO dto) {
         Long userId = getUserId(request);
         Long userId = getUserId(request);
-        ChildInfoDTO child = userService.createChild(userId, dto);
+        FamilyMemberInfoDTO child = userService.createFamilyMember(userId, dto);
         return Result.success(child);
         return Result.success(child);
     }
     }
 
 
     @Operation(summary = "获取孩子列表")
     @Operation(summary = "获取孩子列表")
     @PostMapping("/children/list")
     @PostMapping("/children/list")
-    public Result<List<ChildInfoDTO>> getChildren(javax.servlet.http.HttpServletRequest request) {
+    public Result<List<FamilyMemberInfoDTO>> getFamilyMemberren(javax.servlet.http.HttpServletRequest request) {
         Long userId = getUserId(request);
         Long userId = getUserId(request);
-        List<ChildInfoDTO> children = userService.getChildren(userId);
+        List<FamilyMemberInfoDTO> children = userService.getFamilyMemberren(userId);
         return Result.success(children);
         return Result.success(children);
     }
     }
 
 
     @Operation(summary = "更新孩子信息")
     @Operation(summary = "更新孩子信息")
     @PostMapping("/children/update")
     @PostMapping("/children/update")
-    public Result<Boolean> updateChild(javax.servlet.http.HttpServletRequest request,
-                                       @RequestBody UpdateChildDTO dto) {
+    public Result<Boolean> updateFamilyMember(javax.servlet.http.HttpServletRequest request,
+                                       @RequestBody UpdateFamilyMemberDTO dto) {
         Long userId = getUserId(request);
         Long userId = getUserId(request);
-        Long childId = dto.getChildId();
-        boolean success = userService.updateChild(userId, childId, dto);
+        Long childId = dto.getFamilyMemberId();
+        boolean success = userService.updateFamilyMember(userId, childId, dto);
         return Result.success(success);
         return Result.success(success);
     }
     }
 
 
@@ -193,9 +193,9 @@ public class FamilyUserController {
         );
         );
 
 
         // 查询孩子列表
         // 查询孩子列表
-        List<Child> children = childMapper.selectList(
-            new LambdaQueryWrapper<Child>()
-                .eq(Child::getFamilyId, familyId)
+        List<FamilyMember> children = familyMemberMapper.selectList(
+            new LambdaQueryWrapper<FamilyMember>()
+                .eq(FamilyMember::getFamilyId, familyId)
         );
         );
 
 
         Map<String, Object> result = new HashMap<>();
         Map<String, Object> result = new HashMap<>();

+ 22 - 22
cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideFamilyTaskController.java

@@ -7,10 +7,10 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.TaskReviewDTO;
 import com.etotem.cfc.dto.TaskReviewDTO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.TaskService;
 import com.etotem.cfc.service.TaskService;
@@ -32,7 +32,7 @@ public class GuideFamilyTaskController {
     private TaskService taskService;
     private TaskService taskService;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
@@ -91,16 +91,16 @@ public class GuideFamilyTaskController {
             item.put("parentName", parent.getNickname());
             item.put("parentName", parent.getNickname());
 
 
             // 获取家庭孩子列表
             // 获取家庭孩子列表
-            List<Child> children = childMapper.selectList(
-                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Child>()
+            List<FamilyMember> children = familyMemberMapper.selectList(
+                new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<FamilyMember>()
                     .eq("family_id", familyId)
                     .eq("family_id", familyId)
             );
             );
             item.put("childrenCount", children.size());
             item.put("childrenCount", children.size());
 
 
             // 获取待审核任务数量
             // 获取待审核任务数量
             long pendingCount = 0;
             long pendingCount = 0;
-            for (Child child : children) {
-                List<Task> pendingTasks = taskService.getPendingReviewTasksForChild(child.getId());
+            for (FamilyMember child : children) {
+                List<Task> pendingTasks = taskService.getPendingReviewTasksForFamilyMember(child.getId());
                 pendingCount += pendingTasks.size();
                 pendingCount += pendingTasks.size();
             }
             }
             item.put("pendingReviewCount", pendingCount);
             item.put("pendingReviewCount", pendingCount);
@@ -127,8 +127,8 @@ public class GuideFamilyTaskController {
             @PathVariable Long familyId) {
             @PathVariable Long familyId) {
         if (!"teacher".equals(role)) { return Result.error("Access denied"); }
         if (!"teacher".equals(role)) { return Result.error("Access denied"); }
 
 
-        List<Child> children = childMapper.selectList(
-            new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<Child>()
+        List<FamilyMember> children = familyMemberMapper.selectList(
+            new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<FamilyMember>()
                 .eq("family_id", familyId)
                 .eq("family_id", familyId)
         );
         );
 
 
@@ -137,7 +137,7 @@ public class GuideFamilyTaskController {
         int totalCompleted = 0;
         int totalCompleted = 0;
         int totalOverdue = 0;
         int totalOverdue = 0;
 
 
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             Map<String, Object> childInfo = new HashMap<>();
             Map<String, Object> childInfo = new HashMap<>();
             childInfo.put("childId", child.getId());
             childInfo.put("childId", child.getId());
             childInfo.put("childName", child.getNickname());
             childInfo.put("childName", child.getNickname());
@@ -152,12 +152,12 @@ public class GuideFamilyTaskController {
                 .count());
                 .count());
 
 
             // 获取待审核任务
             // 获取待审核任务
-            List<Task> pendingTasks = taskService.getPendingReviewTasksForChild(child.getId());
+            List<Task> pendingTasks = taskService.getPendingReviewTasksForFamilyMember(child.getId());
             childInfo.put("pendingReviewCount", pendingTasks.size());
             childInfo.put("pendingReviewCount", pendingTasks.size());
             totalPending += pendingTasks.size();
             totalPending += pendingTasks.size();
 
 
             // 统计已完成和超时
             // 统计已完成和超时
-            List<Task> allTasks = taskService.getAllTasksByChild(child.getId());
+            List<Task> allTasks = taskService.getAllTasksByFamilyMember(child.getId());
             totalCompleted += allTasks.stream()
             totalCompleted += allTasks.stream()
                 .filter(t -> "completed".equals(t.getStatus()))
                 .filter(t -> "completed".equals(t.getStatus()))
                 .count();
                 .count();
@@ -171,7 +171,7 @@ public class GuideFamilyTaskController {
         Map<String, Object> result = new HashMap<>();
         Map<String, Object> result = new HashMap<>();
         result.put("familyId", familyId);
         result.put("familyId", familyId);
         result.put("children", childTaskList);
         result.put("children", childTaskList);
-        result.put("totalChildren", children.size());
+        result.put("totalFamilyMemberren", children.size());
         result.put("totalPendingReview", totalPending);
         result.put("totalPendingReview", totalPending);
         result.put("totalCompleted", totalCompleted);
         result.put("totalCompleted", totalCompleted);
         result.put("totalOverdue", totalOverdue);
         result.put("totalOverdue", totalOverdue);
@@ -189,7 +189,7 @@ public class GuideFamilyTaskController {
         @ApiResponse(responseCode = "401", description = "未登录")
         @ApiResponse(responseCode = "401", description = "未登录")
     })
     })
     @PostMapping("/{familyId}/children/{childId}/tasks")
     @PostMapping("/{familyId}/children/{childId}/tasks")
-    public Result<Page<Task>> getChildTasks(
+    public Result<Page<Task>> getFamilyMemberTasks(
             @PathVariable Long familyId,
             @PathVariable Long familyId,
             @PathVariable Long childId,
             @PathVariable Long childId,
             @RequestAttribute("role") String role,
             @RequestAttribute("role") String role,
@@ -200,7 +200,7 @@ public class GuideFamilyTaskController {
         Integer size = params != null && params.get("size") != null ? Integer.valueOf(params.get("size").toString()) : 10;
         Integer size = params != null && params.get("size") != null ? Integer.valueOf(params.get("size").toString()) : 10;
         String status = params != null && params.get("status") != null ? params.get("status").toString() : null;
         String status = params != null && params.get("status") != null ? params.get("status").toString() : null;
 
 
-        Page<Task> tasks = taskService.getTaskHistoryByChild(childId, page, size, status);
+        Page<Task> tasks = taskService.getTaskHistoryByFamilyMember(childId, page, size, status);
         return Result.success(tasks);
         return Result.success(tasks);
     }
     }
 
 
@@ -220,7 +220,7 @@ public class GuideFamilyTaskController {
             @RequestAttribute("role") String role) {
             @RequestAttribute("role") String role) {
 
 
         if (!"teacher".equals(role)) { return Result.error("Access denied"); }
         if (!"teacher".equals(role)) { return Result.error("Access denied"); }
-        List<Task> tasks = taskService.getPendingReviewTasksForChild(childId);
+        List<Task> tasks = taskService.getPendingReviewTasksForFamilyMember(childId);
         return Result.success(tasks);
         return Result.success(tasks);
     }
     }
 
 
@@ -405,14 +405,14 @@ public class GuideFamilyTaskController {
             Date todayStart = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());
             Date todayStart = Date.from(LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant());
 
 
             for (Long familyId : familyIds) {
             for (Long familyId : familyIds) {
-                List<Child> children = childMapper.selectList(
-                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Child>()
-                        .eq(Child::getFamilyId, familyId));
-                for (Child child : children) {
+                List<FamilyMember> children = familyMemberMapper.selectList(
+                    new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId));
+                for (FamilyMember child : children) {
                     // 今日完成任务:status=completed 且 completedAt >= todayStart
                     // 今日完成任务:status=completed 且 completedAt >= todayStart
                     long completed = taskMapper.selectCount(
                     long completed = taskMapper.selectCount(
                         new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Task>()
                         new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Task>()
-                            .eq(Task::getChildId, child.getId())
+                            .eq(Task::getFamilyMemberId, child.getId())
                             .eq(Task::getStatus, "completed")
                             .eq(Task::getStatus, "completed")
                             .ge(Task::getCompletedAt, todayStart));
                             .ge(Task::getCompletedAt, todayStart));
                     todayCompleted += completed;
                     todayCompleted += completed;
@@ -420,7 +420,7 @@ public class GuideFamilyTaskController {
                     // 待审核任务
                     // 待审核任务
                     long pending = taskMapper.selectCount(
                     long pending = taskMapper.selectCount(
                         new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Task>()
                         new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<Task>()
-                            .eq(Task::getChildId, child.getId())
+                            .eq(Task::getFamilyMemberId, child.getId())
                             .eq(Task::getNeedReview, 1)
                             .eq(Task::getNeedReview, 1)
                             .eq(Task::getStatus, "completed"));
                             .eq(Task::getStatus, "completed"));
                     pendingReview += pending;
                     pendingReview += pending;

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/controller/mind/EmotionAlertController.java

@@ -2,10 +2,10 @@ package com.etotem.cfc.controller.mind;
 
 
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.EmotionAlertVO;
 import com.etotem.cfc.dto.EmotionAlertVO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.EmotionAlert;
 import com.etotem.cfc.entity.EmotionAlert;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.EmotionAlertMapper;
 import com.etotem.cfc.mapper.EmotionAlertMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.EmotionAlertService;
 import com.etotem.cfc.service.EmotionAlertService;
@@ -36,7 +36,7 @@ public class EmotionAlertController {
     private EmotionAlertMapper emotionAlertMapper;
     private EmotionAlertMapper emotionAlertMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
@@ -97,7 +97,7 @@ public class EmotionAlertController {
             return Result.error("告警不存在");
             return Result.error("告警不存在");
         }
         }
 
 
-        Child child = childMapper.selectById(alert.getChildId());
+        FamilyMember child = familyMemberMapper.selectById(alert.getChildId());
         if (child == null) {
         if (child == null) {
             return Result.error("孩子不存在");
             return Result.error("孩子不存在");
         }
         }

+ 7 - 7
cfc-backend/src/main/java/com/etotem/cfc/controller/parent/ParentAssessmentController.java

@@ -7,7 +7,7 @@ import com.etotem.cfc.entity.AssessmentPointsConfig;
 import com.etotem.cfc.entity.AssessmentRecord;
 import com.etotem.cfc.entity.AssessmentRecord;
 import com.etotem.cfc.entity.FamilyAssessmentConfig;
 import com.etotem.cfc.entity.FamilyAssessmentConfig;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.AssessmentService;
 import com.etotem.cfc.service.AssessmentService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Operation;
@@ -31,7 +31,7 @@ public class ParentAssessmentController {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Operation(summary = "获取当前测评任务")
     @Operation(summary = "获取当前测评任务")
     @PostMapping("/current")
     @PostMapping("/current")
@@ -100,7 +100,7 @@ public class ParentAssessmentController {
             return Result.error("暂无可用测评资料");
             return Result.error("暂无可用测评资料");
         }
         }
 
 
-        Long childId = getChildIdByUserId(userId);
+        Long childId = getFamilyMemberIdByUserId(userId);
         AssessmentRecord record = assessmentService.createRecord(familyId, childId, material.getId());
         AssessmentRecord record = assessmentService.createRecord(familyId, childId, material.getId());
 
 
         return Result.success(record);
         return Result.success(record);
@@ -118,12 +118,12 @@ public class ParentAssessmentController {
         return user != null ? user.getFamilyId() : null;
         return user != null ? user.getFamilyId() : null;
     }
     }
 
 
-    private Long getChildIdByUserId(Long userId) {
+    private Long getFamilyMemberIdByUserId(Long userId) {
         User user = userMapper.selectById(userId);
         User user = userMapper.selectById(userId);
         if (user == null) return null;
         if (user == null) return null;
-        LambdaQueryWrapper<com.etotem.cfc.entity.Child> wrapper = new LambdaQueryWrapper<>();
-        wrapper.eq(com.etotem.cfc.entity.Child::getFamilyId, user.getFamilyId());
-        com.etotem.cfc.entity.Child child = childMapper.selectOne(wrapper);
+        LambdaQueryWrapper<com.etotem.cfc.entity.FamilyMember> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(com.etotem.cfc.entity.FamilyMember::getFamilyId, user.getFamilyId());
+        com.etotem.cfc.entity.FamilyMember child = familyMemberMapper.selectOne(wrapper);
         return child != null ? child.getId() : null;
         return child != null ? child.getId() : null;
     }
     }
 }
 }

+ 5 - 5
cfc-backend/src/main/java/com/etotem/cfc/controller/stats/StatsController.java

@@ -2,14 +2,14 @@ package com.etotem.cfc.controller.stats;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.CommissionRecord;
 import com.etotem.cfc.entity.CommissionRecord;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.MemberUpgradeRecord;
 import com.etotem.cfc.entity.MemberUpgradeRecord;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.TrialMembership;
 import com.etotem.cfc.entity.TrialMembership;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.CommissionRecordMapper;
 import com.etotem.cfc.mapper.CommissionRecordMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.GuidePackageMapper;
 import com.etotem.cfc.mapper.GuidePackageMapper;
@@ -46,7 +46,7 @@ public class StatsController {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private FamilyMapper familyMapper;
     private FamilyMapper familyMapper;
@@ -79,7 +79,7 @@ public class StatsController {
         CompletableFuture<Long> parentFuture = CompletableFuture.supplyAsync(() ->
         CompletableFuture<Long> parentFuture = CompletableFuture.supplyAsync(() ->
             userMapper.selectCount(new QueryWrapper<User>().eq("role", "parent")), taskExecutor);
             userMapper.selectCount(new QueryWrapper<User>().eq("role", "parent")), taskExecutor);
         CompletableFuture<Long> childFuture = CompletableFuture.supplyAsync(() ->
         CompletableFuture<Long> childFuture = CompletableFuture.supplyAsync(() ->
-            childMapper.selectCount(null), taskExecutor);
+            familyMemberMapper.selectCount(null), taskExecutor);
         CompletableFuture<Long> teacherFuture = CompletableFuture.supplyAsync(() ->
         CompletableFuture<Long> teacherFuture = CompletableFuture.supplyAsync(() ->
             userMapper.selectCount(new QueryWrapper<User>().eq("role", "teacher")), taskExecutor);
             userMapper.selectCount(new QueryWrapper<User>().eq("role", "teacher")), taskExecutor);
         CompletableFuture<Long> pendingGuideFuture = CompletableFuture.supplyAsync(() ->
         CompletableFuture<Long> pendingGuideFuture = CompletableFuture.supplyAsync(() ->
@@ -158,7 +158,7 @@ public class StatsController {
             CompletableFuture<Long> parentFuture = CompletableFuture.supplyAsync(() ->
             CompletableFuture<Long> parentFuture = CompletableFuture.supplyAsync(() ->
                 userMapper.selectCount(new QueryWrapper<User>().eq("role", "parent")), taskExecutor);
                 userMapper.selectCount(new QueryWrapper<User>().eq("role", "parent")), taskExecutor);
             CompletableFuture<Long> childFuture = CompletableFuture.supplyAsync(() ->
             CompletableFuture<Long> childFuture = CompletableFuture.supplyAsync(() ->
-                childMapper.selectCount(null), taskExecutor);
+                familyMemberMapper.selectCount(null), taskExecutor);
             CompletableFuture<Long> teacherFuture = CompletableFuture.supplyAsync(() ->
             CompletableFuture<Long> teacherFuture = CompletableFuture.supplyAsync(() ->
                 userMapper.selectCount(new QueryWrapper<User>().eq("role", "teacher")), taskExecutor);
                 userMapper.selectCount(new QueryWrapper<User>().eq("role", "teacher")), taskExecutor);
 
 

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/AddFamilyMemberDTO.java

@@ -22,4 +22,7 @@ public class AddFamilyMemberDTO {
 
 
     /** 关系类型key(必填) */
     /** 关系类型key(必填) */
     private String relationshipType;
     private String relationshipType;
+
+    /** 角色覆盖: auto/parent/child/elderly(选填,默认auto) */
+    private String roleOverride;
 }
 }

+ 11 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/CreateProductOrderDTO.java

@@ -15,4 +15,15 @@ public class CreateProductOrderDTO {
     private Integer discountAmount;
     private Integer discountAmount;
     private Long couponId;
     private Long couponId;
     private Integer deliveryMethod = 1; // 1=快递 2=自提
     private Integer deliveryMethod = 1; // 1=快递 2=自提
+
+    // Explicit getters needed due to Lombok issues
+    public List<OrderItemVO> getItems() { return items; }
+    public Long getProductId() { return productId; }
+    public Integer getQuantity() { return quantity; }
+    public String getPaymentMethod() { return paymentMethod; }
+    public String getRemark() { return remark; }
+    public Integer getDiscountAmount() { return discountAmount; }
+    public Long getCouponId() { return couponId; }
+    public String getAddressSnapshot() { return addressSnapshot; }
+    public Integer getDeliveryMethod() { return deliveryMethod; }
 }
 }

+ 10 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/CreateWishDTO.java

@@ -23,4 +23,14 @@ public class CreateWishDTO {
     private Long childId;          // 目标孩子ID(家长创建时必填)
     private Long childId;          // 目标孩子ID(家长创建时必填)
 
 
     private Integer pointsRequired; // 所需积分(家长创建时必填)
     private Integer pointsRequired; // 所需积分(家长创建时必填)
+
+    // Explicit setters (Lombok fallback)
+    public void setTitle(String title) { this.title = title; }
+    public void setDescription(String description) { this.description = description; }
+    public void setCategory(String category) { this.category = category; }
+    public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; }
+    public void setLinkUrl(String linkUrl) { this.linkUrl = linkUrl; }
+    public void setEstimatedPrice(Long estimatedPrice) { this.estimatedPrice = estimatedPrice; }
+    public void setChildId(Long childId) { this.childId = childId; }
+    public void setPointsRequired(Integer pointsRequired) { this.pointsRequired = pointsRequired; }
 }
 }

+ 5 - 2
cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyMemberVO.java

@@ -34,8 +34,11 @@ public class FamilyMemberVO {
     /** 关系类型显示名 */
     /** 关系类型显示名 */
     private String relationshipTypeName;
     private String relationshipTypeName;
 
 
-    /** 能力角色: parent/child */
-    private String capabilityRole;
+    /** 计算后的实际角色(前端展示用) */
+    private String effectiveRole;
+
+    /** 管理员设置的角色覆盖值 */
+    private String roleOverride;
 
 
     /** 角色标签显示文本(家长/孩子/配偶/父母等) */
     /** 角色标签显示文本(家长/孩子/配偶/父母等) */
     private String roleLabel;
     private String roleLabel;

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/HealthDimensionVO.java

@@ -11,6 +11,8 @@ public class HealthDimensionVO {
     private BigDecimal healthIndex;
     private BigDecimal healthIndex;
     private List<DimensionItem> dimensions;
     private List<DimensionItem> dimensions;
     private Date updateTime;
     private Date updateTime;
+    /** 是否为同龄同性平均值(无个人数据时展示参考值) */
+    private Boolean isAverage;
 
 
     @Data
     @Data
     public static class DimensionItem {
     public static class DimensionItem {

+ 5 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/OrderItemVO.java

@@ -7,4 +7,9 @@ public class OrderItemVO {
     private Long productId;
     private Long productId;
     private Integer quantity = 1;
     private Integer quantity = 1;
     private String coverImage;
     private String coverImage;
+
+    // Explicit getters needed due to Lombok issues
+    public Long getProductId() { return productId; }
+    public Integer getQuantity() { return quantity; }
+    public String getCoverImage() { return coverImage; }
 }
 }

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/dto/SwitchMemberVO.java

@@ -11,8 +11,8 @@ public class SwitchMemberVO {
     /** 成员来源: family_member / child */
     /** 成员来源: family_member / child */
     private String source;
     private String source;
 
 
-    /** 能力角色: parent/child(前端根据此设置currentRole) */
-    private String capabilityRole;
+    /** 计算后的实际角色(前端根据此设置 currentRole) */
+    private String effectiveRole;
 
 
     /** 成员昵称 */
     /** 成员昵称 */
     private String nickname;
     private String nickname;

+ 21 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/WishDTO.java

@@ -38,4 +38,25 @@ public class WishDTO {
     private String createdAt;
     private String createdAt;
     private String exchangeRequestedAt;
     private String exchangeRequestedAt;
     private String fulfilledAt;
     private String fulfilledAt;
+
+    // Explicit setters (Lombok fallback)
+    public void setId(Long id) { this.id = id; }
+    public void setFamilyId(Long familyId) { this.familyId = familyId; }
+    public void setChildId(Long childId) { this.childId = childId; }
+    public void setChildName(String childName) { this.childName = childName; }
+    public void setCreatedBy(Long createdBy) { this.createdBy = createdBy; }
+    public void setCreatorType(String creatorType) { this.creatorType = creatorType; }
+    public void setCreatorName(String creatorName) { this.creatorName = creatorName; }
+    public void setTitle(String title) { this.title = title; }
+    public void setDescription(String description) { this.description = description; }
+    public void setCategory(String category) { this.category = category; }
+    public void setPointsRequired(Integer pointsRequired) { this.pointsRequired = pointsRequired; }
+    public void setPointsSetBy(Long pointsSetBy) { this.pointsSetBy = pointsSetBy; }
+    public void setStatus(String status) { this.status = status; }
+    public void setStatusText(String statusText) { this.statusText = statusText; }
+    public void setCurrentPoints(Integer currentPoints) { this.currentPoints = currentPoints; }
+    public void setProgress(Integer progress) { this.progress = progress; }
+    public void setCreatedAt(String createdAt) { this.createdAt = createdAt; }
+    public void setExchangeRequestedAt(String exchangeRequestedAt) { this.exchangeRequestedAt = exchangeRequestedAt; }
+    public void setFulfilledAt(String fulfilledAt) { this.fulfilledAt = fulfilledAt; }
 }
 }

+ 10 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/DanAssessmentResult.java

@@ -17,6 +17,16 @@ public class DanAssessmentResult implements Serializable {
 
 
     private Long childId;
     private Long childId;
 
 
+    private Long familyMemberId;
+
+    private String familyMemberName;
+
+    private Date familyMemberBirthday;
+
+    private String familyMemberGender;
+
+    private Integer familyMemberAge;
+
     private Long teacherId;
     private Long teacherId;
 
 
     private Long appointmentId;
     private Long appointmentId;

+ 43 - 2
cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyMember.java

@@ -40,8 +40,8 @@ public class FamilyMember implements Serializable {
     /** 关系类型key(关联relationship_types.type_key) */
     /** 关系类型key(关联relationship_types.type_key) */
     private String relationshipType;
     private String relationshipType;
 
 
-    /** 能力角色: parent/child(决定界面权限) */
-    private String capabilityRole;
+    /** 角色覆盖: auto/parent/child/elderly,空=auto */
+    private String roleOverride;
 
 
     /** 对家庭成员可见: 1=可见, 0=不可见 */
     /** 对家庭成员可见: 1=可见, 0=不可见 */
     private Integer showToFamily;
     private Integer showToFamily;
@@ -49,6 +49,9 @@ public class FamilyMember implements Serializable {
     /** 总积分 */
     /** 总积分 */
     private Integer totalPoints;
     private Integer totalPoints;
 
 
+    /** 年龄(冗余字段,用于匹配时自动计算设置,getAge()通过birthday计算) */
+    private Integer age;
+
     /** 信任度 0-100 */
     /** 信任度 0-100 */
     private BigDecimal trustScore;
     private BigDecimal trustScore;
 
 
@@ -63,5 +66,43 @@ public class FamilyMember implements Serializable {
 
 
     private Date createdAt;
     private Date createdAt;
 
 
+    /** 孩子专属字段(原children表) */
+    /** 身份证号(孩子) */
+    private String idCard;
+
+    /** 是否扣减积分(0否1是) */
+    private Integer penaltyEnabled;
+
+    /** 主题 */
+    private String theme;
+
+    /** 系统积分(干预/平台奖励) */
+    private Integer systemPoints;
+
+    /** 连续打卡天数 */
+    private Integer streakDays;
+
+    /** 上次任务日期 */
+    private Date lastTaskDate;
+
+    /** 每日专注上限 */
+    private Integer focusMaxDaily;
+
+    /** 今日剩余专注次数 */
+    private Integer focusRemaining;
+
+    /** 专注次数重置日期 */
+    private Date focusResetDate;
+
     private Date updatedAt;
     private Date updatedAt;
+
+    /**
+     * 计算年龄(根据birthday)
+     */
+    public int getAge() {
+        if (birthday == null) return 0;
+        java.time.LocalDate birth = birthday.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate();
+        java.time.LocalDate now = java.time.LocalDate.now();
+        return java.time.Period.between(birth, now).getYears();
+    }
 }
 }

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/GrowthPlan.java

@@ -17,6 +17,8 @@ public class GrowthPlan implements Serializable {
 
 
     private Long childId;
     private Long childId;
 
 
+    private Long familyMemberId;
+
     private Long teacherId;
     private Long teacherId;
 
 
     private Long assessmentId;
     private Long assessmentId;

+ 4 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/GrowthRecord.java

@@ -17,6 +17,10 @@ public class GrowthRecord implements Serializable {
 
 
     private Long childId;
     private Long childId;
 
 
+    private Long familyMemberId;
+
+    private Integer familyMemberAge;
+
     private Long parentId;
     private Long parentId;
 
 
     private Long assessmentId;
     private Long assessmentId;

+ 15 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/MediaRecord.java

@@ -63,4 +63,19 @@ public class MediaRecord implements Serializable {
      * 创建时间
      * 创建时间
      */
      */
     private Date createdAt;
     private Date createdAt;
+
+    // Explicit getters/setters needed due to Lombok issues
+    public void setType(String type) { this.type = type; }
+    public void setUrl(String url) { this.url = url; }
+    public void setTaskId(Long taskId) { this.taskId = taskId; }
+    public void setCreatorId(Long creatorId) { this.creatorId = creatorId; }
+    public void setDescription(String description) { this.description = description; }
+    public void setOriginalFilename(String originalFilename) { this.originalFilename = originalFilename; }
+    public void setFileSize(Long fileSize) { this.fileSize = fileSize; }
+    public void setContentType(String contentType) { this.contentType = contentType; }
+    public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
+    public Long getTaskId() { return taskId; }
+    public Date getCreatedAt() { return createdAt; }
+    public Long getCreatorId() { return creatorId; }
+    public String getUrl() { return url; }
 }
 }

+ 19 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/PackageOrder.java

@@ -46,4 +46,23 @@ public class PackageOrder implements Serializable {
     private Date createdAt;
     private Date createdAt;
 
 
     private Date updatedAt;
     private Date updatedAt;
+
+    // Explicit setters (Lombok fallback)
+    public void setId(Long id) { this.id = id; }
+    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
+    public void setUserId(Long userId) { this.userId = userId; }
+    public void setFamilyId(Long familyId) { this.familyId = familyId; }
+    public void setPackageId(Long packageId) { this.packageId = packageId; }
+    public void setPackageName(String packageName) { this.packageName = packageName; }
+    public void setPrice(Integer price) { this.price = price; }
+    public void setPlatformFee(Integer platformFee) { this.platformFee = platformFee; }
+    public void setUserCouponId(Long userCouponId) { this.userCouponId = userCouponId; }
+    public void setGuideId(Long guideId) { this.guideId = guideId; }
+    public void setStatus(String status) { this.status = status; }
+    public void setPayMethod(String payMethod) { this.payMethod = payMethod; }
+    public void setTransactionId(String transactionId) { this.transactionId = transactionId; }
+    public void setPaidAt(Date paidAt) { this.paidAt = paidAt; }
+    public void setNotifyData(String notifyData) { this.notifyData = notifyData; }
+    public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
+    public void setUpdatedAt(Date updatedAt) { this.updatedAt = updatedAt; }
 }
 }

+ 1 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/PointsExchangeRecord.java

@@ -14,6 +14,7 @@ public class PointsExchangeRecord implements Serializable {
     private Long id;
     private Long id;
     private Long userId;
     private Long userId;
     private Long childId;
     private Long childId;
+    private Long familyMemberId;
     private Long productId;
     private Long productId;
     private String productName;
     private String productName;
     private Integer pointsCost;
     private Integer pointsCost;

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/PointsLog.java

@@ -17,6 +17,8 @@ private Long id;
 
 
 private Long childId;
 private Long childId;
 
 
+private Long familyMemberId;
+
 private Long taskId;
 private Long taskId;
 
 
 private Integer amount;
 private Integer amount;

+ 20 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java

@@ -44,4 +44,24 @@ public class Product implements Serializable {
 
 
     private Date createdAt;
     private Date createdAt;
     private Date updatedAt;
     private Date updatedAt;
+
+    // Explicit getters needed due to Lombok issues
+    public Long getId() { return id; }
+    public String getName() { return name; }
+    public String getProductType() { return productType; }
+    public Integer getPrice() { return price; }
+    public String getCoverImage() { return coverImage; }
+    public Integer getStock() { return stock; }
+    public String getStatus() { return status; }
+    public String getMemberEligible() { return memberEligible; }
+    public String getDomain() { return domain; }
+    public Long getVendorId() { return vendorId; }
+    public String getVendorName() { return vendorName; }
+    public String getIntro() { return intro; }
+    public String getDescription() { return description; }
+    public String getImages() { return images; }
+    public Integer getMemberPrice() { return memberPrice; }
+    public Integer getSalesCount() { return salesCount; }
+    public Date getCreatedAt() { return createdAt; }
+    public Date getUpdatedAt() { return updatedAt; }
 }
 }

+ 15 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/ProductOrder.java

@@ -48,4 +48,19 @@ public class ProductOrder implements Serializable {
     private Date refundTime;           // 退款时间
     private Date refundTime;           // 退款时间
     private Date createdAt;
     private Date createdAt;
     private Date updatedAt;
     private Date updatedAt;
+
+    // Explicit setters needed due to Lombok issues
+    public void setOrderNo(String orderNo) { this.orderNo = orderNo; }
+    public void setBuyerId(Long buyerId) { this.buyerId = buyerId; }
+    public void setFamilyId(Long familyId) { this.familyId = familyId; }
+    public void setQuantity(Integer quantity) { this.quantity = quantity; }
+    public void setTotalAmount(Integer totalAmount) { this.totalAmount = totalAmount; }
+    public void setStatus(String status) { this.status = status; }
+    public void setPaymentMethod(String paymentMethod) { this.paymentMethod = paymentMethod; }
+    public void setCoverImage(String coverImage) { this.coverImage = coverImage; }
+    public void setDeliveryMethod(Integer deliveryMethod) { this.deliveryMethod = deliveryMethod; }
+    public void setPickupCode(String pickupCode) { this.pickupCode = pickupCode; }
+    public void setCancelAt(Date cancelAt) { this.cancelAt = cancelAt; }
+    public void setCreatedAt(Date createdAt) { this.createdAt = createdAt; }
+    public void setUpdatedAt(Date updatedAt) { this.updatedAt = updatedAt; }
 }
 }

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/entity/RelationshipType.java

@@ -21,8 +21,8 @@ public class RelationshipType implements Serializable {
     /** 类型显示名: 配偶/父母/子女/兄弟姐妹 */
     /** 类型显示名: 配偶/父母/子女/兄弟姐妹 */
     private String typeName;
     private String typeName;
 
 
-    /** 能力角色: parent/child */
-    private String capabilityRole;
+    /** 默认角色模板: parent/child */
+    private String defaultRole;
 
 
     /** 排序序号 */
     /** 排序序号 */
     private Integer sortOrder;
     private Integer sortOrder;

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Task.java

@@ -17,6 +17,8 @@ private Long id;
 
 
 private Long familyId;
 private Long familyId;
 
 
+private Long familyMemberId;
+
 private Long creatorId;
 private Long creatorId;
 
 
 // 执行者类型: child(孩子) 或 parent(家长)
 // 执行者类型: child(孩子) 或 parent(家长)

+ 5 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/ActivityService.java

@@ -7,11 +7,11 @@ import com.etotem.cfc.common.Result;
 import com.etotem.cfc.dto.ActivityDTO;
 import com.etotem.cfc.dto.ActivityDTO;
 import com.etotem.cfc.entity.Activity;
 import com.etotem.cfc.entity.Activity;
 import com.etotem.cfc.entity.ActivityRegistration;
 import com.etotem.cfc.entity.ActivityRegistration;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.mapper.ActivityMapper;
 import com.etotem.cfc.mapper.ActivityMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -33,7 +33,7 @@ public class ActivityService extends ServiceImpl<ActivityMapper, Activity> {
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -220,7 +220,7 @@ public class ActivityService extends ServiceImpl<ActivityMapper, Activity> {
 
 
         // 5. Create task with status='completed' (auto-completed by check-in)
         // 5. Create task with status='completed' (auto-completed by check-in)
         Task task = new Task();
         Task task = new Task();
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child != null) {
         if (child != null) {
             task.setFamilyId(child.getFamilyId());
             task.setFamilyId(child.getFamilyId());
         }
         }
@@ -261,7 +261,7 @@ public class ActivityService extends ServiceImpl<ActivityMapper, Activity> {
         if (child != null) {
         if (child != null) {
             child.setTotalPoints(child.getTotalPoints() + points);
             child.setTotalPoints(child.getTotalPoints() + points);
             child.setUpdatedAt(new Date());
             child.setUpdatedAt(new Date());
-            childMapper.updateById(child);
+            familyMemberMapper.updateById(child);
         }
         }
 
 
         // 8. Insert PointsLog
         // 8. Insert PointsLog

+ 5 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/ArticleService.java

@@ -7,13 +7,13 @@ import com.etotem.cfc.entity.Article;
 import com.etotem.cfc.entity.ArticleCategory;
 import com.etotem.cfc.entity.ArticleCategory;
 import com.etotem.cfc.entity.ArticleQuizRecord;
 import com.etotem.cfc.entity.ArticleQuizRecord;
 import com.etotem.cfc.entity.ArticleReadingRecord;
 import com.etotem.cfc.entity.ArticleReadingRecord;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.mapper.ArticleMapper;
 import com.etotem.cfc.mapper.ArticleMapper;
 import com.etotem.cfc.mapper.ArticleQuizRecordMapper;
 import com.etotem.cfc.mapper.ArticleQuizRecordMapper;
 import com.etotem.cfc.mapper.ArticleReadingRecordMapper;
 import com.etotem.cfc.mapper.ArticleReadingRecordMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.fasterxml.jackson.core.type.TypeReference;
 import com.fasterxml.jackson.core.type.TypeReference;
@@ -49,7 +49,7 @@ public class ArticleService {
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -238,7 +238,7 @@ public class ArticleService {
         task.setDeadline(cal.getTime());
         task.setDeadline(cal.getTime());
 
 
         // Get child to set familyId
         // Get child to set familyId
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child != null) {
         if (child != null) {
             task.setFamilyId(child.getFamilyId());
             task.setFamilyId(child.getFamilyId());
         }
         }
@@ -249,7 +249,7 @@ public class ArticleService {
         if (child != null) {
         if (child != null) {
             child.setTotalPoints(child.getTotalPoints() + points);
             child.setTotalPoints(child.getTotalPoints() + points);
             child.setUpdatedAt(new Date());
             child.setUpdatedAt(new Date());
-            childMapper.updateById(child);
+            familyMemberMapper.updateById(child);
         }
         }
 
 
         // 10. Insert PointsLog
         // 10. Insert PointsLog

+ 92 - 91
cfc-backend/src/main/java/com/etotem/cfc/service/CartService.java

@@ -1,130 +1,131 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.common.Result;
-import com.etotem.cfc.entity.CartItem;
+import com.etotem.cfc.dto.CartItemDTO;
+import com.etotem.cfc.entity.Cart;
 import com.etotem.cfc.entity.Product;
 import com.etotem.cfc.entity.Product;
-import com.etotem.cfc.mapper.CartItemMapper;
+import com.etotem.cfc.mapper.CartMapper;
 import com.etotem.cfc.mapper.ProductMapper;
 import com.etotem.cfc.mapper.ProductMapper;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
-import java.util.*;
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.List;
 
 
+@Slf4j
 @Service
 @Service
 public class CartService {
 public class CartService {
 
 
     @Resource
     @Resource
-    private CartItemMapper cartItemMapper;
+    private CartMapper cartMapper;
 
 
     @Resource
     @Resource
     private ProductMapper productMapper;
     private ProductMapper productMapper;
 
 
-    public Result<List<Map<String, Object>>> list(Long userId) {
-        List<CartItem> items = cartItemMapper.selectList(
-            new LambdaQueryWrapper<CartItem>()
-                .eq(CartItem::getUserId, userId)
-                .orderByDesc(CartItem::getCreatedAt)
-        );
-        List<Map<String, Object>> result = new ArrayList<>();
-        for (CartItem item : items) {
-            Product product = productMapper.selectById(item.getProductId());
-            Map<String, Object> vo = new LinkedHashMap<>();
-            vo.put("productId", item.getProductId());
-            vo.put("quantity", item.getQuantity());
-            vo.put("selected", item.getSelected());
-            if (product != null) {
-                vo.put("productName", product.getName());
-                vo.put("coverImage", product.getCoverImage());
-                vo.put("unitPrice", product.getPrice());
-                vo.put("stock", product.getStock());
-                vo.put("invalid", !"on_shelf".equals(product.getStatus()));
-            } else {
-                vo.put("productName", "商品已删除");
-                vo.put("coverImage", "");
-                vo.put("unitPrice", 0);
-                vo.put("stock", 0);
-                vo.put("invalid", true);
-            }
-            result.add(vo);
-        }
-        return Result.success(result);
-    }
-
-    public Result<String> add(Long userId, Long productId) {
+    @Transactional
+    public CartItemDTO addToCart(Long userId, Long productId, Integer quantity) {
         Product product = productMapper.selectById(productId);
         Product product = productMapper.selectById(productId);
-        if (product == null) return Result.error("商品不存在");
-        if (!"on_shelf".equals(product.getStatus())) return Result.error("商品已下架");
+        if (product == null) {
+            log.warn("addToCart failed: product not found, productId={}", productId);
+            return null;
+        }
 
 
-        CartItem existing = cartItemMapper.selectOne(
-            new LambdaQueryWrapper<CartItem>()
-                .eq(CartItem::getUserId, userId)
-                .eq(CartItem::getProductId, productId)
+        Cart existing = cartMapper.selectOne(
+            new LambdaQueryWrapper<Cart>()
+                .eq(Cart::getUserId, userId)
+                .eq(Cart::getProductId, productId)
         );
         );
+
         if (existing != null) {
         if (existing != null) {
-            existing.setQuantity(existing.getQuantity() + 1);
-            existing.setUpdatedAt(new Date());
-            cartItemMapper.updateById(existing);
+            existing.setQuantity(existing.getQuantity() + quantity);
+            existing.setUpdatedAt(LocalDateTime.now());
+            cartMapper.updateById(existing);
         } else {
         } else {
-            CartItem item = new CartItem();
-            item.setUserId(userId);
-            item.setProductId(productId);
-            item.setQuantity(1);
-            item.setSelected(true);
-            item.setCreatedAt(new Date());
-            item.setUpdatedAt(new Date());
-            cartItemMapper.insert(item);
+            Cart cart = new Cart();
+            cart.setUserId(userId);
+            cart.setProductId(productId);
+            cart.setQuantity(quantity);
+            cart.setCreatedAt(LocalDateTime.now());
+            cart.setUpdatedAt(LocalDateTime.now());
+            cartMapper.insert(cart);
         }
         }
-        return Result.success("已加入购物车");
+
+        return getCartItemDTO(userId, productId);
     }
     }
 
 
-    public Result<String> update(Long userId, Long productId, Integer quantity) {
-        CartItem item = cartItemMapper.selectOne(
-            new LambdaQueryWrapper<CartItem>()
-                .eq(CartItem::getUserId, userId)
-                .eq(CartItem::getProductId, productId)
+    public List<CartItemDTO> getCartList(Long userId) {
+        List<Cart> cartList = cartMapper.selectList(
+            new LambdaQueryWrapper<Cart>()
+                .eq(Cart::getUserId, userId)
+                .orderByDesc(Cart::getCreatedAt)
         );
         );
-        if (item == null) return Result.error("购物车记录不存在");
 
 
-        if (quantity <= 0) {
-            cartItemMapper.deleteById(item.getId());
-            return Result.success("已移除");
+        List<CartItemDTO> result = new ArrayList<>();
+        for (Cart item : cartList) {
+            CartItemDTO dto = buildCartItemDTO(item);
+            if (dto != null) {
+                result.add(dto);
+            }
         }
         }
-        item.setQuantity(quantity);
-        item.setUpdatedAt(new Date());
-        cartItemMapper.updateById(item);
-        return Result.success("更新成功");
+        return result;
     }
     }
 
 
-    public Result<String> remove(Long userId, Long productId) {
-        cartItemMapper.delete(
-            new LambdaQueryWrapper<CartItem>()
-                .eq(CartItem::getUserId, userId)
-                .eq(CartItem::getProductId, productId)
+    @Transactional
+    public void updateQuantity(Long userId, Long productId, Integer quantity) {
+        Cart cart = cartMapper.selectOne(
+            new LambdaQueryWrapper<Cart>()
+                .eq(Cart::getUserId, userId)
+                .eq(Cart::getProductId, productId)
         );
         );
-        return Result.success("已移除");
+
+        if (cart != null) {
+            if (quantity <= 0) {
+                cartMapper.deleteById(cart.getId());
+                return;
+            }
+            cart.setQuantity(quantity);
+            cart.setUpdatedAt(LocalDateTime.now());
+            cartMapper.updateById(cart);
+        }
     }
     }
 
 
-    public Result<String> toggle(Long userId, Long productId) {
-        CartItem item = cartItemMapper.selectOne(
-            new LambdaQueryWrapper<CartItem>()
-                .eq(CartItem::getUserId, userId)
-                .eq(CartItem::getProductId, productId)
+    @Transactional
+    public void removeItem(Long userId, Long productId) {
+        cartMapper.delete(
+            new LambdaQueryWrapper<Cart>()
+                .eq(Cart::getUserId, userId)
+                .eq(Cart::getProductId, productId)
         );
         );
-        if (item == null) return Result.error("购物车记录不存在");
-
-        item.setSelected(!Boolean.TRUE.equals(item.getSelected()));
-        item.setUpdatedAt(new Date());
-        cartItemMapper.updateById(item);
-        return Result.success("操作成功");
     }
     }
 
 
-    public Result<String> clearSelected(Long userId) {
-        cartItemMapper.delete(
-            new LambdaQueryWrapper<CartItem>()
-                .eq(CartItem::getUserId, userId)
-                .eq(CartItem::getSelected, true)
+    private CartItemDTO getCartItemDTO(Long userId, Long productId) {
+        Cart cart = cartMapper.selectOne(
+            new LambdaQueryWrapper<Cart>()
+                .eq(Cart::getUserId, userId)
+                .eq(Cart::getProductId, productId)
         );
         );
-        return Result.success("已清空");
+        if (cart == null) return null;
+        return buildCartItemDTO(cart);
+    }
+
+    private CartItemDTO buildCartItemDTO(Cart cart) {
+        Product product = productMapper.selectById(cart.getProductId());
+        CartItemDTO dto = new CartItemDTO();
+        dto.setId(cart.getId());
+        dto.setUserId(cart.getUserId());
+        dto.setProductId(cart.getProductId());
+        dto.setQuantity(cart.getQuantity());
+        if (product != null) {
+            dto.setProductName(product.getName());
+            dto.setCoverImage(product.getCoverImage());
+            // Convert price from 分(integer) to 元(BigDecimal)
+            dto.setProductPrice(BigDecimal.valueOf(product.getPrice() != null ? product.getPrice() : 0)
+                .divide(BigDecimal.valueOf(100), 2, BigDecimal.ROUND_HALF_UP));
+        }
+        return dto;
     }
     }
 }
 }

+ 0 - 200
cfc-backend/src/main/java/com/etotem/cfc/service/ChildNutritionProfileService.java

@@ -1,200 +0,0 @@
-package com.etotem.cfc.service;
-
-import com.etotem.cfc.entity.Child;
-import com.etotem.cfc.entity.HealthCheckin;
-import com.etotem.cfc.entity.HealthReport;
-import com.etotem.cfc.entity.NutritionDeficiencyRecord;
-import com.etotem.cfc.entity.UserNutritionProfile;
-import com.etotem.cfc.mapper.ChildMapper;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.stereotype.Service;
-
-import javax.annotation.Resource;
-import java.util.*;
-import java.util.stream.Collectors;
-
-@Slf4j
-@Service
-public class ChildNutritionProfileService {
-
-    @Resource
-    private HealthReportService healthReportService;
-
-    @Resource
-    private HealthCheckinService healthCheckinService;
-
-    @Resource
-    private NutritionDeficiencyService nutritionDeficiencyService;
-
-    @Resource
-    private UserNutritionProfileService userNutritionProfileService;
-
-    @Resource
-    private ChildMapper childMapper;
-
-    public Map<String, Object> getProfile(Long childId) {
-        Map<String, Object> profile = new HashMap<>();
-
-        Child child = childMapper.selectById(childId);
-        if (child == null) {
-            return profile;
-        }
-        profile.put("childId", childId);
-        profile.put("nickname", child.getNickname());
-        profile.put("age", child.getAge());
-        profile.put("gender", child.getGender());
-
-        HealthReport latestReport = healthReportService.getLatestReport(childId);
-        profile.put("latestReport", latestReport);
-        if (latestReport != null) {
-            profile.put("indicators", healthReportService.getReportIndicators(latestReport.getId()));
-            List<NutritionDeficiencyRecord> records = nutritionDeficiencyService.getDeficiencyRecords(childId, latestReport.getId());
-            if (records == null || records.isEmpty()) {
-                records = nutritionDeficiencyService.analyzeDeficiency(childId, latestReport.getId());
-            }
-            profile.put("deficiencyRecords", records);
-        } else {
-            profile.put("indicators", Collections.emptyList());
-            profile.put("deficiencyRecords", Collections.emptyList());
-        }
-
-        List<HealthReport> reports = healthReportService.getUserReports(childId);
-        profile.put("reportCount", reports != null ? reports.size() : 0);
-        profile.put("reportTimeline", reports);
-
-        Map<String, Object> checkinSummary = buildCheckinSummary(childId);
-        profile.put("checkinSummary", checkinSummary);
-
-        UserNutritionProfile nutritionProfile = userNutritionProfileService.getByUserId(childId);
-        profile.put("nutritionProfile", nutritionProfile);
-
-        return profile;
-    }
-
-    public Map<String, Object> getDeficiencySummary(Long childId) {
-        Map<String, Object> summary = new HashMap<>();
-
-        List<NutritionDeficiencyRecord> allRecords = nutritionDeficiencyService.getDeficiencyRecords(childId, null);
-        summary.put("total", allRecords.size());
-
-        Map<String, List<NutritionDeficiencyRecord>> byType = new HashMap<>();
-        byType.put("deficiency", new ArrayList<>());
-        byType.put("excess", new ArrayList<>());
-        byType.put("borderline", new ArrayList<>());
-
-        for (NutritionDeficiencyRecord r : allRecords) {
-            String type = r.getDeficiencyType();
-            if (byType.containsKey(type)) {
-                byType.get(type).add(r);
-            }
-        }
-
-        summary.put("deficiencyCount", byType.get("deficiency").size());
-        summary.put("excessCount", byType.get("excess").size());
-        summary.put("borderlineCount", byType.get("borderline").size());
-
-        Map<String, List<Map<String, Object>>> nutrientsByType = new HashMap<>();
-        for (Map.Entry<String, List<NutritionDeficiencyRecord>> entry : byType.entrySet()) {
-            List<Map<String, Object>> nutrientList = new ArrayList<>();
-            for (NutritionDeficiencyRecord r : entry.getValue()) {
-                Map<String, Object> item = new HashMap<>();
-                item.put("nutrient", r.getNutrient());
-                item.put("severity", r.getSeverity());
-                item.put("symptoms", r.getSymptoms());
-                item.put("foodSuggestions", r.getFoodSuggestions());
-                item.put("indicatorName", r.getIndicatorName());
-                item.put("indicatorValue", r.getIndicatorValue());
-                item.put("refRange", r.getRefRange());
-                nutrientList.add(item);
-            }
-            nutrientsByType.put(entry.getKey(), nutrientList);
-        }
-        summary.put("nutrientsByType", nutrientsByType);
-
-        Set<String> allFoodSuggestions = new LinkedHashSet<>();
-        for (NutritionDeficiencyRecord r : allRecords) {
-            if (r.getFoodSuggestions() != null) {
-                for (String food : r.getFoodSuggestions().split("[,,]")) {
-                    String trimmed = food.trim();
-                    if (!trimmed.isEmpty()) {
-                        allFoodSuggestions.add(trimmed);
-                    }
-                }
-            }
-        }
-        summary.put("allFoodSuggestions", new ArrayList<>(allFoodSuggestions));
-
-        return summary;
-    }
-
-    public Map<String, Object> getReportTrendData(Long childId) {
-        Map<String, Object> trend = new HashMap<>();
-
-        List<HealthReport> reports = healthReportService.getUserReports(childId);
-        if (reports == null || reports.isEmpty()) {
-            trend.put("dates", Collections.emptyList());
-            trend.put("scores", Collections.emptyList());
-            return trend;
-        }
-
-        List<HealthReport> sorted = new ArrayList<>(reports);
-        sorted.sort((a, b) -> {
-            if (a.getReportDate() == null) return 1;
-            if (b.getReportDate() == null) return -1;
-            return a.getReportDate().compareTo(b.getReportDate());
-        });
-
-        List<String> dates = new ArrayList<>();
-        List<Integer> scores = new ArrayList<>();
-        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
-
-        for (HealthReport r : sorted) {
-            dates.add(r.getReportDate() != null ? sdf.format(r.getReportDate()) : "");
-            scores.add(r.getOverallScore() != null ? r.getOverallScore() : 0);
-        }
-
-        trend.put("dates", dates);
-        trend.put("scores", scores);
-
-        Map<String, List<Integer>> subScores = new HashMap<>();
-        List<Integer> nutritionScores = new ArrayList<>();
-        List<Integer> gutScores = new ArrayList<>();
-        for (HealthReport r : sorted) {
-            nutritionScores.add(r.getNutritionScore() != null ? r.getNutritionScore() : 0);
-            gutScores.add(r.getGutHealthScore() != null ? r.getGutHealthScore() : 0);
-        }
-        subScores.put("nutrition", nutritionScores);
-        subScores.put("gutHealth", gutScores);
-        trend.put("subScores", subScores);
-
-        return trend;
-    }
-
-    private Map<String, Object> buildCheckinSummary(Long childId) {
-        Map<String, Object> summary = new HashMap<>();
-        List<HealthCheckin> checkins = healthCheckinService.getCheckins(childId, childId, null);
-        long totalCheckins = checkins != null ? checkins.size() : 0;
-        summary.put("totalCheckins", totalCheckins);
-
-        long recent30Days = 0;
-        Calendar cal = Calendar.getInstance();
-        cal.add(Calendar.DAY_OF_MONTH, -30);
-        Date thirtyDaysAgo = cal.getTime();
-        if (checkins != null) {
-            for (HealthCheckin c : checkins) {
-                if (c.getCheckinDate() != null && c.getCheckinDate().after(thirtyDaysAgo)) {
-                    recent30Days++;
-                }
-            }
-        }
-        summary.put("recent30Days", recent30Days);
-
-        if (totalCheckins > 0) {
-            summary.put("streakDays", Math.min((int) totalCheckins, 30));
-        } else {
-            summary.put("streakDays", 0);
-        }
-
-        return summary;
-    }
-}

+ 26 - 12
cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java

@@ -1,10 +1,10 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.DanAssessmentResult;
 import com.etotem.cfc.entity.DanAssessmentResult;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.DanAssessmentResultMapper;
 import com.etotem.cfc.mapper.DanAssessmentResultMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -27,7 +27,7 @@ public class CognitiveService {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     /**
     /**
      * 获取指定孩子的最新认知六维得分
      * 获取指定孩子的最新认知六维得分
@@ -35,15 +35,29 @@ public class CognitiveService {
      * @param childId 孩子ID
      * @param childId 孩子ID
      * @return DanAssessmentResult(最新一条记录)
      * @return DanAssessmentResult(最新一条记录)
      */
      */
-    public DanAssessmentResult getChildLatestResult(Long childId) {
+    public DanAssessmentResult getFamilyMemberLatestResult(Long childId) {
         LambdaQueryWrapper<DanAssessmentResult> wrapper = new LambdaQueryWrapper<DanAssessmentResult>()
         LambdaQueryWrapper<DanAssessmentResult> wrapper = new LambdaQueryWrapper<DanAssessmentResult>()
-                .eq(DanAssessmentResult::getChildId, childId)
+                .eq(DanAssessmentResult::getFamilyMemberId, childId)
                 .eq(DanAssessmentResult::getStatus, "completed")
                 .eq(DanAssessmentResult::getStatus, "completed")
                 .orderByDesc(DanAssessmentResult::getAssessmentDate)
                 .orderByDesc(DanAssessmentResult::getAssessmentDate)
                 .last("LIMIT 1");
                 .last("LIMIT 1");
         return danAssessmentResultMapper.selectOne(wrapper);
         return danAssessmentResultMapper.selectOne(wrapper);
     }
     }
 
 
+    /**
+     * 获取指定孩子的最新认知六维得分(别名,兼容旧调用)
+     */
+    public DanAssessmentResult getChildLatestResult(Long childId) {
+        return getFamilyMemberLatestResult(childId);
+    }
+
+    /**
+     * 更新指定孩子的认知维度得分(别名,兼容旧调用)
+     */
+    public boolean updateChildDimensionScore(Long childId, String dimension, Integer score) {
+        return updateFamilyMemberDimensionScore(childId, dimension, score);
+    }
+
     /**
     /**
      * 获取家庭所有孩子的最新认知六维得分
      * 获取家庭所有孩子的最新认知六维得分
      *
      *
@@ -58,15 +72,15 @@ public class CognitiveService {
         }
         }
 
 
         // 2. 查找该家庭下所有孩子
         // 2. 查找该家庭下所有孩子
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getFamilyId, user.getFamilyId())
+        List<FamilyMember> children = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, user.getFamilyId())
         );
         );
 
 
         // 3. 获取每个孩子的最新测评结果
         // 3. 获取每个孩子的最新测评结果
         Map<Long, DanAssessmentResult> resultMap = new LinkedHashMap<>();
         Map<Long, DanAssessmentResult> resultMap = new LinkedHashMap<>();
-        for (Child child : children) {
-            DanAssessmentResult latest = getChildLatestResult(child.getId());
+        for (FamilyMember child : children) {
+            DanAssessmentResult latest = getFamilyMemberLatestResult(child.getId());
             if (latest != null) {
             if (latest != null) {
                 resultMap.put(child.getId(), latest);
                 resultMap.put(child.getId(), latest);
             }
             }
@@ -82,8 +96,8 @@ public class CognitiveService {
      * @param score     新得分
      * @param score     新得分
      * @return true=更新成功, false=未找到最新结果或维度不存在
      * @return true=更新成功, false=未找到最新结果或维度不存在
      */
      */
-    public boolean updateChildDimensionScore(Long childId, String dimension, Integer score) {
-        DanAssessmentResult latest = getChildLatestResult(childId);
+    public boolean updateFamilyMemberDimensionScore(Long childId, String dimension, Integer score) {
+        DanAssessmentResult latest = getFamilyMemberLatestResult(childId);
         if (latest == null) {
         if (latest == null) {
             log.warn("未找到孩子 {} 的最新测评结果,无法更新", childId);
             log.warn("未找到孩子 {} 的最新测评结果,无法更新", childId);
             return false;
             return false;

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveTrainingConfigService.java

@@ -3,10 +3,10 @@ package com.etotem.cfc.service;
 import com.etotem.cfc.dto.CognitiveRecommendationDTO;
 import com.etotem.cfc.dto.CognitiveRecommendationDTO;
 import com.etotem.cfc.dto.CreateTaskDTO;
 import com.etotem.cfc.dto.CreateTaskDTO;
 import com.etotem.cfc.entity.Article;
 import com.etotem.cfc.entity.Article;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.DanAssessmentResult;
 import com.etotem.cfc.entity.DanAssessmentResult;
 import com.etotem.cfc.mapper.ArticleMapper;
 import com.etotem.cfc.mapper.ArticleMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.DanAssessmentResultMapper;
 import com.etotem.cfc.mapper.DanAssessmentResultMapper;
 import lombok.Data;
 import lombok.Data;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -35,7 +35,7 @@ public class CognitiveTrainingConfigService implements InitializingBean {
     private DanAssessmentResultMapper danAssessmentResultMapper;
     private DanAssessmentResultMapper danAssessmentResultMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private ArticleMapper articleMapper;
     private ArticleMapper articleMapper;
@@ -147,7 +147,7 @@ public class CognitiveTrainingConfigService implements InitializingBean {
         CognitiveRecommendationDTO dto = new CognitiveRecommendationDTO();
         CognitiveRecommendationDTO dto = new CognitiveRecommendationDTO();
         dto.setChildId(childId);
         dto.setChildId(childId);
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         dto.setChildName(child != null ? child.getNickname() : "");
         dto.setChildName(child != null ? child.getNickname() : "");
 
 
         List<CognitiveRecommendationDTO.DimensionRecommendation> recs = new ArrayList<>();
         List<CognitiveRecommendationDTO.DimensionRecommendation> recs = new ArrayList<>();

+ 42 - 42
cfc-backend/src/main/java/com/etotem/cfc/service/DataMigrationService.java

@@ -28,7 +28,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
     @Resource
     @Resource
     private TeacherMapper teacherMapper;
     private TeacherMapper teacherMapper;
     @Resource
     @Resource
@@ -45,7 +45,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
     // ============ 统计结果 ============
     // ============ 统计结果 ============
     private int migratedFamilies;
     private int migratedFamilies;
     private int migratedParents;
     private int migratedParents;
-    private int migratedChildren;
+    private int migratedFamilyMemberren;
     private int migratedTeachers;
     private int migratedTeachers;
     private int migratedOrders;
     private int migratedOrders;
     private int migratedResults;
     private int migratedResults;
@@ -55,7 +55,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
 
 
     // ============ ID 映射缓存 ============
     // ============ ID 映射缓存 ============
     private final Map<String, Long> sfmsPhoneToFamilyId = new HashMap<>();
     private final Map<String, Long> sfmsPhoneToFamilyId = new HashMap<>();
-    private final Map<Long, Long> sfmsClientIdToChildUserId = new HashMap<>();
+    private final Map<Long, Long> sfmsClientIdToFamilyMemberUserId = new HashMap<>();
     private final Map<Long, Long> sfmsClientIdToFamilyId = new HashMap<>();
     private final Map<Long, Long> sfmsClientIdToFamilyId = new HashMap<>();
     private final Map<String, Long> sfmsOpenIdToTeacherUserId = new HashMap<>();
     private final Map<String, Long> sfmsOpenIdToTeacherUserId = new HashMap<>();
 
 
@@ -68,7 +68,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
             // 按依赖顺序执行
             // 按依赖顺序执行
             migrateFamilies();
             migrateFamilies();
             migrateParentUsers();
             migrateParentUsers();
-            migrateChildUsersAndChildren();
+            migrateFamilyMemberUsersAndFamilyMemberren();
             migrateTeachers();
             migrateTeachers();
             migrateGuideFamilies();
             migrateGuideFamilies();
             migratePackageOrders();
             migratePackageOrders();
@@ -78,7 +78,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
 
 
             log.info("========== SFMS 数据迁移完成,耗时: {}ms ==========", System.currentTimeMillis() - start);
             log.info("========== SFMS 数据迁移完成,耗时: {}ms ==========", System.currentTimeMillis() - start);
             log.info("迁移统计: {} 家庭, {} 家长, {} 孩子, {} 规划师, {} 订单, {} 测评结果, {} 成长档案, {} 成长方案",
             log.info("迁移统计: {} 家庭, {} 家长, {} 孩子, {} 规划师, {} 订单, {} 测评结果, {} 成长档案, {} 成长方案",
-                migratedFamilies, migratedParents, migratedChildren, migratedTeachers,
+                migratedFamilies, migratedParents, migratedFamilyMemberren, migratedTeachers,
                 migratedOrders, migratedResults, migratedRecords, migratedPlans);
                 migratedOrders, migratedResults, migratedRecords, migratedPlans);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("迁移过程中出错", e);
             log.error("迁移过程中出错", e);
@@ -91,7 +91,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
     private void resetCounters() {
     private void resetCounters() {
         migratedFamilies = 0;
         migratedFamilies = 0;
         migratedParents = 0;
         migratedParents = 0;
-        migratedChildren = 0;
+        migratedFamilyMemberren = 0;
         migratedTeachers = 0;
         migratedTeachers = 0;
         migratedOrders = 0;
         migratedOrders = 0;
         migratedResults = 0;
         migratedResults = 0;
@@ -99,7 +99,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
         migratedPlans = 0;
         migratedPlans = 0;
         errors.clear();
         errors.clear();
         sfmsPhoneToFamilyId.clear();
         sfmsPhoneToFamilyId.clear();
-        sfmsClientIdToChildUserId.clear();
+        sfmsClientIdToFamilyMemberUserId.clear();
         sfmsClientIdToFamilyId.clear();
         sfmsClientIdToFamilyId.clear();
         sfmsOpenIdToTeacherUserId.clear();
         sfmsOpenIdToTeacherUserId.clear();
     }
     }
@@ -217,7 +217,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
     //  3. 孩子用户 + children 表迁移
     //  3. 孩子用户 + children 表迁移
     // ================================================================
     // ================================================================
     @Transactional
     @Transactional
-    public void migrateChildUsersAndChildren() {
+    public void migrateFamilyMemberUsersAndFamilyMemberren() {
         log.info("--- 3/9: 迁移孩子用户 ---");
         log.info("--- 3/9: 迁移孩子用户 ---");
         try {
         try {
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
             SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
@@ -261,15 +261,15 @@ public class DataMigrationService implements DataMigrationServiceInterface {
                     // 孩子 openid
                     // 孩子 openid
                     String childOpenid = (openid != null && !openid.isEmpty()) ? openid + "_child_" + sfmsClientId : "migrated_child_" + sfmsClientId;
                     String childOpenid = (openid != null && !openid.isEmpty()) ? openid + "_child_" + sfmsClientId : "migrated_child_" + sfmsClientId;
 
 
-                    User existingChildUser = userMapper.selectOne(
+                    User existingFamilyMemberUser = userMapper.selectOne(
                         new LambdaQueryWrapper<User>()
                         new LambdaQueryWrapper<User>()
                             .eq(User::getOpenid, childOpenid)
                             .eq(User::getOpenid, childOpenid)
                             .last("LIMIT 1")
                             .last("LIMIT 1")
                     );
                     );
-                    if (existingChildUser != null) {
-                        sfmsClientIdToChildUserId.put(sfmsClientId, existingChildUser.getId());
-                        if (existingChildUser.getFamilyId() != null) {
-                            sfmsClientIdToFamilyId.put(sfmsClientId, existingChildUser.getFamilyId());
+                    if (existingFamilyMemberUser != null) {
+                        sfmsClientIdToFamilyMemberUserId.put(sfmsClientId, existingFamilyMemberUser.getId());
+                        if (existingFamilyMemberUser.getFamilyId() != null) {
+                            sfmsClientIdToFamilyId.put(sfmsClientId, existingFamilyMemberUser.getFamilyId());
                         }
                         }
                         count++;
                         count++;
                         if (count % 100 == 0) {
                         if (count % 100 == 0) {
@@ -296,7 +296,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
                     userMapper.insert(childUser);
                     userMapper.insert(childUser);
 
 
                     // 创建 children 记录
                     // 创建 children 记录
-                    Child child = new Child();
+                    FamilyMember child = new FamilyMember();
                     child.setUserId(childUser.getId());
                     child.setUserId(childUser.getId());
                     child.setFamilyId(familyId);
                     child.setFamilyId(familyId);
                     child.setNickname((String) row.get("name"));
                     child.setNickname((String) row.get("name"));
@@ -308,9 +308,9 @@ public class DataMigrationService implements DataMigrationServiceInterface {
                     child.setStreakDays(0);
                     child.setStreakDays(0);
                     child.setFocusMaxDaily(3);
                     child.setFocusMaxDaily(3);
                     child.setFocusRemaining(3);
                     child.setFocusRemaining(3);
-                    childMapper.insert(child);
+                    familyMemberMapper.insert(child);
 
 
-                    sfmsClientIdToChildUserId.put(sfmsClientId, childUser.getId());
+                    sfmsClientIdToFamilyMemberUserId.put(sfmsClientId, childUser.getId());
                     sfmsClientIdToFamilyId.put(sfmsClientId, familyId);
                     sfmsClientIdToFamilyId.put(sfmsClientId, familyId);
                     count++;
                     count++;
                     if (count % 100 == 0) {
                     if (count % 100 == 0) {
@@ -320,8 +320,8 @@ public class DataMigrationService implements DataMigrationServiceInterface {
                     log.warn("迁移孩子失败 clientId={}: {}", row.get("crm_client_info_id"), e.getMessage());
                     log.warn("迁移孩子失败 clientId={}: {}", row.get("crm_client_info_id"), e.getMessage());
                 }
                 }
             }
             }
-            migratedChildren = count;
-            log.info("  迁移 {} 个孩子", migratedChildren);
+            migratedFamilyMemberren = count;
+            log.info("  迁移 {} 个孩子", migratedFamilyMemberren);
         } catch (Exception e) {
         } catch (Exception e) {
             log.error("迁移孩子出错", e);
             log.error("迁移孩子出错", e);
             errors.add("迁移孩子出错: " + e.getMessage());
             errors.add("迁移孩子出错: " + e.getMessage());
@@ -460,7 +460,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
             for (Map<String, Object> row : rows) {
             for (Map<String, Object> row : rows) {
                 try {
                 try {
                     long sfmsClientId = ((Number) row.get("client_id")).longValue();
                     long sfmsClientId = ((Number) row.get("client_id")).longValue();
-                    Long userId = sfmsClientIdToChildUserId.get(sfmsClientId);
+                    Long userId = sfmsClientIdToFamilyMemberUserId.get(sfmsClientId);
                     Long familyId = sfmsClientIdToFamilyId.get(sfmsClientId);
                     Long familyId = sfmsClientIdToFamilyId.get(sfmsClientId);
                     if (userId == null || familyId == null) continue;
                     if (userId == null || familyId == null) continue;
 
 
@@ -513,13 +513,13 @@ public class DataMigrationService implements DataMigrationServiceInterface {
             for (Map<String, Object> row : rows) {
             for (Map<String, Object> row : rows) {
                 try {
                 try {
                     long sfmsClientId = ((Number) row.get("client_id")).longValue();
                     long sfmsClientId = ((Number) row.get("client_id")).longValue();
-                    Long childUserId = sfmsClientIdToChildUserId.get(sfmsClientId);
+                    Long childUserId = sfmsClientIdToFamilyMemberUserId.get(sfmsClientId);
                     if (childUserId == null) continue;
                     if (childUserId == null) continue;
 
 
                     // 找 child_id
                     // 找 child_id
-                    Child child = childMapper.selectOne(
-                        new LambdaQueryWrapper<Child>()
-                            .eq(Child::getUserId, childUserId)
+                    FamilyMember child = familyMemberMapper.selectOne(
+                        new LambdaQueryWrapper<FamilyMember>()
+                            .eq(FamilyMember::getUserId, childUserId)
                             .last("LIMIT 1")
                             .last("LIMIT 1")
                     );
                     );
                     if (child == null) continue;
                     if (child == null) continue;
@@ -541,11 +541,11 @@ public class DataMigrationService implements DataMigrationServiceInterface {
                     }
                     }
 
 
                     DanAssessmentResult result = new DanAssessmentResult();
                     DanAssessmentResult result = new DanAssessmentResult();
-                    result.setChildId(child.getId());
-                    result.setChildName(childName);
-                    result.setChildBirthday(childBirthday);
-                    result.setChildGender(childGender);
-                    result.setChildAge(childAge);
+                    result.setFamilyMemberId(child.getId());
+                    result.setFamilyMemberName(childName);
+                    result.setFamilyMemberBirthday(childBirthday);
+                    result.setFamilyMemberGender(childGender);
+                    result.setFamilyMemberAge(childAge);
                     result.setTeacherId(teacherUserId != null ? teacherUserId : 0L);
                     result.setTeacherId(teacherUserId != null ? teacherUserId : 0L);
                     result.setAnalysisReport((String) row.get("comm_cont"));
                     result.setAnalysisReport((String) row.get("comm_cont"));
                     result.setProcessDesc((String) row.get("description"));
                     result.setProcessDesc((String) row.get("description"));
@@ -600,7 +600,7 @@ public class DataMigrationService implements DataMigrationServiceInterface {
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'nation') cip_nation ON cip_nation.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'nation') cip_nation ON cip_nation.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'parentDegree') cip_degree ON cip_degree.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'parentDegree') cip_degree ON cip_degree.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'familyStatus') cip_family ON cip_family.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'familyStatus') cip_family ON cip_family.client_id = ci.crm_client_info_id " +
-                "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'singleChild') cip_single ON cip_single.client_id = ci.crm_client_info_id " +
+                "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'singleFamilyMember') cip_single ON cip_single.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'schoolScore') cip_score ON cip_score.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'schoolScore') cip_score ON cip_score.client_id = ci.crm_client_info_id " +
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'address') cip_address ON cip_address.client_id = ci.crm_client_info_id "
                 "LEFT JOIN (SELECT client_id, value FROM crm_client_info_properties WHERE CODE = 'address') cip_address ON cip_address.client_id = ci.crm_client_info_id "
             );
             );
@@ -608,13 +608,13 @@ public class DataMigrationService implements DataMigrationServiceInterface {
             for (Map<String, Object> row : rows) {
             for (Map<String, Object> row : rows) {
                 try {
                 try {
                     long sfmsClientId = ((Number) row.get("crm_client_info_id")).longValue();
                     long sfmsClientId = ((Number) row.get("crm_client_info_id")).longValue();
-                    Long childUserId = sfmsClientIdToChildUserId.get(sfmsClientId);
+                    Long childUserId = sfmsClientIdToFamilyMemberUserId.get(sfmsClientId);
                     Long familyId = sfmsClientIdToFamilyId.get(sfmsClientId);
                     Long familyId = sfmsClientIdToFamilyId.get(sfmsClientId);
                     if (childUserId == null || familyId == null) continue;
                     if (childUserId == null || familyId == null) continue;
 
 
-                    Child child = childMapper.selectOne(
-                        new LambdaQueryWrapper<Child>()
-                            .eq(Child::getUserId, childUserId)
+                    FamilyMember child = familyMemberMapper.selectOne(
+                        new LambdaQueryWrapper<FamilyMember>()
+                            .eq(FamilyMember::getUserId, childUserId)
                             .last("LIMIT 1")
                             .last("LIMIT 1")
                     );
                     );
                     if (child == null) continue;
                     if (child == null) continue;
@@ -628,10 +628,10 @@ public class DataMigrationService implements DataMigrationServiceInterface {
                     );
                     );
 
 
                     GrowthRecord record = new GrowthRecord();
                     GrowthRecord record = new GrowthRecord();
-                    record.setChildId(child.getId());
+                    record.setFamilyMemberId(child.getId());
                     record.setParentId(parent != null ? parent.getId() : 0L);
                     record.setParentId(parent != null ? parent.getId() : 0L);
                     record.setRecordTitle("历史成长档案 - " + (String) row.get("name"));
                     record.setRecordTitle("历史成长档案 - " + (String) row.get("name"));
-                    record.setChildAge(calcAge((String) row.get("birth_day")));
+                    record.setFamilyMemberAge(calcAge((String) row.get("birth_day")));
                     record.setHeight((String) row.get("height"));
                     record.setHeight((String) row.get("height"));
                     record.setWeight((String) row.get("weight"));
                     record.setWeight((String) row.get("weight"));
                     record.setStatus("completed");
                     record.setStatus("completed");
@@ -684,18 +684,18 @@ public class DataMigrationService implements DataMigrationServiceInterface {
             for (Map<String, Object> row : rows) {
             for (Map<String, Object> row : rows) {
                 try {
                 try {
                     long sfmsClientId = ((Number) row.get("client_id")).longValue();
                     long sfmsClientId = ((Number) row.get("client_id")).longValue();
-                    Long childUserId = sfmsClientIdToChildUserId.get(sfmsClientId);
+                    Long childUserId = sfmsClientIdToFamilyMemberUserId.get(sfmsClientId);
                     if (childUserId == null) continue;
                     if (childUserId == null) continue;
 
 
-                    Child child = childMapper.selectOne(
-                        new LambdaQueryWrapper<Child>()
-                            .eq(Child::getUserId, childUserId)
+                    FamilyMember child = familyMemberMapper.selectOne(
+                        new LambdaQueryWrapper<FamilyMember>()
+                            .eq(FamilyMember::getUserId, childUserId)
                             .last("LIMIT 1")
                             .last("LIMIT 1")
                     );
                     );
                     if (child == null) continue;
                     if (child == null) continue;
 
 
                     GrowthPlan plan = new GrowthPlan();
                     GrowthPlan plan = new GrowthPlan();
-                    plan.setChildId(child.getId());
+                    plan.setFamilyMemberId(child.getId());
                     plan.setPlanTitle((String) row.get("plan_name"));
                     plan.setPlanTitle((String) row.get("plan_name"));
                     plan.setPlanContent((String) row.get("plan_desc"));
                     plan.setPlanContent((String) row.get("plan_desc"));
                     plan.setStatus("active".equals(row.get("plan_status")) ? "active" : "draft");
                     plan.setStatus("active".equals(row.get("plan_status")) ? "active" : "draft");
@@ -807,13 +807,13 @@ public class DataMigrationService implements DataMigrationServiceInterface {
         result.put("success", errors.isEmpty());
         result.put("success", errors.isEmpty());
         result.put("migratedFamilies", migratedFamilies);
         result.put("migratedFamilies", migratedFamilies);
         result.put("migratedParents", migratedParents);
         result.put("migratedParents", migratedParents);
-        result.put("migratedChildren", migratedChildren);
+        result.put("migratedFamilyMemberren", migratedFamilyMemberren);
         result.put("migratedTeachers", migratedTeachers);
         result.put("migratedTeachers", migratedTeachers);
         result.put("migratedOrders", migratedOrders);
         result.put("migratedOrders", migratedOrders);
         result.put("migratedResults", migratedResults);
         result.put("migratedResults", migratedResults);
         result.put("migratedRecords", migratedRecords);
         result.put("migratedRecords", migratedRecords);
         result.put("migratedPlans", migratedPlans);
         result.put("migratedPlans", migratedPlans);
-        result.put("total", migratedFamilies + migratedParents + migratedChildren + migratedTeachers
+        result.put("total", migratedFamilies + migratedParents + migratedFamilyMemberren + migratedTeachers
             + migratedOrders + migratedResults + migratedRecords + migratedPlans);
             + migratedOrders + migratedResults + migratedRecords + migratedPlans);
         if (!errors.isEmpty()) {
         if (!errors.isEmpty()) {
             result.put("errors", errors);
             result.put("errors", errors);

+ 2 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/DatabaseSyncService.java

@@ -21,6 +21,8 @@ import java.util.stream.Collectors;
 @Slf4j
 @Slf4j
 @Service
 @Service
 public class DatabaseSyncService {
 public class DatabaseSyncService {
+    // Explicit log field since @Slf4j Lombok not generating it
+    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(DatabaseSyncService.class);
 
 
     @Resource
     @Resource
     private JdbcTemplate jdbcTemplate;
     private JdbcTemplate jdbcTemplate;

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/EmotionAlertService.java

@@ -2,11 +2,11 @@ package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.dto.EmotionAlertVO;
 import com.etotem.cfc.dto.EmotionAlertVO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.EmotionAlert;
 import com.etotem.cfc.entity.EmotionAlert;
 import com.etotem.cfc.entity.EmotionCheckin;
 import com.etotem.cfc.entity.EmotionCheckin;
 import com.etotem.cfc.entity.FollowUp;
 import com.etotem.cfc.entity.FollowUp;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.EmotionAlertMapper;
 import com.etotem.cfc.mapper.EmotionAlertMapper;
 import com.etotem.cfc.mapper.EmotionCheckinMapper;
 import com.etotem.cfc.mapper.EmotionCheckinMapper;
 import com.etotem.cfc.mapper.FollowUpMapper;
 import com.etotem.cfc.mapper.FollowUpMapper;
@@ -37,7 +37,7 @@ public class EmotionAlertService {
     private FollowUpMapper followUpMapper;
     private FollowUpMapper followUpMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private WechatAlertTemplateService wechatAlertTemplateService;
     private WechatAlertTemplateService wechatAlertTemplateService;
@@ -170,7 +170,7 @@ public class EmotionAlertService {
             try {
             try {
                 FollowUp followUp = new FollowUp();
                 FollowUp followUp = new FollowUp();
                 followUp.setChildId(alert.getChildId());
                 followUp.setChildId(alert.getChildId());
-                Child child = childMapper.selectById(alert.getChildId());
+                FamilyMember child = familyMemberMapper.selectById(alert.getChildId());
                 if (child != null && child.getFamilyId() != null) {
                 if (child != null && child.getFamilyId() != null) {
                     followUp.setFamilyId(child.getFamilyId());
                     followUp.setFamilyId(child.getFamilyId());
                 }
                 }

+ 6 - 6
cfc-backend/src/main/java/com/etotem/cfc/service/EmotionCheckinService.java

@@ -8,9 +8,9 @@ import com.etotem.cfc.dto.EmotionStatsVO;
 import com.etotem.cfc.dto.EmotionTrendVO;
 import com.etotem.cfc.dto.EmotionTrendVO;
 import com.etotem.cfc.dto.MoodTrendPoint;
 import com.etotem.cfc.dto.MoodTrendPoint;
 import com.etotem.cfc.dto.AffectPoint;
 import com.etotem.cfc.dto.AffectPoint;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.EmotionCheckin;
 import com.etotem.cfc.entity.EmotionCheckin;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.EmotionCheckinMapper;
 import com.etotem.cfc.mapper.EmotionCheckinMapper;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -28,7 +28,7 @@ public class EmotionCheckinService {
     private EmotionCheckinMapper emotionCheckinMapper;
     private EmotionCheckinMapper emotionCheckinMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private EnergyService energyService;
     private EnergyService energyService;
@@ -38,7 +38,7 @@ public class EmotionCheckinService {
 
 
     @Transactional
     @Transactional
     public EmotionCheckinVO createCheckin(EmotionCheckinDTO dto, Long userId) {
     public EmotionCheckinVO createCheckin(EmotionCheckinDTO dto, Long userId) {
-        Child child = childMapper.selectById(dto.getChildId());
+        FamilyMember child = familyMemberMapper.selectById(dto.getChildId());
         if (child == null) {
         if (child == null) {
             throw new RuntimeException("孩子不存在");
             throw new RuntimeException("孩子不存在");
         }
         }
@@ -90,7 +90,7 @@ public class EmotionCheckinService {
                 .orderByDesc(EmotionCheckin::getCheckinDate);
                 .orderByDesc(EmotionCheckin::getCheckinDate);
         List<EmotionCheckin> list = emotionCheckinMapper.selectList(wrapper);
         List<EmotionCheckin> list = emotionCheckinMapper.selectList(wrapper);
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         String childName = child != null ? child.getNickname() : "";
         String childName = child != null ? child.getNickname() : "";
 
 
         int start = (page - 1) * size;
         int start = (page - 1) * size;
@@ -113,7 +113,7 @@ public class EmotionCheckinService {
         EmotionCheckin checkin = emotionCheckinMapper.selectOne(wrapper);
         EmotionCheckin checkin = emotionCheckinMapper.selectOne(wrapper);
         if (checkin == null) return null;
         if (checkin == null) return null;
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         String childName = child != null ? child.getNickname() : "";
         String childName = child != null ? child.getNickname() : "";
         boolean isOwner = currentUserId != null && currentUserId.equals(childId);
         boolean isOwner = currentUserId != null && currentUserId.equals(childId);
         return toVO(checkin, childName, hidePhoto && !isOwner);
         return toVO(checkin, childName, hidePhoto && !isOwner);

+ 25 - 17
cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java

@@ -28,7 +28,7 @@ public class EnergyService {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
@@ -64,6 +64,9 @@ public class EnergyService {
      * @return 家庭能量沙盘DTO(家庭聚合 + 每个成员的个人数据)
      * @return 家庭能量沙盘DTO(家庭聚合 + 每个成员的个人数据)
      */
      */
     public EnergySandboxDTO calculateFamilyEnergy(Long familyId) {
     public EnergySandboxDTO calculateFamilyEnergy(Long familyId) {
+        if (familyId == null) {
+            return buildEmptyDTO();
+        }
         EnergySandboxDTO dto = new EnergySandboxDTO();
         EnergySandboxDTO dto = new EnergySandboxDTO();
         List<MemberEnergyDTO> members = new ArrayList<>();
         List<MemberEnergyDTO> members = new ArrayList<>();
 
 
@@ -81,9 +84,9 @@ public class EnergyService {
             }
             }
         }
         }
 
 
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getFamilyId, familyId)
+        List<FamilyMember> children = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId)
         );
         );
 
 
         // 2. 计算每个家长的能量
         // 2. 计算每个家长的能量
@@ -93,7 +96,7 @@ public class EnergyService {
         }
         }
 
 
         // 3. 计算每个孩子的能量
         // 3. 计算每个孩子的能量
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             MemberEnergyDTO member = calcChildEnergy(child);
             MemberEnergyDTO member = calcChildEnergy(child);
             members.add(member);
             members.add(member);
         }
         }
@@ -159,13 +162,13 @@ public class EnergyService {
      */
      */
     private int calcParentBody(User parent) {
     private int calcParentBody(User parent) {
         if (parent.getFamilyId() != null) {
         if (parent.getFamilyId() != null) {
-            List<Child> children = childMapper.selectList(
-                    new LambdaQueryWrapper<Child>()
-                            .eq(Child::getFamilyId, parent.getFamilyId())
+            List<FamilyMember> children = familyMemberMapper.selectList(
+                    new LambdaQueryWrapper<FamilyMember>()
+                            .eq(FamilyMember::getFamilyId, parent.getFamilyId())
             );
             );
             // Batch query: fetch health reports for ALL children at once
             // Batch query: fetch health reports for ALL children at once
             List<Long> childUserIds = children.stream()
             List<Long> childUserIds = children.stream()
-                    .map(Child::getUserId)
+                    .map(FamilyMember::getUserId)
                     .collect(Collectors.toList());
                     .collect(Collectors.toList());
             if (!childUserIds.isEmpty()) {
             if (!childUserIds.isEmpty()) {
                 // Single query for all children — picks the first available health score
                 // Single query for all children — picks the first available health score
@@ -174,7 +177,12 @@ public class EnergyService {
                         .eq(HealthReport::getStatus, "active")
                         .eq(HealthReport::getStatus, "active")
                         .orderByDesc(HealthReport::getReportDate)
                         .orderByDesc(HealthReport::getReportDate)
                         .last("LIMIT 1");
                         .last("LIMIT 1");
-                HealthReport report = healthReportMapper.selectOne(hrWrapper);
+                HealthReport report = null;
+                try {
+                    report = healthReportMapper.selectOne(hrWrapper);
+                } catch (Exception e) {
+                    log.warn("健康报告查询异常, userIds={}: {}", childUserIds, e.getMessage());
+                }
                 if (report != null && report.getOverallScore() != null) {
                 if (report != null && report.getOverallScore() != null) {
                     return report.getOverallScore();
                     return report.getOverallScore();
                 }
                 }
@@ -216,7 +224,7 @@ public class EnergyService {
 
 
     // ==================== 孩子能量计算 ====================
     // ==================== 孩子能量计算 ====================
 
 
-    private MemberEnergyDTO calcChildEnergy(Child child) {
+    private MemberEnergyDTO calcChildEnergy(FamilyMember child) {
         MemberEnergyDTO dto = new MemberEnergyDTO();
         MemberEnergyDTO dto = new MemberEnergyDTO();
         dto.setMemberId(child.getId());
         dto.setMemberId(child.getId());
         dto.setMemberType("child");
         dto.setMemberType("child");
@@ -241,7 +249,7 @@ public class EnergyService {
      * 孩子 身 — 优先使用健康检测报告得分
      * 孩子 身 — 优先使用健康检测报告得分
      * 数据源优先级:健康报告overallScore > 子项加权 > 运动/户外类任务完成率(降级)
      * 数据源优先级:健康报告overallScore > 子项加权 > 运动/户外类任务完成率(降级)
      */
      */
-    private int calcChildBody(Child child) {
+    private int calcChildBody(FamilyMember child) {
         // 1. 优先使用健康报告中的身体评分
         // 1. 优先使用健康报告中的身体评分
         Integer healthScore = healthReportService.calculateBodyScore(child.getUserId());
         Integer healthScore = healthReportService.calculateBodyScore(child.getUserId());
         if (healthScore != null) {
         if (healthScore != null) {
@@ -253,7 +261,7 @@ public class EnergyService {
     }
     }
 
 
     /** 孩子 心 — DAN EMI 70% + 先天 30% + 行为修正 */
     /** 孩子 心 — DAN EMI 70% + 先天 30% + 行为修正 */
-    private int calcChildMind(Child child) {
+    private int calcChildMind(FamilyMember child) {
         Long childId = child.getId();
         Long childId = child.getId();
         Long userId = child.getUserId();
         Long userId = child.getUserId();
 
 
@@ -296,7 +304,7 @@ public class EnergyService {
     }
     }
 
 
     /** 孩子 智 — DAN COG 70% + 先天 30% + 行为修正 */
     /** 孩子 智 — DAN COG 70% + 先天 30% + 行为修正 */
-    private int calcChildWisdom(Child child) {
+    private int calcChildWisdom(FamilyMember child) {
         Long childId = child.getId();
         Long childId = child.getId();
         Long userId = child.getUserId();
         Long userId = child.getUserId();
 
 
@@ -394,7 +402,7 @@ public class EnergyService {
     }
     }
 
 
     /** Old wisdom calculation as fallback (游戏 + 测评overallScore + 阅读时长) */
     /** Old wisdom calculation as fallback (游戏 + 测评overallScore + 阅读时长) */
-    private int calcOldChildWisdom(Child child) {
+    private int calcOldChildWisdom(FamilyMember child) {
         Long childId = child.getId();
         Long childId = child.getId();
         double totalWeight = 0;
         double totalWeight = 0;
         double weightedSum = 0;
         double weightedSum = 0;
@@ -442,7 +450,7 @@ public class EnergyService {
     }
     }
 
 
     /** 孩子 行 — 打卡天数 + 习惯类任务完成率 */
     /** 孩子 行 — 打卡天数 + 习惯类任务完成率 */
-    private int calcChildAction(Child child) {
+    private int calcChildAction(FamilyMember child) {
         Long childId = child.getId();
         Long childId = child.getId();
 
 
         // 1. 连续打卡天数 (0-100)
         // 1. 连续打卡天数 (0-100)
@@ -468,7 +476,7 @@ public class EnergyService {
     }
     }
 
 
     /** 孩子 富 — 积分获取效率 */
     /** 孩子 富 — 积分获取效率 */
-    private int calcChildWealth(Child child) {
+    private int calcChildWealth(FamilyMember child) {
         Long childId = child.getId();
         Long childId = child.getId();
 
 
         // 已获得积分: points_log 中正向入账
         // 已获得积分: points_log 中正向入账

+ 8 - 8
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyInvitationService.java

@@ -3,11 +3,11 @@ package com.etotem.cfc.service;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.dto.FamilyStatusDTO;
 import com.etotem.cfc.dto.FamilyStatusDTO;
 import com.etotem.cfc.dto.InviteValidateDTO;
 import com.etotem.cfc.dto.InviteValidateDTO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.FamilyInvitation;
 import com.etotem.cfc.entity.FamilyInvitation;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.FamilyInvitationMapper;
 import com.etotem.cfc.mapper.FamilyInvitationMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
@@ -31,7 +31,7 @@ public class FamilyInvitationService {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     public InviteValidateDTO validateInvitation(String token) {
     public InviteValidateDTO validateInvitation(String token) {
         FamilyInvitation invitation = familyInvitationMapper.selectOne(
         FamilyInvitation invitation = familyInvitationMapper.selectOne(
@@ -148,14 +148,14 @@ public class FamilyInvitationService {
         user.setUpdatedAt(new Date());
         user.setUpdatedAt(new Date());
         userMapper.updateById(user);
         userMapper.updateById(user);
 
 
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getFamilyId, oldFamilyId)
+        List<FamilyMember> children = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, oldFamilyId)
         );
         );
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             child.setFamilyId(targetFamilyId);
             child.setFamilyId(targetFamilyId);
             child.setUpdatedAt(new Date());
             child.setUpdatedAt(new Date());
-            childMapper.updateById(child);
+            familyMemberMapper.updateById(child);
         }
         }
 
 
         if (!oldFamilyId.equals(targetFamilyId)) {
         if (!oldFamilyId.equals(targetFamilyId)) {

+ 49 - 122
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyMemberService.java

@@ -34,15 +34,15 @@ public class FamilyMemberService {
     @Resource
     @Resource
     private FamilyMapper familyMapper;
     private FamilyMapper familyMapper;
 
 
-    @Resource
-    private ChildMapper childMapper;
-
     @Resource
     @Resource
     private FamilyMemberAttributesMapper familyMemberAttributesMapper;
     private FamilyMemberAttributesMapper familyMemberAttributesMapper;
 
 
     @Resource
     @Resource
     private OnboardingService onboardingService;
     private OnboardingService onboardingService;
 
 
+    @Resource
+    private SystemConfigService systemConfigService;
+
     /**
     /**
      * 添加家庭成员
      * 添加家庭成员
      */
      */
@@ -89,7 +89,7 @@ public class FamilyMemberService {
             }
             }
         }
         }
         member.setRelationshipType(dto.getRelationshipType());
         member.setRelationshipType(dto.getRelationshipType());
-        member.setCapabilityRole(relType.getCapabilityRole());
+        member.setRoleOverride("auto");
         member.setShowToFamily(1);
         member.setShowToFamily(1);
         member.setTotalPoints(0);
         member.setTotalPoints(0);
         member.setCreatedAt(new Date());
         member.setCreatedAt(new Date());
@@ -112,44 +112,8 @@ public class FamilyMemberService {
 
 
         familyMemberMapper.insert(member);
         familyMemberMapper.insert(member);
 
 
-        // 如果能力角色为 child,同步创建 children 表记录(游戏/任务/专注度等功能需要)
-        if ("child".equals(relType.getCapabilityRole())) {
-            Child child = new Child();
-            child.setUserId(userId);
-            child.setFamilyId(familyId);
-            child.setNickname(dto.getNickname().trim());
-            child.setPhone(dto.getPhone());
-            child.setGender(dto.getGender());
-            if (member.getBirthday() != null) {
-                child.setBirthday(member.getBirthday());
-                // 根据生日计算年龄
-                java.util.Calendar cal = java.util.Calendar.getInstance();
-                cal.setTime(member.getBirthday());
-                int birthYear = cal.get(java.util.Calendar.YEAR);
-                int birthMonth = cal.get(java.util.Calendar.MONTH);
-                int birthDay = cal.get(java.util.Calendar.DAY_OF_MONTH);
-                java.util.Calendar now = java.util.Calendar.getInstance();
-                int age = now.get(java.util.Calendar.YEAR) - birthYear;
-                if (now.get(java.util.Calendar.MONTH) < birthMonth ||
-                        (now.get(java.util.Calendar.MONTH) == birthMonth && now.get(java.util.Calendar.DAY_OF_MONTH) < birthDay)) {
-                    age--;
-                }
-                child.setAge(Math.max(0, age));
-            }
-            // 默认年龄>=6开启扣分
-            Integer age = child.getAge();
-            child.setPenaltyEnabled(age != null && age >= 6 ? 1 : 0);
-            child.setTheme("default");
-            child.setTotalPoints(0);
-            child.setStreakDays(0);
-            child.setFocusMaxDaily(3);
-            child.setFocusRemaining(3);
-            child.setFocusResetDate(new Date());
-            child.setCreatedAt(new Date());
-            child.setUpdatedAt(new Date());
-            childMapper.insert(child);
-            try { onboardingService.completeTask(userId, "ADD_CHILD"); } catch (Exception e) { /* onboarding trigger failure should not block member add */ }
-        }
+        // children 表已废除,无需同步。角色信息通过 computeEffectiveRole() 自动计算。
+        // 若需要 child 专属初始化(如游戏数据),后续在 computeEffectiveRole() 返回 child 时处理。
 
 
         return toFamilyMemberVO(member, relType);
         return toFamilyMemberVO(member, relType);
     }
     }
@@ -185,8 +149,9 @@ public class FamilyMemberService {
             vo.setGender(fm.getGender());
             vo.setGender(fm.getGender());
             vo.setRelationshipType(fm.getRelationshipType());
             vo.setRelationshipType(fm.getRelationshipType());
             vo.setRelationshipTypeName(relTypeNameMap.getOrDefault(fm.getRelationshipType(), fm.getRelationshipType()));
             vo.setRelationshipTypeName(relTypeNameMap.getOrDefault(fm.getRelationshipType(), fm.getRelationshipType()));
-            vo.setCapabilityRole(fm.getCapabilityRole());
-            vo.setRoleLabel(relTypeNameMap.getOrDefault(fm.getRelationshipType(), fm.getCapabilityRole()));
+            vo.setEffectiveRole(computeEffectiveRole(fm));
+            vo.setRoleOverride(fm.getRoleOverride());
+            vo.setRoleLabel(relTypeNameMap.getOrDefault(fm.getRelationshipType(), computeEffectiveRole(fm)));
             vo.setTotalPoints(fm.getTotalPoints());
             vo.setTotalPoints(fm.getTotalPoints());
             vo.setShowToFamily(fm.getShowToFamily());
             vo.setShowToFamily(fm.getShowToFamily());
             if (fm.getBirthday() != null) {
             if (fm.getBirthday() != null) {
@@ -195,26 +160,6 @@ public class FamilyMemberService {
             result.add(vo);
             result.add(vo);
         }
         }
 
 
-        // 3. 查询 children 表(已有孩子数据)
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>().eq(Child::getFamilyId, familyId));
-        for (Child c : children) {
-            FamilyMemberVO vo = new FamilyMemberVO();
-            vo.setId(c.getId());
-            vo.setSource("child");
-            vo.setNickname(c.getNickname());
-            vo.setPhone(c.getPhone());
-            vo.setGender(c.getGender());
-            vo.setRelationshipType("child");
-            vo.setRelationshipTypeName("子女");
-            vo.setCapabilityRole("child");
-            vo.setRoleLabel("孩子");
-            vo.setAge(c.getAge());
-            vo.setTotalPoints(c.getTotalPoints());
-            vo.setShowToFamily(c.getShowToFamily());
-            result.add(vo);
-        }
-
         return result;
         return result;
     }
     }
 
 
@@ -233,22 +178,11 @@ public class FamilyMemberService {
             SwitchMemberVO vo = new SwitchMemberVO();
             SwitchMemberVO vo = new SwitchMemberVO();
             vo.setMemberId(member.getId());
             vo.setMemberId(member.getId());
             vo.setSource("family_member");
             vo.setSource("family_member");
-            vo.setCapabilityRole(member.getCapabilityRole());
+            vo.setEffectiveRole(computeEffectiveRole(member));
             vo.setNickname(member.getNickname());
             vo.setNickname(member.getNickname());
             return vo;
             return vo;
         }
         }
 
 
-        // 从 children 表查找(兼容已有数据)
-        Child child = childMapper.selectById(memberId);
-        if (child != null && child.getFamilyId() != null && child.getFamilyId().equals(user.getFamilyId())) {
-            SwitchMemberVO vo = new SwitchMemberVO();
-            vo.setMemberId(child.getId());
-            vo.setSource("child");
-            vo.setCapabilityRole("child");
-            vo.setNickname(child.getNickname());
-            return vo;
-        }
-
         throw new RuntimeException("成员不存在或不属于您的家庭");
         throw new RuntimeException("成员不存在或不属于您的家庭");
     }
     }
 
 
@@ -283,26 +217,14 @@ public class FamilyMemberService {
                     userMapper.updateById(targetUser);
                     userMapper.updateById(targetUser);
                 }
                 }
             }
             }
-            // 如果是 child 角色,删除对应的 children 记录
-            if ("child".equals(member.getCapabilityRole())) {
-                List<Child> children = childMapper.selectList(
-                        new LambdaQueryWrapper<Child>()
-                                .eq(Child::getFamilyId, member.getFamilyId())
-                                .eq(Child::getNickname, member.getNickname()));
-                for (Child c : children) {
-                    childMapper.deleteById(c.getId());
-                }
+            // children 表已废除,无需额外清理
+            // 如果成员有效角色为 child,可能需要清理其他关联数据(按需扩展)
+            if ("child".equals(computeEffectiveRole(member))) {
+                // child 专属数据(如游戏记录等)后续按需清理
             }
             }
             return;
             return;
         }
         }
 
 
-        // 从 children 踢出(兼容已有数据)
-        Child child = childMapper.selectById(memberId);
-        if (child != null && child.getFamilyId() != null && child.getFamilyId().equals(operator.getFamilyId())) {
-            childMapper.deleteById(memberId);
-            return;
-        }
-
         throw new RuntimeException("成员不存在");
         throw new RuntimeException("成员不存在");
     }
     }
 
 
@@ -341,37 +263,16 @@ public class FamilyMemberService {
                 throw new IllegalArgumentException("出生日期格式错误,应为yyyy-MM-dd");
                 throw new IllegalArgumentException("出生日期格式错误,应为yyyy-MM-dd");
             }
             }
         }
         }
+        if (dto.getRoleOverride() != null) {
+            member.setRoleOverride(dto.getRoleOverride());
+        }
 
 
         member.setUpdatedAt(new Date());
         member.setUpdatedAt(new Date());
         familyMemberMapper.updateById(member);
         familyMemberMapper.updateById(member);
 
 
-        // 同步更新 children 记录(如果存在)
-        if ("child".equals(member.getCapabilityRole())) {
-            List<Child> children = childMapper.selectList(
-                    new LambdaQueryWrapper<Child>()
-                            .eq(Child::getFamilyId, member.getFamilyId())
-                            .eq(Child::getNickname, member.getNickname()));
-            for (Child c : children) {
-                if (dto.getNickname() != null && !dto.getNickname().trim().isEmpty()) {
-                    c.setNickname(dto.getNickname().trim());
-                }
-                if (dto.getGender() != null) {
-                    c.setGender(dto.getGender());
-                }
-                if (dto.getPhone() != null) {
-                    c.setPhone(dto.getPhone());
-                }
-                if (dto.getBirthday() != null) {
-                    try {
-                        java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
-                        c.setBirthday(sdf.parse(dto.getBirthday()));
-                    } catch (java.text.ParseException e) {
-                        // ignore
-                    }
-                }
-                c.setUpdatedAt(new Date());
-                childMapper.updateById(c);
-            }
+        // 同步更新 family_members 中的 child 专属字段
+        if ("child".equals(computeEffectiveRole(member))) {
+            // family_members 本身已包含 child 专属字段,直接 updateById(member) 已足够
         }
         }
 
 
         // 查询关系类型名称
         // 查询关系类型名称
@@ -415,7 +316,7 @@ public class FamilyMemberService {
             if (existing != null) {
             if (existing != null) {
                 existing.setTypeKey(type.getTypeKey());
                 existing.setTypeKey(type.getTypeKey());
                 existing.setTypeName(type.getTypeName());
                 existing.setTypeName(type.getTypeName());
-                existing.setCapabilityRole(type.getCapabilityRole());
+                existing.setDefaultRole(type.getDefaultRole());
                 existing.setSortOrder(type.getSortOrder());
                 existing.setSortOrder(type.getSortOrder());
                 existing.setEnabled(type.getEnabled());
                 existing.setEnabled(type.getEnabled());
                 relationshipTypeMapper.updateById(existing);
                 relationshipTypeMapper.updateById(existing);
@@ -449,9 +350,10 @@ public class FamilyMemberService {
             vo.setRoleLabel(relType.getTypeName());
             vo.setRoleLabel(relType.getTypeName());
         } else {
         } else {
             vo.setRelationshipTypeName(member.getRelationshipType());
             vo.setRelationshipTypeName(member.getRelationshipType());
-            vo.setRoleLabel(member.getCapabilityRole());
+            vo.setRoleLabel(computeEffectiveRole(member));
         }
         }
-        vo.setCapabilityRole(member.getCapabilityRole());
+        vo.setEffectiveRole(computeEffectiveRole(member));
+        vo.setRoleOverride(member.getRoleOverride());
         vo.setTotalPoints(member.getTotalPoints());
         vo.setTotalPoints(member.getTotalPoints());
         vo.setShowToFamily(member.getShowToFamily());
         vo.setShowToFamily(member.getShowToFamily());
         if (member.getBirthday() != null) {
         if (member.getBirthday() != null) {
@@ -473,4 +375,29 @@ public class FamilyMemberService {
         }
         }
         return Math.max(0, age);
         return Math.max(0, age);
     }
     }
+
+    /**
+     * 计算成员的实际角色(effective role)
+     * 优先级: roleOverride > defaultRole(关系类型模板) > 按年龄计算
+     */
+    private String computeEffectiveRole(FamilyMember member) {
+        // 1. roleOverride 非 auto/null → 直接返回
+        if (member.getRoleOverride() != null && !"auto".equals(member.getRoleOverride())) {
+            return member.getRoleOverride();
+        }
+        // 2. 取关系类型模板值
+        RelationshipType relType = relationshipTypeMapper.selectOne(
+            new LambdaQueryWrapper<RelationshipType>()
+                .eq(RelationshipType::getTypeKey, member.getRelationshipType()));
+        if (relType != null && relType.getDefaultRole() != null) {
+            return relType.getDefaultRole();
+        }
+        // 3. 按年龄计算
+        if (member.getBirthday() != null) {
+            int age = calculateAge(member.getBirthday());
+            int childThreshold = systemConfigService.getChildAgeThreshold();
+            if (age < childThreshold) return "child";
+        }
+        return "parent";
+    }
 }
 }

+ 5 - 11
cfc-backend/src/main/java/com/etotem/cfc/service/FinanceCheckinService.java

@@ -1,14 +1,8 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.FinanceCheckin;
-import com.etotem.cfc.mapper.FinanceCheckinMapper;
-import com.etotem.cfc.entity.Task;
-import com.etotem.cfc.entity.Child;
-import com.etotem.cfc.entity.PointsLog;
-import com.etotem.cfc.mapper.TaskMapper;
-import com.etotem.cfc.mapper.ChildMapper;
-import com.etotem.cfc.mapper.PointsLogMapper;
+import com.etotem.cfc.entity.*;
+import com.etotem.cfc.mapper.*;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
@@ -33,7 +27,7 @@ public class FinanceCheckinService {
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -84,7 +78,7 @@ public class FinanceCheckinService {
                 );
                 );
                 if (existingTask == null) {
                 if (existingTask == null) {
                     Task task = new Task();
                     Task task = new Task();
-                    Child child = childMapper.selectById(childId);
+                    FamilyMember child = familyMemberMapper.selectById(childId);
                     if (child != null) {
                     if (child != null) {
                         task.setFamilyId(child.getFamilyId());
                         task.setFamilyId(child.getFamilyId());
                     }
                     }
@@ -117,7 +111,7 @@ public class FinanceCheckinService {
                     if (child != null) {
                     if (child != null) {
                         child.setTotalPoints(child.getTotalPoints() + points);
                         child.setTotalPoints(child.getTotalPoints() + points);
                         child.setUpdatedAt(new Date());
                         child.setUpdatedAt(new Date());
-                        childMapper.updateById(child);
+                        familyMemberMapper.updateById(child);
                     }
                     }
 
 
                     // Insert PointsLog
                     // Insert PointsLog

+ 6 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/GrowthTaskService.java

@@ -2,9 +2,10 @@ package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.entity.Child;
 import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.GrowthTask;
 import com.etotem.cfc.entity.GrowthTask;
 import com.etotem.cfc.entity.GrowthTaskLog;
 import com.etotem.cfc.entity.GrowthTaskLog;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.GrowthTaskLogMapper;
 import com.etotem.cfc.mapper.GrowthTaskLogMapper;
 import com.etotem.cfc.mapper.GrowthTaskMapper;
 import com.etotem.cfc.mapper.GrowthTaskMapper;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -26,7 +27,7 @@ public class GrowthTaskService {
     private GrowthTaskLogMapper growthTaskLogMapper;
     private GrowthTaskLogMapper growthTaskLogMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsService pointsService;
     private PointsService pointsService;
@@ -183,9 +184,9 @@ public class GrowthTaskService {
         growthTaskLogMapper.updateById(taskLog);
         growthTaskLogMapper.updateById(taskLog);
 
 
         // Find child by userId
         // Find child by userId
-        Child child = childMapper.selectOne(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getUserId, userId)
+        FamilyMember child = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
                         .last("LIMIT 1")
                         .last("LIMIT 1")
         );
         );
 
 

+ 5 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/HealthCheckinService.java

@@ -6,10 +6,10 @@ import com.etotem.cfc.entity.HealthCheckin;
 import com.etotem.cfc.mapper.EnergyLogMapper;
 import com.etotem.cfc.mapper.EnergyLogMapper;
 import com.etotem.cfc.mapper.HealthCheckinMapper;
 import com.etotem.cfc.mapper.HealthCheckinMapper;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -36,7 +36,7 @@ public class HealthCheckinService {
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -103,7 +103,7 @@ public class HealthCheckinService {
                 );
                 );
                 if (existingTask == null) {
                 if (existingTask == null) {
                     Task task = new Task();
                     Task task = new Task();
-                    Child child = childMapper.selectById(childId);
+                    FamilyMember child = familyMemberMapper.selectById(childId);
                     if (child != null) {
                     if (child != null) {
                         task.setFamilyId(child.getFamilyId());
                         task.setFamilyId(child.getFamilyId());
                     }
                     }
@@ -136,7 +136,7 @@ public class HealthCheckinService {
                     if (child != null) {
                     if (child != null) {
                         child.setTotalPoints(child.getTotalPoints() + points);
                         child.setTotalPoints(child.getTotalPoints() + points);
                         child.setUpdatedAt(new Date());
                         child.setUpdatedAt(new Date());
-                        childMapper.updateById(child);
+                        familyMemberMapper.updateById(child);
                     }
                     }
 
 
                     // Insert PointsLog
                     // Insert PointsLog

+ 23 - 29
cfc-backend/src/main/java/com/etotem/cfc/service/HealthReportService.java

@@ -1,14 +1,8 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
-import com.etotem.cfc.entity.HealthDiseaseRisk;
-import com.etotem.cfc.entity.HealthGutFlora;
-import com.etotem.cfc.entity.HealthIndicator;
-import com.etotem.cfc.entity.HealthReport;
-import com.etotem.cfc.entity.HealthReportPermission;
-import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.entity.*;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.HealthDiseaseRiskMapper;
 import com.etotem.cfc.mapper.HealthDiseaseRiskMapper;
 import com.etotem.cfc.mapper.HealthGutFloraMapper;
 import com.etotem.cfc.mapper.HealthGutFloraMapper;
 import com.etotem.cfc.mapper.HealthIndicatorMapper;
 import com.etotem.cfc.mapper.HealthIndicatorMapper;
@@ -49,7 +43,7 @@ public class HealthReportService {
     private HealthDiseaseRiskMapper healthDiseaseRiskMapper;
     private HealthDiseaseRiskMapper healthDiseaseRiskMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
@@ -307,11 +301,11 @@ public class HealthReportService {
     }
     }
 
 
     /**
     /**
-     * 根据姓名模糊匹配家庭成员(优先匹配 Child,其次匹配 User)
+     * 根据姓名模糊匹配家庭成员(优先匹配 FamilyMember,其次匹配 User)
      *
      *
      * @param name     从PDF提取的姓名
      * @param name     从PDF提取的姓名
      * @param familyId 家庭ID
      * @param familyId 家庭ID
-     * @return 匹配到的成员ID(Child.id 或 User.id),未匹配返回 null
+     * @return 匹配到的成员ID(FamilyMember.id 或 User.id),未匹配返回 null
      */
      */
     public Long matchFamilyMemberByName(String name, Long familyId) {
     public Long matchFamilyMemberByName(String name, Long familyId) {
         if (name == null || name.trim().isEmpty() || familyId == null) {
         if (name == null || name.trim().isEmpty() || familyId == null) {
@@ -323,8 +317,8 @@ public class HealthReportService {
             return null;
             return null;
         }
         }
 
 
-        // 第1轮:匹配 Child 表(孩子昵称)
-        Long childId = matchChildByName(cleanName, familyId);
+        // 第1轮:匹配 FamilyMember 表(孩子昵称)
+        Long childId = matchFamilyMemberByNameInternal(cleanName, familyId);
         if (childId != null) {
         if (childId != null) {
             return childId;
             return childId;
         }
         }
@@ -340,43 +334,43 @@ public class HealthReportService {
     }
     }
 
 
     /**
     /**
-     * 在 Child 表中匹配姓名
+     * 在 FamilyMember 表中匹配姓名
      */
      */
-    private Long matchChildByName(String cleanName, Long familyId) {
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getFamilyId, familyId)
+    private Long matchFamilyMemberByNameInternal(String cleanName, Long familyId) {
+        List<FamilyMember> children = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId)
         );
         );
         if (children == null || children.isEmpty()) {
         if (children == null || children.isEmpty()) {
             return null;
             return null;
         }
         }
 
 
         // 精确匹配
         // 精确匹配
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             if (child.getNickname() != null) {
             if (child.getNickname() != null) {
                 String nn = child.getNickname().trim().replaceAll("\\s+", "");
                 String nn = child.getNickname().trim().replaceAll("\\s+", "");
                 if (nn.equals(cleanName)) {
                 if (nn.equals(cleanName)) {
-                    log.info("Child 姓名精确匹配: {} -> childId={}", cleanName, child.getId());
+                    log.info("FamilyMember 姓名精确匹配: {} -> childId={}", cleanName, child.getId());
                     return child.getId();
                     return child.getId();
                 }
                 }
             }
             }
         }
         }
 
 
         // 模糊包含匹配
         // 模糊包含匹配
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             if (child.getNickname() != null) {
             if (child.getNickname() != null) {
                 String nn = child.getNickname();
                 String nn = child.getNickname();
                 if (cleanName.contains(nn) || nn.contains(cleanName)) {
                 if (cleanName.contains(nn) || nn.contains(cleanName)) {
-                    log.info("Child 姓名模糊匹配: {} -> childId={}", cleanName, child.getId());
+                    log.info("FamilyMember 姓名模糊匹配: {} -> childId={}", cleanName, child.getId());
                     return child.getId();
                     return child.getId();
                 }
                 }
             }
             }
         }
         }
 
 
         // fuzzyMatch 匹配
         // fuzzyMatch 匹配
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             if (child.getNickname() != null && fuzzyMatch(cleanName, child.getNickname())) {
             if (child.getNickname() != null && fuzzyMatch(cleanName, child.getNickname())) {
-                log.info("Child 姓名fuzzy匹配: {} -> childId={}", cleanName, child.getId());
+                log.info("FamilyMember 姓名fuzzy匹配: {} -> childId={}", cleanName, child.getId());
                 return child.getId();
                 return child.getId();
             }
             }
         }
         }
@@ -452,13 +446,13 @@ public class HealthReportService {
         return false;
         return false;
     }
     }
 
 
-    public Long matchChildIdByName(String name, Long familyId) {
+    public Long matchFamilyMemberIdByName(String name, Long familyId) {
         if (name == null || familyId == null) return null;
         if (name == null || familyId == null) return null;
-        List<Child> children = childMapper.selectList(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getFamilyId, familyId)
+        List<FamilyMember> children = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, familyId)
         );
         );
-        for (Child c : children) {
+        for (FamilyMember c : children) {
             if (name.contains(c.getNickname()) || (c.getNickname() != null && c.getNickname().contains(name))) {
             if (name.contains(c.getNickname()) || (c.getNickname() != null && c.getNickname().contains(name))) {
                 return c.getId();
                 return c.getId();
             }
             }

+ 8 - 8
cfc-backend/src/main/java/com/etotem/cfc/service/InviteCardService.java

@@ -21,7 +21,7 @@ public class InviteCardService {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private FamilyMapper familyMapper;
     private FamilyMapper familyMapper;
@@ -94,7 +94,7 @@ private String generateCode(String type) {
         result.put("refId", card.getRefId());
         result.put("refId", card.getRefId());
 
 
         if ("child".equals(card.getType())) {
         if ("child".equals(card.getType())) {
-            Child child = childMapper.selectById(card.getRefId());
+            FamilyMember child = familyMemberMapper.selectById(card.getRefId());
             if (child != null) {
             if (child != null) {
                 result.put("nickname", child.getNickname());
                 result.put("nickname", child.getNickname());
                 Family family = familyMapper.selectById(child.getFamilyId());
                 Family family = familyMapper.selectById(child.getFamilyId());
@@ -121,7 +121,7 @@ private String generateCode(String type) {
         if (card.getExpiresAt().isBefore(LocalDateTime.now())) return Result.error("邀请码已过期");
         if (card.getExpiresAt().isBefore(LocalDateTime.now())) return Result.error("邀请码已过期");
 
 
         if ("child".equals(card.getType())) {
         if ("child".equals(card.getType())) {
-            return acceptChild(card, phone);
+            return acceptFamilyMember(card, phone);
         } else if ("family".equals(card.getType())) {
         } else if ("family".equals(card.getType())) {
             return acceptFamily(card, phone);
             return acceptFamily(card, phone);
         } else if ("guide".equals(card.getType())) {
         } else if ("guide".equals(card.getType())) {
@@ -130,8 +130,8 @@ private String generateCode(String type) {
         return Result.error("未知邀请类型");
         return Result.error("未知邀请类型");
     }
     }
 
 
-    private Result<Map<String, Object>> acceptChild(InviteCard card, String phone) {
-        Child child = childMapper.selectById(card.getRefId());
+    private Result<Map<String, Object>> acceptFamilyMember(InviteCard card, String phone) {
+        FamilyMember child = familyMemberMapper.selectById(card.getRefId());
         if (child == null) return Result.error("孩子信息不存在");
         if (child == null) return Result.error("孩子信息不存在");
         if (child.getPhone() == null || !child.getPhone().equals(phone)) {
         if (child.getPhone() == null || !child.getPhone().equals(phone)) {
             return Result.error("手机号不匹配,请联系家长确认");
             return Result.error("手机号不匹配,请联系家长确认");
@@ -281,7 +281,7 @@ private String generateCode(String type) {
         parent.setUpdatedAt(new Date());
         parent.setUpdatedAt(new Date());
         userMapper.updateById(parent);
         userMapper.updateById(parent);
 
 
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setUserId(userId);
         child.setUserId(userId);
         child.setFamilyId(family.getId());
         child.setFamilyId(family.getId());
         child.setNickname("孩子");
         child.setNickname("孩子");
@@ -295,10 +295,10 @@ private String generateCode(String type) {
         child.setTheme("default");
         child.setTheme("default");
         child.setCreatedAt(new Date());
         child.setCreatedAt(new Date());
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.insert(child);
+        familyMemberMapper.insert(child);
 
 
         GrowthRecord record = new GrowthRecord();
         GrowthRecord record = new GrowthRecord();
-        record.setChildId(child.getId());
+        record.setFamilyMemberId(child.getId());
         record.setParentId(userId);
         record.setParentId(userId);
         record.setRecordTitle("基础成长档案");
         record.setRecordTitle("基础成长档案");
         record.setStatus("pending");
         record.setStatus("pending");

+ 6 - 6
cfc-backend/src/main/java/com/etotem/cfc/service/InviteMilestoneService.java

@@ -1,10 +1,10 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.CommissionRecord;
 import com.etotem.cfc.entity.CommissionRecord;
 import com.etotem.cfc.entity.InviteMilestone;
 import com.etotem.cfc.entity.InviteMilestone;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.CommissionRecordMapper;
 import com.etotem.cfc.mapper.CommissionRecordMapper;
 import com.etotem.cfc.mapper.InviteMilestoneMapper;
 import com.etotem.cfc.mapper.InviteMilestoneMapper;
 import org.slf4j.Logger;
 import org.slf4j.Logger;
@@ -27,7 +27,7 @@ public class InviteMilestoneService {
     private CommissionRecordMapper commissionRecordMapper;
     private CommissionRecordMapper commissionRecordMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsService pointsService;
     private PointsService pointsService;
@@ -110,9 +110,9 @@ public class InviteMilestoneService {
         inviteMilestoneMapper.updateById(milestone);
         inviteMilestoneMapper.updateById(milestone);
 
 
         // Resolve childId from userId for points/energy award
         // Resolve childId from userId for points/energy award
-        Child child = childMapper.selectOne(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getUserId, userId)
+        FamilyMember child = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
                         .last("LIMIT 1")
                         .last("LIMIT 1")
         );
         );
         if (child != null) {
         if (child != null) {

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/MentalHealthScreenService.java

@@ -4,11 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.dto.EmotionAlertVO;
 import com.etotem.cfc.dto.EmotionAlertVO;
 import com.etotem.cfc.dto.ScreenResultVO;
 import com.etotem.cfc.dto.ScreenResultVO;
 import com.etotem.cfc.dto.ScreenSubmitDTO;
 import com.etotem.cfc.dto.ScreenSubmitDTO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.EmotionAlert;
 import com.etotem.cfc.entity.EmotionAlert;
 import com.etotem.cfc.entity.FollowUp;
 import com.etotem.cfc.entity.FollowUp;
 import com.etotem.cfc.entity.MentalHealthScreen;
 import com.etotem.cfc.entity.MentalHealthScreen;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.EmotionAlertMapper;
 import com.etotem.cfc.mapper.EmotionAlertMapper;
 import com.etotem.cfc.mapper.FollowUpMapper;
 import com.etotem.cfc.mapper.FollowUpMapper;
 import com.etotem.cfc.mapper.MentalHealthScreenMapper;
 import com.etotem.cfc.mapper.MentalHealthScreenMapper;
@@ -36,7 +36,7 @@ public class MentalHealthScreenService {
     private FollowUpMapper followUpMapper;
     private FollowUpMapper followUpMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private WechatAlertTemplateService wechatAlertTemplateService;
     private WechatAlertTemplateService wechatAlertTemplateService;
@@ -201,7 +201,7 @@ public class MentalHealthScreenService {
             try {
             try {
                 FollowUp followUp = new FollowUp();
                 FollowUp followUp = new FollowUp();
                 followUp.setChildId(alert.getChildId());
                 followUp.setChildId(alert.getChildId());
-                Child child = childMapper.selectById(alert.getChildId());
+                FamilyMember child = familyMemberMapper.selectById(alert.getChildId());
                 if (child != null && child.getFamilyId() != null) {
                 if (child != null && child.getFamilyId() != null) {
                     followUp.setFamilyId(child.getFamilyId());
                     followUp.setFamilyId(child.getFamilyId());
                 }
                 }

+ 5 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/MiniGameService.java

@@ -3,10 +3,10 @@ package com.etotem.cfc.service;
 import com.etotem.cfc.service.api.MiniGameServiceInterface;
 import com.etotem.cfc.service.api.MiniGameServiceInterface;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.MiniGame;
 import com.etotem.cfc.entity.MiniGame;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.MiniGameMapper;
 import com.etotem.cfc.mapper.MiniGameMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
@@ -27,7 +27,7 @@ public class MiniGameService implements MiniGameServiceInterface {
     private MiniGameMapper miniGameMapper;
     private MiniGameMapper miniGameMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -92,7 +92,7 @@ public class MiniGameService implements MiniGameServiceInterface {
             throw new RuntimeException("游戏不存在");
             throw new RuntimeException("游戏不存在");
         }
         }
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             throw new RuntimeException("孩子不存在");
             throw new RuntimeException("孩子不存在");
         }
         }
@@ -104,7 +104,7 @@ public class MiniGameService implements MiniGameServiceInterface {
         // 更新孩子积分
         // 更新孩子积分
         child.setTotalPoints(child.getTotalPoints() + earnedPoints);
         child.setTotalPoints(child.getTotalPoints() + earnedPoints);
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
         // 记录积分流水
         // 记录积分流水
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();

+ 6 - 6
cfc-backend/src/main/java/com/etotem/cfc/service/OnboardingService.java

@@ -1,9 +1,9 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.OnboardingTask;
 import com.etotem.cfc.entity.OnboardingTask;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.OnboardingTaskMapper;
 import com.etotem.cfc.mapper.OnboardingTaskMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
@@ -24,7 +24,7 @@ public class OnboardingService {
     private EnergyService energyService;
     private EnergyService energyService;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     private static final Map<String, int[]> REWARDS = new LinkedHashMap<>();
     private static final Map<String, int[]> REWARDS = new LinkedHashMap<>();
     static {
     static {
@@ -90,9 +90,9 @@ public class OnboardingService {
         onboardingTaskMapper.updateById(task);
         onboardingTaskMapper.updateById(task);
 
 
         // Find child by userId to award points and energy
         // Find child by userId to award points and energy
-        Child child = childMapper.selectOne(
-                new LambdaQueryWrapper<Child>()
-                        .eq(Child::getUserId, userId)
+        FamilyMember child = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getUserId, userId)
                         .last("LIMIT 1")
                         .last("LIMIT 1")
         );
         );
         if (child != null) {
         if (child != null) {

+ 11 - 11
cfc-backend/src/main/java/com/etotem/cfc/service/PointsExchangeService.java

@@ -2,10 +2,10 @@ package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsExchangeProduct;
 import com.etotem.cfc.entity.PointsExchangeProduct;
 import com.etotem.cfc.entity.PointsExchangeRecord;
 import com.etotem.cfc.entity.PointsExchangeRecord;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsExchangeProductMapper;
 import com.etotem.cfc.mapper.PointsExchangeProductMapper;
 import com.etotem.cfc.mapper.PointsExchangeRecordMapper;
 import com.etotem.cfc.mapper.PointsExchangeRecordMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -30,7 +30,7 @@ public class PointsExchangeService {
     private PointsService pointsService;
     private PointsService pointsService;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private SysConfigService sysConfigService;
     private SysConfigService sysConfigService;
@@ -67,16 +67,16 @@ public class PointsExchangeService {
             throw new RuntimeException("库存不足");
             throw new RuntimeException("库存不足");
         }
         }
 
 
-        Long targetChildId = childId;
-        if (targetChildId == null) {
-            Child child = childMapper.selectOne(
-                new LambdaQueryWrapper<Child>().eq(Child::getUserId, userId).last("LIMIT 1")
+        Long targetFamilyMemberId = childId;
+        if (targetFamilyMemberId == null) {
+            FamilyMember child = familyMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getUserId, userId).last("LIMIT 1")
             );
             );
             if (child != null) {
             if (child != null) {
-                targetChildId = child.getId();
+                targetFamilyMemberId = child.getId();
             }
             }
         }
         }
-        if (targetChildId == null) {
+        if (targetFamilyMemberId == null) {
             throw new RuntimeException("未找到关联的孩子信息");
             throw new RuntimeException("未找到关联的孩子信息");
         }
         }
 
 
@@ -123,7 +123,7 @@ public class PointsExchangeService {
             throw new RuntimeException("兑换积分不得低于最低限制");
             throw new RuntimeException("兑换积分不得低于最低限制");
         }
         }
 
 
-        int deductResult = pointsService.deductSystemPoints(targetChildId, totalCost,
+        int deductResult = pointsService.deductSystemPoints(targetFamilyMemberId, totalCost,
             "积分兑换商品: " + product.getName());
             "积分兑换商品: " + product.getName());
         if (deductResult < 0) {
         if (deductResult < 0) {
             throw new RuntimeException("积分不足");
             throw new RuntimeException("积分不足");
@@ -133,7 +133,7 @@ public class PointsExchangeService {
 
 
         PointsExchangeRecord record = new PointsExchangeRecord();
         PointsExchangeRecord record = new PointsExchangeRecord();
         record.setUserId(userId);
         record.setUserId(userId);
-        record.setChildId(targetChildId);
+        record.setFamilyMemberId(targetFamilyMemberId);
         record.setProductId(productId);
         record.setProductId(productId);
         record.setProductName(product.getName());
         record.setProductName(product.getName());
         record.setPointsCost(totalCost);
         record.setPointsCost(totalCost);

+ 11 - 11
cfc-backend/src/main/java/com/etotem/cfc/service/PointsService.java

@@ -4,9 +4,9 @@ import com.etotem.cfc.service.api.PointsServiceInterface;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
@@ -20,13 +20,13 @@ import java.util.Map;
 public class PointsService implements PointsServiceInterface {
 public class PointsService implements PointsServiceInterface {
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
 
 
     public Map<String, Object> getBalance(Long childId) {
     public Map<String, Object> getBalance(Long childId) {
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             return null;
             return null;
         }
         }
@@ -56,7 +56,7 @@ public class PointsService implements PointsServiceInterface {
         // 验证密码(简化版,实际应从数据库验证)
         // 验证密码(简化版,实际应从数据库验证)
         // 这里需要调用UserService验证密码
         // 这里需要调用UserService验证密码
         
         
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             return null;
             return null;
         }
         }
@@ -71,7 +71,7 @@ public class PointsService implements PointsServiceInterface {
         // 更新积分
         // 更新积分
         child.setTotalPoints(newBalance);
         child.setTotalPoints(newBalance);
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
         // 记录流水
         // 记录流水
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();
@@ -94,7 +94,7 @@ public class PointsService implements PointsServiceInterface {
             throw new IllegalArgumentException("系统积分数量必须为正数");
             throw new IllegalArgumentException("系统积分数量必须为正数");
         }
         }
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             throw new RuntimeException("孩子不存在");
             throw new RuntimeException("孩子不存在");
         }
         }
@@ -106,7 +106,7 @@ public class PointsService implements PointsServiceInterface {
         child.setTotalPoints(newTotal);
         child.setTotalPoints(newTotal);
 
 
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();
         log.setChildId(childId);
         log.setChildId(childId);
@@ -127,7 +127,7 @@ public class PointsService implements PointsServiceInterface {
             throw new IllegalArgumentException("扣除数量必须为正数");
             throw new IllegalArgumentException("扣除数量必须为正数");
         }
         }
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             throw new RuntimeException("孩子不存在");
             throw new RuntimeException("孩子不存在");
         }
         }
@@ -144,7 +144,7 @@ public class PointsService implements PointsServiceInterface {
         child.setTotalPoints(newTotal);
         child.setTotalPoints(newTotal);
 
 
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();
         log.setChildId(childId);
         log.setChildId(childId);
@@ -159,7 +159,7 @@ public class PointsService implements PointsServiceInterface {
     }
     }
 
 
     public int getSystemPointsBalance(Long childId) {
     public int getSystemPointsBalance(Long childId) {
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             return 0;
             return 0;
         }
         }

+ 11 - 11
cfc-backend/src/main/java/com/etotem/cfc/service/RewardService.java

@@ -4,11 +4,11 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.dto.CreateRewardDTO;
 import com.etotem.cfc.dto.CreateRewardDTO;
 import com.etotem.cfc.dto.RewardDTO;
 import com.etotem.cfc.dto.RewardDTO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.Reward;
 import com.etotem.cfc.entity.Reward;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.RewardMapper;
 import com.etotem.cfc.mapper.RewardMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
@@ -32,7 +32,7 @@ public class RewardService implements RewardServiceInterface {
 private RewardMapper rewardMapper;
 private RewardMapper rewardMapper;
 
 
 @Resource
 @Resource
-private ChildMapper childMapper;
+private FamilyMemberMapper familyMemberMapper;
 
 
 @Resource
 @Resource
 private PointsLogMapper pointsLogMapper;
 private PointsLogMapper pointsLogMapper;
@@ -49,7 +49,7 @@ private UserMapper userMapper;
 */
 */
 @Transactional
 @Transactional
 public Long createWishlist(Long childId, CreateRewardDTO dto) {
 public Long createWishlist(Long childId, CreateRewardDTO dto) {
-Child child = childMapper.selectById(childId);
+FamilyMember child = familyMemberMapper.selectById(childId);
 if (child == null) {
 if (child == null) {
 throw new IllegalArgumentException("孩子不存在");
 throw new IllegalArgumentException("孩子不存在");
 }
 }
@@ -140,7 +140,7 @@ public Long createReward(Long userId, CreateRewardDTO dto) {
 
 
     public List<RewardDTO> getWishlist(Long childId) {
     public List<RewardDTO> getWishlist(Long childId) {
         // 获取孩子信息以验证家庭归属
         // 获取孩子信息以验证家庭归属
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             return null;
             return null;
         }
         }
@@ -178,7 +178,7 @@ public Long createReward(Long userId, CreateRewardDTO dto) {
             return null;
             return null;
         }
         }
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child.getTotalPoints() < reward.getPointsRequired()) {
         if (child.getTotalPoints() < reward.getPointsRequired()) {
             return null; // 积分不足
             return null; // 积分不足
         }
         }
@@ -186,7 +186,7 @@ public Long createReward(Long userId, CreateRewardDTO dto) {
         // 扣除积分
         // 扣除积分
         child.setTotalPoints(child.getTotalPoints() - reward.getPointsRequired());
         child.setTotalPoints(child.getTotalPoints() - reward.getPointsRequired());
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
         // 记录积分消耗
         // 记录积分消耗
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();
@@ -221,10 +221,10 @@ public Long createReward(Long userId, CreateRewardDTO dto) {
             reward.setExchangedAt(new Date());
             reward.setExchangedAt(new Date());
         } else {
         } else {
             // 拒绝,返还积分
             // 拒绝,返还积分
-            Child child = childMapper.selectById(reward.getChildId());
+            FamilyMember child = familyMemberMapper.selectById(reward.getChildId());
             child.setTotalPoints(child.getTotalPoints() + reward.getPointsRequired());
             child.setTotalPoints(child.getTotalPoints() + reward.getPointsRequired());
             child.setUpdatedAt(new Date());
             child.setUpdatedAt(new Date());
-            childMapper.updateById(child);
+            familyMemberMapper.updateById(child);
 
 
             // 记录积分退还
             // 记录积分退还
             PointsLog log = new PointsLog();
             PointsLog log = new PointsLog();
@@ -319,7 +319,7 @@ return true;
 * 只返回指定孩子的心愿,不包含其他孩子
 * 只返回指定孩子的心愿,不包含其他孩子
 */
 */
 public List<RewardDTO> getChildPendingWishlists(Long childId) {
 public List<RewardDTO> getChildPendingWishlists(Long childId) {
-Child child = childMapper.selectById(childId);
+FamilyMember child = familyMemberMapper.selectById(childId);
 if (child == null) {
 if (child == null) {
 return null;
 return null;
 }
 }
@@ -363,7 +363,7 @@ dto.setStatus(r.getStatus());
 dto.setChildId(r.getChildId());
 dto.setChildId(r.getChildId());
 
 
 // 获取孩子昵称
 // 获取孩子昵称
-Child child = childMapper.selectById(r.getChildId());
+FamilyMember child = familyMemberMapper.selectById(r.getChildId());
 if (child != null) {
 if (child != null) {
 dto.setChildName(child.getNickname());
 dto.setChildName(child.getNickname());
 }
 }

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/ShareService.java

@@ -1,10 +1,10 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.ShareEvent;
 import com.etotem.cfc.entity.ShareEvent;
 import com.etotem.cfc.entity.ShareRead;
 import com.etotem.cfc.entity.ShareRead;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.ShareEventMapper;
 import com.etotem.cfc.mapper.ShareEventMapper;
 import com.etotem.cfc.mapper.ShareReadMapper;
 import com.etotem.cfc.mapper.ShareReadMapper;
 import com.etotem.cfc.service.SysConfigService;
 import com.etotem.cfc.service.SysConfigService;
@@ -35,7 +35,7 @@ public class ShareService {
     private PointsService pointsService;
     private PointsService pointsService;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     public ShareEvent createShareEvent(Long userId, String targetType, Long targetId,
     public ShareEvent createShareEvent(Long userId, String targetType, Long targetId,
             String shareChannel, String title) {
             String shareChannel, String title) {
@@ -93,7 +93,7 @@ public class ShareService {
         String readRewardStr = sysConfigService.getValue("share_read_reward_points");
         String readRewardStr = sysConfigService.getValue("share_read_reward_points");
         int readReward = readRewardStr != null ? Integer.parseInt(readRewardStr) : 5;
         int readReward = readRewardStr != null ? Integer.parseInt(readRewardStr) : 5;
         try {
         try {
-            Child child = childMapper.selectOne(new LambdaQueryWrapper<Child>().eq(Child::getUserId, userId).last("LIMIT 1"));
+            FamilyMember child = familyMemberMapper.selectOne(new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getUserId, userId).last("LIMIT 1"));
             if (child != null) {
             if (child != null) {
                 pointsService.awardSystemPoints(child.getId(), readReward, "share read reward: " + shareEventId);
                 pointsService.awardSystemPoints(child.getId(), readReward, "share read reward: " + shareEventId);
             }
             }

+ 13 - 13
cfc-backend/src/main/java/com/etotem/cfc/service/StreakService.java

@@ -1,10 +1,10 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.StreakMilestone;
 import com.etotem.cfc.entity.StreakMilestone;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.StreakMilestoneMapper;
 import com.etotem.cfc.mapper.StreakMilestoneMapper;
 import com.etotem.cfc.service.api.StreakServiceInterface;
 import com.etotem.cfc.service.api.StreakServiceInterface;
@@ -23,7 +23,7 @@ public class StreakService implements StreakServiceInterface {
     private StreakMilestoneMapper streakMilestoneMapper;
     private StreakMilestoneMapper streakMilestoneMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -53,7 +53,7 @@ public class StreakService implements StreakServiceInterface {
         // 获取孩子已获得的里程碑奖励记录
         // 获取孩子已获得的里程碑奖励记录
         List<PointsLog> bonusLogs = pointsLogMapper.selectList(
         List<PointsLog> bonusLogs = pointsLogMapper.selectList(
             new LambdaQueryWrapper<PointsLog>()
             new LambdaQueryWrapper<PointsLog>()
-                .eq(PointsLog::getChildId, childId)
+                .eq(PointsLog::getFamilyMemberId, childId)
                 .eq(PointsLog::getType, "bonus")
                 .eq(PointsLog::getType, "bonus")
                 .like(PointsLog::getDescription, "打卡里程碑")
                 .like(PointsLog::getDescription, "打卡里程碑")
         );
         );
@@ -72,7 +72,7 @@ public class StreakService implements StreakServiceInterface {
                 rewardPoints = milestone.getRewardPoints();
                 rewardPoints = milestone.getRewardPoints();
 
 
                 // 记录积分
                 // 记录积分
-                Child child = childMapper.selectById(childId);
+                FamilyMember child = familyMemberMapper.selectById(childId);
                 if (child != null) {
                 if (child != null) {
                     recordBonusPoints(child, milestone);
                     recordBonusPoints(child, milestone);
                 }
                 }
@@ -118,14 +118,14 @@ public class StreakService implements StreakServiceInterface {
     /**
     /**
      * 记录里程碑奖励积分
      * 记录里程碑奖励积分
      */
      */
-    private void recordBonusPoints(Child child, StreakMilestone milestone) {
+    private void recordBonusPoints(FamilyMember child, StreakMilestone milestone) {
         // 更新孩子积分
         // 更新孩子积分
         child.setTotalPoints(child.getTotalPoints() + milestone.getRewardPoints());
         child.setTotalPoints(child.getTotalPoints() + milestone.getRewardPoints());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
         
         
         // 记录积分流水
         // 记录积分流水
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();
-        log.setChildId(child.getId());
+        log.setFamilyMemberId(child.getId());
         log.setAmount(milestone.getRewardPoints());
         log.setAmount(milestone.getRewardPoints());
         log.setType("bonus");
         log.setType("bonus");
         log.setDescription("打卡里程碑-" + milestone.getDays() + "天奖励");
         log.setDescription("打卡里程碑-" + milestone.getDays() + "天奖励");
@@ -137,7 +137,7 @@ public class StreakService implements StreakServiceInterface {
      * 获取孩子的打卡进度信息
      * 获取孩子的打卡进度信息
      */
      */
     public Map<String, Object> getStreakProgress(Long childId) {
     public Map<String, Object> getStreakProgress(Long childId) {
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             return null;
             return null;
         }
         }
@@ -155,7 +155,7 @@ public class StreakService implements StreakServiceInterface {
         // 获取已获得的奖励
         // 获取已获得的奖励
         List<PointsLog> bonusLogs = pointsLogMapper.selectList(
         List<PointsLog> bonusLogs = pointsLogMapper.selectList(
             new LambdaQueryWrapper<PointsLog>()
             new LambdaQueryWrapper<PointsLog>()
-                .eq(PointsLog::getChildId, childId)
+                .eq(PointsLog::getFamilyMemberId, childId)
                 .eq(PointsLog::getType, "bonus")
                 .eq(PointsLog::getType, "bonus")
                 .like(PointsLog::getDescription, "打卡里程碑")
                 .like(PointsLog::getDescription, "打卡里程碑")
         );
         );
@@ -200,7 +200,7 @@ public class StreakService implements StreakServiceInterface {
      */
      */
     public void resetStreakIfNeeded() {
     public void resetStreakIfNeeded() {
         // 查询所有孩子
         // 查询所有孩子
-        List<Child> children = childMapper.selectList(null);
+        List<FamilyMember> children = familyMemberMapper.selectList(null);
         
         
         Calendar cal = Calendar.getInstance();
         Calendar cal = Calendar.getInstance();
         cal.add(Calendar.DAY_OF_MONTH, -1);
         cal.add(Calendar.DAY_OF_MONTH, -1);
@@ -210,14 +210,14 @@ public class StreakService implements StreakServiceInterface {
         cal.set(Calendar.MILLISECOND, 0);
         cal.set(Calendar.MILLISECOND, 0);
         Date yesterday = cal.getTime();
         Date yesterday = cal.getTime();
         
         
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             if (child.getLastTaskDate() != null && child.getLastTaskDate().before(yesterday)) {
             if (child.getLastTaskDate() != null && child.getLastTaskDate().before(yesterday)) {
                 // 上次任务日期是昨天之前,重置连续天数
                 // 上次任务日期是昨天之前,重置连续天数
                 if (child.getStreakDays() > 0) {
                 if (child.getStreakDays() > 0) {
                     log.info("孩子{}打卡中断,连续天数从{}重置为0", child.getId(), child.getStreakDays());
                     log.info("孩子{}打卡中断,连续天数从{}重置为0", child.getId(), child.getStreakDays());
                     child.setStreakDays(0);
                     child.setStreakDays(0);
                     child.setUpdatedAt(new Date());
                     child.setUpdatedAt(new Date());
-                    childMapper.updateById(child);
+                    familyMemberMapper.updateById(child);
                 }
                 }
             }
             }
         }
         }

+ 43 - 8
cfc-backend/src/main/java/com/etotem/cfc/service/TaskService.java

@@ -22,7 +22,7 @@ public class TaskService implements TaskServiceInterface {
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
@@ -322,7 +322,7 @@ long diffMinutes = (now.getTime() - task.getDeadline().getTime()) / (1000 * 60);
 isLateGrace = diffMinutes <= LATE_GRACE_MINUTES;
 isLateGrace = diffMinutes <= LATE_GRACE_MINUTES;
 }
 }
 
 
-Child child = childMapper.selectById(childId);
+FamilyMember child = familyMemberMapper.selectById(childId);
 
 
 // POINT-002: 计算积分(支持自定义超额规则)
 // POINT-002: 计算积分(支持自定义超额规则)
 String bonusRuleJson = task.getBonusRule();
 String bonusRuleJson = task.getBonusRule();
@@ -365,7 +365,7 @@ pointsEarned += EARLY_BONUS;
         }
         }
         child.setLastTaskDate(today);
         child.setLastTaskDate(today);
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
 // 检查并发放打卡里程碑奖励
 // 检查并发放打卡里程碑奖励
 int milestoneReward = streakService.checkAndAwardMilestone(childId, child.getStreakDays());
 int milestoneReward = streakService.checkAndAwardMilestone(childId, child.getStreakDays());
@@ -578,7 +578,7 @@ return result;
         Map<String, Object> result = new HashMap<>();
         Map<String, Object> result = new HashMap<>();
         result.put("pointsEarned", pointsEarned);
         result.put("pointsEarned", pointsEarned);
 
 
-        try { Child child = childMapper.selectById(task.getChildId()); if (child != null) { onboardingService.completeTask(child.getUserId(), "FIRST_TASK"); } } catch (Exception e) { /* onboarding trigger failure should not block task completion */ }
+        try { FamilyMember child = familyMemberMapper.selectById(task.getChildId()); if (child != null) { onboardingService.completeTask(child.getUserId(), "FIRST_TASK"); } } catch (Exception e) { /* onboarding trigger failure should not block task completion */ }
     result.put("newBalance", user.getTotalPoints());
     result.put("newBalance", user.getTotalPoints());
     result.put("needReview", task.getNeedReview() == 1);
     result.put("needReview", task.getNeedReview() == 1);
 
 
@@ -655,7 +655,7 @@ return taskMapper.selectPage(pageParam, wrapper);
 * "qualityThreshold": 90  // 质量评分阈值(可选)
 * "qualityThreshold": 90  // 质量评分阈值(可选)
 * }
 * }
 */
 */
-private int calculatePointsWithBonusRule(Task task, Date completedAt, boolean isEarly, boolean isLateGrace, Child child) {
+private int calculatePointsWithBonusRule(Task task, Date completedAt, boolean isEarly, boolean isLateGrace, FamilyMember child) {
 int points = task.getPoints();
 int points = task.getPoints();
 
 
 try {
 try {
@@ -727,7 +727,7 @@ return null;
 /**
 /**
  * 计算超时扣分
  * 计算超时扣分
  */
  */
-private int calculateLatePenalty(Task task, Child child, Long childId) {
+private int calculateLatePenalty(Task task, FamilyMember child, Long childId) {
     if (child == null) {
     if (child == null) {
         return 0;
         return 0;
     }
     }
@@ -794,7 +794,7 @@ return 0; // 达到每日扣分上限
         taskMapper.updateById(task);
         taskMapper.updateById(task);
 
 
         // 更新连续打卡
         // 更新连续打卡
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         Date lastTaskDate = child.getLastTaskDate();
         Date lastTaskDate = child.getLastTaskDate();
         Date today = new Date();
         Date today = new Date();
         Calendar cal = Calendar.getInstance();
         Calendar cal = Calendar.getInstance();
@@ -810,7 +810,7 @@ return 0; // 达到每日扣分上限
         }
         }
         child.setLastTaskDate(today);
         child.setLastTaskDate(today);
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
 
 
         // 检查并发放打卡里程碑奖励
         // 检查并发放打卡里程碑奖励
         int milestoneReward = streakService.checkAndAwardMilestone(childId, child.getStreakDays());
         int milestoneReward = streakService.checkAndAwardMilestone(childId, child.getStreakDays());
@@ -827,4 +827,39 @@ return 0; // 达到每日扣分上限
 
 
         return result;
         return result;
     }
     }
+
+    // ==================== Family Member Task queries ====================
+
+    /**
+     * 获取指定家庭成员待审核的任务列表
+     */
+    public List<Task> getPendingReviewTasksForFamilyMember(Long familyMemberId) {
+        return taskMapper.selectList(new LambdaQueryWrapper<Task>()
+                .eq(Task::getFamilyMemberId, familyMemberId)
+                .eq(Task::getNeedReview, 1)
+                .in(Task::getStatus, Arrays.asList("completed", "pending"))
+                .orderByDesc(Task::getCreatedAt));
+    }
+
+    /**
+     * 获取指定家庭成员的全部任务
+     */
+    public List<Task> getAllTasksByFamilyMember(Long familyMemberId) {
+        return taskMapper.selectList(new LambdaQueryWrapper<Task>()
+                .eq(Task::getFamilyMemberId, familyMemberId)
+                .orderByDesc(Task::getCreatedAt));
+    }
+
+    /**
+     * 分页获取指定家庭成员的任务历史
+     */
+    public Page<Task> getTaskHistoryByFamilyMember(Long familyMemberId, Integer page, Integer size, String status) {
+        LambdaQueryWrapper<Task> wrapper = new LambdaQueryWrapper<Task>()
+                .eq(Task::getFamilyMemberId, familyMemberId);
+        if (status != null && !status.isEmpty()) {
+            wrapper.eq(Task::getStatus, status);
+        }
+        wrapper.orderByDesc(Task::getCreatedAt);
+        return taskMapper.selectPage(new Page<>(page, size), wrapper);
+    }
 }
 }

+ 7 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/TaskStatsService.java

@@ -2,8 +2,10 @@ package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.entity.Child;
 import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
-import com.etotem.cfc.mapper.ChildMapper;
+
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
@@ -19,7 +21,7 @@ public class TaskStatsService {
     private TaskMapper taskMapper;
     private TaskMapper taskMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     /**
     /**
      * 获取单个孩子的任务完成统计
      * 获取单个孩子的任务完成统计
@@ -60,10 +62,10 @@ public class TaskStatsService {
         Map<String, Object> stats = buildStats(tasks, familyId, null);
         Map<String, Object> stats = buildStats(tasks, familyId, null);
 
 
         // 按孩子分组
         // 按孩子分组
-        List<Child> children = childMapper.selectList(
-            new LambdaQueryWrapper<Child>().eq(Child::getFamilyId, familyId));
+        List<FamilyMember> children = familyMemberMapper.selectList(
+            new LambdaQueryWrapper<FamilyMember>().eq(FamilyMember::getFamilyId, familyId));
         List<Map<String, Object>> byChild = new ArrayList<>();
         List<Map<String, Object>> byChild = new ArrayList<>();
-        for (Child child : children) {
+        for (FamilyMember child : children) {
             List<Task> childTasks = tasks.stream()
             List<Task> childTasks = tasks.stream()
                 .filter(t -> child.getId().equals(t.getChildId()))
                 .filter(t -> child.getId().equals(t.getChildId()))
                 .collect(Collectors.toList());
                 .collect(Collectors.toList());

+ 5 - 5
cfc-backend/src/main/java/com/etotem/cfc/service/TeacherService.java

@@ -28,7 +28,7 @@ public class TeacherService {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private UserService userService;
     private UserService userService;
@@ -175,10 +175,10 @@ public class TeacherService {
         danAssessmentResultMapper.insert(result);
         danAssessmentResultMapper.insert(result);
         
         
         // 更新孩子的评估结果
         // 更新孩子的评估结果
-        Child child = childMapper.selectById(dto.getChildId());
+        FamilyMember child = familyMemberMapper.selectById(dto.getChildId());
         if (child != null) {
         if (child != null) {
             child.setUpdatedAt(new Date());
             child.setUpdatedAt(new Date());
-            childMapper.updateById(child);
+            familyMemberMapper.updateById(child);
         }
         }
         
         
         // 更新老师的测评次数
         // 更新老师的测评次数
@@ -260,7 +260,7 @@ public class TeacherService {
         dto.setCreatedAt(appt.getCreatedAt());
         dto.setCreatedAt(appt.getCreatedAt());
         
         
         // 补充关联信息
         // 补充关联信息
-        Child child = childMapper.selectById(appt.getChildId());
+        FamilyMember child = familyMemberMapper.selectById(appt.getChildId());
         if (child != null) {
         if (child != null) {
             dto.setChildName(child.getNickname());
             dto.setChildName(child.getNickname());
         }
         }
@@ -289,7 +289,7 @@ public class TeacherService {
         dto.setCreatedAt(result.getCreatedAt());
         dto.setCreatedAt(result.getCreatedAt());
         
         
         // 补充关联信息
         // 补充关联信息
-        Child child = childMapper.selectById(result.getChildId());
+        FamilyMember child = familyMemberMapper.selectById(result.getChildId());
         if (child != null) {
         if (child != null) {
             dto.setChildName(child.getNickname());
             dto.setChildName(child.getNickname());
             dto.setChildNickname(child.getNickname());
             dto.setChildNickname(child.getNickname());

+ 2 - 2
cfc-backend/src/main/java/com/etotem/cfc/service/TrainingPlanService.java

@@ -5,7 +5,7 @@ import com.etotem.cfc.entity.TrainingPlan;
 import com.etotem.cfc.entity.TrainingPlanItem;
 import com.etotem.cfc.entity.TrainingPlanItem;
 import com.etotem.cfc.entity.TrainingPlanTask;
 import com.etotem.cfc.entity.TrainingPlanTask;
 import com.etotem.cfc.mapper.TrainingPlanMapper;
 import com.etotem.cfc.mapper.TrainingPlanMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.annotation.Transactional;
 
 
@@ -19,7 +19,7 @@ public class TrainingPlanService {
     private TrainingPlanMapper trainingPlanMapper;
     private TrainingPlanMapper trainingPlanMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private CognitiveTrainingConfigService trainingConfigService;
     private CognitiveTrainingConfigService trainingConfigService;

+ 147 - 18
cfc-backend/src/main/java/com/etotem/cfc/service/UserService.java

@@ -5,10 +5,10 @@ import com.etotem.cfc.config.JwtConfig;
 import com.etotem.cfc.dto.*;
 import com.etotem.cfc.dto.*;
 import com.etotem.cfc.dto.RegisterWithIdCardDTO;
 import com.etotem.cfc.dto.RegisterWithIdCardDTO;
 import com.etotem.cfc.dto.RegisterWithInviteCodeDTO;
 import com.etotem.cfc.dto.RegisterWithInviteCodeDTO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.Family;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.FamilyMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.VerificationCodeService;
 import com.etotem.cfc.service.VerificationCodeService;
@@ -34,7 +34,7 @@ private UserMapper userMapper;
 private FamilyMapper familyMapper;
 private FamilyMapper familyMapper;
 
 
 @Resource
 @Resource
-private ChildMapper childMapper;
+private FamilyMemberMapper familyMemberMapper;
 
 
 @Resource
 @Resource
 private JwtConfig jwtConfig;
 private JwtConfig jwtConfig;
@@ -613,7 +613,7 @@ private WechatService wechatService;
       return null;
       return null;
     }
     }
 
 
-    Child child = childMapper.selectById(childId);
+    FamilyMember child = familyMemberMapper.selectById(childId);
     if (child == null || !child.getFamilyId().equals(user.getFamilyId())) {
     if (child == null || !child.getFamilyId().equals(user.getFamilyId())) {
       return null;
       return null;
     }
     }
@@ -658,7 +658,7 @@ private WechatService wechatService;
                 vo.setId(p.getId());
                 vo.setId(p.getId());
                 vo.setNickname(p.getNickname());
                 vo.setNickname(p.getNickname());
                 vo.setAvatar(p.getAvatar());
                 vo.setAvatar(p.getAvatar());
-                vo.setCapabilityRole("parent");
+                vo.setEffectiveRole("parent");
                 vo.setRoleLabel(p.getFamilyRole() != null ? p.getFamilyRole() : "家长");
                 vo.setRoleLabel(p.getFamilyRole() != null ? p.getFamilyRole() : "家长");
                 vo.setTotalPoints(p.getTotalPoints());
                 vo.setTotalPoints(p.getTotalPoints());
                 vo.setShowToFamily(p.getShowToFamily() != null ? p.getShowToFamily() : 1);
                 vo.setShowToFamily(p.getShowToFamily() != null ? p.getShowToFamily() : 1);
@@ -667,15 +667,15 @@ private WechatService wechatService;
         }
         }
 
 
         // 孩子:show_to_family=1 的同家庭孩子
         // 孩子:show_to_family=1 的同家庭孩子
-        List<Child> children = childMapper.selectList(new LambdaQueryWrapper<Child>()
-                .eq(Child::getFamilyId, familyId));
-        for (Child c : children) {
+        List<FamilyMember> children = familyMemberMapper.selectList(new LambdaQueryWrapper<FamilyMember>()
+                .eq(FamilyMember::getFamilyId, familyId));
+        for (FamilyMember c : children) {
             if (c.getShowToFamily() == null || c.getShowToFamily() == 1) {
             if (c.getShowToFamily() == null || c.getShowToFamily() == 1) {
                 FamilyMemberVO vo = new FamilyMemberVO();
                 FamilyMemberVO vo = new FamilyMemberVO();
                 vo.setId(c.getId());
                 vo.setId(c.getId());
                 vo.setNickname(c.getNickname());
                 vo.setNickname(c.getNickname());
                 vo.setAvatar("");
                 vo.setAvatar("");
-                vo.setCapabilityRole("child");
+                vo.setEffectiveRole("child");
                 vo.setRoleLabel("孩子");
                 vo.setRoleLabel("孩子");
                 vo.setTotalPoints(c.getTotalPoints());
                 vo.setTotalPoints(c.getTotalPoints());
                 vo.setShowToFamily(c.getShowToFamily() != null ? c.getShowToFamily() : 1);
                 vo.setShowToFamily(c.getShowToFamily() != null ? c.getShowToFamily() : 1);
@@ -695,13 +695,13 @@ private WechatService wechatService;
             return false;
             return false;
         }
         }
         if ("child".equals(memberType)) {
         if ("child".equals(memberType)) {
-            Child child = childMapper.selectById(memberId);
+            FamilyMember child = familyMemberMapper.selectById(memberId);
             if (child == null || !child.getFamilyId().equals(user.getFamilyId())) {
             if (child == null || !child.getFamilyId().equals(user.getFamilyId())) {
                 return false;
                 return false;
             }
             }
             child.setShowToFamily(showToFamily);
             child.setShowToFamily(showToFamily);
             child.setUpdatedAt(new Date());
             child.setUpdatedAt(new Date());
-            childMapper.updateById(child);
+            familyMemberMapper.updateById(child);
             return true;
             return true;
         } else if ("parent".equals(memberType)) {
         } else if ("parent".equals(memberType)) {
             // 家长只能改自己的可见性
             // 家长只能改自己的可见性
@@ -798,7 +798,7 @@ private WechatService wechatService;
             throw new IllegalArgumentException("请输入有效的年龄(1-18岁)");
             throw new IllegalArgumentException("请输入有效的年龄(1-18岁)");
         }
         }
 
 
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setUserId(userId);
         child.setUserId(userId);
         child.setFamilyId(user.getFamilyId());
         child.setFamilyId(user.getFamilyId());
         child.setNickname(dto.getNickname().trim());
         child.setNickname(dto.getNickname().trim());
@@ -823,7 +823,7 @@ private WechatService wechatService;
         child.setFocusResetDate(new Date());
         child.setFocusResetDate(new Date());
         child.setCreatedAt(new Date());
         child.setCreatedAt(new Date());
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.insert(child);
+        familyMemberMapper.insert(child);
 
 
         return toChildInfoDTO(child);
         return toChildInfoDTO(child);
     }
     }
@@ -834,8 +834,8 @@ private WechatService wechatService;
             return null;
             return null;
         }
         }
 
 
-        List<Child> children = childMapper.selectList(new LambdaQueryWrapper<Child>()
-                .eq(Child::getFamilyId, user.getFamilyId()));
+        List<FamilyMember> children = familyMemberMapper.selectList(new LambdaQueryWrapper<FamilyMember>()
+                .eq(FamilyMember::getFamilyId, user.getFamilyId()));
 
 
         return children.stream().map(this::toChildInfoDTO).collect(java.util.stream.Collectors.toList());
         return children.stream().map(this::toChildInfoDTO).collect(java.util.stream.Collectors.toList());
     }
     }
@@ -846,7 +846,7 @@ private WechatService wechatService;
             return false;
             return false;
         }
         }
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null || child.getFamilyId() == null || !child.getFamilyId().equals(user.getFamilyId())) {
         if (child == null || child.getFamilyId() == null || !child.getFamilyId().equals(user.getFamilyId())) {
             return false;
             return false;
         }
         }
@@ -879,7 +879,7 @@ private WechatService wechatService;
             child.setShowToFamily(dto.getShowToFamily());
             child.setShowToFamily(dto.getShowToFamily());
         }
         }
         child.setUpdatedAt(new Date());
         child.setUpdatedAt(new Date());
-        childMapper.updateById(child);
+        familyMemberMapper.updateById(child);
         return true;
         return true;
     }
     }
 
 
@@ -891,7 +891,7 @@ private WechatService wechatService;
         return UUID.randomUUID().toString().substring(0, 8).toUpperCase();
         return UUID.randomUUID().toString().substring(0, 8).toUpperCase();
     }
     }
 
 
-    private ChildInfoDTO toChildInfoDTO(Child child) {
+    private ChildInfoDTO toChildInfoDTO(FamilyMember child) {
         ChildInfoDTO dto = new ChildInfoDTO();
         ChildInfoDTO dto = new ChildInfoDTO();
         dto.setId(child.getId());
         dto.setId(child.getId());
         dto.setNickname(child.getNickname());
         dto.setNickname(child.getNickname());
@@ -1238,6 +1238,135 @@ private WechatService wechatService;
         userMapper.updateById(user);
         userMapper.updateById(user);
     }
     }
 
 
+    // ==================== Family Member API (new) ====================
+
+    /**
+     * 获取家庭成员列表(孩子)
+     */
+    public List<FamilyMemberInfoDTO> getFamilyMemberren(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getFamilyId() == null) {
+            return new ArrayList<>();
+        }
+        List<FamilyMember> members = familyMemberMapper.selectList(
+                new LambdaQueryWrapper<FamilyMember>()
+                        .eq(FamilyMember::getFamilyId, user.getFamilyId()));
+        return members.stream().map(this::toFamilyMemberInfoDTO).collect(java.util.stream.Collectors.toList());
+    }
+
+    /**
+     * 创建家庭成员
+     */
+    public FamilyMemberInfoDTO createFamilyMember(Long userId, CreateFamilyMemberDTO dto) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        FamilyMember member = new FamilyMember();
+        member.setUserId(userId);
+        member.setFamilyId(user.getFamilyId());
+        member.setNickname(dto.getNickname());
+        member.setPhone(dto.getPhone());
+        member.setAvatar(dto.getAvatar());
+        member.setGender(dto.getGender());
+        try {
+            if (dto.getBirthday() != null && !dto.getBirthday().isEmpty()) {
+                java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
+                member.setBirthday(sdf.parse(dto.getBirthday()));
+            }
+        } catch (Exception e) {
+            // ignore parse error
+        }
+        if (dto.getAge() != null) {
+            member.setAge(dto.getAge());
+        }
+        member.setRelationshipType(dto.getRelationshipType());
+        member.setRoleOverride(dto.getRoleOverride());
+        member.setTotalPoints(0);
+        member.setCreatedAt(new Date());
+        member.setUpdatedAt(new Date());
+        familyMemberMapper.insert(member);
+        return toFamilyMemberInfoDTO(member);
+    }
+
+    /**
+     * 更新家庭成员信息
+     */
+    public boolean updateFamilyMember(Long userId, Long memberId, UpdateFamilyMemberDTO dto) {
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getFamilyId() == null) {
+            return false;
+        }
+        FamilyMember member = familyMemberMapper.selectById(memberId);
+        if (member == null || !member.getFamilyId().equals(user.getFamilyId())) {
+            return false;
+        }
+        if (dto.getNickname() != null) {
+            member.setNickname(dto.getNickname());
+        }
+        if (dto.getPhone() != null) {
+            member.setPhone(dto.getPhone());
+        }
+        if (dto.getAvatar() != null) {
+            member.setAvatar(dto.getAvatar());
+        }
+        if (dto.getGender() != null) {
+            member.setGender(dto.getGender());
+        }
+        try {
+            if (dto.getBirthday() != null && !dto.getBirthday().isEmpty()) {
+                java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
+                member.setBirthday(sdf.parse(dto.getBirthday()));
+            }
+        } catch (Exception e) {
+            // ignore parse error
+        }
+        if (dto.getAge() != null) {
+            member.setAge(dto.getAge());
+        }
+        if (dto.getShowToFamily() != null) {
+            member.setShowToFamily(dto.getShowToFamily());
+        }
+        member.setUpdatedAt(new Date());
+        return familyMemberMapper.updateById(member) > 0;
+    }
+
+    /**
+     * 家长切换到指定家庭成员身份
+     */
+    public Long switchToFamilyMember(Long userId, Long memberId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        // 验证家庭成员属于该用户所在家庭
+        FamilyMember member = familyMemberMapper.selectById(memberId);
+        if (member == null || !member.getFamilyId().equals(user.getFamilyId())) {
+            throw new RuntimeException("家庭成员不存在或不属于您的家庭");
+        }
+        // 切换用户role为child,记录原始角色以便切回
+        user.setRole("child");
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
+        return member.getId();
+    }
+
+    private FamilyMemberInfoDTO toFamilyMemberInfoDTO(FamilyMember member) {
+        FamilyMemberInfoDTO dto = new FamilyMemberInfoDTO();
+        dto.setId(member.getId());
+        dto.setNickname(member.getNickname());
+        dto.setAge(member.getAge());
+        dto.setBirthday(member.getBirthday());
+        dto.setGender(member.getGender());
+        dto.setPhone(member.getPhone());
+        dto.setAvatar(member.getAvatar());
+        dto.setRelationshipType(member.getRelationshipType());
+        dto.setRoleOverride(member.getRoleOverride());
+        dto.setShowToFamily(member.getShowToFamily());
+        dto.setTotalPoints(member.getTotalPoints());
+        return dto;
+    }
+
     private void populateTeacherFields(LoginResultDTO result, User user) {
     private void populateTeacherFields(LoginResultDTO result, User user) {
         result.setTeacherStatus(user.getTeacherStatus() != null ? user.getTeacherStatus() : "pending");
         result.setTeacherStatus(user.getTeacherStatus() != null ? user.getTeacherStatus() : "pending");
         result.setTeacherRejectReason(user.getTeacherRejectReason());
         result.setTeacherRejectReason(user.getTeacherRejectReason());

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/service/WechatAlertTemplateService.java

@@ -1,9 +1,9 @@
 package com.etotem.cfc.service;
 package com.etotem.cfc.service;
 
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import lombok.extern.slf4j.Slf4j;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.beans.factory.annotation.Value;
@@ -23,7 +23,7 @@ import java.util.Map;
 public class WechatAlertTemplateService {
 public class WechatAlertTemplateService {
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
@@ -73,7 +73,7 @@ public class WechatAlertTemplateService {
             }
             }
 
 
             // 1. 查找孩子信息
             // 1. 查找孩子信息
-            Child child = childMapper.selectById(childId);
+            FamilyMember child = familyMemberMapper.selectById(childId);
             if (child == null) {
             if (child == null) {
                 log.warn("孩子不存在, childId={}", childId);
                 log.warn("孩子不存在, childId={}", childId);
                 return;
                 return;

+ 10 - 10
cfc-backend/src/main/java/com/etotem/cfc/service/WishService.java

@@ -5,11 +5,11 @@ import com.etotem.cfc.service.api.WishServiceInterface;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.dto.CreateWishDTO;
 import com.etotem.cfc.dto.CreateWishDTO;
 import com.etotem.cfc.dto.WishDTO;
 import com.etotem.cfc.dto.WishDTO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.Wish;
 import com.etotem.cfc.entity.Wish;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.WishMapper;
 import com.etotem.cfc.mapper.WishMapper;
@@ -35,7 +35,7 @@ public class WishService {
     private WishMapper wishMapper;
     private WishMapper wishMapper;
 
 
     @Resource
     @Resource
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
@@ -50,7 +50,7 @@ public class WishService {
      */
      */
     @Transactional
     @Transactional
     public Long createByChild(Long childId, CreateWishDTO dto) {
     public Long createByChild(Long childId, CreateWishDTO dto) {
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child == null) {
         if (child == null) {
             throw new RuntimeException("孩子不存在");
             throw new RuntimeException("孩子不存在");
         }
         }
@@ -89,7 +89,7 @@ public class WishService {
             throw new RuntimeException("请设置所需积分");
             throw new RuntimeException("请设置所需积分");
         }
         }
 
 
-        Child child = childMapper.selectById(dto.getChildId());
+        FamilyMember child = familyMemberMapper.selectById(dto.getChildId());
         if (child == null || !child.getFamilyId().equals(parent.getFamilyId())) {
         if (child == null || !child.getFamilyId().equals(parent.getFamilyId())) {
             throw new RuntimeException("孩子不存在或不属于该家庭");
             throw new RuntimeException("孩子不存在或不属于该家庭");
         }
         }
@@ -199,7 +199,7 @@ public class WishService {
         }
         }
 
 
         // 检查积分是否充足
         // 检查积分是否充足
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         if (child.getTotalPoints() < wish.getPointsRequired()) {
         if (child.getTotalPoints() < wish.getPointsRequired()) {
             throw new RuntimeException("积分不足,无法申请兑换");
             throw new RuntimeException("积分不足,无法申请兑换");
         }
         }
@@ -238,7 +238,7 @@ public class WishService {
 
 
         if (approved) {
         if (approved) {
             // 再次检查积分是否充足(防止并发问题)
             // 再次检查积分是否充足(防止并发问题)
-            Child child = childMapper.selectById(wish.getChildId());
+            FamilyMember child = familyMemberMapper.selectById(wish.getChildId());
             if (child.getTotalPoints() < wish.getPointsRequired()) {
             if (child.getTotalPoints() < wish.getPointsRequired()) {
                 throw new RuntimeException("积分不足,无法兑换");
                 throw new RuntimeException("积分不足,无法兑换");
             }
             }
@@ -246,7 +246,7 @@ public class WishService {
             // 扣减积分
             // 扣减积分
             child.setTotalPoints(child.getTotalPoints() - wish.getPointsRequired());
             child.setTotalPoints(child.getTotalPoints() - wish.getPointsRequired());
             child.setUpdatedAt(new Date());
             child.setUpdatedAt(new Date());
-            childMapper.updateById(child);
+            familyMemberMapper.updateById(child);
 
 
             // 记录积分消耗
             // 记录积分消耗
             PointsLog pointsLog = new PointsLog();
             PointsLog pointsLog = new PointsLog();
@@ -364,7 +364,7 @@ public class WishService {
                 .notIn(Wish::getStatus, Arrays.asList("cancelled"))
                 .notIn(Wish::getStatus, Arrays.asList("cancelled"))
                 .orderByDesc(Wish::getCreatedAt));
                 .orderByDesc(Wish::getCreatedAt));
 
 
-        Child child = childMapper.selectById(childId);
+        FamilyMember child = familyMemberMapper.selectById(childId);
         int currentPoints = child != null ? child.getTotalPoints() : 0;
         int currentPoints = child != null ? child.getTotalPoints() : 0;
 
 
         return wishes.stream().map(w -> toDTO(w, currentPoints)).collect(Collectors.toList());
         return wishes.stream().map(w -> toDTO(w, currentPoints)).collect(Collectors.toList());
@@ -445,7 +445,7 @@ public class WishService {
 
 
         // 获取孩子昵称
         // 获取孩子昵称
         if (wish.getChildId() != null) {
         if (wish.getChildId() != null) {
-            Child child = childMapper.selectById(wish.getChildId());
+            FamilyMember child = familyMemberMapper.selectById(wish.getChildId());
             if (child != null) {
             if (child != null) {
                 dto.setChildName(child.getNickname());
                 dto.setChildName(child.getNickname());
             }
             }

+ 38 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/impl/DimensionScoreServiceImpl.java

@@ -10,7 +10,9 @@ import com.etotem.cfc.entity.HealthIndicator;
 import com.etotem.cfc.entity.HealthDiseaseRisk;
 import com.etotem.cfc.entity.HealthDiseaseRisk;
 import com.etotem.cfc.entity.HealthNormReference;
 import com.etotem.cfc.entity.HealthNormReference;
 import com.etotem.cfc.entity.HealthReport;
 import com.etotem.cfc.entity.HealthReport;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.mapper.HealthNormReferenceMapper;
 import com.etotem.cfc.mapper.HealthNormReferenceMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.service.DimensionScoreService;
 import com.etotem.cfc.service.DimensionScoreService;
 import com.etotem.cfc.service.HealthDimensionScoreService;
 import com.etotem.cfc.service.HealthDimensionScoreService;
 import com.etotem.cfc.service.HealthReportService;
 import com.etotem.cfc.service.HealthReportService;
@@ -44,6 +46,9 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
     @Resource
     @Resource
     private HealthNormReferenceMapper normMapper;
     private HealthNormReferenceMapper normMapper;
 
 
+    @Resource
+    private FamilyMemberMapper familyMemberMapper;
+
     @Resource
     @Resource
     private HealthReportService healthReportService;
     private HealthReportService healthReportService;
 
 
@@ -110,12 +115,45 @@ public class DimensionScoreServiceImpl implements DimensionScoreService {
             items.add(item);
             items.add(item);
         }
         }
 
 
+        // 如果没有任何个人数据,使用同龄同性平均值填充
+        if (validCount == 0) {
+            FamilyMember member = familyMemberMapper.selectById(memberId);
+            if (member != null && member.getBirthday() != null) {
+                long ageMonths = java.time.temporal.ChronoUnit.MONTHS.between(
+                    java.time.LocalDate.from(member.getBirthday().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDate()),
+                    java.time.LocalDate.now()
+                );
+                String gender = (member.getGender() != null && !member.getGender().isEmpty())
+                    ? member.getGender() : "all";
+                for (HealthDimensionVO.DimensionItem item : items) {
+                    String dimCode = item.getDimension();
+                    HealthNormReference norm = normMapper.selectNorm(dimCode, gender, (int) ageMonths);
+                    if (norm != null && norm.getPercentile50() != null) {
+                        item.setScore(BigDecimal.valueOf(norm.getPercentile50()));
+                        item.setLevel(calcLevel(norm.getPercentile50()));
+                        item.setPercentile(50);
+                        item.setDataSource("AVERAGE");
+                        totalScore = totalScore.add(BigDecimal.valueOf(norm.getPercentile50()));
+                    } else {
+                        // 无参照数据时使用默认值 65
+                        item.setScore(BigDecimal.valueOf(65));
+                        item.setLevel(calcLevel(65));
+                        item.setPercentile(null);
+                        item.setDataSource("AVERAGE");
+                        totalScore = totalScore.add(BigDecimal.valueOf(65));
+                    }
+                }
+                validCount = items.size();
+            }
+        }
+
         HealthDimensionVO vo = new HealthDimensionVO();
         HealthDimensionVO vo = new HealthDimensionVO();
         vo.setMemberId(memberId);
         vo.setMemberId(memberId);
         vo.setHealthIndex(validCount > 0
         vo.setHealthIndex(validCount > 0
             ? totalScore.divide(BigDecimal.valueOf(validCount), 2, RoundingMode.HALF_UP) : null);
             ? totalScore.divide(BigDecimal.valueOf(validCount), 2, RoundingMode.HALF_UP) : null);
         vo.setDimensions(items);
         vo.setDimensions(items);
         vo.setUpdateTime(new Date());
         vo.setUpdateTime(new Date());
+        vo.setIsAverage(validCount > 0 && items.stream().allMatch(i -> "AVERAGE".equals(i.getDataSource())));
         return vo;
         return vo;
     }
     }
 
 

+ 118 - 31
cfc-backend/src/main/resources/schema.sql

@@ -34,29 +34,32 @@ CREATE TABLE IF NOT EXISTS users (
     INDEX idx_phone (phone)
     INDEX idx_phone (phone)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
--- 孩子详情表
-CREATE TABLE IF NOT EXISTS children (
-    id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    user_id BIGINT NOT NULL,
-    family_id BIGINT NOT NULL,
-    nickname VARCHAR(50) NOT NULL,
-    age TINYINT NOT NULL,
-    birthday DATE,
-    gender ENUM('male','female'),
-    penalty_enabled TINYINT DEFAULT 1 COMMENT '是否开启扣分(0否1是)',
-    theme VARCHAR(32) DEFAULT 'default',
-    total_points INT DEFAULT 0,
-    system_points INT DEFAULT 0 COMMENT '系统积分(测评建档/平台发放)',
-    streak_days INT DEFAULT 0,
-    last_task_date DATE,
-    focus_max_daily TINYINT DEFAULT 3,
-    focus_remaining TINYINT DEFAULT 3,
-    focus_reset_date DATE,
-    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    INDEX idx_user_id (user_id),
-    INDEX idx_family_id (family_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+-- ============================================================
+-- 【已废除】children 表已合并到 family_members
+-- 保留此处仅作参考,勿执行
+-- ============================================================
+-- CREATE TABLE IF NOT EXISTS children (
+--     id BIGINT AUTO_INCREMENT PRIMARY KEY,
+--     user_id BIGINT NOT NULL,
+--     family_id BIGINT NOT NULL,
+--     nickname VARCHAR(50) NOT NULL,
+--     age TINYINT NOT NULL,
+--     birthday DATE,
+--     gender ENUM('male','female'),
+--     penalty_enabled TINYINT DEFAULT 1 COMMENT '是否开启扣分(0否1是)',
+--     theme VARCHAR(32) DEFAULT 'default',
+--     total_points INT DEFAULT 0,
+--     system_points INT DEFAULT 0 COMMENT '系统积分(测评建档/平台发放)',
+--     streak_days INT DEFAULT 0,
+--     last_task_date DATE,
+--     focus_max_daily TINYINT DEFAULT 3,
+--     focus_remaining TINYINT DEFAULT 3,
+--     focus_reset_date DATE,
+--     created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+--     updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+--     INDEX idx_user_id (user_id),
+--     INDEX idx_family_id (family_id)
+-- ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 
 -- 任务表
 -- 任务表
 CREATE TABLE IF NOT EXISTS tasks (
 CREATE TABLE IF NOT EXISTS tasks (
@@ -1638,11 +1641,21 @@ CREATE TABLE IF NOT EXISTS family_members (
     gender           VARCHAR(10)  COMMENT '性别: male/female',
     gender           VARCHAR(10)  COMMENT '性别: male/female',
     birthday         DATE         COMMENT '出生日期',
     birthday         DATE         COMMENT '出生日期',
     relationship_type VARCHAR(50) NOT NULL COMMENT '关系类型key',
     relationship_type VARCHAR(50) NOT NULL COMMENT '关系类型key',
-    capability_role  VARCHAR(20)  NOT NULL COMMENT '能力角色: parent/child(决定界面权限)',
-    show_to_family   INT DEFAULT 1 COMMENT '对家庭成员可见: 1=可见, 0=不可见',
-    total_points     INT DEFAULT 0 COMMENT '总积分',
-    created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
-    updated_at       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    role_override VARCHAR(20) DEFAULT 'auto' COMMENT '角色覆盖: auto/parent/child/elderly,空=auto',
+    show_to_family INT DEFAULT 1 COMMENT '对家庭成员可见: 1=可见, 0=不可见',
+    total_points INT DEFAULT 0 COMMENT '总积分',
+    -- 孩子专属字段(原children表合并)
+    id_card VARCHAR(18) DEFAULT NULL COMMENT '身份证号(孩子)',
+    penalty_enabled TINYINT DEFAULT 1 COMMENT '是否扣减积分(0否1是)',
+    theme VARCHAR(32) DEFAULT 'default' COMMENT '主题',
+    system_points INT DEFAULT 0 COMMENT '系统积分(干预/平台奖励)',
+    streak_days INT DEFAULT 0 COMMENT '连续打卡天数',
+    last_task_date DATE COMMENT '上次任务日期',
+    focus_max_daily TINYINT DEFAULT 3 COMMENT '每日专注上限',
+    focus_remaining TINYINT DEFAULT 3 COMMENT '今日剩余专注次数',
+    focus_reset_date DATE COMMENT '专注次数重置日期',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     INDEX idx_family_id (family_id),
     INDEX idx_family_id (family_id),
     INDEX idx_phone (phone)
     INDEX idx_phone (phone)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员表(手动添加)';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员表(手动添加)';
@@ -1652,14 +1665,14 @@ CREATE TABLE IF NOT EXISTS relationship_types (
     id               BIGINT AUTO_INCREMENT PRIMARY KEY,
     id               BIGINT AUTO_INCREMENT PRIMARY KEY,
     type_key         VARCHAR(50)  NOT NULL UNIQUE COMMENT '类型标识: spouse/parent/child/sibling',
     type_key         VARCHAR(50)  NOT NULL UNIQUE COMMENT '类型标识: spouse/parent/child/sibling',
     type_name        VARCHAR(100) NOT NULL COMMENT '类型显示名: 配偶/父母/子女/兄弟姐妹',
     type_name        VARCHAR(100) NOT NULL COMMENT '类型显示名: 配偶/父母/子女/兄弟姐妹',
-    capability_role  VARCHAR(20)  NOT NULL COMMENT '能力角色: parent/child',
+    default_role     VARCHAR(20)  DEFAULT NULL COMMENT '默认角色模板: parent/child',
     sort_order       INT DEFAULT 0 COMMENT '排序序号',
     sort_order       INT DEFAULT 0 COMMENT '排序序号',
     enabled          INT DEFAULT 1 COMMENT '是否启用: 1=启用, 0=禁用',
     enabled          INT DEFAULT 1 COMMENT '是否启用: 1=启用, 0=禁用',
     created_at       DATETIME DEFAULT CURRENT_TIMESTAMP
     created_at       DATETIME DEFAULT CURRENT_TIMESTAMP
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关系类型配置表';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关系类型配置表';
 
 
--- 默认关系类型数据
-INSERT IGNORE INTO relationship_types (type_key, type_name, capability_role, sort_order, enabled) VALUES
+-- 默认关系类型数据(default_role: parent/child 为该关系类型的默认角色模板)
+INSERT IGNORE INTO relationship_types (type_key, type_name, default_role, sort_order, enabled) VALUES
 ('spouse', '配偶', 'parent', 1, 1),
 ('spouse', '配偶', 'parent', 1, 1),
 ('parent', '父母', 'parent', 2, 1),
 ('parent', '父母', 'parent', 2, 1),
 ('child', '子女', 'child', 3, 1),
 ('child', '子女', 'child', 3, 1),
@@ -2013,3 +2026,77 @@ INSERT IGNORE INTO indicator_definitions (domain, category, code, name, data_typ
 ALTER TABLE activities ADD COLUMN require_registration TINYINT DEFAULT 1 COMMENT '是否需要报名: 1=需要, 0=直接参与';
 ALTER TABLE activities ADD COLUMN require_registration TINYINT DEFAULT 1 COMMENT '是否需要报名: 1=需要, 0=直接参与';
 ALTER TABLE activities ADD COLUMN require_checkin_review TINYINT DEFAULT 1 COMMENT '打卡是否需要审核: 1=需要, 0=直接通过';
 ALTER TABLE activities ADD COLUMN require_checkin_review TINYINT DEFAULT 1 COMMENT '打卡是否需要审核: 1=需要, 0=直接通过';
 ALTER TABLE activities ADD COLUMN energy_penalty_unregistered INT DEFAULT 5 COMMENT '未报名打卡的行能量扣减值';
 ALTER TABLE activities ADD COLUMN energy_penalty_unregistered INT DEFAULT 5 COMMENT '未报名打卡的行能量扣减值';
+
+-- ============================================================
+-- 新增:系统配置表(角色年龄阈值等)
+-- ============================================================
+CREATE TABLE IF NOT EXISTS system_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    config_key VARCHAR(100) NOT NULL UNIQUE COMMENT '配置键',
+    config_value VARCHAR(500) COMMENT '配置值',
+    description VARCHAR(255) COMMENT '说明',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
+
+-- ============================================================
+-- 数据迁移: children → family_members + roleOverride 变更
+-- 执行时机: Phase 9(所有代码变更完成后、上线前)
+-- ============================================================
+
+-- 1. family_members.capability_role → role_override (已在 DDL 中 ALTER)
+-- 2. relationship_types.capability_role → default_role (已在 DDL 中 ALTER)
+
+-- ============================================================
+-- 数据迁移: children → family_members + roleOverride 变更
+-- 执行时机: Phase 9(所有代码变更完成后、上线前)
+-- ============================================================
+
+-- 1. family_members 已包含所有必要字段(role_override, 孩子专属字段已在 DDL 中)
+-- relationship_types.capability_role → default_role 已在 DDL 中 ALTER
+
+-- 2. 迁移 children 数据到 family_members(匹配更新:family_members 已存在该成员记录时更新)
+UPDATE family_members fm
+INNER JOIN children c ON fm.family_id = c.family_id AND fm.nickname = c.nickname AND fm.user_id = c.user_id
+SET
+    fm.id_card = c.id_card,
+    fm.penalty_enabled = c.penalty_enabled,
+    fm.theme = c.theme,
+    fm.system_points = c.system_points,
+    fm.streak_days = c.streak_days,
+    fm.last_task_date = c.last_task_date,
+    fm.focus_max_daily = c.focus_max_daily,
+    fm.focus_remaining = c.focus_remaining,
+    fm.focus_reset_date = c.focus_reset_date,
+    fm.total_points = c.total_points,
+    fm.show_to_family = c.show_to_family,
+    fm.birthday = c.birthday,
+    fm.gender = c.gender,
+    fm.phone = c.phone,
+    fm.role_override = 'child';
+
+-- 3. 迁移 children 数据到 family_members(不匹配则插入新记录)
+INSERT INTO family_members (family_id, user_id, nickname, birthday, gender, phone,
+  id_card, penalty_enabled, theme, system_points, streak_days, last_task_date,
+  focus_max_daily, focus_remaining, focus_reset_date, show_to_family, total_points,
+  role_override, created_at, updated_at)
+SELECT family_id, user_id, nickname, birthday, gender, phone,
+  id_card, penalty_enabled, theme, system_points, streak_days, last_task_date,
+  focus_max_daily, focus_remaining, focus_reset_date, show_to_family, total_points,
+  'child', created_at, updated_at
+FROM children
+WHERE NOT EXISTS (
+    SELECT 1 FROM family_members fm2
+    WHERE fm2.family_id = children.family_id
+      AND fm2.nickname = children.nickname
+      AND fm2.user_id = children.user_id
+);
+
+-- 4. 插入 system_config 初始数据
+INSERT INTO system_config (config_key, config_value, description) VALUES
+  ('child_age_threshold', '18', '未成年年龄分界线(岁)'),
+  ('elderly_age_threshold', '65', '老年年龄分界线(岁)')
+ON DUPLICATE KEY UPDATE config_value = VALUES(config_value);
+
+-- 5. 删除 children 表(最后执行,确认迁移完成后再执行此步)
+-- DROP TABLE IF EXISTS children;

+ 17 - 17
cfc-backend/src/test/java/com/etotem/cfc/controller/GuideFamilyTaskControllerTest.java

@@ -5,10 +5,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.controller.guide.GuideFamilyTaskController;
 import com.etotem.cfc.controller.guide.GuideFamilyTaskController;
 import com.etotem.cfc.dto.TaskReviewDTO;
 import com.etotem.cfc.dto.TaskReviewDTO;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.Task;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.TaskMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.TaskService;
 import com.etotem.cfc.service.TaskService;
@@ -34,7 +34,7 @@ public class GuideFamilyTaskControllerTest {
     private TaskService taskService;
     private TaskService taskService;
 
 
     @MockBean
     @MockBean
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @MockBean
     @MockBean
     private UserMapper userMapper;
     private UserMapper userMapper;
@@ -50,14 +50,14 @@ public class GuideFamilyTaskControllerTest {
         return user;
         return user;
     }
     }
 
 
-    private Child mockChild(Long id, Long familyId, String nickname) {
-        Child child = new Child();
-        child.setId(id);
-        child.setFamilyId(familyId);
-        child.setNickname(nickname);
-        child.setTotalPoints(100);
-        child.setStreakDays(5);
-        return child;
+    private FamilyMember mockFamilyMember(Long id, Long familyId, String nickname) {
+        FamilyMember member = new FamilyMember();
+        member.setId(id);
+        member.setFamilyId(familyId);
+        member.setNickname(nickname);
+        member.setTotalPoints(100);
+        member.setStreakDays(5);
+        return member;
     }
     }
 
 
     private Task mockTask(Long id, Long childId, Long familyId, String status) {
     private Task mockTask(Long id, Long childId, Long familyId, String status) {
@@ -94,7 +94,7 @@ public class GuideFamilyTaskControllerTest {
         params.put("page", 1);
         params.put("page", 1);
         params.put("size", 10);
         params.put("size", 10);
         for (String role : NON_TEACHER_ROLES) {
         for (String role : NON_TEACHER_ROLES) {
-            Result<?> result = controller.getChildTasks(100L, 20L, role, params);
+            Result<?> result = controller.getFamilyMemberTasks(100L, 20L, role, params);
             assertRoleDenied(result);
             assertRoleDenied(result);
         }
         }
     }
     }
@@ -158,8 +158,8 @@ public class GuideFamilyTaskControllerTest {
         User parent = mockUser(10L, "parent", "家长");
         User parent = mockUser(10L, "parent", "家长");
         when(userMapper.selectList(any(QueryWrapper.class))).thenReturn(Collections.singletonList(parent));
         when(userMapper.selectList(any(QueryWrapper.class))).thenReturn(Collections.singletonList(parent));
 
 
-        Child child = mockChild(20L, 100L, "孩子");
-        when(childMapper.selectList(any(QueryWrapper.class))).thenReturn(Collections.singletonList(child));
+        FamilyMember member = mockFamilyMember(20L, 100L, "孩子");
+        when(familyMemberMapper.selectList(any(QueryWrapper.class))).thenReturn(Collections.singletonList(member));
         when(taskService.getPendingReviewTasksForChild(20L)).thenReturn(new ArrayList<>());
         when(taskService.getPendingReviewTasksForChild(20L)).thenReturn(new ArrayList<>());
 
 
         @SuppressWarnings("unchecked")
         @SuppressWarnings("unchecked")
@@ -172,8 +172,8 @@ public class GuideFamilyTaskControllerTest {
     public void getFamilyTaskOverview_WithChildren() {
     public void getFamilyTaskOverview_WithChildren() {
         Long guideId = 1L;
         Long guideId = 1L;
         Long familyId = 100L;
         Long familyId = 100L;
-        Child child = mockChild(20L, familyId, "孩子A");
-        when(childMapper.selectList(any(QueryWrapper.class))).thenReturn(Collections.singletonList(child));
+        FamilyMember member = mockFamilyMember(20L, familyId, "孩子A");
+        when(familyMemberMapper.selectList(any(QueryWrapper.class))).thenReturn(Collections.singletonList(member));
         when(taskService.getTodayTasks(20L)).thenReturn(new ArrayList<>());
         when(taskService.getTodayTasks(20L)).thenReturn(new ArrayList<>());
         when(taskService.getPendingReviewTasksForChild(20L)).thenReturn(new ArrayList<>());
         when(taskService.getPendingReviewTasksForChild(20L)).thenReturn(new ArrayList<>());
         when(taskService.getAllTasksByChild(20L)).thenReturn(new ArrayList<>());
         when(taskService.getAllTasksByChild(20L)).thenReturn(new ArrayList<>());
@@ -210,7 +210,7 @@ public class GuideFamilyTaskControllerTest {
         params.put("page", 1);
         params.put("page", 1);
         params.put("size", 10);
         params.put("size", 10);
 
 
-        Result<Page<Task>> result = controller.getChildTasks(familyId, childId, "teacher", params);
+        Result<Page<Task>> result = controller.getFamilyMemberTasks(familyId, childId, "teacher", params);
         Page<Task> page = result.getData();
         Page<Task> page = result.getData();
         assertEquals(2, page.getTotal());
         assertEquals(2, page.getTotal());
         assertEquals(2, page.getRecords().size());
         assertEquals(2, page.getRecords().size());

+ 9 - 11
cfc-backend/src/test/java/com/etotem/cfc/controller/GuideRolePermissionTest.java

@@ -47,7 +47,7 @@ public class GuideRolePermissionTest {
     private TaskService taskService;
     private TaskService taskService;
 
 
     @MockBean
     @MockBean
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @MockBean
     @MockBean
     private UserMapper userMapper;
     private UserMapper userMapper;
@@ -99,14 +99,12 @@ public class GuideRolePermissionTest {
         return user;
         return user;
     }
     }
 
 
-    private Child mockChild(Long id, Long familyId) {
-        Child child = new Child();
-        child.setId(id);
-        child.setFamilyId(familyId);
-        child.setNickname("孩子" + id);
-        child.setTotalPoints(100);
-        child.setStreakDays(3);
-        return child;
+    private FamilyMember mockFamilyMember(Long id, Long familyId) {
+        FamilyMember member = new FamilyMember();
+        member.setId(id);
+        member.setFamilyId(familyId);
+        member.setNickname("孩子" + id);
+        return member;
     }
     }
 
 
     private Task mockTask(Long id, Long childId, Long familyId, String status) {
     private Task mockTask(Long id, Long childId, Long familyId, String status) {
@@ -163,8 +161,8 @@ public class GuideRolePermissionTest {
 
 
     @Test
     @Test
     public void familyTaskOverview_TeacherAllowed() {
     public void familyTaskOverview_TeacherAllowed() {
-        Child child = mockChild(20L, 100L);
-        when(childMapper.selectList(any(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class)))
+        FamilyMember child = mockFamilyMember(20L, 100L);
+        when(familyMemberMapper.selectList(any(com.baomidou.mybatisplus.core.conditions.query.QueryWrapper.class)))
             .thenReturn(Collections.singletonList(child));
             .thenReturn(Collections.singletonList(child));
         when(taskService.getTodayTasks(20L)).thenReturn(new ArrayList<>());
         when(taskService.getTodayTasks(20L)).thenReturn(new ArrayList<>());
         when(taskService.getPendingReviewTasksForChild(20L)).thenReturn(new ArrayList<>());
         when(taskService.getPendingReviewTasksForChild(20L)).thenReturn(new ArrayList<>());

+ 16 - 16
cfc-backend/src/test/java/com/etotem/cfc/controller/PointsControllerTest.java

@@ -3,9 +3,9 @@ package com.etotem.cfc.controller;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.common.Result;
 import com.etotem.cfc.controller.family.PointsController;
 import com.etotem.cfc.controller.family.PointsController;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.entity.PointsLog;
 import com.etotem.cfc.entity.PointsLog;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.mapper.PointsLogMapper;
 import com.etotem.cfc.service.PointsService;
 import com.etotem.cfc.service.PointsService;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.api.Test;
@@ -33,7 +33,7 @@ public class PointsControllerTest {
     private PointsService pointsService;
     private PointsService pointsService;
 
 
     @MockBean
     @MockBean
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     @MockBean
     @MockBean
     private PointsLogMapper pointsLogMapper;
     private PointsLogMapper pointsLogMapper;
@@ -60,7 +60,7 @@ public class PointsControllerTest {
     }
     }
 
 
     @Test
     @Test
-    public void getBalance_NullChildId_ReturnsNull() {
+    public void getBalance_NullFamilyMemberId_ReturnsNull() {
         Map<String, Object> params = new HashMap<>();
         Map<String, Object> params = new HashMap<>();
         // childId not provided
         // childId not provided
 
 
@@ -222,7 +222,7 @@ public class PointsControllerTest {
     }
     }
 
 
     @Test
     @Test
-    public void getSystemBalance_NullChildId_ReturnsError() {
+    public void getSystemBalance_NullFamilyMemberId_ReturnsError() {
         Map<String, Object> params = new HashMap<>();
         Map<String, Object> params = new HashMap<>();
         // childId not provided
         // childId not provided
 
 
@@ -236,11 +236,11 @@ public class PointsControllerTest {
 
 
     @Test
     @Test
     public void pointsService_AdjustPoints_Success() {
     public void pointsService_AdjustPoints_Success() {
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setId(10L);
         child.setId(10L);
         child.setTotalPoints(100);
         child.setTotalPoints(100);
 
 
-        when(childMapper.selectById(10L)).thenReturn(child);
+        when(familyMemberMapper.selectById(10L)).thenReturn(child);
 
 
         // Simulate adjustPoints behavior
         // Simulate adjustPoints behavior
         int newBalance = 100 + 50; // +50 adjustment
         int newBalance = 100 + 50; // +50 adjustment
@@ -253,11 +253,11 @@ public class PointsControllerTest {
 
 
     @Test
     @Test
     public void pointsService_AdjustPoints_NegativeResult() {
     public void pointsService_AdjustPoints_NegativeResult() {
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setId(10L);
         child.setId(10L);
         child.setTotalPoints(30);
         child.setTotalPoints(30);
 
 
-        when(childMapper.selectById(10L)).thenReturn(child);
+        when(familyMemberMapper.selectById(10L)).thenReturn(child);
 
 
         // If adjustment is -50, new balance would be -20
         // If adjustment is -50, new balance would be -20
         int newBalance = 30 + (-50);
         int newBalance = 30 + (-50);
@@ -266,12 +266,12 @@ public class PointsControllerTest {
 
 
     @Test
     @Test
     public void pointsService_DeductSystemPoints_InsufficientBalance() {
     public void pointsService_DeductSystemPoints_InsufficientBalance() {
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setId(10L);
         child.setId(10L);
         child.setSystemPoints(10);
         child.setSystemPoints(10);
         child.setTotalPoints(10);
         child.setTotalPoints(10);
 
 
-        when(childMapper.selectById(10L)).thenReturn(child);
+        when(familyMemberMapper.selectById(10L)).thenReturn(child);
 
 
         // deduct 20 when only 10 available → should return -1
         // deduct 20 when only 10 available → should return -1
         int newBalance = 10 - 20;
         int newBalance = 10 - 20;
@@ -280,12 +280,12 @@ public class PointsControllerTest {
 
 
     @Test
     @Test
     public void pointsService_DeductSystemPoints_ExactBalance() {
     public void pointsService_DeductSystemPoints_ExactBalance() {
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setId(10L);
         child.setId(10L);
         child.setSystemPoints(50);
         child.setSystemPoints(50);
         child.setTotalPoints(50);
         child.setTotalPoints(50);
 
 
-        when(childMapper.selectById(10L)).thenReturn(child);
+        when(familyMemberMapper.selectById(10L)).thenReturn(child);
 
 
         int newBalance = 50 - 50;
         int newBalance = 50 - 50;
         assertEquals(0, newBalance);
         assertEquals(0, newBalance);
@@ -293,12 +293,12 @@ public class PointsControllerTest {
 
 
     @Test
     @Test
     public void pointsService_AwardSystemPoints_Success() {
     public void pointsService_AwardSystemPoints_Success() {
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setId(10L);
         child.setId(10L);
         child.setSystemPoints(100);
         child.setSystemPoints(100);
         child.setTotalPoints(200);
         child.setTotalPoints(200);
 
 
-        when(childMapper.selectById(10L)).thenReturn(child);
+        when(familyMemberMapper.selectById(10L)).thenReturn(child);
 
 
         int amount = 30;
         int amount = 30;
         int newSystemPoints = 100 + amount; // 130
         int newSystemPoints = 100 + amount; // 130
@@ -313,7 +313,7 @@ public class PointsControllerTest {
     private PointsLog createLog(Long id, Long childId, Integer amount, String type, String description) {
     private PointsLog createLog(Long id, Long childId, Integer amount, String type, String description) {
         PointsLog log = new PointsLog();
         PointsLog log = new PointsLog();
         log.setId(id);
         log.setId(id);
-        log.setChildId(childId);
+        log.setFamilyMemberId(childId);
         log.setAmount(amount);
         log.setAmount(amount);
         log.setType(type);
         log.setType(type);
         log.setDescription(description);
         log.setDescription(description);

+ 1 - 1
cfc-backend/src/test/java/com/etotem/cfc/controller/StatsControllerTest.java

@@ -29,7 +29,7 @@ public class StatsControllerTest {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @MockBean
     @MockBean
-    private com.etotem.cfc.mapper.ChildMapper childMapper;
+    private com.etotem.cfc.mapper.FamilyMemberMapper familyMemberMapper;
 
 
     private User mockParentUser(Long id, Long familyId) {
     private User mockParentUser(Long id, Long familyId) {
         User user = new User();
         User user = new User();

+ 5 - 5
cfc-backend/src/test/java/com/etotem/cfc/integration/DailyCheckinStreakFlowTest.java

@@ -5,11 +5,11 @@ import com.etotem.cfc.controller.DailyCheckinController;
 import com.etotem.cfc.controller.StreakController;
 import com.etotem.cfc.controller.StreakController;
 import com.etotem.cfc.entity.DailyHealthCheckin;
 import com.etotem.cfc.entity.DailyHealthCheckin;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
-import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.FamilyMember;
 import com.etotem.cfc.service.DailyCheckinService;
 import com.etotem.cfc.service.DailyCheckinService;
 import com.etotem.cfc.service.StreakService;
 import com.etotem.cfc.service.StreakService;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
-import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.FamilyMemberMapper;
 import org.junit.jupiter.api.*;
 import org.junit.jupiter.api.*;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.context.SpringBootTest;
 import org.springframework.boot.test.mock.mockito.MockBean;
 import org.springframework.boot.test.mock.mockito.MockBean;
@@ -61,7 +61,7 @@ public class DailyCheckinStreakFlowTest {
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
     @MockBean
     @MockBean
-    private ChildMapper childMapper;
+    private FamilyMemberMapper familyMemberMapper;
 
 
     // ========== 测试数据 ==========
     // ========== 测试数据 ==========
 
 
@@ -154,7 +154,7 @@ public class DailyCheckinStreakFlowTest {
         user.setId(USER_ID);
         user.setId(USER_ID);
         user.setFamilyId(FAMILY_ID);
         user.setFamilyId(FAMILY_ID);
 
 
-        Child child = new Child();
+        FamilyMember child = new FamilyMember();
         child.setId(CHILD_ID);
         child.setId(CHILD_ID);
         child.setFamilyId(FAMILY_ID);
         child.setFamilyId(FAMILY_ID);
 
 
@@ -167,7 +167,7 @@ public class DailyCheckinStreakFlowTest {
         mockProgress.put("pendingReward", true); // 有待领取奖励
         mockProgress.put("pendingReward", true); // 有待领取奖励
 
 
         when(userMapper.selectById(USER_ID)).thenReturn(user);
         when(userMapper.selectById(USER_ID)).thenReturn(user);
-        when(childMapper.selectById(CHILD_ID)).thenReturn(child);
+        when(familyMemberMapper.selectById(CHILD_ID)).thenReturn(child);
         when(streakService.getStreakProgress(CHILD_ID)).thenReturn(mockProgress);
         when(streakService.getStreakProgress(CHILD_ID)).thenReturn(mockProgress);
 
 
         // === When:请求签到进度 ===
         // === When:请求签到进度 ===

+ 2 - 2
cfc-frontend/components/DimensionActivities.vue

@@ -8,7 +8,7 @@
       <text class="empty-tip">{{ isLoggedIn ? '暂无相关活动' : '登录后查看更多活动' }}</text>
       <text class="empty-tip">{{ isLoggedIn ? '暂无相关活动' : '登录后查看更多活动' }}</text>
     </view>
     </view>
     <view class="activity-list" v-if="displayActivities && displayActivities.length > 0">
     <view class="activity-list" v-if="displayActivities && displayActivities.length > 0">
-      <view class="activity-card" v-for="act in displayActivities" :key="act.id" @click="$emit('activityClick', act)">
+      <view class="activity-card" v-for="act in displayActivities" :key="act.id" @click.stop="$emit('activityClick', act)">
         <image class="activity-cover" :src="act.coverImage || '/static/default-activity.png'" mode="aspectFill" />
         <image class="activity-cover" :src="act.coverImage || '/static/default-activity.png'" mode="aspectFill" />
         <view class="activity-info">
         <view class="activity-info">
           <text class="activity-name line-clamp-1">{{ act.title }}</text>
           <text class="activity-name line-clamp-1">{{ act.title }}</text>
@@ -41,7 +41,7 @@ export default {
   },
   },
   computed: {
   computed: {
     displayActivities: function() {
     displayActivities: function() {
-      return this.activities
+      return (this.activities || []).slice(0, 3)
     }
     }
   },
   },
   methods: {
   methods: {

+ 4 - 6
cfc-frontend/pages.json

@@ -80,12 +80,6 @@
         "navigationBarTitleText": "发现"
         "navigationBarTitleText": "发现"
       }
       }
     },
     },
-    {
-      "path": "pages/shop/index",
-      "style": {
-        "navigationBarTitleText": "商城"
-      }
-    },
     {
     {
       "path": "pages/action/index",
       "path": "pages/action/index",
       "style": {
       "style": {
@@ -398,6 +392,10 @@
     {
     {
       "root": "pages/shop",
       "root": "pages/shop",
       "pages": [
       "pages": [
+        {
+          "path": "index/index",
+          "style": { "navigationBarTitleText": "商品首页" }
+        },
         {
         {
           "path": "order-list/order-list",
           "path": "order-list/order-list",
           "style": { "navigationBarTitleText": "我的订单" }
           "style": { "navigationBarTitleText": "我的订单" }

+ 2 - 2
cfc-frontend/pages/article-center/index.vue

@@ -7,7 +7,7 @@
           v-for="tab in tabList"
           v-for="tab in tabList"
           :key="tab.code"
           :key="tab.code"
           :class="['tab-item', currentTab === tab.code ? 'active' : '']"
           :class="['tab-item', currentTab === tab.code ? 'active' : '']"
-          :style="currentTab === tab.code ? { color: tab.color, borderBottomColor: tab.color } : {}"
+          :style="currentTab === tab.code ? 'color:' + tab.color + ';border-bottom-color:' + tab.color : ''"
           @click="onTabChange(tab.code)"
           @click="onTabChange(tab.code)"
         >
         >
           {{ tab.name }}
           {{ tab.name }}
@@ -67,7 +67,7 @@
                 v-for="dim in parseDimensions(item.relatedDimensions)"
                 v-for="dim in parseDimensions(item.relatedDimensions)"
                 :key="dim.code"
                 :key="dim.code"
                 class="dim-dot"
                 class="dim-dot"
-                :style="{ background: dim.color }"
+                :style="'background:' + dim.color"
               ></view>
               ></view>
             </view>
             </view>
           </view>
           </view>

+ 105 - 9
cfc-frontend/pages/body/index.vue

@@ -27,6 +27,18 @@
       :sandboxData="sandboxData"
       :sandboxData="sandboxData"
       :dualDimension="dualDimension" />
       :dualDimension="dualDimension" />
 
 
+    <!-- ===== 重要关系维护 ===== -->
+    <FamilyRelationshipSection
+      :contactList="contactList"
+      :healthAlerts="healthAlerts"
+      :milestones="milestones"
+      :isLoggedIn="isLoggedIn"
+      @contactClick="onContactClick"
+      @refresh="loadContacts"
+      @import="onShowImport"
+      @interactionLog="goInteractionLog"
+      @questionnaire="goRelationshipQuestionnaire" />
+
     <!-- 关系图谱(登录后可见) -->
     <!-- 关系图谱(登录后可见) -->
     <FamilyRelationGraph
     <FamilyRelationGraph
       v-if="isLoggedIn && familyMembersVisible.length > 0"
       v-if="isLoggedIn && familyMembersVisible.length > 0"
@@ -47,11 +59,26 @@
       @childChanged="onChildChanged"
       @childChanged="onChildChanged"
       @scrollTo="scrollToSection" />
       @scrollTo="scrollToSection" />
 
 
-    <!-- 七维健康全景 -->
-    <HealthDimensionsSection
-      v-if="isLoggedIn"
-      :healthData="dimensionData"
-      :activeChildId="activeChildId" />
+    <!-- 雷达图 -->
+    <view class="section" v-if="dimensionData">
+      <view class="section-header" v-if="dimensionData.isAverage">
+        <text class="section-title">七维健康图</text>
+        <text class="avg-badge">同龄同性平均值 · 上传报告后显示个人数据</text>
+      </view>
+      <view class="section-header" v-else>
+        <text class="section-title">七维健康图</text>
+      </view>
+      <RadarChart
+        v-if="dimensionData && dimensionData.dimensions"
+        :dimensions="dimensionData.dimensions.map(function(d) { return { label: d.label, key: d.dimension } })"
+        :scores="dimensionData.dimensions.map(function(d) { return d.score })"
+        fillColor="#FF8C42"
+        gridColor="#FED7AA"
+        labelColor="#9A3412"
+        :width="580"
+        :height="580"
+        @dimensionClick="goDimensionDetail" />
+    </view>
 
 
     <!-- 功能入口 -->
     <!-- 功能入口 -->
     <view class="func-section" v-if="sectionVisible('func_entries')">
     <view class="func-section" v-if="sectionVisible('func_entries')">
@@ -197,9 +224,10 @@ import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionArticles from '../../components/DimensionArticles.vue'
 import DimensionArticles from '../../components/DimensionArticles.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
-import HealthDimensionsSection from '../../components/HealthDimensionsSection.vue'
+import RadarChart from '../../components/RadarChart.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
-import { getVisibleSections, getEnergyOverview, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getVisibleFamilyMembers, getDimensionOverview, getFeaturedArticles } from '../../utils/api.js'
+import FamilyRelationshipSection from '../../components/FamilyRelationshipSection.vue'
+import { getVisibleSections, getEnergyOverview, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getVisibleFamilyMembers, getDimensionOverview, getFeaturedArticles, getContactList, getHealthAlerts, getMilestones } from '../../utils/api.js'
 import config from '../../config.js'
 import config from '../../config.js'
 
 
 var BASE_URL = config.api('')
 var BASE_URL = config.api('')
@@ -230,7 +258,7 @@ const healthRequest = function(url, data) {
 }
 }
 
 
 export default {
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, HealthTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, HealthDimensionsSection, AIFloatingAvatar },
+  components: { TabTransition, PageBanner, LoginGuideCard, HealthTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, RadarChart, AIFloatingAvatar, FamilyRelationshipSection },
   data() {
   data() {
     return {
     return {
       isLoggedIn: false,
       isLoggedIn: false,
@@ -258,6 +286,9 @@ export default {
         { icon: '\u{1F9D8}', label: '冥想', needLogin: true, page: '/pages/health/meditation-index' },
         { icon: '\u{1F9D8}', label: '冥想', needLogin: true, page: '/pages/health/meditation-index' },
         { icon: '\u{1F4CA}', label: '舌诊', needLogin: true, page: '/pages/health/tongue-index' }
         { icon: '\u{1F4CA}', label: '舌诊', needLogin: true, page: '/pages/health/tongue-index' }
       ],
       ],
+      contactList: [],
+      healthAlerts: [],
+      milestones: [],
       healthTips: [
       healthTips: [
         { id: 1, title: '儿童每日运动指南', summary: '不同年龄段儿童每天需要多少运动量?科学运动助力健康成长。' },
         { id: 1, title: '儿童每日运动指南', summary: '不同年龄段儿童每天需要多少运动量?科学运动助力健康成长。' },
         { id: 2, title: '五行饮食搭配法则', summary: '根据五行理论合理搭配膳食,均衡营养从每一餐开始。' },
         { id: 2, title: '五行饮食搭配法则', summary: '根据五行理论合理搭配膳食,均衡营养从每一餐开始。' },
@@ -307,6 +338,7 @@ export default {
     if (this.isLoggedIn) {
     if (this.isLoggedIn) {
       this.loadChildren()
       this.loadChildren()
       this.loadFamilyMembersVisible()
       this.loadFamilyMembersVisible()
+      this.loadRelationshipHealth()
     }
     }
     // 游客也能浏览活动和商品
     // 游客也能浏览活动和商品
     this.loadDimensionActivities()
     this.loadDimensionActivities()
@@ -357,6 +389,7 @@ export default {
           if (self.activeChildId) {
           if (self.activeChildId) {
             self.loadDashboardData()
             self.loadDashboardData()
           }
           }
+          self.loadContacts()
         }
         }
       }).catch(function(e) {
       }).catch(function(e) {
         console.log('获取孩子列表失败', e)
         console.log('获取孩子列表失败', e)
@@ -449,7 +482,7 @@ export default {
     },
     },
     loadFeaturedArticles: function() {
     loadFeaturedArticles: function() {
       var self = this
       var self = this
-      getFeaturedArticles({ size: 5, dimensionCode: 'body' }).then(function(res) {
+      getFeaturedArticles({ size: 5 }).then(function(res) {
         if (res.code === 200 && res.data) {
         if (res.code === 200 && res.data) {
           var gradientColors = [
           var gradientColors = [
             'linear-gradient(135deg, #FF8C42, #FFB074)',
             'linear-gradient(135deg, #FF8C42, #FFB074)',
@@ -533,6 +566,14 @@ export default {
         })
         })
         return
         return
       }
       }
+      if (item.page === 'health-dimensions') {
+        if (!this.activeChildId) {
+          uni.showToast({ title: '请先选择孩子', icon: 'none' })
+          return
+        }
+        uni.navigateTo({ url: '/pages/body-detail/health-dimensions?childId=' + this.activeChildId })
+        return
+      }
       if (item.page) uni.navigateTo({ url: item.page })
       if (item.page) uni.navigateTo({ url: item.page })
     },
     },
     goToHealthReport: function() {
     goToHealthReport: function() {
@@ -542,6 +583,9 @@ export default {
       }
       }
       uni.navigateTo({ url: '/pages/body-detail/health-report?childId=' + this.activeChildId })
       uni.navigateTo({ url: '/pages/body-detail/health-report?childId=' + this.activeChildId })
     },
     },
+    goDimensionDetail: function(dimKey) {
+      uni.navigateTo({ url: '/pages/body/dimension-detail?dimension=' + dimKey + '&childId=' + this.activeChildId })
+    },
     goToCheckin: function() {
     goToCheckin: function() {
       if (!this.activeChildId) {
       if (!this.activeChildId) {
         uni.showToast({ title: '请先选择孩子', icon: 'none' })
         uni.showToast({ title: '请先选择孩子', icon: 'none' })
@@ -561,6 +605,50 @@ export default {
       var id = this.activeChildId || uni.getStorageSync('currentChildId') || ''
       var id = this.activeChildId || uni.getStorageSync('currentChildId') || ''
       uni.navigateTo({ url: '/pages/health/report-upload?childId=' + id + '&from=body' })
       uni.navigateTo({ url: '/pages/health/report-upload?childId=' + id + '&from=body' })
     },
     },
+    // ===== 重要关系 =====
+    loadContacts: function() {
+      var self = this
+      getContactList({}).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.contactList = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function(e) {
+        console.log('获取联系人失败', e)
+      })
+    },
+    loadRelationshipHealth: function() {
+      var self = this
+      getHealthAlerts({}).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.healthAlerts = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function() {
+        self.healthAlerts = []
+      })
+      getMilestones({}, 30).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.milestones = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function() {
+        self.milestones = []
+      })
+    },
+    onContactClick: function(item) {
+      uni.navigateTo({ url: '/pages/body-detail/contact-detail?id=' + item.id })
+    },
+    onShowImport: function() {
+      uni.showToast({ title: '导入通讯录', icon: 'none' })
+    },
+    goInteractionLog: function(memberId) {
+      if (memberId) {
+        uni.navigateTo({ url: '/pages/body-detail/interaction-log?memberId=' + memberId })
+      } else {
+        uni.navigateTo({ url: '/pages/body-detail/interaction-log' })
+      }
+    },
+    goRelationshipQuestionnaire: function() {
+      uni.navigateTo({ url: '/pages/body-detail/relationship-questionnaire' })
+    },
     formatDate: function(dateStr) {
     formatDate: function(dateStr) {
       if (!dateStr) return '--'
       if (!dateStr) return '--'
       try {
       try {
@@ -638,6 +726,14 @@ export default {
   font-weight: bold;
   font-weight: bold;
   color: #333;
   color: #333;
 }
 }
+.avg-badge {
+  font-size: 20rpx;
+  color: #F97316;
+  background: #FFF7ED;
+  padding: 4rpx 12rpx;
+  border-radius: 20rpx;
+  margin-left: 12rpx;
+}
 .section-more {
 .section-more {
   font-size: 24rpx;
   font-size: 24rpx;
   color: #5B9BD5;
   color: #5B9BD5;

+ 32 - 1
cfc-frontend/pages/discover-detail/product-detail/product-detail.vue

@@ -8,6 +8,7 @@
         class="cover"
         class="cover"
         :src="product.coverImage || '/static/default-product.png'"
         :src="product.coverImage || '/static/default-product.png'"
         mode="widthFix"
         mode="widthFix"
+        @error="onImageError"
       />
       />
       <view class="info-section">
       <view class="info-section">
         <view class="name-row">
         <view class="name-row">
@@ -44,6 +45,9 @@
           <text v-if="product.memberPrice" class="bottom-member">会员{{ formatPriceWithSymbol(product.memberPrice) }}</text>
           <text v-if="product.memberPrice" class="bottom-member">会员{{ formatPriceWithSymbol(product.memberPrice) }}</text>
         </view>
         </view>
         <view class="action-btns">
         <view class="action-btns">
+          <view class="action-extra">
+            <button class="btn-cart" @click="onAddToCart">加入购物车</button>
+          </view>
           <button class="btn-buy" @click="onBuy">立即购买</button>
           <button class="btn-buy" @click="onBuy">立即购买</button>
         </view>
         </view>
       </view>
       </view>
@@ -55,7 +59,7 @@
 </template>
 </template>
 
 
 <script>
 <script>
-import { productDetail, productOrderCreate, productOrderPay } from '@/utils/api.js'
+import { productDetail, productOrderCreate, productOrderPay, cartAdd } from '@/utils/api.js'
 
 
 export default {
 export default {
   data() {
   data() {
@@ -92,6 +96,10 @@ export default {
     }
     }
   },
   },
   methods: {
   methods: {
+    onImageError(e) {
+      // 图片加载失败时,使用占位图(default-activity.png 已存在于 static/)
+      this.product.coverImage = '/static/default-activity.png'
+    },
     loadDetail(id) {
     loadDetail(id) {
       this.loading = true
       this.loading = true
       productDetail({ id: parseInt(id) }).then(res => {
       productDetail({ id: parseInt(id) }).then(res => {
@@ -135,6 +143,29 @@ export default {
         uni.showToast({ title: '创建订单失败', icon: 'none' })
         uni.showToast({ title: '创建订单失败', icon: 'none' })
       })
       })
     },
     },
+    onAddToCart() {
+      const userId = uni.getStorageSync('userId')
+      if (!userId) {
+        uni.navigateTo({ url: '/pages/login/login' })
+        return
+      }
+      uni.showLoading({ title: '加入购物车...' })
+      cartAdd({
+        userId: userId,
+        productId: this.product.id,
+        quantity: 1
+      }).then(res => {
+        uni.hideLoading()
+        if (res.code === 200) {
+          uni.showToast({ title: '已加入购物车', icon: 'success' })
+        } else {
+          uni.showToast({ title: res.message || '加入购物车失败', icon: 'none' })
+        }
+      }).catch(() => {
+        uni.hideLoading()
+        uni.showToast({ title: '网络异常', icon: 'none' })
+      })
+    },
     doPay(orderNo) {
     doPay(orderNo) {
       uni.showLoading({ title: '支付中...' })
       uni.showLoading({ title: '支付中...' })
       productOrderPay({ orderNo }).then(res => {
       productOrderPay({ orderNo }).then(res => {

+ 1 - 0
cfc-frontend/pages/index/parent-index.vue

@@ -231,6 +231,7 @@
       <DimensionActivities
       <DimensionActivities
         dimensionCode="all"
         dimensionCode="all"
         :activities="recommendedActivities"
         :activities="recommendedActivities"
+        :isLoggedIn="!!token"
         @activityClick="goActivityDetail"
         @activityClick="goActivityDetail"
         @moreActivities="goMoreActivities" />
         @moreActivities="goMoreActivities" />
       <view class="error-state" v-if="errorActivities">
       <view class="error-state" v-if="errorActivities">

+ 3 - 3
cfc-frontend/pages/profile/family-members.vue

@@ -176,10 +176,10 @@ export default {
   },
   },
   computed: {
   computed: {
     parentMembers() {
     parentMembers() {
-      return this.members.filter(function(m) { return m.capabilityRole === 'parent' })
+      return this.members.filter(function(m) { return m.effectiveRole === 'parent' })
     },
     },
     childMembers() {
     childMembers() {
-      return this.members.filter(function(m) { return m.capabilityRole === 'child' })
+      return this.members.filter(function(m) { return m.effectiveRole === 'child' })
     },
     },
     editableMembers() {
     editableMembers() {
       return this.members.filter(function(m) { return m.source === 'family_member' })
       return this.members.filter(function(m) { return m.source === 'family_member' })
@@ -232,7 +232,7 @@ export default {
         if (res.code === 200 && res.data) {
         if (res.code === 200 && res.data) {
           store.commit('switchToMember', {
           store.commit('switchToMember', {
             memberId: res.data.memberId,
             memberId: res.data.memberId,
-            capabilityRole: res.data.capabilityRole
+            effectiveRole: res.data.effectiveRole
           })
           })
           uni.showToast({ title: '已切换到' + (member.nickname || '该成员'), icon: 'success' })
           uni.showToast({ title: '已切换到' + (member.nickname || '该成员'), icon: 'success' })
           setTimeout(function() {
           setTimeout(function() {

+ 65 - 2
cfc-frontend/pages/wisdom/index.vue

@@ -26,6 +26,18 @@
       :sandboxData="sandboxData"
       :sandboxData="sandboxData"
       :dualDimension="dualDimension" />
       :dualDimension="dualDimension" />
 
 
+    <!-- ===== 重要关系维护 ===== -->
+    <FamilyRelationshipSection
+      :contactList="contactList"
+      :healthAlerts="healthAlerts"
+      :milestones="milestones"
+      :isLoggedIn="isLoggedIn"
+      @contactClick="onContactClick"
+      @refresh="loadContacts"
+      @import="onShowImport"
+      @interactionLog="goInteractionLog"
+      @questionnaire="goRelationshipQuestionnaire" />
+
     <!-- 4. 认知雷达图(登录后) -->
     <!-- 4. 认知雷达图(登录后) -->
     <view class="section" v-if="isLoggedIn && cognitiveReport">
     <view class="section" v-if="isLoggedIn && cognitiveReport">
       <view class="section-header">
       <view class="section-header">
@@ -157,10 +169,11 @@ import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionArticles from '../../components/DimensionArticles.vue'
 import DimensionArticles from '../../components/DimensionArticles.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import AIFloatingAvatar from '../../components/AIFloatingAvatar.vue'
-import { getVisibleSections, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getAssessmentLatestResult, getFeaturedArticles } from '../../utils/api.js'
+import FamilyRelationshipSection from '../../components/FamilyRelationshipSection.vue'
+import { getVisibleSections, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getAssessmentLatestResult, getFeaturedArticles, getContactList, getHealthAlerts, getMilestones } from '../../utils/api.js'
 
 
 export default {
 export default {
-  components: { TabTransition, PageBanner, LoginGuideCard, RadarChart, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, AIFloatingAvatar },
+  components: { TabTransition, PageBanner, LoginGuideCard, RadarChart, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, DimensionArticles, FamilyRelationGraph, AIFloatingAvatar, FamilyRelationshipSection },
   data() {
   data() {
     return {
     return {
       isLoggedIn: false,
       isLoggedIn: false,
@@ -180,6 +193,9 @@ export default {
       dimensionProducts: [],
       dimensionProducts: [],
       articles: [],
       articles: [],
       wisdomTips: [],
       wisdomTips: [],
+      contactList: [],
+      healthAlerts: [],
+      milestones: [],
       funcList: [
       funcList: [
         { icon: '\u{1F4CA}', label: '认知报告', needLogin: true, page: '/pages/wisdom-detail/cognitive-report' },
         { icon: '\u{1F4CA}', label: '认知报告', needLogin: true, page: '/pages/wisdom-detail/cognitive-report' },
         { icon: '\u{270F}\uFE0F', label: '自己出题', needLogin: true, page: '/pages/wisdom-detail/self-quiz' },
         { icon: '\u{270F}\uFE0F', label: '自己出题', needLogin: true, page: '/pages/wisdom-detail/self-quiz' },
@@ -259,6 +275,7 @@ export default {
       this.loadChildren()
       this.loadChildren()
       this.loadFamilyMembersVisible()
       this.loadFamilyMembersVisible()
       this.loadCognitiveReport()
       this.loadCognitiveReport()
+      this.loadRelationshipHealth()
     }
     }
     this.loadDimensionActivities()
     this.loadDimensionActivities()
     this.loadDimensionProducts()
     this.loadDimensionProducts()
@@ -310,6 +327,7 @@ export default {
           // Load cognitive report AFTER activeChildId is confirmed set
           // Load cognitive report AFTER activeChildId is confirmed set
           self.loadCognitiveReport()
           self.loadCognitiveReport()
         }
         }
+        self.loadContacts()
       }).catch(function(e) {})
       }).catch(function(e) {})
     },
     },
     loadFamilyMembersVisible: function() {
     loadFamilyMembersVisible: function() {
@@ -467,6 +485,51 @@ export default {
     },
     },
     goArticleDetail: function(article) {
     goArticleDetail: function(article) {
       uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + (article.id || article) })
       uni.navigateTo({ url: '/pages/mind-detail/article-detail?id=' + (article.id || article) })
+    },
+
+    // ===== 重要关系 =====
+    loadContacts: function() {
+      var self = this
+      getContactList({}).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.contactList = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function(e) {
+        console.log('获取联系人失败', e)
+      })
+    },
+    loadRelationshipHealth: function() {
+      var self = this
+      getHealthAlerts({}).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.healthAlerts = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function() {
+        self.healthAlerts = []
+      })
+      getMilestones({}, 30).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.milestones = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function() {
+        self.milestones = []
+      })
+    },
+    onContactClick: function(item) {
+      uni.navigateTo({ url: '/pages/wisdom-detail/contact-detail?id=' + item.id })
+    },
+    onShowImport: function() {
+      uni.showToast({ title: '导入通讯录', icon: 'none' })
+    },
+    goInteractionLog: function(memberId) {
+      if (memberId) {
+        uni.navigateTo({ url: '/pages/wisdom-detail/interaction-log?memberId=' + memberId })
+      } else {
+        uni.navigateTo({ url: '/pages/wisdom-detail/interaction-log' })
+      }
+    },
+    goRelationshipQuestionnaire: function() {
+      uni.navigateTo({ url: '/pages/wisdom-detail/relationship-questionnaire' })
     }
     }
   }
   }
 }
 }

+ 7 - 7
cfc-frontend/store/index.js

@@ -13,7 +13,7 @@ const store = new Vuex.Store({
     nickname: '',
     nickname: '',
     currentChildId: '',
     currentChildId: '',
     currentMemberId: '', // 当前切换到的家庭成员ID(family_members表)
     currentMemberId: '', // 当前切换到的家庭成员ID(family_members表)
-    currentMemberRole: '', // 当前切换到的家庭成员capabilityRole
+    currentMemberRole: '', // 当前切换到的家庭成员effectiveRole
     children: [],
     children: [],
     isSwitchedChild: false, // 家长切换到孩子状态的标记
     isSwitchedChild: false, // 家长切换到孩子状态的标记
     isSwitchedMember: false // 通过family_members切换的标记
     isSwitchedMember: false // 通过family_members切换的标记
@@ -68,19 +68,19 @@ const store = new Vuex.Store({
     },
     },
     // 通过family_members切换到某个成员
     // 通过family_members切换到某个成员
     switchToMember(state, payload) {
     switchToMember(state, payload) {
-      state.currentRole = payload.capabilityRole
+      state.currentRole = payload.effectiveRole
       state.currentMemberId = payload.memberId
       state.currentMemberId = payload.memberId
-      state.currentMemberRole = payload.capabilityRole
+      state.currentMemberRole = payload.effectiveRole
       state.isSwitchedMember = true
       state.isSwitchedMember = true
-      if (payload.capabilityRole === 'child') {
+      if (payload.effectiveRole === 'child') {
         state.isSwitchedChild = true
         state.isSwitchedChild = true
         state.currentChildId = payload.memberId
         state.currentChildId = payload.memberId
       }
       }
-      uni.setStorageSync('currentRole', payload.capabilityRole)
+      uni.setStorageSync('currentRole', payload.effectiveRole)
       uni.setStorageSync('currentMemberId', payload.memberId)
       uni.setStorageSync('currentMemberId', payload.memberId)
-      uni.setStorageSync('currentMemberRole', payload.capabilityRole)
+      uni.setStorageSync('currentMemberRole', payload.effectiveRole)
       uni.setStorageSync('isSwitchedMember', true)
       uni.setStorageSync('isSwitchedMember', true)
-      if (payload.capabilityRole === 'child') {
+      if (payload.effectiveRole === 'child') {
         uni.setStorageSync('currentChildId', payload.memberId)
         uni.setStorageSync('currentChildId', payload.memberId)
         uni.setStorageSync('isSwitchedChild', true)
         uni.setStorageSync('isSwitchedChild', true)
       }
       }

+ 6 - 0
cfc-frontend/utils/api.js

@@ -1599,3 +1599,9 @@ export const getPointsExchangeProductDetail = (data) => request('/api/points/exc
 export const submitPointsExchange = (data) => request('/api/points/exchange/submit', 'POST', data)
 export const submitPointsExchange = (data) => request('/api/points/exchange/submit', 'POST', data)
 export const getPointsExchangeRecords = () => request('/api/points/exchange/records', 'POST', {})
 export const getPointsExchangeRecords = () => request('/api/points/exchange/records', 'POST', {})
 export const getPointsExchangeStats = () => request('/api/points/exchange/stats', 'POST', {})
 export const getPointsExchangeStats = () => request('/api/points/exchange/stats', 'POST', {})
+
+// ===== 购物车 =====
+export const cartAdd = (data) => request('/api/cart/add', 'POST', data)
+export const cartList = (data) => request('/api/cart/list', 'POST', data)
+export const cartUpdate = (data) => request('/api/cart/update', 'POST', data)
+export const cartRemove = (data) => request('/api/cart/remove', 'POST', data)

+ 6 - 6
cfc-web/src/views/admin/RelationshipTypes.vue

@@ -9,10 +9,10 @@
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
         <el-table-column prop="typeKey" label="编码" width="120"></el-table-column>
         <el-table-column prop="typeKey" label="编码" width="120"></el-table-column>
         <el-table-column prop="typeName" label="显示名称" width="120"></el-table-column>
         <el-table-column prop="typeName" label="显示名称" width="120"></el-table-column>
-        <el-table-column prop="capabilityRole" label="能力角色" width="120">
+        <el-table-column prop="defaultRole" label="默认角色" width="120">
           <template slot-scope="scope">
           <template slot-scope="scope">
-            <el-tag :type="scope.row.capabilityRole === 'parent' ? 'primary' : 'success'" size="mini">
-              {{ scope.row.capabilityRole === 'parent' ? '家长' : '孩子' }}
+            <el-tag :type="scope.row.defaultRole === 'parent' ? 'primary' : 'success'" size="mini">
+              {{ scope.row.defaultRole === 'parent' ? '家长' : '孩子' }}
             </el-tag>
             </el-tag>
           </template>
           </template>
         </el-table-column>
         </el-table-column>
@@ -41,8 +41,8 @@
         <el-form-item label="显示名称">
         <el-form-item label="显示名称">
           <el-input v-model="form.typeName" placeholder="如: 子女, 配偶, 父母, 兄弟姐妹"></el-input>
           <el-input v-model="form.typeName" placeholder="如: 子女, 配偶, 父母, 兄弟姐妹"></el-input>
         </el-form-item>
         </el-form-item>
-        <el-form-item label="能力角色">
-          <el-select v-model="form.capabilityRole" placeholder="选择能力角色">
+        <el-form-item label="默认角色">
+          <el-select v-model="form.defaultRole" placeholder="选择默认角色">
             <el-option label="家长(配偶/父母等)" value="parent"></el-option>
             <el-option label="家长(配偶/父母等)" value="parent"></el-option>
             <el-option label="孩子(子女/兄弟姐妹等)" value="child"></el-option>
             <el-option label="孩子(子女/兄弟姐妹等)" value="child"></el-option>
           </el-select>
           </el-select>
@@ -84,7 +84,7 @@ export default {
         id: null,
         id: null,
         typeKey: '',
         typeKey: '',
         typeName: '',
         typeName: '',
-        capabilityRole: 'child',
+        defaultRole: 'child',
         sortOrder: 0,
         sortOrder: 0,
         enabled: 1
         enabled: 1
       }
       }