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

Merge branch 'cfclub' of http://git.iwintrue.com/liaoxg/cfc into cfclub

User 3 месяцев назад
Родитель
Сommit
27f44284e8
63 измененных файлов с 3894 добавлено и 247 удалено
  1. 122 22
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  2. 17 17
      cfc-backend/src/main/java/com/etotem/cfc/controller/DanAssessmentController.java
  3. 131 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyEarningsController.java
  4. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/MediaController.java
  5. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/MiniGameController.java
  6. 3 3
      cfc-backend/src/main/java/com/etotem/cfc/controller/TaskTemplatePackageController.java
  7. 173 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminButlerController.java
  8. 9 9
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java
  9. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminGuideController.java
  10. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminSubscriptionController.java
  11. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/PackageTemplateController.java
  12. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/ai/ButlerController.java
  13. 25 42
      cfc-backend/src/main/java/com/etotem/cfc/controller/butler/ButlerController.java
  14. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/family/InteractionLogController.java
  15. 44 9
      cfc-backend/src/main/java/com/etotem/cfc/controller/family/ParentController.java
  16. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/family/RelationshipQuestionnaireController.java
  17. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideFamilyTaskController.java
  18. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideManagementController.java
  19. 28 72
      cfc-backend/src/main/java/com/etotem/cfc/controller/guide/TeacherController.java
  20. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/task/TaskController.java
  21. 3 3
      cfc-backend/src/main/java/com/etotem/cfc/controller/vendor/VendorController.java
  22. 6 6
      cfc-backend/src/main/java/com/etotem/cfc/controller/wish/WishController.java
  23. 15 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyEarningsMemberDTO.java
  24. 16 0
      cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyEarningsSummaryDTO.java
  25. 45 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/ButlerCommissionSettlement.java
  26. 39 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/ButlerServiceRecord.java
  27. 2 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/CommissionRecord.java
  28. 31 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarnings.java
  29. 33 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarningsMember.java
  30. 31 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarningsRecord.java
  31. 1 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/SupplySystem.java
  32. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/ButlerCommissionSettlementMapper.java
  33. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/ButlerServiceRecordMapper.java
  34. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsMapper.java
  35. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsMemberMapper.java
  36. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsRecordMapper.java
  37. 226 0
      cfc-backend/src/main/java/com/etotem/cfc/service/ButlerCommissionService.java
  38. 23 8
      cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java
  39. 312 0
      cfc-backend/src/main/java/com/etotem/cfc/service/FamilyEarningsService.java
  40. 5 3
      cfc-backend/src/main/java/com/etotem/cfc/service/PaymentService.java
  41. 7 6
      cfc-backend/src/main/java/com/etotem/cfc/service/TeacherService.java
  42. 3 3
      cfc-backend/src/main/java/com/etotem/cfc/service/VendorService.java
  43. 6 3
      cfc-backend/src/main/java/com/etotem/cfc/service/VerificationCodeService.java
  44. 34 0
      cfc-backend/src/main/java/com/etotem/cfc/task/ButlerCommissionScheduledTask.java
  45. 50 0
      cfc-backend/src/main/resources/schema.sql
  46. 258 0
      cfc-frontend/components/member-detail-sections/action-section.vue
  47. 199 0
      cfc-frontend/components/member-detail-sections/body-section.vue
  48. 219 0
      cfc-frontend/components/member-detail-sections/mind-section.vue
  49. 206 0
      cfc-frontend/components/member-detail-sections/wisdom-section.vue
  50. 29 1
      cfc-frontend/pages/member-detail/member-detail.vue
  51. 10 1
      cfc-frontend/utils/api.js
  52. 1 1
      cfc-web/.last_build_commit
  53. 2 2
      cfc-web/package-lock.json
  54. 1 1
      cfc-web/package.json
  55. 97 0
      cfc-web/public/CHANGELOG-v1.0.md
  56. 98 1
      cfc-web/public/CHANGELOG.md
  57. 16 16
      cfc-web/src/api/admin.js
  58. 46 0
      cfc-web/src/api/butler.js
  59. 6 0
      cfc-web/src/router/index.js
  60. 1 0
      cfc-web/src/views/Layout.vue
  61. 264 0
      cfc-web/src/views/admin/ButlerReview.vue
  62. 825 0
      docs/superpowers/plans/2026-07-14-member-detail-dimension-sections.md
  63. 113 0
      docs/superpowers/specs/2026-07-14-member-detail-dimension-sections.md

+ 122 - 22
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -1,8 +1,9 @@
 package com.etotem.cfc.config;
 package com.etotem.cfc.config;
 
 
 import com.etotem.cfc.mapper.*;
 import com.etotem.cfc.mapper.*;
-import lombok.extern.slf4j.Slf4j;
 import javax.annotation.Resource;
 import javax.annotation.Resource;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.core.io.ResourceLoader;
 import org.springframework.core.io.ResourceLoader;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.jdbc.core.JdbcTemplate;
@@ -15,10 +16,11 @@ import java.util.List;
 import java.util.regex.Matcher;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 import java.util.regex.Pattern;
 
 
-@Slf4j
 @Component
 @Component
 public class DatabaseInitializer implements CommandLineRunner {
 public class DatabaseInitializer implements CommandLineRunner {
 
 
+    private static final Logger log = LoggerFactory.getLogger(DatabaseInitializer.class);
+
     @Resource
     @Resource
     private JdbcTemplate jdbcTemplate;
     private JdbcTemplate jdbcTemplate;
 
 
@@ -671,18 +673,22 @@ log.info("已添加template_id列到tasks表");
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN user_id BIGINT COMMENT '用户ID'");
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN user_id BIGINT COMMENT '用户ID'");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID'");
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID'");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN package_id BIGINT COMMENT '套餐ID'");
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN package_id BIGINT COMMENT '套餐ID'");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN remark VARCHAR(500) COMMENT '备注'");
             jdbcTemplate.execute("ALTER TABLE assessment_appointments ADD COLUMN remark VARCHAR(500) COMMENT '备注'");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         // assessment_orders 表
         // assessment_orders 表
@@ -711,6 +717,7 @@ log.info("已添加template_id列到tasks表");
                     "INDEX idx_status (status)) " +
                     "INDEX idx_status (status)) " +
                     "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评估订单表'");
                     "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评估订单表'");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         // 迁移26: guide_applications表添加address和bank_card字段
         // 迁移26: guide_applications表添加address和bank_card字段
@@ -1008,12 +1015,12 @@ log.info("已添加template_id列到tasks表");
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN logistics_no VARCHAR(64) COMMENT '物流单号'");
             jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN logistics_no VARCHAR(64) COMMENT '物流单号'");
             log.info("已添加logistics_no列到product_orders表");
             log.info("已添加logistics_no列到product_orders表");
-        } catch (Exception e) { }
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN logistics_company VARCHAR(100) COMMENT '物流公司'");
             jdbcTemplate.execute("ALTER TABLE product_orders ADD COLUMN logistics_company VARCHAR(100) COMMENT '物流公司'");
             log.info("已添加logistics_company列到product_orders表");
             log.info("已添加logistics_company列到product_orders表");
-        } catch (Exception e) { }
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         // SysConfig 种子数据
         // SysConfig 种子数据
         try {
         try {
@@ -1811,35 +1818,35 @@ log.info("已添加template_id列到tasks表");
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN teacher_no VARCHAR(20) COMMENT '服务商编号'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN teacher_no VARCHAR(20) COMMENT '服务商编号'");
             log.info("已添加teacher_no列到guides表");
             log.info("已添加teacher_no列到guides表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN title VARCHAR(50) COMMENT '职称/称号'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN title VARCHAR(50) COMMENT '职称/称号'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN specialty VARCHAR(200) COMMENT '专长领域'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN specialty VARCHAR(200) COMMENT '专长领域'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN bio TEXT COMMENT '简介'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN bio TEXT COMMENT '简介'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN experience_years TINYINT DEFAULT 0 COMMENT '从业年限'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN experience_years TINYINT DEFAULT 0 COMMENT '从业年限'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN rating INT DEFAULT 500 COMMENT '评分(百分位,500=5.00)'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN rating INT DEFAULT 500 COMMENT '评分(百分位,500=5.00)'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN total_appointments INT DEFAULT 0 COMMENT '总服务次数'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN total_appointments INT DEFAULT 0 COMMENT '总服务次数'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street_id BIGINT COMMENT '街道ID'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street_id BIGINT COMMENT '街道ID'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN province VARCHAR(50) COMMENT '省份'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN province VARCHAR(50) COMMENT '省份'");
@@ -1847,11 +1854,11 @@ log.info("已添加template_id列到tasks表");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN district VARCHAR(50) COMMENT '区县'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN district VARCHAR(50) COMMENT '区县'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street VARCHAR(100) COMMENT '街道'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street VARCHAR(100) COMMENT '街道'");
             log.info("已添加地址字段到guides表");
             log.info("已添加地址字段到guides表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN verified_at DATETIME COMMENT '审核通过时间'");
             jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN verified_at DATETIME COMMENT '审核通过时间'");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         // 迁移: 将 teachers 表数据合并到 guides(type='teacher')
         // 迁移: 将 teachers 表数据合并到 guides(type='teacher')
         try {
         try {
@@ -3589,44 +3596,52 @@ try {
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID' AFTER id");
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN guide_id BIGINT COMMENT '成长规划师ID' AFTER id");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN package_id BIGINT COMMENT '关联模板' AFTER guide_id");
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN package_id BIGINT COMMENT '关联模板' AFTER guide_id");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN commission_rate INT COMMENT '佣金比例(bps,100=1%)' AFTER package_id");
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN commission_rate INT COMMENT '佣金比例(bps,100=1%)' AFTER package_id");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN teacher_id BIGINT NULL");
             jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN teacher_id BIGINT NULL");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN template_id BIGINT NULL");
             jdbcTemplate.execute("ALTER TABLE guide_packages MODIFY COLUMN template_id BIGINT NULL");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         // 迁移:guide_packages 表添加 level 列(GuidePackage 实体需要)
         // 迁移:guide_packages 表添加 level 列(GuidePackage 实体需要)
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN level VARCHAR(20) COMMENT '套餐等级' AFTER name");
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN level VARCHAR(20) COMMENT '套餐等级' AFTER name");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         // 迁移:guide_packages 表添加 validity_days 列
         // 迁移:guide_packages 表添加 validity_days 列
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN validity_days INT DEFAULT 0 COMMENT '有效期天数' AFTER validity_months");
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN validity_days INT DEFAULT 0 COMMENT '有效期天数' AFTER validity_months");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         // 迁移:guide_packages 表添加 sort_order 列
         // 迁移:guide_packages 表添加 sort_order 列
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN sort_order INT DEFAULT 0 COMMENT '排序' AFTER status");
             jdbcTemplate.execute("ALTER TABLE guide_packages ADD COLUMN sort_order INT DEFAULT 0 COMMENT '排序' AFTER status");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         // 成长规划师套餐服务项表
         // 成长规划师套餐服务项表
@@ -4110,42 +4125,42 @@ try {
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN source_type VARCHAR(20) DEFAULT 'manual' COMMENT '来源类型: manual/platform_purchase/self_upload/supplement'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN source_type VARCHAR(20) DEFAULT 'manual' COMMENT '来源类型: manual/platform_purchase/self_upload/supplement'");
             log.info("已添加source_type列到growth_records表");
             log.info("已添加source_type列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN source_file_url VARCHAR(500) COMMENT '原始报告文件URL'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN source_file_url VARCHAR(500) COMMENT '原始报告文件URL'");
             log.info("已添加source_file_url列到growth_records表");
             log.info("已添加source_file_url列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN order_id BIGINT COMMENT '关联订单ID'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN order_id BIGINT COMMENT '关联订单ID'");
             log.info("已添加order_id列到growth_records表");
             log.info("已添加order_id列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN report_date DATETIME COMMENT '测评/报告日期'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN report_date DATETIME COMMENT '测评/报告日期'");
             log.info("已添加report_date列到growth_records表");
             log.info("已添加report_date列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN parent_record_id BIGINT COMMENT '关联的原始档案ID(补充档案专用)'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN parent_record_id BIGINT COMMENT '关联的原始档案ID(补充档案专用)'");
             log.info("已添加parent_record_id列到growth_records表");
             log.info("已添加parent_record_id列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN is_latest TINYINT(1) DEFAULT 1 COMMENT '是否同组最新记录'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN is_latest TINYINT(1) DEFAULT 1 COMMENT '是否同组最新记录'");
             log.info("已添加is_latest列到growth_records表");
             log.info("已添加is_latest列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN dedup_hash VARCHAR(64) COMMENT '去重哈希值(SHA256)'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN dedup_hash VARCHAR(64) COMMENT '去重哈希值(SHA256)'");
             log.info("已添加dedup_hash列到growth_records表");
             log.info("已添加dedup_hash列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN parsed_data TEXT COMMENT '上传报告的解析结果(JSON)'");
             jdbcTemplate.execute("ALTER TABLE growth_records ADD COLUMN parsed_data TEXT COMMENT '上传报告的解析结果(JSON)'");
             log.info("已添加parsed_data列到growth_records表");
             log.info("已添加parsed_data列到growth_records表");
-        } catch (Exception e) {}
+        } catch (Exception e) { log.warn("Migration skipped (already exists): {}", e.getMessage()); }
 
 
         try {
         try {
             jdbcTemplate.execute("CREATE UNIQUE INDEX IF NOT EXISTS uk_growth_dedup_hash ON growth_records(dedup_hash)");
             jdbcTemplate.execute("CREATE UNIQUE INDEX IF NOT EXISTS uk_growth_dedup_hash ON growth_records(dedup_hash)");
@@ -5236,16 +5251,20 @@ try {
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE pending_refund ADD INDEX idx_order_type (order_type)");
             jdbcTemplate.execute("ALTER TABLE pending_refund ADD INDEX idx_order_type (order_type)");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
         try {
         try {
             jdbcTemplate.execute("ALTER TABLE pending_refund ADD INDEX idx_order_no (order_no)");
             jdbcTemplate.execute("ALTER TABLE pending_refund ADD INDEX idx_order_no (order_no)");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         runMigration80();
         runMigration80();
         runMigration81();
         runMigration81();
         runMigration82();
         runMigration82();
         runMigration86();
         runMigration86();
+        runMigration88();
+        runMigration89();
     }
     }
 
 
     private void runMigration81() {
     private void runMigration81() {
@@ -5351,6 +5370,18 @@ try {
         ensureColumn("cart", "sku_id", "BIGINT COMMENT '选中的SKU ID'");
         ensureColumn("cart", "sku_id", "BIGINT COMMENT '选中的SKU ID'");
         ensureColumn("cart", "spec_option_ids", "VARCHAR(500) COMMENT '选中的规格选项IDs JSON'");
         ensureColumn("cart", "spec_option_ids", "VARCHAR(500) COMMENT '选中的规格选项IDs JSON'");
 
 
+        // 迁移86: product_orders表添加purchase_info字段(购买信息快照JSON,OrderTimeoutCancelTask查询需要)
+        ensureColumn("product_orders", "purchase_info", "TEXT COMMENT '购买信息JSON: {id_card, hand_signature, ...}'");
+
+        // 迁移87: articles表添加content_type/difficulty_level/dimension_ids/dimension_weights字段
+        ensureColumn("articles", "content_type", "VARCHAR(20) DEFAULT 'text' COMMENT '内容格式:text/html/markdown'");
+        ensureColumn("articles", "difficulty_level", "INT DEFAULT 0 COMMENT '难度等级:1-5'");
+        ensureColumn("articles", "dimension_ids", "VARCHAR(100) DEFAULT '' COMMENT '关联维度IDs JSON'");
+        ensureColumn("articles", "dimension_weights", "TEXT COMMENT '五维能量权重JSON'");
+
+        // 迁移88: activities表添加dimension_weights列(权重感知活动查询)
+        ensureColumn("activities", "dimension_weights", "TEXT COMMENT '五维能量权重JSON'");
+
         // 迁移85: 创建社交圈子表(珍珠图/圈子系统)
         // 迁移85: 创建社交圈子表(珍珠图/圈子系统)
         try {
         try {
             jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS social_circle (" +
             jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS social_circle (" +
@@ -5537,6 +5568,7 @@ try {
             jdbcTemplate.execute("ALTER TABLE users ADD COLUMN butler_approved_at DATETIME");
             jdbcTemplate.execute("ALTER TABLE users ADD COLUMN butler_approved_at DATETIME");
             log.info("已添加 butler 相关列到 users 表");
             log.info("已添加 butler 相关列到 users 表");
         } catch (Exception e) {
         } catch (Exception e) {
+            log.warn("Migration skipped (already exists): {}", e.getMessage());
         }
         }
 
 
         // 迁移 86: 创建 butler_assignments 表(会员 - 管家分配记录)
         // 迁移 86: 创建 butler_assignments 表(会员 - 管家分配记录)
@@ -5557,4 +5589,72 @@ try {
             log.warn("创建 butler_assignments 表失败:{}", e.getMessage());
             log.warn("创建 butler_assignments 表失败:{}", e.getMessage());
         }
         }
     }
     }
+
+    private void runMigration88() {
+        // 迁移 88: 创建家庭公共账户体系(family_earnings / family_earnings_member / family_earnings_record)
+        // 表 1: family_earnings(家庭总账)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_earnings (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL UNIQUE COMMENT '家庭 ID', " +
+                    "total_earned BIGINT NOT NULL DEFAULT 0 COMMENT '累计推荐收入(分)', " +
+                    "available_amount BIGINT NOT NULL DEFAULT 0 COMMENT '当前可支配余额(分)', " +
+                    "withdrawn_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已提现金额(分)', " +
+                    "distributed_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已颁发给成员的金额(分)', " +
+                    "updated_at DATETIME, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭公共账户'");
+            log.info("已创建 family_earnings 表");
+        } catch (Exception e) {
+            log.warn("创建 family_earnings 表失败:{}", e.getMessage());
+        }
+
+        // 表 2: family_earnings_member(成员已分配余额)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_earnings_member (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL, " +
+                    "user_id BIGINT NOT NULL, " +
+                    "allocated_amount BIGINT NOT NULL DEFAULT 0 COMMENT '管理员累计分配(分)', " +
+                    "consumed_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已消费金额(分)', " +
+                    "available_amount BIGINT NOT NULL DEFAULT 0 COMMENT '可消费余额=allocated-consumed(分)', " +
+                    "can_use_family_balance TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许使用家庭公共池', " +
+                    "updated_at DATETIME, " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "UNIQUE KEY idx_family_user (family_id, user_id)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员已分配余额'");
+            log.info("已创建 family_earnings_member 表");
+        } catch (Exception e) {
+            log.warn("创建 family_earnings_member 表失败:{}", e.getMessage());
+        }
+
+        // 表 3: family_earnings_record(收支流水)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS family_earnings_record (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "family_id BIGINT NOT NULL, " +
+                    "type VARCHAR(20) NOT NULL COMMENT 'earn 收入/withdraw 提现/distribute 颁发/consume 消费', " +
+                    "amount BIGINT NOT NULL COMMENT '金额(分)', " +
+                    "from_user_id BIGINT COMMENT '操作人', " +
+                    "to_user_id BIGINT COMMENT '受益人(颁发/消费时填写)', " +
+                    "note VARCHAR(255) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_type (type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭收益收支流水'");
+            log.info("已创建 family_earnings_record 表");
+        } catch (Exception e) {
+            log.warn("创建 family_earnings_record 表失败:{}", e.getMessage());
+        }
+
+        // commission_records 加 family_id 列
+        ensureColumn("commission_records", "family_id", "BIGINT COMMENT '归属家庭 ID'");
+    }
+
+    private void runMigration89() {
+        // 迁移 89: supply_system表添加models字段(AI模型供应商配置)
+        ensureColumn("supply_system", "models", "TEXT COMMENT 'AI模型列表JSON,如[\"agnes-image-2.0-flash\",\"agnes-2.5-flash\"]'");
+        log.info("已添加models列到supply_system表");
+    }
 }
 }

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

@@ -41,31 +41,31 @@ public class DanAssessmentController {
 
 
     // ===== 测评材料 =====
     // ===== 测评材料 =====
 
 
-    @Operation(summary = "获取当前测评材料")
-    @GetMapping("/material/active")
+@Operation(summary = "获取当前测评材料")
+    @PostMapping("/material/active")
     public Result<AssessmentMaterial> getActiveMaterial() {
     public Result<AssessmentMaterial> getActiveMaterial() {
         AssessmentMaterial material = assessmentService.getActiveMaterial();
         AssessmentMaterial material = assessmentService.getActiveMaterial();
         return Result.success(material);
         return Result.success(material);
     }
     }
 
 
-    @Operation(summary = "获取测评材料列表")
-    @GetMapping("/material/list")
+@Operation(summary = "获取测评材料列表")
+    @PostMapping("/material/list")
     public Result<List<AssessmentMaterial>> getMaterialList() {
     public Result<List<AssessmentMaterial>> getMaterialList() {
         return Result.success(assessmentService.getMaterialList());
         return Result.success(assessmentService.getMaterialList());
     }
     }
 
 
     // ===== 积分配置 =====
     // ===== 积分配置 =====
 
 
-    @Operation(summary = "获取积分配置")
-    @GetMapping("/points-config")
+@Operation(summary = "获取积分配置")
+    @PostMapping("/points-config")
     public Result<AssessmentPointsConfig> getPointsConfig() {
     public Result<AssessmentPointsConfig> getPointsConfig() {
         return Result.success(assessmentService.getPointsConfig());
         return Result.success(assessmentService.getPointsConfig());
     }
     }
 
 
     // ===== 家庭配置 =====
     // ===== 家庭配置 =====
 
 
-    @Operation(summary = "获取家庭测评配置")
-    @GetMapping("/family/config")
+@Operation(summary = "获取家庭测评配置")
+    @PostMapping("/family/config")
     public Result<FamilyAssessmentConfig> getFamilyConfig(@RequestParam Long familyId) {
     public Result<FamilyAssessmentConfig> getFamilyConfig(@RequestParam Long familyId) {
         return Result.success(assessmentService.getFamilyConfig(familyId));
         return Result.success(assessmentService.getFamilyConfig(familyId));
     }
     }
@@ -112,14 +112,14 @@ public class DanAssessmentController {
         return ok ? Result.success("完成成功") : Result.error("记录不存在");
         return ok ? Result.success("完成成功") : Result.error("记录不存在");
     }
     }
 
 
-    @Operation(summary = "获取家庭测评记录")
-    @GetMapping("/record/family")
+@Operation(summary = "获取家庭测评记录")
+    @PostMapping("/record/family")
     public Result<List<AssessmentRecord>> getFamilyRecords(@RequestParam Long familyId) {
     public Result<List<AssessmentRecord>> getFamilyRecords(@RequestParam Long familyId) {
         return Result.success(assessmentService.getFamilyRecords(familyId));
         return Result.success(assessmentService.getFamilyRecords(familyId));
     }
     }
 
 
-    @Operation(summary = "获取当前测评")
-    @GetMapping("/record/current")
+@Operation(summary = "获取当前测评")
+    @PostMapping("/record/current")
     public Result<AssessmentRecord> getCurrentAssessment(@RequestParam Long familyId) {
     public Result<AssessmentRecord> getCurrentAssessment(@RequestParam Long familyId) {
         return Result.success(assessmentService.getCurrentAssessment(familyId));
         return Result.success(assessmentService.getCurrentAssessment(familyId));
     }
     }
@@ -201,14 +201,14 @@ public class DanAssessmentController {
         return ok ? Result.success("取消成功") : Result.error("取消失败");
         return ok ? Result.success("取消成功") : Result.error("取消失败");
     }
     }
 
 
-    @Operation(summary = "获取用户的预约列表")
-    @GetMapping("/appointment/user")
+@Operation(summary = "获取用户的预约列表")
+    @PostMapping("/appointment/user")
     public Result<List<AssessmentAppointment>> getUserAppointments(@RequestParam Long userId) {
     public Result<List<AssessmentAppointment>> getUserAppointments(@RequestParam Long userId) {
         return Result.success(appointmentService.getByUserId(userId));
         return Result.success(appointmentService.getByUserId(userId));
     }
     }
 
 
     @Operation(summary = "获取规划师的预约列表")
     @Operation(summary = "获取规划师的预约列表")
-    @GetMapping("/appointment/guide")
+    @PostMapping("/appointment/guide")
     public Result<List<AssessmentAppointment>> getGuideAppointments(@RequestParam Long guideId) {
     public Result<List<AssessmentAppointment>> getGuideAppointments(@RequestParam Long guideId) {
         return Result.success(appointmentService.getByGuideId(guideId));
         return Result.success(appointmentService.getByGuideId(guideId));
     }
     }
@@ -252,13 +252,13 @@ public class DanAssessmentController {
     }
     }
 
 
     @Operation(summary = "获取订单详情")
     @Operation(summary = "获取订单详情")
-    @GetMapping("/order/detail")
+    @PostMapping("/order/detail")
     public Result<AssessmentOrder> getOrderDetail(@RequestParam String orderNo) {
     public Result<AssessmentOrder> getOrderDetail(@RequestParam String orderNo) {
         return Result.success(orderService.getByOrderNo(orderNo));
         return Result.success(orderService.getByOrderNo(orderNo));
     }
     }
 
 
     @Operation(summary = "获取家庭订单列表")
     @Operation(summary = "获取家庭订单列表")
-    @GetMapping("/order/family")
+    @PostMapping("/order/family")
     public Result<List<AssessmentOrder>> getFamilyOrders(@RequestParam Long familyId) {
     public Result<List<AssessmentOrder>> getFamilyOrders(@RequestParam Long familyId) {
         return Result.success(orderService.getFamilyOrders(familyId));
         return Result.success(orderService.getFamilyOrders(familyId));
     }
     }

+ 131 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/FamilyEarningsController.java

@@ -0,0 +1,131 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.dto.FamilyEarningsSummaryDTO;
+import com.etotem.cfc.entity.FamilyEarningsRecord;
+import com.etotem.cfc.service.FamilyEarningsService;
+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.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/earnings")
+public class FamilyEarningsController {
+    
+    @Resource
+    private FamilyEarningsService familyEarningsService;
+    
+    /**
+     * 获取家庭收益总览
+     */
+    @PostMapping("/family/summary")
+    public Result<FamilyEarningsSummaryDTO> familySummary(@RequestAttribute("userId") Long userId) {
+        // 检查用户是否是家庭管理员
+        Long familyId = getFamilyId(userId);
+        return Result.success(familyEarningsService.getFamilySummary(familyId));
+    }
+    
+    /**
+     * 获取家庭成员贡献列表
+     */
+    @PostMapping("/family/members")
+    public Result<List> memberContributions(@RequestAttribute("userId") Long userId) {
+        Long familyId = getFamilyId(userId);
+        return Result.success(familyEarningsService.getMemberContributions(familyId));
+    }
+    
+    /**
+     * 获取收支流水
+     */
+    @PostMapping("/family/records")
+    public Result<List<FamilyEarningsRecord>> records(
+            @RequestAttribute("userId") Long userId,
+            @RequestBody Map<String, String> params) {
+        Long familyId = getFamilyId(userId);
+        String type = params.get("type");
+        return Result.success(familyEarningsService.getRecords(familyId, type));
+    }
+    
+    /**
+     * 管理员分配收益给成员
+     */
+    @PostMapping("/admin/distribute")
+    public Result<String> distribute(
+            @RequestAttribute("userId") Long adminId,
+            @RequestBody Map<String, Object> params) {
+        // 检查是否是家庭管理员
+        if (!isFamilyAdmin(adminId)) {
+            return Result.error("无权限操作");
+        }
+        
+        Long familyId = getFamilyId(adminId);
+        Long userId = ((Integer) params.get("userId")).longValue();
+        Long amount = ((Integer) params.get("amount")).longValue();
+        
+        familyEarningsService.distribute(familyId, userId, amount, adminId);
+        return Result.success("分配成功");
+    }
+    
+    /**
+     * 切换成员是否允许使用家庭公共池
+     */
+    @PostMapping("/admin/toggle-member")
+    public Result<String> toggleMember(
+            @RequestAttribute("userId") Long adminId,
+            @RequestBody Map<String, Object> params) {
+        // 检查是否是家庭管理员
+        if (!isFamilyAdmin(adminId)) {
+            return Result.error("无权限操作");
+        }
+        
+        Long familyId = getFamilyId(adminId);
+        Long userId = ((Integer) params.get("userId")).longValue();
+        Boolean enabled = (Boolean) params.get("enabled");
+        
+        familyEarningsService.toggleMemberAccess(familyId, userId, enabled);
+        return Result.success("操作成功");
+    }
+    
+    /**
+     * 管理员提现
+     */
+    @PostMapping("/admin/withdraw")
+    public Result<String> withdraw(
+            @RequestAttribute("userId") Long adminId,
+            @RequestBody Map<String, Object> params) {
+        // 检查是否是家庭管理员
+        if (!isFamilyAdmin(adminId)) {
+            return Result.error("无权限操作");
+        }
+        
+        Long familyId = getFamilyId(adminId);
+        Long amount = ((Integer) params.get("amount")).longValue();
+        
+        familyEarningsService.withdraw(familyId, amount, adminId);
+        return Result.success("提现成功");
+    }
+    
+    /**
+     * 获取用户的家庭ID
+     */
+    private Long getFamilyId(Long userId) {
+        // 通过userId查询familyId
+        // 实际项目中需要实现这个方法
+        return 1L; // 示例值,需要替换为实际查询逻辑
+    }
+    
+    /**
+     * 检查用户是否是家庭管理员
+     */
+    private boolean isFamilyAdmin(Long userId) {
+        // 通过userId查询用户信息,检查isFamilyAdmin字段
+        // 实际项目中需要实现这个方法
+        return true; // 示例值,需要替换为实际查询逻辑
+    }
+}

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/MediaController.java

@@ -56,7 +56,7 @@ public class MediaController {
     }
     }
 
 
     @Operation(summary = "删除媒体")
     @Operation(summary = "删除媒体")
-    @DeleteMapping("/{mediaId}")
+    @PostMapping("/{mediaId}")
     public Result<Boolean> deleteMedia(
     public Result<Boolean> deleteMedia(
             @PathVariable Long mediaId,
             @PathVariable Long mediaId,
             @RequestAttribute("userId") Long userId) {
             @RequestAttribute("userId") Long userId) {

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/MiniGameController.java

@@ -43,7 +43,7 @@ public class MiniGameController {
         return Result.success(games);
         return Result.success(games);
     }
     }
     @Operation(summary = "更新游戏状态")
     @Operation(summary = "更新游戏状态")
-    @PutMapping("/{id}/status")
+    @PostMapping("/{id}/status")
     public Result<Boolean> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
     public Result<Boolean> updateStatus(@PathVariable Long id, @RequestParam Integer status) {
         boolean success = miniGameService.updateGameStatus(id, status);
         boolean success = miniGameService.updateGameStatus(id, status);
         return Result.success(success);
         return Result.success(success);

+ 3 - 3
cfc-backend/src/main/java/com/etotem/cfc/controller/TaskTemplatePackageController.java

@@ -38,7 +38,7 @@ public class TaskTemplatePackageController {
      * 更新套餐
      * 更新套餐
      */
      */
     @Operation(summary = "更新套餐")
     @Operation(summary = "更新套餐")
-    @PutMapping("/{id}")
+    @PostMapping("/{id}/update")
     public Result<Boolean> updatePackage(@PathVariable Long id,
     public Result<Boolean> updatePackage(@PathVariable Long id,
                                           @RequestBody CreatePackageDTO dto) {
                                           @RequestBody CreatePackageDTO dto) {
         boolean success = packageService.updatePackage(id, dto);
         boolean success = packageService.updatePackage(id, dto);
@@ -103,7 +103,7 @@ public class TaskTemplatePackageController {
      * 更新任务项
      * 更新任务项
      */
      */
     @Operation(summary = "更新任务项")
     @Operation(summary = "更新任务项")
-    @PutMapping("/items/{itemId}")
+    @PostMapping("/items/{itemId}")
     public Result<Boolean> updateItem(@PathVariable Long itemId,
     public Result<Boolean> updateItem(@PathVariable Long itemId,
                                         @RequestBody CreatePackageItemDTO dto) {
                                         @RequestBody CreatePackageItemDTO dto) {
         boolean success = packageService.updateItem(itemId, dto);
         boolean success = packageService.updateItem(itemId, dto);
@@ -114,7 +114,7 @@ public class TaskTemplatePackageController {
      * 删除任务项
      * 删除任务项
      */
      */
     @Operation(summary = "删除任务项")
     @Operation(summary = "删除任务项")
-    @DeleteMapping("/items/{itemId}")
+    @PostMapping("/items/{itemId}/delete")
     public Result<Boolean> deleteItem(@PathVariable Long itemId) {
     public Result<Boolean> deleteItem(@PathVariable Long itemId) {
         boolean success = packageService.deleteItem(itemId);
         boolean success = packageService.deleteItem(itemId);
         return Result.success(success);
         return Result.success(success);

+ 173 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminButlerController.java

@@ -0,0 +1,173 @@
+package com.etotem.cfc.controller.admin;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.ButlerCommissionSettlement;
+import com.etotem.cfc.entity.ButlerProfile;
+import com.etotem.cfc.entity.ButlerServiceRecord;
+import com.etotem.cfc.mapper.ButlerCommissionSettlementMapper;
+import com.etotem.cfc.mapper.ButlerProfileMapper;
+import com.etotem.cfc.mapper.ButlerServiceRecordMapper;
+import com.etotem.cfc.service.ButlerService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/admin/butler")
+public class AdminButlerController {
+
+    @Resource
+    private ButlerService butlerService;
+
+    @Resource
+    private ButlerProfileMapper butlerProfileMapper;
+
+    @Resource
+    private ButlerServiceRecordMapper butlerServiceRecordMapper;
+
+    @Resource
+    private ButlerCommissionSettlementMapper butlerCommissionSettlementMapper;
+
+    @PostMapping("/list")
+    public Result<Map<String, Object>> list(@RequestBody Map<String, Object> params,
+                                             @RequestAttribute("role") String role) {
+        if (!"admin".equals(role)) {
+            return Result.error("权限不足");
+        }
+
+        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
+        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
+        String status = (String) params.get("status");
+        String tier = (String) params.get("tier");
+
+        Page<ButlerProfile> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<ButlerProfile> wrapper = new LambdaQueryWrapper<>();
+
+        if (status != null && !status.isEmpty()) {
+            wrapper.eq(ButlerProfile::getStatus, status);
+        }
+        if (tier != null && !tier.isEmpty()) {
+            wrapper.eq(ButlerProfile::getTier, tier);
+        }
+        wrapper.orderByDesc(ButlerProfile::getCreatedAt);
+
+        Page<ButlerProfile> result = butlerProfileMapper.selectPage(pageParam, wrapper);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("list", result.getRecords());
+        data.put("total", result.getTotal());
+        data.put("page", page);
+        data.put("size", size);
+        return Result.success(data);
+    }
+
+    @PostMapping("/review")
+    public Result<String> review(@RequestBody Map<String, Object> params,
+                                  @RequestAttribute("role") String role) {
+        if (!"admin".equals(role)) {
+            return Result.error("权限不足");
+        }
+
+        Long userId = params.get("userId") != null
+                ? Long.valueOf(params.get("userId").toString())
+                : null;
+        String action = (String) params.get("action");
+        String reason = (String) params.getOrDefault("reason", "");
+
+        if (userId == null) {
+            return Result.error("userId不能为空");
+        }
+        if (action == null || (!"approve".equals(action) && !"reject".equals(action))) {
+            return Result.error("action必须为 approve 或 reject");
+        }
+
+        if ("approve".equals(action)) {
+            return butlerService.approve(userId, null);
+        } else {
+            return butlerService.reject(userId, reason);
+        }
+    }
+
+    @PostMapping("/assign-member")
+    public Result<String> assignMember(@RequestBody Map<String, Object> params,
+                                        @RequestAttribute("role") String role) {
+        if (!"admin".equals(role)) {
+            return Result.error("权限不足");
+        }
+
+        Long profileId = params.get("profileId") != null
+                ? Long.valueOf(params.get("profileId").toString())
+                : null;
+        Long memberId = params.get("memberId") != null
+                ? Long.valueOf(params.get("memberId").toString())
+                : null;
+
+        if (profileId == null || memberId == null) {
+            return Result.error("profileId和memberId不能为空");
+        }
+
+        return butlerService.assignMemberToButler(profileId, memberId);
+    }
+
+    @PostMapping("/service-records")
+    public Result<Map<String, Object>> serviceRecords(@RequestBody Map<String, Object> params,
+                                                       @RequestAttribute("role") String role) {
+        if (!"admin".equals(role)) {
+            return Result.error("权限不足");
+        }
+
+        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
+        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
+        String serviceType = (String) params.get("serviceType");
+
+        Page<ButlerServiceRecord> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<ButlerServiceRecord> wrapper = new LambdaQueryWrapper<>();
+
+        if (serviceType != null && !serviceType.isEmpty()) {
+            wrapper.eq(ButlerServiceRecord::getServiceType, serviceType);
+        }
+        wrapper.orderByDesc(ButlerServiceRecord::getCreatedAt);
+
+        Page<ButlerServiceRecord> result = butlerServiceRecordMapper.selectPage(pageParam, wrapper);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("list", result.getRecords());
+        data.put("total", result.getTotal());
+        data.put("page", page);
+        data.put("size", size);
+        return Result.success(data);
+    }
+
+    @PostMapping("/settlements")
+    public Result<Map<String, Object>> settlements(@RequestBody Map<String, Object> params,
+                                                    @RequestAttribute("role") String role) {
+        if (!"admin".equals(role)) {
+            return Result.error("权限不足");
+        }
+
+        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
+        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
+        String status = (String) params.get("status");
+
+        Page<ButlerCommissionSettlement> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<ButlerCommissionSettlement> wrapper = new LambdaQueryWrapper<>();
+
+        if (status != null && !status.isEmpty()) {
+            wrapper.eq(ButlerCommissionSettlement::getStatus, status);
+        }
+        wrapper.orderByDesc(ButlerCommissionSettlement::getCreatedAt);
+
+        Page<ButlerCommissionSettlement> result = butlerCommissionSettlementMapper.selectPage(pageParam, wrapper);
+
+        Map<String, Object> data = new HashMap<>();
+        data.put("list", result.getRecords());
+        data.put("total", result.getTotal());
+        data.put("page", page);
+        data.put("size", size);
+        return Result.success(data);
+    }
+}

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

@@ -124,7 +124,7 @@ public class AdminController {
         return Result.success(userMapper.selectById(id));
         return Result.success(userMapper.selectById(id));
     }
     }
 
 
-    @PutMapping("/users/{id}")
+    @PostMapping("/users/{id}/update")
     public Result<Boolean> updateUser(@PathVariable Long id, @RequestBody User user) {
     public Result<Boolean> updateUser(@PathVariable Long id, @RequestBody User user) {
         user.setId(id);
         user.setId(id);
         userMapper.updateById(user);
         userMapper.updateById(user);
@@ -173,7 +173,7 @@ public class AdminController {
         return Result.success(users);
         return Result.success(users);
     }
     }
 
 
-    @DeleteMapping("/users/{id}")
+    @PostMapping("/users/{id}/delete")
     public Result<Boolean> deleteUser(@PathVariable Long id) {
     public Result<Boolean> deleteUser(@PathVariable Long id) {
         userMapper.deleteById(id);
         userMapper.deleteById(id);
         return Result.success(true);
         return Result.success(true);
@@ -194,7 +194,7 @@ public class AdminController {
         return Result.success(familyMemberMapper.selectById(id));
         return Result.success(familyMemberMapper.selectById(id));
     }
     }
 
 
-    @PutMapping("/children/{id}")
+    @PostMapping("/children/{id}/update")
     public Result<Boolean> updateChild(@PathVariable Long id, @RequestBody FamilyMember child) {
     public Result<Boolean> updateChild(@PathVariable Long id, @RequestBody FamilyMember child) {
         child.setId(id);
         child.setId(id);
         familyMemberMapper.updateById(child);
         familyMemberMapper.updateById(child);
@@ -321,7 +321,7 @@ public class AdminController {
         return Result.success(reward.getId());
         return Result.success(reward.getId());
     }
     }
 
 
-    @DeleteMapping("/rewards/templates/{id}")
+    @PostMapping("/rewards/templates/{id}/delete")
     public Result<Boolean> deleteRewardTemplate(@PathVariable Long id, @RequestAttribute("userId") Long userId) {
     public Result<Boolean> deleteRewardTemplate(@PathVariable Long id, @RequestAttribute("userId") Long userId) {
         // 验证家庭归属
         // 验证家庭归属
         com.etotem.cfc.entity.User user = userService.getUserInfo(userId);
         com.etotem.cfc.entity.User user = userService.getUserInfo(userId);
@@ -335,7 +335,7 @@ public class AdminController {
         return Result.success(true);
         return Result.success(true);
     }
     }
 
 
-    @PutMapping("/rewards/templates/{id}")
+    @PostMapping("/rewards/templates/{id}/update")
     public Result<Boolean> updateRewardTemplate(@PathVariable Long id, @RequestBody Reward reward, @RequestAttribute("userId") Long userId) {
     public Result<Boolean> updateRewardTemplate(@PathVariable Long id, @RequestBody Reward reward, @RequestAttribute("userId") Long userId) {
         // 验证家庭归属
         // 验证家庭归属
         com.etotem.cfc.entity.User user = userService.getUserInfo(userId);
         com.etotem.cfc.entity.User user = userService.getUserInfo(userId);
@@ -501,7 +501,7 @@ public class AdminController {
         return Result.success(result);
         return Result.success(result);
     }
     }
 
 
-    @PutMapping("/families/{id}")
+    @PostMapping("/families/{id}/update")
     public Result<Boolean> updateFamily(@PathVariable Long id, @RequestBody Family family) {
     public Result<Boolean> updateFamily(@PathVariable Long id, @RequestBody Family family) {
         family.setId(id);
         family.setId(id);
         family.setUpdatedAt(new Date());
         family.setUpdatedAt(new Date());
@@ -509,7 +509,7 @@ public class AdminController {
         return Result.success(true);
         return Result.success(true);
     }
     }
 
 
-    @DeleteMapping("/families/{id}")
+    @PostMapping("/families/{id}/delete")
     public Result<Boolean> deleteFamily(@PathVariable Long id) {
     public Result<Boolean> deleteFamily(@PathVariable Long id) {
         familyMapper.deleteById(id);
         familyMapper.deleteById(id);
         return Result.success(true);
         return Result.success(true);
@@ -556,7 +556,7 @@ public class AdminController {
         return Result.success(template.getId());
         return Result.success(template.getId());
     }
     }
 
 
-    @PutMapping("/task-templates/{id}")
+    @PostMapping("/task-templates/{id}/update")
     public Result<Boolean> updateTaskTemplate(@PathVariable Long id, @RequestBody TaskTemplate template) {
     public Result<Boolean> updateTaskTemplate(@PathVariable Long id, @RequestBody TaskTemplate template) {
         template.setId(id);
         template.setId(id);
         template.setUpdatedAt(new Date());
         template.setUpdatedAt(new Date());
@@ -564,7 +564,7 @@ public class AdminController {
         return Result.success(true);
         return Result.success(true);
     }
     }
 
 
-    @DeleteMapping("/task-templates/{id}")
+    @PostMapping("/task-templates/{id}/delete")
     public Result<Boolean> deleteTaskTemplate(@PathVariable Long id) {
     public Result<Boolean> deleteTaskTemplate(@PathVariable Long id) {
         taskTemplateMapper.deleteById(id);
         taskTemplateMapper.deleteById(id);
         return Result.success(true);
         return Result.success(true);

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

@@ -7,22 +7,22 @@ import com.etotem.cfc.entity.GuidePackage;
 import com.etotem.cfc.service.GuideApplicationService;
 import com.etotem.cfc.service.GuideApplicationService;
 import com.etotem.cfc.service.GuidePackageService;
 import com.etotem.cfc.service.GuidePackageService;
 import com.etotem.cfc.service.GuideService;
 import com.etotem.cfc.service.GuideService;
-import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
+import javax.annotation.Resource;
 import java.util.List;
 import java.util.List;
 
 
 @RestController
 @RestController
 @RequestMapping("/api/admin/guide")
 @RequestMapping("/api/admin/guide")
 public class AdminGuideController {
 public class AdminGuideController {
 
 
-    @Autowired
+    @Resource
     private GuideApplicationService guideApplicationService;
     private GuideApplicationService guideApplicationService;
 
 
-    @Autowired
+    @Resource
     private GuideService guideService;
     private GuideService guideService;
 
 
-    @Autowired
+    @Resource
     private GuidePackageService guidePackageService;
     private GuidePackageService guidePackageService;
 
 
     @PostMapping("/applications/pending")
     @PostMapping("/applications/pending")

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminSubscriptionController.java

@@ -103,7 +103,7 @@ public class AdminSubscriptionController {
         return Result.success(null);
         return Result.success(null);
     }
     }
 
 
-    @GetMapping("/benefits")
+    @PostMapping("/benefits")
     public Result<List<Map<String, Object>>> getBenefitConfig() {
     public Result<List<Map<String, Object>>> getBenefitConfig() {
         List<Map<String, Object>> benefits = new ArrayList<>();
         List<Map<String, Object>> benefits = new ArrayList<>();
         return Result.success(benefits);
         return Result.success(benefits);

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

@@ -50,7 +50,7 @@ public class PackageTemplateController {
         return Result.success(created);
         return Result.success(created);
     }
     }
 
 
-    @PutMapping("/{id}")
+    @PostMapping("/{id}/update")
     @Operation(summary = "更新套餐模板")
     @Operation(summary = "更新套餐模板")
     public Result<GuidePackageTemplate> update(@PathVariable Long id, @RequestBody GuidePackageTemplate template) {
     public Result<GuidePackageTemplate> update(@PathVariable Long id, @RequestBody GuidePackageTemplate template) {
         template.setId(id);
         template.setId(id);
@@ -59,7 +59,7 @@ public class PackageTemplateController {
         return success ? Result.success(template) : Result.error("更新失败");
         return success ? Result.success(template) : Result.error("更新失败");
     }
     }
 
 
-    @DeleteMapping("/{id}")
+    @PostMapping("/{id}/delete")
     @Operation(summary = "删除套餐模板")
     @Operation(summary = "删除套餐模板")
     public Result<Void> delete(@PathVariable Long id) {
     public Result<Void> delete(@PathVariable Long id) {
         boolean success = templateService.deleteTemplate(id);
         boolean success = templateService.deleteTemplate(id);

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/ai/ButlerController.java

@@ -13,7 +13,7 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.Map;
 
 
 @Tag(name = "AI管家", description = "AI管家会话管理接口")
 @Tag(name = "AI管家", description = "AI管家会话管理接口")
-@RestController
+@RestController("aiButlerController")
 @RequestMapping("/api/ai/butler")
 @RequestMapping("/api/ai/butler")
 public class ButlerController {
 public class ButlerController {
 
 

+ 25 - 42
cfc-backend/src/main/java/com/etotem/cfc/controller/butler/ButlerController.java

@@ -6,6 +6,7 @@ import com.etotem.cfc.common.Result;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.ButlerService;
 import com.etotem.cfc.service.ButlerService;
+import io.swagger.v3.oas.annotations.Operation;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
@@ -14,7 +15,7 @@ import java.util.Date;
 import java.util.HashMap;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map;
 
 
-@RestController
+@RestController("butlerController")
 @RequestMapping("/api/butler")
 @RequestMapping("/api/butler")
 public class ButlerController {
 public class ButlerController {
 
 
@@ -80,45 +81,27 @@ public class ButlerController {
         return Result.success(data);
         return Result.success(data);
     }
     }
 
 
-    /**
-     * 获取服务记录
-     * POST /api/butler/service-records
-     */
-    @PostMapping("/service-records")
-    public Result<Map<String, Object>> getServiceRecords(@RequestBody Map<String, Object> params,
-                                                           @RequestAttribute("userId") Long userId) {
-        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
-        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
-        String status = (String) params.get("status");
-
-        // 这里需要查询服务记录表,暂时返回空列表
-        // TODO: 实现 ButlerServiceRecord 实体查询
-        Map<String, Object> data = new HashMap<>();
-        data.put("list", java.util.Collections.emptyList());
-        data.put("total", 0L);
-        data.put("page", page);
-        data.put("size", size);
-        return Result.success(data);
-    }
-
-    /**
-     * 获取佣金记录
-     * POST /api/butler/commissions
-     */
-    @PostMapping("/commissions")
-    public Result<Map<String, Object>> getCommissions(@RequestBody Map<String, Object> params,
-                                                       @RequestAttribute("userId") Long userId) {
-        int page = params.get("page") != null ? ((Number) params.get("page")).intValue() : 1;
-        int size = params.get("size") != null ? ((Number) params.get("size")).intValue() : 20;
-        String status = (String) params.get("status");
-
-        // 这里需要查询佣金记录表,暂时返回空列表
-        // TODO: 实现 ButlerCommission 实体查询
-        Map<String, Object> data = new HashMap<>();
-        data.put("list", java.util.Collections.emptyList());
-        data.put("total", 0L);
-        data.put("page", page);
-        data.put("size", size);
-        return Result.success(data);
-    }
+/**
+ * 获取服务记录
+ * POST /api/butler/service-records
+ */
+@Deprecated
+@Operation(summary = "获取服务记录", deprecated = true)
+@PostMapping("/service-records")
+public Result<Map<String, Object>> getServiceRecords(@RequestBody Map<String, Object> params,
+@RequestAttribute("userId") Long userId) {
+throw new UnsupportedOperationException("管家服务记录功能暂未开放");
+}
+
+/**
+ * 获取佣金记录
+ * POST /api/butler/commissions
+ */
+@Deprecated
+@Operation(summary = "获取佣金记录", deprecated = true)
+@PostMapping("/commissions")
+public Result<Map<String, Object>> getCommissions(@RequestBody Map<String, Object> params,
+@RequestAttribute("userId") Long userId) {
+throw new UnsupportedOperationException("管家佣金功能暂未开放");
+}
 }
 }

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/family/InteractionLogController.java

@@ -76,7 +76,7 @@ public class InteractionLogController {
     }
     }
 
 
     @Operation(summary = "获取互动类型列表")
     @Operation(summary = "获取互动类型列表")
-    @GetMapping("/types")
+    @PostMapping("/types")
     public Result<Map<String, String>> getInteractionTypes() {
     public Result<Map<String, String>> getInteractionTypes() {
         return Result.success(interactionLogService.getInteractionTypes());
         return Result.success(interactionLogService.getInteractionTypes());
     }
     }

+ 44 - 9
cfc-backend/src/main/java/com/etotem/cfc/controller/family/ParentController.java

@@ -4,11 +4,16 @@ import com.etotem.cfc.common.Result;
 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.User;
 import com.etotem.cfc.entity.User;
+import com.etotem.cfc.entity.Wish;
+import com.etotem.cfc.mapper.WishMapper;
+import com.etotem.cfc.service.PointsService;
 import com.etotem.cfc.service.RewardService;
 import com.etotem.cfc.service.RewardService;
 import com.etotem.cfc.service.UserService;
 import com.etotem.cfc.service.UserService;
 import javax.annotation.Resource;
 import javax.annotation.Resource;
 import org.springframework.web.bind.annotation.*;
 import org.springframework.web.bind.annotation.*;
 
 
+import java.util.Date;
+import java.util.HashMap;
 import java.util.List;
 import java.util.List;
 import java.util.Map;
 import java.util.Map;
 
 
@@ -22,6 +27,12 @@ public class ParentController {
     @Resource
     @Resource
     private UserService userService;
     private UserService userService;
 
 
+    @Resource
+    private WishMapper wishMapper;
+
+    @Resource
+    private PointsService pointsService;
+
     /**
     /**
      * 家长心愿单 - 创建心愿(直接进入可用列表)
      * 家长心愿单 - 创建心愿(直接进入可用列表)
      */
      */
@@ -85,21 +96,45 @@ public class ParentController {
     @PostMapping("/wishlist/{id}/exchange")
     @PostMapping("/wishlist/{id}/exchange")
     public Result<Map<String, Object>> exchangeParentWishlist(@PathVariable Long id,
     public Result<Map<String, Object>> exchangeParentWishlist(@PathVariable Long id,
                                                                @RequestAttribute("userId") Long userId) {
                                                                @RequestAttribute("userId") Long userId) {
-        // 家长兑换自己的心愿,直接扣积分
         User user = userService.getUserInfo(userId);
         User user = userService.getUserInfo(userId);
         if (user == null) {
         if (user == null) {
             return Result.error("用户不存在");
             return Result.error("用户不存在");
         }
         }
-        
-        // 家长积分在user表
-        if (user.getTotalPoints() == null) {
-            user.setTotalPoints(0);
+
+        Wish wish = wishMapper.selectById(id);
+        if (wish == null) {
+            return Result.error("心愿不存在");
         }
         }
-        
-        // TODO: 实现家长兑换逻辑(暂时返回成功)
-        Map<String, Object> result = new java.util.HashMap<>();
+
+        String status = wish.getStatus();
+        if (!"active".equals(status) && !"approved".equals(status)) {
+            return Result.error("心愿当前状态不可兑换(" + status + ")");
+        }
+
+        if (wish.getPointsRequired() == null || wish.getPointsRequired() <= 0) {
+            return Result.error("心愿未设置积分");
+        }
+
+        int currentBalance = user.getTotalPoints() == null ? 0 : user.getTotalPoints();
+        if (currentBalance < wish.getPointsRequired()) {
+            return Result.error("积分不足,需要" + wish.getPointsRequired() + "积分,当前" + currentBalance + "积分");
+        }
+
+        boolean deducted = pointsService.deductUserPoints(userId, wish.getPointsRequired(), "心愿兑换:" + wish.getTitle());
+        if (!deducted) {
+            return Result.error("积分扣除失败");
+        }
+
+        wish.setStatus("exchanging");
+        wish.setPointsDeductedAt(new Date());
+        wish.setExchangeRequestedAt(new Date());
+        wish.setUpdatedAt(new Date());
+        wishMapper.updateById(wish);
+
+        Map<String, Object> result = new HashMap<>();
         result.put("success", true);
         result.put("success", true);
-        result.put("newBalance", user.getTotalPoints());
+        result.put("newBalance", currentBalance - wish.getPointsRequired());
+        result.put("wishId", wish.getId());
         return Result.success(result);
         return Result.success(result);
     }
     }
 }
 }

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

@@ -74,7 +74,7 @@ public class RelationshipQuestionnaireController {
     }
     }
 
 
     @Operation(summary = "获取最新问卷快照")
     @Operation(summary = "获取最新问卷快照")
-    @GetMapping("/snapshot/{memberId}")
+    @PostMapping("/snapshot/{memberId}")
     public Result<QuestionnaireSnapshotVO> getSnapshot(HttpServletRequest request,
     public Result<QuestionnaireSnapshotVO> getSnapshot(HttpServletRequest request,
                                                         @PathVariable Long memberId) {
                                                         @PathVariable Long memberId) {
         Long userId = getUserId(request);
         Long userId = getUserId(request);
@@ -90,7 +90,7 @@ public class RelationshipQuestionnaireController {
     }
     }
 
 
     @Operation(summary = "获取问卷历史记录")
     @Operation(summary = "获取问卷历史记录")
-    @GetMapping("/history/{memberId}")
+    @PostMapping("/history/{memberId}")
     public Result<List<RelationshipQuestionnaireResponse>> getHistory(HttpServletRequest request,
     public Result<List<RelationshipQuestionnaireResponse>> getHistory(HttpServletRequest request,
                                                                         @PathVariable Long memberId) {
                                                                         @PathVariable Long memberId) {
         Long userId = getUserId(request);
         Long userId = getUserId(request);

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

@@ -305,7 +305,7 @@ public class GuideFamilyTaskController {
         @ApiResponse(responseCode = "403", description = "Access denied"),
         @ApiResponse(responseCode = "403", description = "Access denied"),
         @ApiResponse(responseCode = "404", description = "任务不存在")
         @ApiResponse(responseCode = "404", description = "任务不存在")
     })
     })
-    @PutMapping("/{familyId}/tasks/{taskId}")
+    @PostMapping("/{familyId}/tasks/{taskId}")
     public Result<Boolean> updateTask(
     public Result<Boolean> updateTask(
             @PathVariable Long familyId,
             @PathVariable Long familyId,
             @PathVariable Long taskId,
             @PathVariable Long taskId,
@@ -356,7 +356,7 @@ public class GuideFamilyTaskController {
         @ApiResponse(responseCode = "403", description = "Access denied"),
         @ApiResponse(responseCode = "403", description = "Access denied"),
         @ApiResponse(responseCode = "404", description = "任务不存在")
         @ApiResponse(responseCode = "404", description = "任务不存在")
     })
     })
-    @DeleteMapping("/{familyId}/tasks/{taskId}")
+    @PostMapping("/{familyId}/tasks/{taskId}/delete")
     public Result<Boolean> deleteTask(
     public Result<Boolean> deleteTask(
             @PathVariable Long familyId,
             @PathVariable Long familyId,
             @PathVariable Long taskId,
             @PathVariable Long taskId,

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

@@ -297,7 +297,7 @@ public class GuideManagementController {
      * 更新成长规划师套餐价格
      * 更新成长规划师套餐价格
      */
      */
     @Operation(summary = "更新成长规划师套餐价格")
     @Operation(summary = "更新成长规划师套餐价格")
-    @PutMapping("/package/{packageId}")
+    @PostMapping("/package/{packageId}/update")
     public Result<Boolean> updateGuidePackage(@RequestAttribute("userId") Long userId,
     public Result<Boolean> updateGuidePackage(@RequestAttribute("userId") Long userId,
                                                @PathVariable Long packageId,
                                                @PathVariable Long packageId,
                                                @RequestBody Map<String, Object> params) {
                                                @RequestBody Map<String, Object> params) {
@@ -310,7 +310,7 @@ public class GuideManagementController {
      * 删除成长规划师套餐
      * 删除成长规划师套餐
      */
      */
     @Operation(summary = "删除成长规划师套餐")
     @Operation(summary = "删除成长规划师套餐")
-    @DeleteMapping("/package/{packageId}")
+    @PostMapping("/package/{packageId}/delete")
     public Result<Boolean> deleteGuidePackage(@RequestAttribute("userId") Long userId,
     public Result<Boolean> deleteGuidePackage(@RequestAttribute("userId") Long userId,
                                                @PathVariable Long packageId) {
                                                @PathVariable Long packageId) {
         boolean success = guidePackageService.deleteGuidePackage(userId, packageId);
         boolean success = guidePackageService.deleteGuidePackage(userId, packageId);

+ 28 - 72
cfc-backend/src/main/java/com/etotem/cfc/controller/guide/TeacherController.java

@@ -34,119 +34,75 @@ public class TeacherController {
     return Result.success(info);
     return Result.success(info);
   }
   }
 
 
-  /**
-   * 创建教学任务
-   */
-    @Operation(summary = "创建教学任务")
+    @Deprecated
+    @Operation(summary = "创建教学任务", deprecated = true)
     @PostMapping("/tasks")
     @PostMapping("/tasks")
     public Result<Long> createTask(@RequestAttribute("userId") Long teacherId,
     public Result<Long> createTask(@RequestAttribute("userId") Long teacherId,
       @RequestBody CreateTeacherTaskDTO dto) {
       @RequestBody CreateTeacherTaskDTO dto) {
-    // TODO: Implement createTask in TeacherService
-    // Long taskId = teacherService.createTask(teacherId, dto);
-    // return Result.success(taskId);
-    return Result.error("功能开发中");
+    throw new UnsupportedOperationException("教师功能暂未开放:createTask");
   }
   }
 
 
-  /**
-   * 获取教师任务列表
-   */
-    @Operation(summary = "获取教师任务列表")
+    @Deprecated
+    @Operation(summary = "获取教师任务列表", deprecated = true)
     @PostMapping("/tasks/list")
     @PostMapping("/tasks/list")
     public Result<List<TaskDTO>> getTaskList(@RequestAttribute("userId") Long teacherId) {
     public Result<List<TaskDTO>> getTaskList(@RequestAttribute("userId") Long teacherId) {
-    // TODO: Implement getTaskList in TeacherService
-    // List<TaskDTO> tasks = teacherService.getTaskList(teacherId);
-    // return Result.success(tasks);
-    return Result.success(null);
+    throw new UnsupportedOperationException("教师功能暂未开放:getTaskList");
   }
   }
 
 
-  /**
-   * 创建班级
-   */
-    @Operation(summary = "创建班级")
+    @Deprecated
+    @Operation(summary = "创建班级", deprecated = true)
     @PostMapping("/classes")
     @PostMapping("/classes")
     public Result<Long> createClass(@RequestAttribute("userId") Long teacherId,
     public Result<Long> createClass(@RequestAttribute("userId") Long teacherId,
       @RequestBody CreateClassDTO dto) {
       @RequestBody CreateClassDTO dto) {
-    // TODO: Implement createClass in TeacherService
-    // Long classId = teacherService.createClass(teacherId, dto);
-    // return Result.success(classId);
-    return Result.error("功能开发中");
+    throw new UnsupportedOperationException("教师功能暂未开放:createClass");
   }
   }
 
 
-  /**
-   * 获取教师班级列表
-   */
-    @Operation(summary = "获取教师班级列表")
+    @Deprecated
+    @Operation(summary = "获取教师班级列表", deprecated = true)
     @PostMapping("/classes/list")
     @PostMapping("/classes/list")
     public Result<List<ClassInfoDTO>> getClassList(@RequestAttribute("userId") Long teacherId) {
     public Result<List<ClassInfoDTO>> getClassList(@RequestAttribute("userId") Long teacherId) {
-    // TODO: Implement getClassList in TeacherService
-    // List<ClassInfoDTO> classes = teacherService.getClassList(teacherId);
-    // return Result.success(classes);
-    return Result.success(null);
+    throw new UnsupportedOperationException("教师功能暂未开放:getClassList");
   }
   }
 
 
-  /**
-   * 获取班级学生列表
-   */
-    @Operation(summary = "获取班级学生列表")
+    @Deprecated
+    @Operation(summary = "获取班级学生列表", deprecated = true)
     @PostMapping("/classes/{classId}/students")
     @PostMapping("/classes/{classId}/students")
     public Result<List<StudentInfoDTO>> getClassStudents(@RequestAttribute("userId") Long teacherId,
     public Result<List<StudentInfoDTO>> getClassStudents(@RequestAttribute("userId") Long teacherId,
       @PathVariable Long classId) {
       @PathVariable Long classId) {
-    // TODO: Implement getClassStudents in TeacherService
-    // List<StudentInfoDTO> students = teacherService.getClassStudents(teacherId, classId);
-    // return Result.success(students);
-    return Result.success(null);
+    throw new UnsupportedOperationException("教师功能暂未开放:getClassStudents");
   }
   }
 
 
-  /**
-   * 添加学生到班级
-   */
-    @Operation(summary = "添加学生到班级")
+    @Deprecated
+    @Operation(summary = "添加学生到班级", deprecated = true)
     @PostMapping("/classes/{classId}/students/add")
     @PostMapping("/classes/{classId}/students/add")
     public Result<Boolean> addStudentToClass(@RequestAttribute("userId") Long teacherId,
     public Result<Boolean> addStudentToClass(@RequestAttribute("userId") Long teacherId,
       @PathVariable Long classId,
       @PathVariable Long classId,
       @RequestBody Map<String, Long> body) {
       @RequestBody Map<String, Long> body) {
-    // Long studentId = body.get("studentId");
-    // boolean success = teacherService.addStudentToClass(teacherId, classId, studentId);
-    // return Result.success(success);
-    return Result.error("功能开发中");
+    throw new UnsupportedOperationException("教师功能暂未开放:addStudentToClass");
   }
   }
 
 
-  /**
-   * 从班级移除学生
-   */
-    @Operation(summary = "从班级移除学生")
-    @DeleteMapping("/classes/{classId}/students/{studentId}")
+    @Deprecated
+    @Operation(summary = "从班级移除学生", deprecated = true)
+    @PostMapping("/classes/{classId}/students/{studentId}")
     public Result<Boolean> removeStudentFromClass(@RequestAttribute("userId") Long teacherId,
     public Result<Boolean> removeStudentFromClass(@RequestAttribute("userId") Long teacherId,
       @PathVariable Long classId,
       @PathVariable Long classId,
       @PathVariable Long studentId) {
       @PathVariable Long studentId) {
-    // boolean success = teacherService.removeStudentFromClass(teacherId, classId, studentId);
-    // return Result.success(success);
-    return Result.error("功能开发中");
+    throw new UnsupportedOperationException("教师功能暂未开放:removeStudentFromClass");
   }
   }
 
 
-  /**
-   * 获取学生学习进度
-   */
-    @Operation(summary = "获取学生学习进度")
+    @Deprecated
+    @Operation(summary = "获取学生学习进度", deprecated = true)
     @PostMapping("/students/{studentId}/progress")
     @PostMapping("/students/{studentId}/progress")
     public Result<StudentProgressDTO> getStudentProgress(@RequestAttribute("userId") Long teacherId,
     public Result<StudentProgressDTO> getStudentProgress(@RequestAttribute("userId") Long teacherId,
       @PathVariable Long studentId) {
       @PathVariable Long studentId) {
-    // TODO: Implement getStudentProgress in TeacherService
-    // StudentProgressDTO progress = teacherService.getStudentProgress(teacherId, studentId);
-    // return Result.success(progress);
-    return Result.success(null);
+    throw new UnsupportedOperationException("教师功能暂未开放:getStudentProgress");
   }
   }
 
 
-  /**
-   * 获取班级学习报告
-   */
-    @Operation(summary = "获取班级学习报告")
+    @Deprecated
+    @Operation(summary = "获取班级学习报告", deprecated = true)
     @PostMapping("/classes/{classId}/report")
     @PostMapping("/classes/{classId}/report")
     public Result<ClassReportDTO> getClassReport(@RequestAttribute("userId") Long teacherId,
     public Result<ClassReportDTO> getClassReport(@RequestAttribute("userId") Long teacherId,
       @PathVariable Long classId) {
       @PathVariable Long classId) {
-    // TODO: Implement getClassReport in TeacherService
-    // ClassReportDTO report = teacherService.getClassReport(teacherId, classId);
-    // return Result.success(report);
-    return Result.success(null);
+    throw new UnsupportedOperationException("教师功能暂未开放:getClassReport");
   }
   }
 }
 }

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/task/TaskController.java

@@ -113,7 +113,7 @@ public class TaskController {
     }
     }
 
 
     @Operation(summary = "删除任务")
     @Operation(summary = "删除任务")
-    @DeleteMapping("/{id}")
+    @PostMapping("/{id}")
     public Result<Boolean> deleteTask(@PathVariable Long id,
     public Result<Boolean> deleteTask(@PathVariable Long id,
             @RequestAttribute("userId") Long userId) {
             @RequestAttribute("userId") Long userId) {
         boolean success = taskService.deleteTask(id, userId);
         boolean success = taskService.deleteTask(id, userId);

+ 3 - 3
cfc-backend/src/main/java/com/etotem/cfc/controller/vendor/VendorController.java

@@ -19,7 +19,7 @@ public class VendorController {
     private VendorService vendorService;
     private VendorService vendorService;
 
 
     @PostMapping("/apply")
     @PostMapping("/apply")
-    public Result apply(@RequestBody Map<String, Object> params,
+    public Result<String> apply(@RequestBody Map<String, Object> params,
                         @RequestAttribute("userId") Long userId) {
                         @RequestAttribute("userId") Long userId) {
         String vendorType = (String) params.get("vendorType");
         String vendorType = (String) params.get("vendorType");
         String vendorInfo = (String) params.getOrDefault("vendorInfo", "");
         String vendorInfo = (String) params.getOrDefault("vendorInfo", "");
@@ -27,12 +27,12 @@ public class VendorController {
     }
     }
 
 
     @PostMapping("/status")
     @PostMapping("/status")
-    public Result getStatus(@RequestAttribute("userId") Long userId) {
+    public Result<Map<String, Object>> getStatus(@RequestAttribute("userId") Long userId) {
         return vendorService.getStatus(userId);
         return vendorService.getStatus(userId);
     }
     }
 
 
     @PostMapping("/info")
     @PostMapping("/info")
-    public Result getVendorInfo(@RequestAttribute("userId") Long userId) {
+    public Result<Map<String, Object>> getVendorInfo(@RequestAttribute("userId") Long userId) {
         return vendorService.getVendorInfo(userId);
         return vendorService.getVendorInfo(userId);
     }
     }
 }
 }

+ 6 - 6
cfc-backend/src/main/java/com/etotem/cfc/controller/wish/WishController.java

@@ -50,7 +50,7 @@ public class WishController {
     // ========== 定价流程 ==========
     // ========== 定价流程 ==========
 
 
     @Operation(summary = "家长定价")
     @Operation(summary = "家长定价")
-    @PutMapping("/{id}/set-price")
+    @PostMapping("/{id}/set-price")
     public Result<Boolean> setPrice(
     public Result<Boolean> setPrice(
             @PathVariable Long id,
             @PathVariable Long id,
             @RequestAttribute("userId") Long userId,
             @RequestAttribute("userId") Long userId,
@@ -72,7 +72,7 @@ public class WishController {
     }
     }
 
 
     @Operation(summary = "家长拒绝心愿")
     @Operation(summary = "家长拒绝心愿")
-    @PutMapping("/{id}/reject")
+    @PostMapping("/{id}/reject")
     public Result<Boolean> rejectWish(
     public Result<Boolean> rejectWish(
             @PathVariable Long id,
             @PathVariable Long id,
             @RequestAttribute("userId") Long userId,
             @RequestAttribute("userId") Long userId,
@@ -110,7 +110,7 @@ public class WishController {
     }
     }
 
 
     @Operation(summary = "家长审批兑换")
     @Operation(summary = "家长审批兑换")
-    @PutMapping("/{id}/approve-exchange")
+    @PostMapping("/{id}/approve-exchange")
     public Result<Boolean> approveExchange(
     public Result<Boolean> approveExchange(
             @PathVariable Long id,
             @PathVariable Long id,
             @RequestAttribute("userId") Long userId,
             @RequestAttribute("userId") Long userId,
@@ -135,7 +135,7 @@ public class WishController {
     // ========== 兑现流程 ==========
     // ========== 兑现流程 ==========
 
 
     @Operation(summary = "家长标记已购买")
     @Operation(summary = "家长标记已购买")
-    @PutMapping("/{id}/fulfill")
+    @PostMapping("/{id}/fulfill")
     public Result<Boolean> markFulfilled(
     public Result<Boolean> markFulfilled(
             @PathVariable Long id,
             @PathVariable Long id,
             @RequestAttribute("userId") Long userId) {
             @RequestAttribute("userId") Long userId) {
@@ -152,7 +152,7 @@ public class WishController {
     }
     }
 
 
     @Operation(summary = "孩子确认收货")
     @Operation(summary = "孩子确认收货")
-    @PutMapping("/{id}/confirm")
+    @PostMapping("/{id}/confirm")
     public Result<Boolean> confirmReceived(
     public Result<Boolean> confirmReceived(
             @PathVariable Long id,
             @PathVariable Long id,
             @RequestAttribute("userId") Long userId) {
             @RequestAttribute("userId") Long userId) {
@@ -171,7 +171,7 @@ public class WishController {
     // ========== 取消心愿 ==========
     // ========== 取消心愿 ==========
 
 
     @Operation(summary = "取消心愿")
     @Operation(summary = "取消心愿")
-    @DeleteMapping("/{id}")
+    @PostMapping("/{id}/cancel")
     public Result<Boolean> cancelWish(
     public Result<Boolean> cancelWish(
             @PathVariable Long id,
             @PathVariable Long id,
             @RequestAttribute("userId") Long userId,
             @RequestAttribute("userId") Long userId,

+ 15 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyEarningsMemberDTO.java

@@ -0,0 +1,15 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+
+@Data
+public class FamilyEarningsMemberDTO {
+    private Long userId;
+    private String nickname;
+    private String avatar;
+    private Long allocatedAmount; // 已分配金额(分)
+    private Long consumedAmount; // 已消费金额(分)
+    private Long availableAmount; // 可用余额(分)
+    private Boolean canUseFamilyBalance; // 是否允许使用家庭公共池
+    private Boolean isAdmin; // 是否是家庭管理员
+}

+ 16 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/FamilyEarningsSummaryDTO.java

@@ -0,0 +1,16 @@
+package com.etotem.cfc.dto;
+
+import lombok.Data;
+
+import java.util.List;
+
+@Data
+public class FamilyEarningsSummaryDTO {
+    private Long familyId;
+    private Long totalEarned; // 累计推荐收入(分)
+    private Long availableAmount; // 当前可支配余额(分)
+    private Long withdrawnAmount; // 已提现金额(分)
+    private Long distributedAmount; // 已颁发给成员的金额(分)
+    
+    private List<FamilyEarningsMemberDTO> members;
+}

+ 45 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/ButlerCommissionSettlement.java

@@ -0,0 +1,45 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("butler_commission_settlements")
+public class ButlerCommissionSettlement implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long butlerId;
+
+    private String yearMonth;
+
+    private Integer memberCount;
+
+    private Long annualBaseAmountP;
+
+    private Long annualButlerAmountP;
+
+    private Long annualPlatformAmountP;
+
+    private Long perSessionTotalP;
+
+    private Long perSessionPlatformTotalP;
+
+    private Long perSessionButlerTotalP;
+
+    private Long totalButlerAmountP;
+
+    private String status;
+
+    private Date settledAt;
+
+    private Long settledBy;
+
+    private Date createdAt;
+}

+ 39 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/ButlerServiceRecord.java

@@ -0,0 +1,39 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("butler_service_records")
+public class ButlerServiceRecord implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long butlerId;
+
+    private Long memberUserId;
+
+    private Long subscriptionId;
+
+    private String serviceType;
+
+    private Long amountP;
+
+    private Long platformFeeP;
+
+    private Long butlerAmountP;
+
+    private String memberTier;
+
+    private String yearMonth;
+
+    private String remark;
+
+    private Date createdAt;
+}

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

@@ -44,5 +44,7 @@ public class CommissionRecord implements Serializable {
 
 
     private String remark;
     private String remark;
 
 
+    private Long familyId;      // 归属家庭ID(家庭级推广体系)
+
     private Date createdAt;
     private Date createdAt;
 }
 }

+ 31 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarnings.java

@@ -0,0 +1,31 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("family_earnings")
+public class FamilyEarnings implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Long familyId; // 家庭ID
+    
+    private Long totalEarned = 0L; // 累计推荐收入(分)
+    
+    private Long availableAmount = 0L; // 当前可支配余额(分)
+    
+    private Long withdrawnAmount = 0L; // 已提现金额(分)
+    
+    private Long distributedAmount = 0L; // 已颁发给成员的金额(分)
+    
+    private Date updatedAt;
+    
+    private Date createdAt;
+}

+ 33 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarningsMember.java

@@ -0,0 +1,33 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("family_earnings_member")
+public class FamilyEarningsMember implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Long familyId; // 家庭ID
+    
+    private Long userId; // 成员用户ID
+    
+    private Long allocatedAmount = 0L; // 管理员累计分配(分)
+    
+    private Long consumedAmount = 0L; // 已消费金额(分)
+    
+    private Long availableAmount = 0L; // 可消费余额 = allocated - consumed(分)
+    
+    private Boolean canUseFamilyBalance = false; // 是否允许使用家庭公共池
+    
+    private Date updatedAt;
+    
+    private Date createdAt;
+}

+ 31 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FamilyEarningsRecord.java

@@ -0,0 +1,31 @@
+package com.etotem.cfc.entity;
+
+import com.baomidou.mybatisplus.annotation.IdType;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+
+import java.io.Serializable;
+import java.util.Date;
+
+@Data
+@TableName("family_earnings_record")
+public class FamilyEarningsRecord implements Serializable {
+    
+    @TableId(type = IdType.AUTO)
+    private Long id;
+    
+    private Long familyId; // 家庭ID
+    
+    private String type; // earn 收入/withdraw 提现/distribute 颁发/consume 消费
+    
+    private Long amount; // 金额(分)
+    
+    private Long fromUserId; // 操作人
+    
+    private Long toUserId; // 受益人(颁发/消费时填写)
+    
+    private String note; // 备注
+    
+    private Date createdAt;
+}

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

@@ -19,6 +19,7 @@ public class SupplySystem implements Serializable {
     private BigDecimal platformProfitRate;
     private BigDecimal platformProfitRate;
     private String description;
     private String description;
     private String status;
     private String status;
+    private String models;
     private Date createdAt;
     private Date createdAt;
     private Date updatedAt;
     private Date updatedAt;
 }
 }

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/ButlerCommissionSettlementMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.ButlerCommissionSettlement;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ButlerCommissionSettlementMapper extends BaseMapper<ButlerCommissionSettlement> {
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/ButlerServiceRecordMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.ButlerServiceRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface ButlerServiceRecordMapper extends BaseMapper<ButlerServiceRecord> {
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.FamilyEarnings;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface FamilyEarningsMapper extends BaseMapper<FamilyEarnings> {
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsMemberMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.FamilyEarningsMember;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface FamilyEarningsMemberMapper extends BaseMapper<FamilyEarningsMember> {
+}

+ 9 - 0
cfc-backend/src/main/java/com/etotem/cfc/mapper/FamilyEarningsRecordMapper.java

@@ -0,0 +1,9 @@
+package com.etotem.cfc.mapper;
+
+import com.baomidou.mybatisplus.core.mapper.BaseMapper;
+import com.etotem.cfc.entity.FamilyEarningsRecord;
+import org.apache.ibatis.annotations.Mapper;
+
+@Mapper
+public interface FamilyEarningsRecordMapper extends BaseMapper<FamilyEarningsRecord> {
+}

+ 226 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/ButlerCommissionService.java

@@ -0,0 +1,226 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.ButlerCommissionSettlement;
+import com.etotem.cfc.entity.ButlerProfile;
+import com.etotem.cfc.entity.ButlerServiceRecord;
+import com.etotem.cfc.entity.SysConfig;
+import com.etotem.cfc.mapper.ButlerCommissionSettlementMapper;
+import com.etotem.cfc.mapper.ButlerProfileMapper;
+import com.etotem.cfc.mapper.ButlerServiceRecordMapper;
+import com.etotem.cfc.mapper.SysConfigMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.time.LocalDate;
+import java.time.format.DateTimeFormatter;
+import java.util.Date;
+import java.util.List;
+
+@Service
+public class ButlerCommissionService {
+
+    private static final Logger log = LoggerFactory.getLogger(ButlerCommissionService.class);
+
+    @Resource
+    private ButlerServiceRecordMapper serviceRecordMapper;
+
+    @Resource
+    private ButlerCommissionSettlementMapper settlementMapper;
+
+    @Resource
+    private ButlerProfileMapper butlerProfileMapper;
+
+    @Resource
+    private SysConfigMapper sysConfigMapper;
+
+    // 默认配置(基点为10000=100%)
+    private static final int DEFAULT_PLATFORM_RATE = 2000;       // 平台服务费率 20%
+    private static final int DEFAULT_ANNUAL_L1_RATE = 1000;      // L1年费管家分润率 10%
+    private static final int DEFAULT_ANNUAL_L2_RATE = 5000;      // L2年费管家分润率 50%
+    private static final int DEFAULT_PER_SESSION_PLATFORM_RATE = 2000; // 按次平台费率 20%
+
+    /**
+     * 创建按次服务记录
+     * @param butlerId 管家ID
+     * @param memberUserId 会员ID
+     * @param amount 服务金额(P点)
+     * @param remark 备注
+     * @return 创建的服务记录
+     */
+    @Transactional
+    public ButlerServiceRecord createServiceRecord(Long butlerId, Long memberUserId, Long amount, String remark) {
+        int platformRate = getSysConfigInt("butler_per_session_platform_rate", DEFAULT_PER_SESSION_PLATFORM_RATE);
+        long platformFee = amount * platformRate / 10000;
+        long butlerAmount = amount - platformFee;
+
+        ButlerServiceRecord record = new ButlerServiceRecord();
+        record.setButlerId(butlerId);
+        record.setMemberUserId(memberUserId);
+        record.setServiceType("per_session");
+        record.setAmountP(amount);
+        record.setPlatformFeeP(platformFee);
+        record.setButlerAmountP(butlerAmount);
+        record.setYearMonth(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM")));
+        record.setRemark(remark);
+        record.setCreatedAt(new Date());
+        serviceRecordMapper.insert(record);
+
+        log.info("Created per-session service record: butlerId={}, amount={}, platformFee={}, butlerAmount={}",
+                butlerId, amount, platformFee, butlerAmount);
+        return record;
+    }
+
+    /**
+     * 计算指定管家某月的结算
+     * @param butlerId 管家ID
+     * @param yearMonth YYYY-MM格式
+     * @return 结算单ID
+     */
+    @Transactional
+    public Long calculateButlerSettlement(Long butlerId, String yearMonth) {
+        // 查询管家当月所有服务记录
+        List<ButlerServiceRecord> records = serviceRecordMapper.selectList(
+                new LambdaQueryWrapper<ButlerServiceRecord>()
+                        .eq(ButlerServiceRecord::getButlerId, butlerId)
+                        .eq(ButlerServiceRecord::getYearMonth, yearMonth)
+        );
+
+        if (records.isEmpty()) {
+            log.info("No service records for butlerId={} in {}", butlerId, yearMonth);
+            return null;
+        }
+
+        // 统计各项
+        long annualBaseAmount = 0;
+        long annualButlerAmount = 0;
+        long annualPlatformAmount = 0;
+        long perSessionTotal = 0;
+        long perSessionPlatformTotal = 0;
+        long perSessionButlerTotal = 0;
+        long totalButlerAmount = 0;
+        int memberCount = 0;
+
+        for (ButlerServiceRecord r : records) {
+            if ("annual".equals(r.getServiceType())) {
+                annualBaseAmount += r.getAmountP() != null ? r.getAmountP() : 0;
+                annualButlerAmount += r.getButlerAmountP() != null ? r.getButlerAmountP() : 0;
+                annualPlatformAmount += r.getPlatformFeeP() != null ? r.getPlatformFeeP() : 0;
+            } else if ("per_session".equals(r.getServiceType())) {
+                perSessionTotal += r.getAmountP() != null ? r.getAmountP() : 0;
+                perSessionPlatformTotal += r.getPlatformFeeP() != null ? r.getPlatformFeeP() : 0;
+                perSessionButlerTotal += r.getButlerAmountP() != null ? r.getButlerAmountP() : 0;
+            }
+        }
+        totalButlerAmount = annualButlerAmount + perSessionButlerTotal;
+
+        ButlerProfile profile = butlerProfileMapper.selectOne(
+                new LambdaQueryWrapper<ButlerProfile>().eq(ButlerProfile::getUserId, butlerId));
+        if (profile != null) {
+            memberCount = profile.getMemberCount() != null ? profile.getMemberCount() : 0;
+        }
+
+        // 创建或更新结算单
+        ButlerCommissionSettlement existing = settlementMapper.selectOne(
+                new LambdaQueryWrapper<ButlerCommissionSettlement>()
+                        .eq(ButlerCommissionSettlement::getButlerId, butlerId)
+                        .eq(ButlerCommissionSettlement::getYearMonth, yearMonth)
+        );
+
+        if (existing != null) {
+            existing.setMemberCount(memberCount);
+            existing.setAnnualBaseAmountP(annualBaseAmount);
+            existing.setAnnualButlerAmountP(annualButlerAmount);
+            existing.setAnnualPlatformAmountP(annualPlatformAmount);
+            existing.setPerSessionTotalP(perSessionTotal);
+            existing.setPerSessionPlatformTotalP(perSessionPlatformTotal);
+            existing.setPerSessionButlerTotalP(perSessionButlerTotal);
+            existing.setTotalButlerAmountP(totalButlerAmount);
+            settlementMapper.updateById(existing);
+            log.info("Updated settlement id={} for butlerId={}, month={}, totalButlerAmount={}",
+                    existing.getId(), butlerId, yearMonth, totalButlerAmount);
+            return existing.getId();
+        } else {
+            ButlerCommissionSettlement settlement = new ButlerCommissionSettlement();
+            settlement.setButlerId(butlerId);
+            settlement.setYearMonth(yearMonth);
+            settlement.setMemberCount(memberCount);
+            settlement.setAnnualBaseAmountP(annualBaseAmount);
+            settlement.setAnnualButlerAmountP(annualButlerAmount);
+            settlement.setAnnualPlatformAmountP(annualPlatformAmount);
+            settlement.setPerSessionTotalP(perSessionTotal);
+            settlement.setPerSessionPlatformTotalP(perSessionPlatformTotal);
+            settlement.setPerSessionButlerTotalP(perSessionButlerTotal);
+            settlement.setTotalButlerAmountP(totalButlerAmount);
+            settlement.setStatus("pending");
+            settlement.setCreatedAt(new Date());
+            settlementMapper.insert(settlement);
+            log.info("Created settlement id={} for butlerId={}, month={}, totalButlerAmount={}",
+                    settlement.getId(), butlerId, yearMonth, totalButlerAmount);
+            return settlement.getId();
+        }
+    }
+
+    /**
+     * 批量计算所有管家的月度结算
+     * @param yearMonth YYYY-MM格式,null表示上月
+     * @return 处理的管家数
+     */
+    @Transactional
+    public int batchCalculateMonthlySettlement(String yearMonth) {
+        if (yearMonth == null) {
+            LocalDate lastMonth = LocalDate.now().minusMonths(1);
+            yearMonth = lastMonth.format(DateTimeFormatter.ofPattern("yyyy-MM"));
+        }
+
+        // 查询所有审核通过的管家
+        List<ButlerProfile> butlers = butlerProfileMapper.selectList(
+                new LambdaQueryWrapper<ButlerProfile>()
+                        .eq(ButlerProfile::getStatus, "approved")
+        );
+
+        int count = 0;
+        for (ButlerProfile butler : butlers) {
+            Long settlementId = calculateButlerSettlement(butler.getUserId(), yearMonth);
+            if (settlementId != null) {
+                count++;
+            }
+        }
+
+        log.info("Batch settlement for {}: processed {} butlers", yearMonth, count);
+        return count;
+    }
+
+    /**
+     * 标记结算单为已发放
+     */
+    @Transactional
+    public void markSettlementPaid(Long settlementId, Long settledBy) {
+        ButlerCommissionSettlement settlement = settlementMapper.selectById(settlementId);
+        if (settlement == null) {
+            log.warn("Settlement not found: id={}", settlementId);
+            return;
+        }
+        settlement.setStatus("paid");
+        settlement.setSettledAt(new Date());
+        settlement.setSettledBy(settledBy);
+        settlementMapper.updateById(settlement);
+        log.info("Marked settlement id={} as paid", settlementId);
+    }
+
+    private int getSysConfigInt(String key, int defaultValue) {
+        SysConfig config = sysConfigMapper.selectOne(
+                new LambdaQueryWrapper<SysConfig>().eq(SysConfig::getConfigKey, key));
+        if (config != null && config.getConfigValue() != null) {
+            try {
+                return Integer.parseInt(config.getConfigValue());
+            } catch (NumberFormatException e) {
+                log.warn("Invalid sys_config value for {}: {}", key, config.getConfigValue());
+            }
+        }
+        return defaultValue;
+    }
+}

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

@@ -40,6 +40,9 @@ public class CommissionService {
 
 
     @Resource
     @Resource
     private ProductMapper productMapper;
     private ProductMapper productMapper;
+    
+    @Resource
+    private FamilyEarningsService familyEarningsService;
 
 
     @Resource
     @Resource
     private CommissionRecordMapper commissionRecordMapper;
     private CommissionRecordMapper commissionRecordMapper;
@@ -278,14 +281,23 @@ public class CommissionService {
                 }
                 }
             }
             }
 
 
-            // 创建L1佣金记录(金额>0才创建)
-            if (l1Amount > 0) {
-                createCommissionRecord(orderId, orderType, l1ReferrerId, buyerId,
-                        commissionType, orderAmount, 0, l1Amount,
-                        1, "settled", new Date());
+        // 创建L1佣金记录(金额>0才创建)
+        if (l1Amount > 0) {
+            Long familyId = l1Referrer.getFamilyId();
+            createCommissionRecord(orderId, orderType, l1ReferrerId, buyerId, 
+                commissionType, orderAmount, 0, l1Amount,
+                1, "settled", new Date(), familyId);
+            
+            if (familyId != null) {
+                try {
+                    familyEarningsService.earn(familyId, (long) l1Amount, l1ReferrerId);
+                } catch (Exception e) {
+                    log.error("更新家庭公共账户失败: familyId={}, amount={}", familyId, l1Amount, e);
+                }
             }
             }
+        }
 
 
-            // 更新推荐人的累计佣金
+        // 更新推荐人的累计佣金
             l1Referrer.setTotalCommissionEarned(
             l1Referrer.setTotalCommissionEarned(
                     (l1Referrer.getTotalCommissionEarned() != null ? l1Referrer.getTotalCommissionEarned() : 0L)
                     (l1Referrer.getTotalCommissionEarned() != null ? l1Referrer.getTotalCommissionEarned() : 0L)
                             + l1Amount);
                             + l1Amount);
@@ -305,7 +317,7 @@ public class CommissionService {
                     if (l2Amount > 0) {
                     if (l2Amount > 0) {
                         createCommissionRecord(orderId, orderType, l2ReferrerId, buyerId,
                         createCommissionRecord(orderId, orderType, l2ReferrerId, buyerId,
                                 "member", orderAmount, 0, l2Amount,
                                 "member", orderAmount, 0, l2Amount,
-                                2, "settled", new Date());
+                                2, "settled", new Date(), l2Referrer.getFamilyId());
 
 
                         l2Referrer.setTotalCommissionEarned(
                         l2Referrer.setTotalCommissionEarned(
                                 (l2Referrer.getTotalCommissionEarned() != null ? l2Referrer.getTotalCommissionEarned() : 0L)
                                 (l2Referrer.getTotalCommissionEarned() != null ? l2Referrer.getTotalCommissionEarned() : 0L)
@@ -334,7 +346,7 @@ public class CommissionService {
     private void createCommissionRecord(Long orderId, String orderType, Long referrerId,
     private void createCommissionRecord(Long orderId, String orderType, Long referrerId,
                                         Long buyerId, String commissionType, Integer orderAmount,
                                         Long buyerId, String commissionType, Integer orderAmount,
                                         Integer profitRate, Integer commissionAmount,
                                         Integer profitRate, Integer commissionAmount,
-                                        Integer level, String status, Date settledAt) {
+                                        Integer level, String status, Date settledAt, Long familyId) {
         CommissionRecord record = new CommissionRecord();
         CommissionRecord record = new CommissionRecord();
         record.setOrderId(orderId);
         record.setOrderId(orderId);
         record.setOrderType(orderType);
         record.setOrderType(orderType);
@@ -348,6 +360,9 @@ public class CommissionService {
         record.setStatus(status);
         record.setStatus(status);
         record.setSettledAt(settledAt);
         record.setSettledAt(settledAt);
         record.setCreatedAt(new Date());
         record.setCreatedAt(new Date());
+        if (familyId != null) {
+            record.setFamilyId(familyId);
+        }
         commissionRecordMapper.insert(record);
         commissionRecordMapper.insert(record);
     }
     }
 
 

+ 312 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/FamilyEarningsService.java

@@ -0,0 +1,312 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.dto.FamilyEarningsMemberDTO;
+import com.etotem.cfc.dto.FamilyEarningsSummaryDTO;
+import com.etotem.cfc.entity.*;
+import com.etotem.cfc.mapper.*;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+import java.util.stream.Collectors;
+
+@Service
+public class FamilyEarningsService {
+    
+    @Resource
+    private FamilyEarningsMapper familyEarningsMapper;
+    
+    @Resource
+    private FamilyEarningsMemberMapper familyEarningsMemberMapper;
+    
+    @Resource
+    private FamilyEarningsRecordMapper familyEarningsRecordMapper;
+    
+    @Resource
+    private UserMapper userMapper;
+    
+    @Resource
+    private FamilyMapper familyMapper;
+
+    /**
+     * 获取家庭收益总览
+     */
+    public FamilyEarningsSummaryDTO getFamilySummary(Long familyId) {
+        FamilyEarningsSummaryDTO summary = new FamilyEarningsSummaryDTO();
+        
+        // 获取家庭总账
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings != null) {
+            summary.setFamilyId(earnings.getFamilyId());
+            summary.setTotalEarned(earnings.getTotalEarned());
+            summary.setAvailableAmount(earnings.getAvailableAmount());
+            summary.setWithdrawnAmount(earnings.getWithdrawnAmount());
+            summary.setDistributedAmount(earnings.getDistributedAmount());
+        }
+        
+        // 获取成员列表
+        List<FamilyEarningsMemberDTO> members = getMemberContributions(familyId);
+        summary.setMembers(members);
+        
+        return summary;
+    }
+    
+    /**
+     * 获取家庭成员贡献列表
+     */
+    public List<FamilyEarningsMemberDTO> getMemberContributions(Long familyId) {
+        // 获取家庭成员
+        List<User> familyUsers = userMapper.selectList(
+            new LambdaQueryWrapper<User>().eq(User::getFamilyId, familyId)
+        );
+        
+        return familyUsers.stream().map(user -> {
+            FamilyEarningsMemberDTO dto = new FamilyEarningsMemberDTO();
+            dto.setUserId(user.getId());
+            dto.setNickname(user.getNickname());
+            dto.setAvatar(user.getAvatar());
+            dto.setIsAdmin(user.getIsFamilyAdmin() != null && user.getIsFamilyAdmin());
+            
+            // 获取成员收益信息
+            FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+                new LambdaQueryWrapper<FamilyEarningsMember>()
+                    .eq(FamilyEarningsMember::getFamilyId, familyId)
+                    .eq(FamilyEarningsMember::getUserId, user.getId())
+            );
+            
+            if (member != null) {
+                dto.setAllocatedAmount(member.getAllocatedAmount());
+                dto.setConsumedAmount(member.getConsumedAmount());
+                dto.setAvailableAmount(member.getAvailableAmount());
+                dto.setCanUseFamilyBalance(member.getCanUseFamilyBalance());
+            } else {
+                // 初始化默认值
+                dto.setAllocatedAmount(0L);
+                dto.setConsumedAmount(0L);
+                dto.setAvailableAmount(0L);
+                dto.setCanUseFamilyBalance(false);
+            }
+            
+            return dto;
+        }).collect(Collectors.toList());
+    }
+    
+    /**
+     * 收入(佣金结算时调用)
+     */
+    @Transactional
+    public void earn(Long familyId, Long amount, Long fromUserId) {
+        if (familyId == null || amount == null || amount <= 0) {
+            return;
+        }
+        
+        // 获取或创建家庭收益记录
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings == null) {
+            earnings = new FamilyEarnings();
+            earnings.setFamilyId(familyId);
+            earnings.setTotalEarned(amount);
+            earnings.setAvailableAmount(amount);
+            earnings.setWithdrawnAmount(0L);
+            earnings.setDistributedAmount(0L);
+            earnings.setCreatedAt(new Date());
+            earnings.setUpdatedAt(new Date());
+            familyEarningsMapper.insert(earnings);
+        } else {
+            earnings.setTotalEarned(earnings.getTotalEarned() + amount);
+            earnings.setAvailableAmount(earnings.getAvailableAmount() + amount);
+            earnings.setUpdatedAt(new Date());
+            familyEarningsMapper.updateById(earnings);
+        }
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("earn");
+        record.setAmount(amount);
+        record.setFromUserId(fromUserId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 提现(管理员发起)
+     */
+    @Transactional
+    public void withdraw(Long familyId, Long amount, Long adminUserId) {
+        if (familyId == null || amount == null || amount <= 0) {
+            throw new RuntimeException("提现金额必须大于0");
+        }
+        
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings == null || earnings.getAvailableAmount() < amount) {
+            throw new RuntimeException("可用余额不足");
+        }
+        
+        // 更新家庭收益
+        earnings.setAvailableAmount(earnings.getAvailableAmount() - amount);
+        earnings.setWithdrawnAmount(earnings.getWithdrawnAmount() + amount);
+        earnings.setUpdatedAt(new Date());
+        familyEarningsMapper.updateById(earnings);
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("withdraw");
+        record.setAmount(amount);
+        record.setFromUserId(adminUserId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 颁发给成员(管理员分配)
+     */
+    @Transactional
+    public void distribute(Long familyId, Long toUserId, Long amount, Long adminUserId) {
+        if (familyId == null || toUserId == null || amount == null || amount <= 0) {
+            throw new RuntimeException("分配金额必须大于0");
+        }
+        
+        FamilyEarnings earnings = familyEarningsMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarnings>().eq(FamilyEarnings::getFamilyId, familyId)
+        );
+        
+        if (earnings == null || earnings.getAvailableAmount() < amount) {
+            throw new RuntimeException("可用余额不足");
+        }
+        
+        // 更新家庭收益
+        earnings.setAvailableAmount(earnings.getAvailableAmount() - amount);
+        earnings.setDistributedAmount(earnings.getDistributedAmount() + amount);
+        earnings.setUpdatedAt(new Date());
+        familyEarningsMapper.updateById(earnings);
+        
+        // 获取或创建成员收益记录
+        FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarningsMember>()
+                .eq(FamilyEarningsMember::getFamilyId, familyId)
+                .eq(FamilyEarningsMember::getUserId, toUserId)
+        );
+        
+        if (member == null) {
+            member = new FamilyEarningsMember();
+            member.setFamilyId(familyId);
+            member.setUserId(toUserId);
+            member.setAllocatedAmount(amount);
+            member.setConsumedAmount(0L);
+            member.setAvailableAmount(amount);
+            member.setCanUseFamilyBalance(false); // 默认不允许使用家庭公共池
+            member.setCreatedAt(new Date());
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.insert(member);
+        } else {
+            member.setAllocatedAmount(member.getAllocatedAmount() + amount);
+            member.setAvailableAmount(member.getAvailableAmount() + amount);
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.updateById(member);
+        }
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("distribute");
+        record.setAmount(amount);
+        record.setFromUserId(adminUserId);
+        record.setToUserId(toUserId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 消费扣款(支付时调用)
+     */
+    @Transactional
+    public void consume(Long familyId, Long userId, Long amount) {
+        if (familyId == null || userId == null || amount == null || amount <= 0) {
+            throw new RuntimeException("消费金额必须大于0");
+        }
+        
+        // 检查成员是否有足够的可用余额
+        FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarningsMember>()
+                .eq(FamilyEarningsMember::getFamilyId, familyId)
+                .eq(FamilyEarningsMember::getUserId, userId)
+        );
+        
+        if (member == null || member.getAvailableAmount() < amount) {
+            throw new RuntimeException("可用余额不足");
+        }
+        
+        // 更新成员收益
+        member.setConsumedAmount(member.getConsumedAmount() + amount);
+        member.setAvailableAmount(member.getAvailableAmount() - amount);
+        member.setUpdatedAt(new Date());
+        familyEarningsMemberMapper.updateById(member);
+        
+        // 记录流水
+        FamilyEarningsRecord record = new FamilyEarningsRecord();
+        record.setFamilyId(familyId);
+        record.setType("consume");
+        record.setAmount(amount);
+        record.setToUserId(userId);
+        record.setCreatedAt(new Date());
+        familyEarningsRecordMapper.insert(record);
+    }
+    
+    /**
+     * 流水查询
+     */
+    public List<FamilyEarningsRecord> getRecords(Long familyId, String type) {
+        LambdaQueryWrapper<FamilyEarningsRecord> wrapper = new LambdaQueryWrapper<FamilyEarningsRecord>()
+            .eq(FamilyEarningsRecord::getFamilyId, familyId);
+        
+        if (type != null && !type.isEmpty()) {
+            wrapper.eq(FamilyEarningsRecord::getType, type);
+        }
+        
+        wrapper.orderByDesc(FamilyEarningsRecord::getCreatedAt);
+        return familyEarningsRecordMapper.selectList(wrapper);
+    }
+    
+    /**
+     * 切换成员是否允许使用家庭公共池
+     */
+    @Transactional
+    public void toggleMemberAccess(Long familyId, Long userId, Boolean enabled) {
+        FamilyEarningsMember member = familyEarningsMemberMapper.selectOne(
+            new LambdaQueryWrapper<FamilyEarningsMember>()
+                .eq(FamilyEarningsMember::getFamilyId, familyId)
+                .eq(FamilyEarningsMember::getUserId, userId)
+        );
+        
+        if (member == null) {
+            member = new FamilyEarningsMember();
+            member.setFamilyId(familyId);
+            member.setUserId(userId);
+            member.setAllocatedAmount(0L);
+            member.setConsumedAmount(0L);
+            member.setAvailableAmount(0L);
+            member.setCanUseFamilyBalance(enabled);
+            member.setCreatedAt(new Date());
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.insert(member);
+        } else {
+            member.setCanUseFamilyBalance(enabled);
+            member.setUpdatedAt(new Date());
+            familyEarningsMemberMapper.updateById(member);
+        }
+    }
+}

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

@@ -159,9 +159,11 @@ public class PaymentService implements PaymentServiceInterface {
 
 
     @Override
     @Override
     public Boolean processPayment(String orderNo, String payType) {
     public Boolean processPayment(String orderNo, String payType) {
-        // TODO: In production, this would integrate with WeChat Pay / Alipay
-        // For test stub, always return true (payment always succeeds)
-        return true;
+        if (testMode) {
+            log.info("【测试模式】处理支付:orderNo={}, payType={}", orderNo, payType);
+            return true;
+        }
+        throw new UnsupportedOperationException("Payment not configured for production");
     }
     }
 
 
     public Map<String, Object> createWechatPrepay(String orderNo, String description, Integer totalFee, String openid) {
     public Map<String, Object> createWechatPrepay(String orderNo, String description, Integer totalFee, String openid) {

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

@@ -8,6 +8,7 @@ import com.etotem.cfc.service.api.TeacherServiceInterface;
 import org.springframework.stereotype.Service;
 import org.springframework.stereotype.Service;
 
 
 import javax.annotation.Resource;
 import javax.annotation.Resource;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Date;
 import java.util.List;
 import java.util.List;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
@@ -43,9 +44,9 @@ public class TeacherService {
         Teacher teacher = teacherMapper.selectOne(new LambdaQueryWrapper<Teacher>()
         Teacher teacher = teacherMapper.selectOne(new LambdaQueryWrapper<Teacher>()
                 .eq(Teacher::getUserId, userId));
                 .eq(Teacher::getUserId, userId));
         
         
-        if (teacher == null) {
-            return null;
-        }
+ if (teacher == null) {
+ throw new RuntimeException("教师信息不存在");
+ }
         
         
         return toDTO(teacher);
         return toDTO(teacher);
     }
     }
@@ -221,9 +222,9 @@ public class TeacherService {
      */
      */
     public List<ChildInfoDTO> getAuthorizedChildren(Long teacherId) {
     public List<ChildInfoDTO> getAuthorizedChildren(Long teacherId) {
         // 这里需要查询家长授权表,获取授权的孩子
         // 这里需要查询家长授权表,获取授权的孩子
-        // 简化实现:返回所有家庭的孩子
-        return null;
-    }
+ // 简化实现:返回所有家庭的孩子
+ return Collections.emptyList();
+}
 
 
     private String generateTeacherNo() {
     private String generateTeacherNo() {
         return "T" + System.currentTimeMillis();
         return "T" + System.currentTimeMillis();

+ 3 - 3
cfc-backend/src/main/java/com/etotem/cfc/service/VendorService.java

@@ -16,7 +16,7 @@ public class VendorService {
     @Resource
     @Resource
     private UserMapper userMapper;
     private UserMapper userMapper;
 
 
-    public Result apply(Long userId, String vendorType, String vendorInfo) {
+    public Result<String> apply(Long userId, String vendorType, String vendorInfo) {
         User user = userMapper.selectById(userId);
         User user = userMapper.selectById(userId);
         if (user == null) {
         if (user == null) {
             return Result.error("用户不存在");
             return Result.error("用户不存在");
@@ -43,7 +43,7 @@ public class VendorService {
         return Result.success("入驻申请已提交,请等待审核");
         return Result.success("入驻申请已提交,请等待审核");
     }
     }
 
 
-    public Result getStatus(Long userId) {
+    public Result<Map<String, Object>> getStatus(Long userId) {
         User user = userMapper.selectById(userId);
         User user = userMapper.selectById(userId);
         if (user == null) {
         if (user == null) {
             return Result.error("用户不存在");
             return Result.error("用户不存在");
@@ -55,7 +55,7 @@ public class VendorService {
         return Result.success(data);
         return Result.success(data);
     }
     }
 
 
-    public Result getVendorInfo(Long userId) {
+    public Result<Map<String, Object>> getVendorInfo(Long userId) {
         User user = userMapper.selectById(userId);
         User user = userMapper.selectById(userId);
         if (user == null) {
         if (user == null) {
             return Result.error("用户不存在");
             return Result.error("用户不存在");

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

@@ -48,9 +48,12 @@ public class VerificationCodeService implements VerificationCodeServiceInterface
         verificationCode.setUsed(0);
         verificationCode.setUsed(0);
         verificationCodeMapper.insert(verificationCode);
         verificationCodeMapper.insert(verificationCode);
 
 
-        // TODO: 调用短信平台发送验证码
-        log.info("生成验证码: phone={}, code={}, type={}", phone, code, type);
-        
+        if (testMode) {
+            log.info("Test mode: code = {}", code);
+        } else {
+            throw new UnsupportedOperationException("SMS provider not configured - cannot send SMS to " + phone);
+        }
+
         return code;
         return code;
     }
     }
 
 

+ 34 - 0
cfc-backend/src/main/java/com/etotem/cfc/task/ButlerCommissionScheduledTask.java

@@ -0,0 +1,34 @@
+package com.etotem.cfc.task;
+
+import com.etotem.cfc.service.ButlerCommissionService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 管家佣金结算定时任务
+ * 每月1日凌晨3:00自动结算上月管家佣金
+ */
+@Slf4j
+@Component
+public class ButlerCommissionScheduledTask {
+
+    @Resource
+    private ButlerCommissionService butlerCommissionService;
+
+    /**
+     * 每月1日 3:00 AM 执行
+     */
+    @Scheduled(cron = "0 0 3 1 * ?")
+    public void settleMonthlyCommissions() {
+        log.info("开始执行管家月度佣金结算定时任务");
+        try {
+            int count = butlerCommissionService.batchCalculateMonthlySettlement(null);
+            log.info("管家月度佣金结算完成,共处理{}个管家", count);
+        } catch (Exception e) {
+            log.error("管家月度佣金结算定时任务执行失败", e);
+        }
+    }
+}

+ 50 - 0
cfc-backend/src/main/resources/schema.sql

@@ -1160,8 +1160,12 @@ CREATE TABLE IF NOT EXISTS articles (
     author VARCHAR(100) DEFAULT '浠艾福' COMMENT '作者',
     author VARCHAR(100) DEFAULT '浠艾福' COMMENT '作者',
     read_time INT DEFAULT 0 COMMENT '预计阅读分钟数',
     read_time INT DEFAULT 0 COMMENT '预计阅读分钟数',
     article_type VARCHAR(20) DEFAULT 'normal' COMMENT '文章类型:normal(普通)/premium(优质)/original(原创)',
     article_type VARCHAR(20) DEFAULT 'normal' COMMENT '文章类型:normal(普通)/premium(优质)/original(原创)',
+    content_type VARCHAR(20) DEFAULT 'text' COMMENT '内容格式:text/html/markdown',
+    difficulty_level INT DEFAULT 0 COMMENT '难度等级:1-5',
     word_count INT DEFAULT 0 COMMENT '文章字数',
     word_count INT DEFAULT 0 COMMENT '文章字数',
+    dimension_ids VARCHAR(100) DEFAULT '' COMMENT '关联维度IDs JSON',
     related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维 JSON 数组',
     related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维 JSON 数组',
+    dimension_weights TEXT COMMENT '五维能量权重 JSON',
     visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private',
     visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限:public/login/private',
     visible_to TEXT COMMENT '私密指定人群 JSON',
     visible_to TEXT COMMENT '私密指定人群 JSON',
     status VARCHAR(20) DEFAULT 'draft' COMMENT '状态:draft/published/archived',
     status VARCHAR(20) DEFAULT 'draft' COMMENT '状态:draft/published/archived',
@@ -1258,10 +1262,12 @@ CREATE TABLE IF NOT EXISTS commission_records (
     status VARCHAR(16) NOT NULL DEFAULT 'settled' COMMENT '状态: pending/settled',
     status VARCHAR(16) NOT NULL DEFAULT 'settled' COMMENT '状态: pending/settled',
     remark TEXT DEFAULT NULL COMMENT '备注',
     remark TEXT DEFAULT NULL COMMENT '备注',
     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    family_id BIGINT DEFAULT NULL COMMENT '归属家庭ID',
     settled_at DATETIME DEFAULT NULL COMMENT '结算时间',
     settled_at DATETIME DEFAULT NULL COMMENT '结算时间',
     withdrawn_at DATETIME DEFAULT NULL COMMENT '提现时间',
     withdrawn_at DATETIME DEFAULT NULL COMMENT '提现时间',
     INDEX idx_referrer (referrer_id),
     INDEX idx_referrer (referrer_id),
     INDEX idx_buyer (buyer_id),
     INDEX idx_buyer (buyer_id),
+    INDEX idx_family (family_id),
     INDEX idx_order (order_id, order_type)
     INDEX idx_order (order_id, order_type)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金记录表';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金记录表';
 
 
@@ -1694,6 +1700,7 @@ CREATE TABLE IF NOT EXISTS activities (
     price INT DEFAULT 0 COMMENT '活动价格(分)',
     price INT DEFAULT 0 COMMENT '活动价格(分)',
     vendor_id        BIGINT COMMENT '服务商ID',
     vendor_id        BIGINT COMMENT '服务商ID',
     vendor_name      VARCHAR(100) COMMENT '服务商名称',
     vendor_name      VARCHAR(100) COMMENT '服务商名称',
+    dimension_weights TEXT COMMENT '五维能量权重JSON',
     created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
     created_at       DATETIME DEFAULT CURRENT_TIMESTAMP,
     updated_at       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     updated_at       DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     INDEX idx_dimension (dimension_code),
     INDEX idx_dimension (dimension_code),
@@ -2236,6 +2243,7 @@ CREATE TABLE IF NOT EXISTS supply_system (
     settlement_period_days INT NOT NULL DEFAULT 30 COMMENT '账期(天)',
     settlement_period_days INT NOT NULL DEFAULT 30 COMMENT '账期(天)',
     platform_profit_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '平台留利比例(%)',
     platform_profit_rate DECIMAL(5,2) NOT NULL DEFAULT 0 COMMENT '平台留利比例(%)',
     description TEXT COMMENT '描述',
     description TEXT COMMENT '描述',
+    models TEXT COMMENT 'AI模型列表JSON,如["agnes-image-2.0-flash","agnes-2.5-flash"]',
     status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active/disabled',
     status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active/disabled',
     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
     created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
     updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
@@ -2886,3 +2894,45 @@ CREATE TABLE IF NOT EXISTS butler_commission_settlements (
     INDEX idx_status (status),
     INDEX idx_status (status),
     INDEX idx_year_month (year_month)
     INDEX idx_year_month (year_month)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家月度佣金结算单';
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管家月度佣金结算单';
+
+-- 家庭公共账户体系
+
+CREATE TABLE family_earnings (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL UNIQUE COMMENT '家庭ID',
+    total_earned BIGINT NOT NULL DEFAULT 0 COMMENT '累计推荐收入(分)',
+    available_amount BIGINT NOT NULL DEFAULT 0 COMMENT '当前可支配余额(分)',
+    withdrawn_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已提现金额(分)',
+    distributed_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已颁发给成员的金额(分)',
+    updated_at DATETIME,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭公共账户';
+
+CREATE TABLE family_earnings_member (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL,
+    user_id BIGINT NOT NULL,
+    allocated_amount BIGINT NOT NULL DEFAULT 0 COMMENT '管理员累计分配(分)',
+    consumed_amount BIGINT NOT NULL DEFAULT 0 COMMENT '已消费金额(分)',
+    available_amount BIGINT NOT NULL DEFAULT 0 COMMENT '可消费余额=allocated-consumed(分)',
+    can_use_family_balance TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许使用家庭公共池',
+    updated_at DATETIME,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    UNIQUE KEY idx_family_user (family_id, user_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭成员已分配余额';
+
+CREATE TABLE family_earnings_record (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY,
+    family_id BIGINT NOT NULL,
+    type VARCHAR(20) NOT NULL COMMENT '类型: earn收入/withdraw提现/distribute颁发/consume消费',
+    amount BIGINT NOT NULL COMMENT '金额(分)',
+    from_user_id BIGINT COMMENT '操作人',
+    to_user_id BIGINT COMMENT '受益人(颁发/消费时填写)',
+    note VARCHAR(255) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    INDEX idx_family_id (family_id),
+    INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭收益收支流水';
+
+ALTER TABLE commission_records ADD COLUMN family_id BIGINT COMMENT '归属家庭ID';

+ 258 - 0
cfc-frontend/components/member-detail-sections/action-section.vue

@@ -0,0 +1,258 @@
+<template>
+<view class="action-section">
+  <!-- 今日任务 -->
+  <view class="section-card" v-if="todayTasks.length > 0">
+    <view class="section-header">
+      <text class="section-title">✅ 今日任务</text>
+    </view>
+    <view class="task-item" v-for="task in todayTasks" :key="task.id" @click="goTaskDetail(task)">
+      <view class="task-status" :class="task.status === 'completed' ? 'task-done' : 'task-pending'"></view>
+      <text class="task-name">{{ task.title || task.name }}</text>
+      <text class="task-points">{{ task.points || task.coin || 0 }}分</text>
+    </view>
+  </view>
+  <view class="section-card" v-else-if="!loading && childId">
+    <view class="empty-state">
+      <text class="empty-text">今日暂无任务</text>
+      <text class="empty-sub">去「行」维度页布置新任务吧</text>
+    </view>
+  </view>
+
+  <!-- 打卡统计 -->
+  <view class="section-card" v-if="checkinStats">
+    <view class="section-header">
+      <text class="section-title">📊 打卡统计</text>
+    </view>
+    <view class="stats-row">
+      <view class="stat-item">
+        <text class="stat-value">{{ checkinStats.totalDays || 0 }}</text>
+        <text class="stat-label">累计天数</text>
+      </view>
+      <view class="stat-item">
+        <text class="stat-value">{{ checkinStats.currentStreak || 0 }}</text>
+        <text class="stat-label">连续打卡</text>
+      </view>
+      <view class="stat-item">
+        <text class="stat-value">{{ checkinStats.thisMonth || 0 }}</text>
+        <text class="stat-label">本月次数</text>
+      </view>
+    </view>
+  </view>
+
+  <!-- 近期活动 -->
+  <view class="section-card" v-if="activityList.length > 0">
+    <view class="section-header">
+      <text class="section-title">🎪 近期活动</text>
+    </view>
+    <view class="activity-item" v-for="act in activityList" :key="act.id" @click="goActivityDetail(act)">
+      <text class="activity-name">{{ act.title || act.name }}</text>
+      <text class="activity-date">{{ formatDate(act.startTime || act.activityDate) }}</text>
+    </view>
+  </view>
+
+  <!-- 非孩子成员提示 -->
+  <view class="section-card" v-if="!childId && !loading">
+    <view class="empty-state">
+      <text class="empty-text">该成员暂无行动数据</text>
+    </view>
+  </view>
+
+  <!-- 加载中 -->
+  <view class="loading" v-if="loading">加载中...</view>
+</view>
+</template>
+
+<script>
+import { getTodayTasksByCategory, getCheckinStats, getActivityList } from '../../utils/api.js'
+
+export default {
+  props: {
+    memberId: {
+      type: [Number, String],
+      default: null
+    },
+    childId: {
+      type: [Number, String],
+      default: null
+    },
+    memberType: {
+      type: String,
+      default: ''
+    },
+    memberInfo: {
+      type: Object,
+      default: null
+    },
+    sandboxData: {
+      type: Object,
+      default: null
+    },
+    energyMap: {
+      type: Object,
+      default: null
+    }
+  },
+  data: function() {
+    return {
+      loading: true,
+      todayTasks: [],
+      checkinStats: null,
+      activityList: []
+    }
+  },
+  mounted: function() {
+    if (this.childId) {
+      this.loadData()
+    } else {
+      this.loading = false
+    }
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      getTodayTasksByCategory(this.childId, 'action').then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.todayTasks = Array.isArray(res.data) ? res.data : (res.data.records || [])
+        }
+      }).catch(function(e) {
+        console.log('加载今日任务失败', e)
+      })
+      
+      getCheckinStats({ childId: this.childId }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.checkinStats = res.data
+        }
+      }).catch(function(e) {
+        console.log('加载打卡统计失败', e)
+      })
+      
+      getActivityList({ page: 1, size: 3 }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.activityList = Array.isArray(res.data) ? res.data : (res.data.records || [])
+        }
+      }).catch(function(e) {
+        console.log('加载活动列表失败', e)
+      })
+      
+      this.loading = false
+    },
+    formatDate: function(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()
+    },
+    goTaskDetail: function(task) {
+      uni.navigateTo({
+        url: '/pages/action/task-detail?id=' + task.id
+      })
+    },
+    goActivityDetail: function(act) {
+      uni.navigateTo({
+        url: '/pages/action/activity-detail?id=' + act.id
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.action-section {}
+.section-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.section-header {
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.task-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 14rpx 0;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.task-status {
+  width: 16rpx;
+  height: 16rpx;
+  border-radius: 50%;
+  margin-right: 16rpx;
+}
+.task-done {
+  background: #10B981;
+}
+.task-pending {
+  background: #F59E0B;
+}
+.task-name {
+  flex: 1;
+  font-size: 28rpx;
+  color: #333;
+}
+.task-points {
+  font-size: 24rpx;
+  color: #10B981;
+}
+.stats-row {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  padding: 16rpx 0;
+}
+.stat-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.stat-value {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #10B981;
+}
+.stat-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 4rpx;
+}
+.activity-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 12rpx 0;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.activity-name {
+  font-size: 26rpx;
+  color: #333;
+  flex: 1;
+}
+.activity-date {
+  font-size: 22rpx;
+  color: #999;
+}
+.empty-state {
+  padding: 30rpx 0;
+  text-align: center;
+}
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+}
+.empty-sub {
+  font-size: 24rpx;
+  color: #ccc;
+  margin-top: 8rpx;
+}
+.loading {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+}
+</style>

+ 199 - 0
cfc-frontend/components/member-detail-sections/body-section.vue

@@ -0,0 +1,199 @@
+<template>
+  <view class="body-section">
+    <!-- 最新健康报告 -->
+    <view class="section-card" v-if="healthReport">
+      <view class="section-header">
+        <text class="section-title">📋 最新健康报告</text>
+      </view>
+      <view class="report-card" @click="goReportDetail">
+        <text class="report-score">{{ healthReport.overallScore || '--' }}</text>
+        <text class="report-label">综合健康分</text>
+      </view>
+      <view class="report-meta">
+        <text class="report-date">报告日期:{{ formatDate(healthReport.reportDate) }}</text>
+      </view>
+    </view>
+    <view class="section-card" v-else-if="!loading && childId">
+      <view class="empty-state">
+        <text class="empty-text">暂无健康报告</text>
+        <text class="empty-sub">规划师录入后将自动显示</text>
+      </view>
+    </view>
+
+    <!-- 七维健康图 -->
+    <view class="section-card" v-if="dimensionOverview && dimensionOverview.dimensions">
+      <view class="section-header">
+        <text class="section-title">📊 七维健康图</text>
+      </view>
+      <RadarChart
+        :dimensions="dimensionOverview.dimensions.map(function(d) { return { label: d.label, key: d.dimension } })"
+        :scores="dimensionOverview.dimensions.map(function(d) { return d.score })"
+        fillColor="#FF8C42"
+        gridColor="#FED7AA"
+        labelColor="#9A3412"
+        :width="580"
+        :height="400" />
+    </view>
+
+    <!-- 最近打卡 -->
+    <view class="section-card" v-if="checkinList.length > 0">
+      <view class="section-header">
+        <text class="section-title">🏃 最近打卡</text>
+      </view>
+      <view class="checkin-item" v-for="item in checkinList" :key="item.id">
+        <text class="checkin-type">{{ item.typeName || item.type }}</text>
+        <text class="checkin-time">{{ formatDate(item.createTime) }}</text>
+      </view>
+    </view>
+
+    <!-- 非孩子成员提示 -->
+    <view class="section-card" v-if="!childId && !loading">
+      <view class="empty-state">
+        <text class="empty-text">该成员暂无健康数据</text>
+      </view>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">加载中...</view>
+  </view>
+</template>
+
+<script>
+import RadarChart from '../RadarChart.vue'
+import { getHealthAnalysis, getDimensionOverview, getCheckinList } from '../../utils/api.js'
+
+export default {
+  components: { RadarChart },
+  props: {
+    memberId: { type: [Number, String], default: null },
+    childId: { type: [Number, String], default: null },
+    memberType: { type: String, default: '' },
+    memberInfo: { type: Object, default: null },
+    sandboxData: { type: Object, default: null },
+    energyMap: { type: Object, default: null }
+  },
+  data: function() {
+    return {
+      loading: true,
+      healthReport: null,
+      dimensionOverview: null,
+      checkinList: []
+    }
+  },
+  mounted: function() {
+    if (this.childId) {
+      this.loadData()
+    } else {
+      this.loading = false
+    }
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      getHealthAnalysis(this.childId).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.healthReport = res.data
+        }
+      }).catch(function(e) { console.log('加载健康分析失败', e) })
+
+      getDimensionOverview({ childId: this.childId }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.dimensionOverview = res.data
+        }
+      }).catch(function(e) { console.log('加载维度概览失败', e) })
+
+      getCheckinList({ childId: this.childId, page: 1, size: 3 }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.checkinList = Array.isArray(res.data) ? res.data : (res.data.records || [])
+        }
+      }).catch(function(e) { console.log('加载打卡记录失败', e) })
+
+      this.loading = false
+    },
+    formatDate: function(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate()
+    },
+    goReportDetail: function() {
+      uni.navigateTo({ url: '/pages/body-detail/health-report?childId=' + this.childId })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.body-section {}
+.section-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.section-header {
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.report-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 0;
+}
+.report-score {
+  font-size: 72rpx;
+  font-weight: bold;
+  color: #FF8C42;
+}
+.report-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 8rpx;
+}
+.report-meta {
+  padding-top: 12rpx;
+  border-top: 1rpx solid #f0f0f0;
+}
+.report-date {
+  font-size: 24rpx;
+  color: #999;
+}
+.checkin-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 16rpx 0;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.checkin-type {
+  font-size: 28rpx;
+  color: #333;
+}
+.checkin-time {
+  font-size: 24rpx;
+  color: #999;
+}
+.empty-state {
+  padding: 30rpx 0;
+  text-align: center;
+}
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+}
+.empty-sub {
+  font-size: 24rpx;
+  color: #ccc;
+  margin-top: 8rpx;
+}
+.loading {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+}
+</style>

+ 219 - 0
cfc-frontend/components/member-detail-sections/mind-section.vue

@@ -0,0 +1,219 @@
+<template>
+  <view class="mind-section">
+    <!-- 亲密关系评分 -->
+    <view class="section-card" v-if="relationScores && relationScores.scores">
+      <view class="section-header">
+        <text class="section-title">💕 亲密关系评分</text>
+      </view>
+      <view class="score-item" v-for="(score, key) in relationScores.scores" :key="key">
+        <text class="score-label">{{ score.label || key }}</text>
+        <view class="score-bar-bg">
+          <view class="score-bar-fill" :style="'width:' + (score.value || 0) + '%'"></view>
+        </view>
+        <text class="score-value">{{ score.value || 0 }}</text>
+      </view>
+    </view>
+
+    <!-- 联系人 -->
+    <view class="section-card" v-if="contactList.length > 0">
+      <view class="section-header">
+        <text class="section-title">📞 重要联系人</text>
+      </view>
+      <view class="contact-item" v-for="contact in contactList" :key="contact.id" @click="goContactDetail(contact)">
+        <text class="contact-name">{{ contact.name }}</text>
+        <text class="contact-relation">{{ contact.relation || '' }}</text>
+        <text class="contact-phone">{{ contact.phone || '' }}</text>
+      </view>
+    </view>
+
+    <!-- 健康告警 -->
+    <view class="section-card" v-if="healthAlerts && healthAlerts.length > 0">
+      <view class="section-header">
+        <text class="section-title">⚠️ 健康告警</text>
+      </view>
+      <view class="alert-item" v-for="alert in healthAlerts" :key="alert.id">
+        <text class="alert-type">{{ alert.type }}</text>
+        <text class="alert-desc">{{ alert.description }}</text>
+      </view>
+    </view>
+
+    <!-- 里程碑 -->
+    <view class="section-card" v-if="milestones && milestones.length > 0">
+      <view class="section-header">
+        <text class="section-title">🎯 即将到来的里程碑</text>
+      </view>
+      <view class="milestone-item" v-for="ms in milestones" :key="ms.id || ms.title">
+        <text class="milestone-title">{{ ms.title }}</text>
+        <text class="milestone-date">{{ ms.date }}</text>
+      </view>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">加载中...</view>
+  </view>
+</template>
+
+<script>
+import { getRelationshipScores, getContactList, getHealthAlerts, getMilestones } from '../../utils/api.js'
+
+export default {
+  props: {
+    memberId: { type: [Number, String], default: null },
+    childId: { type: [Number, String], default: null },
+    memberType: { type: String, default: '' },
+    memberInfo: { type: Object, default: null },
+    sandboxData: { type: Object, default: null },
+    energyMap: { type: Object, default: null }
+  },
+  data: function() {
+    return {
+      loading: true,
+      relationScores: null,
+      contactList: [],
+      healthAlerts: [],
+      milestones: []
+    }
+  },
+  mounted: function() {
+    this.loadData()
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      var familyId = (this.memberInfo && this.memberInfo.familyId) || ''
+
+      if (familyId) {
+        getRelationshipScores(familyId).then(function(res) {
+          if (res.code === 200 && res.data) {
+            self.relationScores = res.data
+          }
+        }).catch(function(e) { console.log('加载关系评分失败', e) })
+
+        getContactList({ familyId: familyId }).then(function(res) {
+          if (res.code === 200 && res.data) {
+            self.contactList = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          }
+        }).catch(function(e) { console.log('加载联系人失败', e) })
+
+        getHealthAlerts(familyId).then(function(res) {
+          if (res.code === 200 && res.data) {
+            self.healthAlerts = Array.isArray(res.data) ? res.data : []
+          }
+        }).catch(function(e) { console.log('加载告警失败', e) })
+
+        getMilestones(familyId, 30).then(function(res) {
+          if (res.code === 200 && res.data) {
+            self.milestones = Array.isArray(res.data) ? res.data : []
+          }
+        }).catch(function(e) { console.log('加载里程碑失败', e) })
+      }
+      this.loading = false
+    },
+    goContactDetail: function(contact) {
+      uni.navigateTo({ url: '/pages/mind/contact-detail?id=' + contact.id })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.mind-section {}
+.section-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.section-header {
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.score-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 10rpx 0;
+}
+.score-label {
+  font-size: 26rpx;
+  color: #555;
+  width: 160rpx;
+}
+.score-bar-bg {
+  flex: 1;
+  height: 20rpx;
+  background: #f0f0f0;
+  border-radius: 10rpx;
+  overflow: hidden;
+}
+.score-bar-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #FF6B9D, #FF9EC4);
+  border-radius: 10rpx;
+}
+.score-value {
+  font-size: 24rpx;
+  color: #FF6B9D;
+  width: 60rpx;
+  text-align: right;
+}
+.contact-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 14rpx 0;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.contact-name {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: bold;
+}
+.contact-relation {
+  font-size: 24rpx;
+  color: #999;
+}
+.contact-phone {
+  font-size: 24rpx;
+  color: #FF6B9D;
+}
+.alert-item {
+  display: flex;
+  flex-direction: row;
+  padding: 10rpx 0;
+}
+.alert-type {
+  font-size: 24rpx;
+  color: #E74C3C;
+  width: 100rpx;
+}
+.alert-desc {
+  font-size: 26rpx;
+  color: #555;
+  flex: 1;
+}
+.milestone-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 10rpx 0;
+}
+.milestone-title {
+  font-size: 26rpx;
+  color: #333;
+}
+.milestone-date {
+  font-size: 24rpx;
+  color: #999;
+}
+.loading {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+}
+</style>

+ 206 - 0
cfc-frontend/components/member-detail-sections/wisdom-section.vue

@@ -0,0 +1,206 @@
+<template>
+  <view class="wisdom-section">
+    <!-- 最新测评结果 -->
+    <view class="section-card" v-if="latestResult">
+      <view class="section-header">
+        <text class="section-title">🧠 最新测评结果</text>
+      </view>
+      <view class="result-overview" @click="goResultDetail">
+        <text class="result-score">{{ latestResult.score || '--' }}</text>
+        <text class="result-dimension">{{ latestResult.dimensionName || latestResult.title || '综合测评' }}</text>
+      </view>
+      <view class="result-date">测评日期:{{ formatDate(latestResult.createTime || latestResult.testDate) }}</view>
+    </view>
+    <view class="section-card" v-else-if="!loading && childId">
+      <view class="empty-state">
+        <text class="empty-text">暂无测评记录</text>
+        <text class="empty-sub">完成测评后结果将显示在这里</text>
+      </view>
+    </view>
+
+    <!-- 测评历史 -->
+    <view class="section-card" v-if="historyList.length > 0">
+      <view class="section-header">
+        <text class="section-title">📋 测评历史</text>
+      </view>
+      <view class="history-item" v-for="item in historyList" :key="item.id" @click="goResultDetail">
+        <text class="history-title">{{ item.title || item.dimensionName || '测评' }}</text>
+        <text class="history-score">{{ item.score || '--' }}</text>
+        <text class="history-date">{{ formatDate(item.createTime || item.testDate) }}</text>
+      </view>
+    </view>
+
+    <!-- 非孩子成员提示 -->
+    <view class="section-card" v-if="!childId && !loading">
+      <view class="empty-state">
+        <text class="empty-text">该成员暂无测评数据</text>
+      </view>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">加载中...</view>
+  </view>
+</template>
+
+<script>
+import { getAssessmentLatestResult, getAssessmentHistory } from '../../utils/api.js'
+
+export default {
+  props: {
+    memberId: {
+      type: [Number, String],
+      default: null
+    },
+    childId: {
+      type: [Number, String],
+      default: null
+    },
+    memberType: {
+      type: String,
+      default: ''
+    },
+    memberInfo: {
+      type: Object,
+      default: null
+    },
+    sandboxData: {
+      type: Object,
+      default: null
+    },
+    energyMap: {
+      type: Object,
+      default: null
+    }
+  },
+  data: function() {
+    return {
+      loading: true,
+      latestResult: null,
+      historyList: []
+    }
+  },
+  mounted: function() {
+    if (this.childId) {
+      this.loadData()
+    } else {
+      this.loading = false
+    }
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      getAssessmentLatestResult(this.childId).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.latestResult = res.data
+        }
+      }).catch(function(e) {
+        console.log('加载测评结果失败', e)
+      })
+      
+      getAssessmentHistory(this.childId, 5).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.historyList = Array.isArray(res.data) ? res.data : (res.data.records || [])
+        }
+      }).catch(function(e) {
+        console.log('加载测评历史失败', e)
+      })
+      
+      this.loading = false
+    },
+    formatDate: function(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()
+    },
+    goResultDetail: function() {
+      uni.navigateTo({
+        url: '/pages/wisdom/assessment-detail?childId=' + this.childId
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.wisdom-section {}
+.section-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.section-header {
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.result-overview {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 0;
+}
+.result-score {
+  font-size: 72rpx;
+  font-weight: bold;
+  color: #6366F1;
+}
+.result-dimension {
+  font-size: 28rpx;
+  color: #555;
+  margin-top: 8rpx;
+}
+.result-date {
+  font-size: 24rpx;
+  color: #999;
+  padding-top: 12rpx;
+  border-top: 1rpx solid #f0f0f0;
+  margin-top: 8rpx;
+}
+.history-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 14rpx 0;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.history-title {
+  font-size: 26rpx;
+  color: #333;
+  flex: 1;
+}
+.history-score {
+  font-size: 26rpx;
+  color: #6366F1;
+  width: 80rpx;
+  text-align: center;
+}
+.history-date {
+  font-size: 22rpx;
+  color: #999;
+  width: 140rpx;
+  text-align: right;
+}
+.empty-state {
+  padding: 30rpx 0;
+  text-align: center;
+}
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+}
+.empty-sub {
+  font-size: 24rpx;
+  color: #ccc;
+  margin-top: 8rpx;
+}
+.loading {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+}
+</style>

+ 29 - 1
cfc-frontend/pages/member-detail/member-detail.vue

@@ -48,6 +48,17 @@
           @memberTap="onGraphMemberTap" />
           @memberTap="onGraphMemberTap" />
       </view>
       </view>
 
 
+      <!-- 维度专属内容 -->
+      <view class="dimension-section">
+        <component :is="dimensionSection"
+          :memberId="targetMemberId"
+          :childId="childId"
+          :memberType="memberType"
+          :memberInfo="memberInfo"
+          :sandboxData="sandboxData"
+          :energyMap="energyMapForGraph" />
+      </view>
+
       <!-- 今日任务(仅当有 childId 时显示) -->
       <!-- 今日任务(仅当有 childId 时显示) -->
       <DimensionTasks
       <DimensionTasks
         v-if="childId"
         v-if="childId"
@@ -84,6 +95,10 @@ import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
 import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
+import BodySection from '../../components/member-detail-sections/body-section.vue'
+import MindSection from '../../components/member-detail-sections/mind-section.vue'
+import WisdomSection from '../../components/member-detail-sections/wisdom-section.vue'
+import ActionSection from '../../components/member-detail-sections/action-section.vue'
 import { getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren } from '../../utils/api.js'
 import { getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren } from '../../utils/api.js'
 
 
 // 维度配置
 // 维度配置
@@ -97,7 +112,7 @@ var dimensionConfig = {
 }
 }
 
 
 export default {
 export default {
-  components: { FamilyEnergyBar, FamilyRelationGraph, DimensionTasks, DimensionActivities, DimensionProducts },
+  components: { FamilyEnergyBar, FamilyRelationGraph, DimensionTasks, DimensionActivities, DimensionProducts, BodySection, MindSection, WisdomSection, ActionSection },
   data() {
   data() {
     return {
     return {
       memberId: null,
       memberId: null,
@@ -215,6 +230,15 @@ export default {
     // 是否显示推荐内容(身卡片入口不显示)
     // 是否显示推荐内容(身卡片入口不显示)
     showRecommendations: function() {
     showRecommendations: function() {
       return this.entrySource !== 'body-card'
       return this.entrySource !== 'body-card'
+    },
+    dimensionSection: function() {
+      var map = {
+        body: BodySection,
+        mind: MindSection,
+        wisdom: WisdomSection,
+        action: ActionSection
+      }
+      return map[this.dimensionCode] || null
     }
     }
   },
   },
   onLoad: function(options) {
   onLoad: function(options) {
@@ -477,4 +501,8 @@ export default {
 .bottom-spacer {
 .bottom-spacer {
   height: 40rpx;
   height: 40rpx;
 }
 }
+
+.dimension-section {
+  margin: 20rpx 30rpx;
+}
 </style>
 </style>

+ 10 - 1
cfc-frontend/utils/api.js

@@ -21,7 +21,16 @@ function _clearAuthAndRedirect() {
   }, 1500)
   }, 1500)
 }
 }
 
 
-const request = (url, method = 'POST', data = {}) => {
+const request = (url, method = 'POST', data = {}, options = {}) => {
+  // Append query params from options to URL if provided (e.g., { guideId } for GET requests)
+  if (options && Object.keys(options).length > 0) {
+    const separator = url.includes('?') ? '&' : '?'
+    const query = Object.entries(options)
+      .filter(([_, v]) => v !== undefined && v !== null && v !== '')
+      .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
+      .join('&')
+    if (query) url = url + separator + query
+  }
   const token = uni.getStorageSync('token')
   const token = uni.getStorageSync('token')
   
   
   return new Promise((resolve, reject) => {
   return new Promise((resolve, reject) => {

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-b61d3479cbe92c826614952d635e473050f50e24
+67105bada7ecd5c837d5b3e8ea3e89876e0fd293

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

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

+ 1 - 1
cfc-web/package.json

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

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

@@ -4,6 +4,103 @@
 
 
 ---
 ---
 
 
+## v1.0.333 (2026-07-15)
+
+### Bug 修复
+- add dimension_weights column to activities table (migration 88)
+
+
+## v1.0.332 (2026-07-15)
+
+### Bug 修复
+- CommissionRecord 添加 family_id 字段 + CommissionService createCommissionRecord 集成 familyId
+
+
+## v1.0.331 (2026-07-15)
+
+### Bug 修复
+- CommissionService 编译错误 - 补充 familyId 字段和 L2 调用参数
+
+
+## v1.0.329 (2026-07-14)
+
+### 文档
+- 成员详情维度分段 设计文档+实施计划
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+### 新功能
+- 创建身/心/智/行四个维度专属内容组件
+- member-detail 维度专属内容动态组件插槽
+
+
+## v1.0.328 (2026-07-14)
+
+### Bug 修复
+- 代码规范修复 — @Autowired→@Resource + Result<T>泛型补全
+
+### 其他
+- - AdminGuideController: 3处@Autowired替换为@Resource,移除Autowired import
+- - VendorService: 3个方法补全Result<T>泛型参数
+- - VendorController: 同步补全Result<T>泛型参数
+- 
+
+
+## v1.0.327 (2026-07-14)
+
+### Bug 修复
+- 后端稳定性 — catch块日志/空值处理/PostMapping统一/管家stub
+
+### 其他
+- - DatabaseInitializer: 36个空catch块添加log.warn日志
+- - TeacherService: getTeacherInfo抛异常, getAuthorizedChildren返回空列表
+- - 14个controller: 42处@GetMapping/@PutMapping/@DeleteMapping→@PostMapping
+- - ButlerController: 2个stub方法标记@Deprecated+抛UnsupportedOperationException
+- 
+
+
+## v1.0.325 (2026-07-14)
+
+### Bug 修复
+- 金融安全修复 — payment绕过/验证码stub/积分兑换/教师端点/类型转换
+
+### 新功能
+- add cfc-web butler admin UI + ButlerCommissionService + scheduled settlement
+
+
+## v1.0.324 (2026-07-14)
+
+### Bug 修复
+- 迁移87: articles表添加content_type/difficulty_level/dimension_ids/dimension_weights字段
+
+
+## v1.0.323 (2026-07-14)
+
+### 新功能
+- add AdminButlerController + ButlerServiceRecord/CommissionSettlement entities
+
+### Bug 修复
+- 迁移86: product_orders表添加purchase_info字段(OrderTimeoutCancelTask)
+
+### 其他
+- 注册ButlerController bean起别名,解决多个ButlerController项目启动报错问题
+
+
+## v1.0.322 (2026-07-14)
+
+### Bug 修复
+- 调整 pages/profile/index 在 tabBar 中的位置并移除 customTabBar 配置
+- 同步 pages/wealth/index 路径与远程一致
+- 修复 Recipes.vue el-dialog 开始标签丢失
+
+
 ## v1.0.321 (2026-07-14)
 ## v1.0.321 (2026-07-14)
 
 
 ### 新功能
 ### 新功能

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

@@ -1,6 +1,6 @@
 # 更新日志
 # 更新日志
 
 
-> 当前版本: v1.0.321
+> 当前版本: v1.0.333
 
 
 ## 历史版本
 ## 历史版本
 
 
@@ -8,6 +8,103 @@
 
 
 ---
 ---
 
 
+## v1.0.333 (2026-07-15)
+
+### Bug 修复
+- add dimension_weights column to activities table (migration 88)
+
+
+## v1.0.332 (2026-07-15)
+
+### Bug 修复
+- CommissionRecord 添加 family_id 字段 + CommissionService createCommissionRecord 集成 familyId
+
+
+## v1.0.331 (2026-07-15)
+
+### Bug 修复
+- CommissionService 编译错误 - 补充 familyId 字段和 L2 调用参数
+
+
+## v1.0.329 (2026-07-14)
+
+### 文档
+- 成员详情维度分段 设计文档+实施计划
+
+### 其他
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+- Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
+- 
+
+### 新功能
+- 创建身/心/智/行四个维度专属内容组件
+- member-detail 维度专属内容动态组件插槽
+
+
+## v1.0.328 (2026-07-14)
+
+### Bug 修复
+- 代码规范修复 — @Autowired→@Resource + Result<T>泛型补全
+
+### 其他
+- - AdminGuideController: 3处@Autowired替换为@Resource,移除Autowired import
+- - VendorService: 3个方法补全Result<T>泛型参数
+- - VendorController: 同步补全Result<T>泛型参数
+- 
+
+
+## v1.0.327 (2026-07-14)
+
+### Bug 修复
+- 后端稳定性 — catch块日志/空值处理/PostMapping统一/管家stub
+
+### 其他
+- - DatabaseInitializer: 36个空catch块添加log.warn日志
+- - TeacherService: getTeacherInfo抛异常, getAuthorizedChildren返回空列表
+- - 14个controller: 42处@GetMapping/@PutMapping/@DeleteMapping→@PostMapping
+- - ButlerController: 2个stub方法标记@Deprecated+抛UnsupportedOperationException
+- 
+
+
+## v1.0.325 (2026-07-14)
+
+### Bug 修复
+- 金融安全修复 — payment绕过/验证码stub/积分兑换/教师端点/类型转换
+
+### 新功能
+- add cfc-web butler admin UI + ButlerCommissionService + scheduled settlement
+
+
+## v1.0.324 (2026-07-14)
+
+### Bug 修复
+- 迁移87: articles表添加content_type/difficulty_level/dimension_ids/dimension_weights字段
+
+
+## v1.0.323 (2026-07-14)
+
+### 新功能
+- add AdminButlerController + ButlerServiceRecord/CommissionSettlement entities
+
+### Bug 修复
+- 迁移86: product_orders表添加purchase_info字段(OrderTimeoutCancelTask)
+
+### 其他
+- 注册ButlerController bean起别名,解决多个ButlerController项目启动报错问题
+
+
+## v1.0.322 (2026-07-14)
+
+### Bug 修复
+- 调整 pages/profile/index 在 tabBar 中的位置并移除 customTabBar 配置
+- 同步 pages/wealth/index 路径与远程一致
+- 修复 Recipes.vue el-dialog 开始标签丢失
+
+
 ## v1.0.321 (2026-07-14)
 ## v1.0.321 (2026-07-14)
 
 
 ### 新功能
 ### 新功能

+ 16 - 16
cfc-web/src/api/admin.js

@@ -233,8 +233,8 @@ export function getPointsStats() {
 export function getWishesList(params) {
 export function getWishesList(params) {
   return request({
   return request({
     url: '/api/wishes',
     url: '/api/wishes',
-    method: 'get',
-    params
+    method: 'post',
+    data: params
   })
   })
 }
 }
 
 
@@ -242,8 +242,8 @@ export function getWishesList(params) {
 export function getPendingWishes(familyId) {
 export function getPendingWishes(familyId) {
   return request({
   return request({
     url: '/api/wishes/pending',
     url: '/api/wishes/pending',
-    method: 'get',
-    params: { familyId }
+    method: 'post',
+    data: { familyId }
   })
   })
 }
 }
 
 
@@ -251,7 +251,7 @@ export function getPendingWishes(familyId) {
 export function getWishDetail(id) {
 export function getWishDetail(id) {
   return request({
   return request({
     url: `/api/wishes/${id}`,
     url: `/api/wishes/${id}`,
-    method: 'get'
+    method: 'post'
   })
   })
 }
 }
 
 
@@ -1038,7 +1038,7 @@ export function getSupplySystemList(params) {
   return request({
   return request({
     url: '/api/admin/supply-system/list',
     url: '/api/admin/supply-system/list',
     method: 'post',
     method: 'post',
-    params: params || {}
+    data: params || {}
   })
   })
 }
 }
 
 
@@ -1046,7 +1046,7 @@ export function getSupplySystemDetail(id) {
   return request({
   return request({
     url: '/api/admin/supply-system/detail',
     url: '/api/admin/supply-system/detail',
     method: 'post',
     method: 'post',
-    params: { id }
+    data: { id }
   })
   })
 }
 }
 
 
@@ -1070,7 +1070,7 @@ export function toggleSupplySystemStatus(id) {
   return request({
   return request({
     url: '/api/admin/supply-system/toggle-status',
     url: '/api/admin/supply-system/toggle-status',
     method: 'post',
     method: 'post',
-    params: { id }
+    data: { id }
   })
   })
 }
 }
 
 
@@ -1085,7 +1085,7 @@ export function getSupplySystemMembers(systemId) {
   return request({
   return request({
     url: '/api/admin/supply-system/member/list',
     url: '/api/admin/supply-system/member/list',
     method: 'post',
     method: 'post',
-    params: { systemId }
+    data: { systemId }
   })
   })
 }
 }
 
 
@@ -1093,7 +1093,7 @@ export function addSupplySystemMember(systemId, userId) {
   return request({
   return request({
     url: '/api/admin/supply-system/member/add',
     url: '/api/admin/supply-system/member/add',
     method: 'post',
     method: 'post',
-    params: { systemId, userId }
+    data: { systemId, userId }
   })
   })
 }
 }
 
 
@@ -1101,7 +1101,7 @@ export function removeSupplySystemMember(memberId) {
   return request({
   return request({
     url: '/api/admin/supply-system/member/remove',
     url: '/api/admin/supply-system/member/remove',
     method: 'post',
     method: 'post',
-    params: { memberId }
+    data: { memberId }
   })
   })
 }
 }
 
 
@@ -1109,7 +1109,7 @@ export function changeSupplySystemMemberRole(memberId, role) {
   return request({
   return request({
     url: '/api/admin/supply-system/member/change-role',
     url: '/api/admin/supply-system/member/change-role',
     method: 'post',
     method: 'post',
-    params: { memberId, role }
+    data: { memberId, role }
   })
   })
 }
 }
 
 
@@ -1225,7 +1225,7 @@ export function getSupplyHierarchyTree(systemId) {
     return request({
     return request({
         url: '/api/admin/supply-hierarchy/tree',
         url: '/api/admin/supply-hierarchy/tree',
         method: 'post',
         method: 'post',
-        params: { systemId }
+        data: { systemId }
     })
     })
 }
 }
 
 
@@ -1338,7 +1338,7 @@ export function cancelSubscriptionAdmin(data) {
 export function getBenefitConfig() {
 export function getBenefitConfig() {
   return request({
   return request({
     url: '/api/admin/subscription/benefits',
     url: '/api/admin/subscription/benefits',
-    method: 'get'
+    method: 'post'
   })
   })
 }
 }
 
 
@@ -1433,7 +1433,7 @@ export function getProductPpointByProduct(data) {
 export function getPromotionTierConfig() {
 export function getPromotionTierConfig() {
   return request({
   return request({
     url: '/api/admin/promotion/config',
     url: '/api/admin/promotion/config',
-    method: 'get'
+    method: 'post'
   })
   })
 }
 }
 
 
@@ -1475,7 +1475,7 @@ export function cancelPendingRefund(params) {
 export function getCommissionConfig() {
 export function getCommissionConfig() {
   return request({
   return request({
     url: '/api/admin/commission/config',
     url: '/api/admin/commission/config',
-    method: 'get'
+    method: 'post'
   })
   })
 }
 }
 
 

+ 46 - 0
cfc-web/src/api/butler.js

@@ -0,0 +1,46 @@
+import request from '@/utils/request'
+
+// 管家列表
+export function getButlerList(params) {
+  return request({
+    url: '/api/admin/butler/list',
+    method: 'post',
+    data: params
+  })
+}
+
+// 审核管家
+export function reviewButler(data) {
+  return request({
+    url: '/api/admin/butler/review',
+    method: 'post',
+    data: data
+  })
+}
+
+// 管家服务记录
+export function getButlerServiceRecords(params) {
+  return request({
+    url: '/api/admin/butler/service-records',
+    method: 'post',
+    data: params
+  })
+}
+
+// 管家佣金结算
+export function getButlerSettlements(params) {
+  return request({
+    url: '/api/admin/butler/settlements',
+    method: 'post',
+    data: params
+  })
+}
+
+// 分配成员到管家
+export function assignMemberToButler(data) {
+  return request({
+    url: '/api/admin/butler/assign-member',
+    method: 'post',
+    data: data
+  })
+}

+ 6 - 0
cfc-web/src/router/index.js

@@ -216,6 +216,12 @@ const routes = [
         component: () => import('@/views/admin/VendorReview.vue'),
         component: () => import('@/views/admin/VendorReview.vue'),
         meta: { title: '服务商审核', perm: 'audit:vendor' }
         meta: { title: '服务商审核', perm: 'audit:vendor' }
       },
       },
+      {
+        path: 'butler-review',
+        name: 'ButlerReview',
+        component: () => import('@/views/admin/ButlerReview.vue'),
+        meta: { title: '管家审核', perm: 'audit:vendor' }
+      },
       {
       {
         path: 'product-manage',
         path: 'product-manage',
         name: 'ProductManage',
         name: 'ProductManage',

+ 1 - 0
cfc-web/src/views/Layout.vue

@@ -184,6 +184,7 @@ export default {
             { path: '/package-audit', label: '套餐审核', icon: 'el-icon-document-checked', perm: 'audit:package' },
             { path: '/package-audit', label: '套餐审核', icon: 'el-icon-document-checked', perm: 'audit:package' },
             { path: '/guide-audit', label: '规划师审核', icon: 'el-icon-document-checked', perm: 'audit:guide' },
             { path: '/guide-audit', label: '规划师审核', icon: 'el-icon-document-checked', perm: 'audit:guide' },
             { path: '/vendor-review', label: '服务商审核', icon: 'el-icon-document-checked', perm: 'audit:vendor' },
             { path: '/vendor-review', label: '服务商审核', icon: 'el-icon-document-checked', perm: 'audit:vendor' },
+            { path: '/butler-review', label: '管家审核', icon: 'el-icon-document-checked', perm: 'audit:vendor' },
             { path: '/withdraw-audit', label: '提现审核', icon: 'el-icon-document-checked', perm: 'audit:withdraw' },
             { path: '/withdraw-audit', label: '提现审核', icon: 'el-icon-document-checked', perm: 'audit:withdraw' },
           ]},
           ]},
 
 

+ 264 - 0
cfc-web/src/views/admin/ButlerReview.vue

@@ -0,0 +1,264 @@
+<template>
+  <div class="butler-review admin-page">
+    <el-tabs v-model="activeTab" @tab-click="onTabChange">
+      <el-tab-pane label="管家审核" name="review">
+        <div class="header admin-page-header">
+          <h2 class="admin-page-title">管家审核</h2>
+          <div class="flex items-center gap-sm">
+            <el-select v-model="statusFilter" placeholder="审核状态" @change="loadList">
+              <el-option label="全部状态" value="" />
+              <el-option label="待审核" value="pending" />
+              <el-option label="已通过" value="approved" />
+              <el-option label="已拒绝" value="rejected" />
+            </el-select>
+          </div>
+        </div>
+
+        <div class="table-scroll-wrap">
+          <el-table :data="list" v-loading="loading" border stripe>
+            <el-table-column prop="id" label="档案ID" width="80" />
+            <el-table-column prop="userId" label="用户ID" width="80" />
+            <el-table-column label="管家等级" width="100">
+              <template slot-scope="{ row }">{{ row.tier || '-' }}</template>
+            </el-table-column>
+            <el-table-column label="审核状态" width="100">
+              <template slot-scope="{ row }">
+                <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="description" label="申请说明" min-width="200" show-overflow-tooltip />
+            <el-table-column prop="memberCount" label="已服务人数" width="100" />
+            <el-table-column prop="maxMembers" label="上限人数" width="80" />
+            <el-table-column prop="rejectReason" label="拒绝原因" width="150" show-overflow-tooltip />
+            <el-table-column label="操作" width="160" fixed="right">
+              <template slot-scope="{ row }">
+                <template v-if="row.status === 'pending'">
+                  <el-button size="mini" type="success" @click="handleReview(row, 'approve')">通过</el-button>
+                  <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
+                    <el-button size="mini">
+                      更多<i class="el-icon-arrow-down el-icon--right"></i>
+                    </el-button>
+                    <el-dropdown-menu slot="dropdown">
+                      <el-dropdown-item command="reject" icon="el-icon-close">拒绝</el-dropdown-item>
+                    </el-dropdown-menu>
+                  </el-dropdown>
+                </template>
+                <span v-else class="no-op">—</span>
+              </template>
+            </el-table-column>
+          </el-table>
+        </div>
+
+        <el-pagination
+          @size-change="onPageChange"
+          @current-change="onPageChange"
+          :current-page="page"
+          :page-size="size"
+          :total="total"
+          layout="total, prev, pager, next"
+          class="pagination-wrap"
+        />
+
+        <el-dialog title="填写拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
+          <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
+          <span slot="footer">
+            <el-button @click="rejectDialogVisible = false">取消</el-button>
+            <el-button type="danger" @click="confirmReject">确认拒绝</el-button>
+          </span>
+        </el-dialog>
+      </el-tab-pane>
+
+      <el-tab-pane label="服务记录" name="service-records">
+        <div class="header admin-page-header">
+          <h2 class="admin-page-title">管家服务记录</h2>
+        </div>
+        <div class="table-scroll-wrap">
+          <el-table :data="serviceRecords" v-loading="srLoading" border stripe>
+            <el-table-column prop="id" label="记录ID" width="80" />
+            <el-table-column prop="butlerId" label="管家ID" width="80" />
+            <el-table-column prop="memberUserId" label="会员ID" width="80" />
+            <el-table-column label="服务类型" width="120">
+              <template slot-scope="{ row }">{{ serviceTypeLabel(row.serviceType) }}</template>
+            </el-table-column>
+            <el-table-column prop="amountP" label="金额(P点)" width="100" />
+            <el-table-column prop="platformFeeP" label="平台留成" width="100" />
+            <el-table-column prop="butlerAmountP" label="管家所得" width="100" />
+            <el-table-column prop="yearMonth" label="结算年月" width="100" />
+            <el-table-column prop="createdAt" label="创建时间" width="160" />
+          </el-table>
+        </div>
+        <el-pagination
+          @size-change="srPage = $event; loadServiceRecords()"
+          @current-change="srPage = $event; loadServiceRecords()"
+          :current-page="srPage"
+          :page-size="srSize"
+          :total="srTotal"
+          layout="total, prev, pager, next"
+          class="pagination-wrap"
+        />
+      </el-tab-pane>
+
+      <el-tab-pane label="佣金结算" name="settlements">
+        <div class="header admin-page-header">
+          <h2 class="admin-page-title">管家佣金结算</h2>
+        </div>
+        <div class="table-scroll-wrap">
+          <el-table :data="settlements" v-loading="stLoading" border stripe>
+            <el-table-column prop="id" label="结算单ID" width="80" />
+            <el-table-column prop="butlerId" label="管家ID" width="80" />
+            <el-table-column prop="yearMonth" label="结算年月" width="100" />
+            <el-table-column prop="memberCount" label="服务家庭数" width="100" />
+            <el-table-column prop="annualBaseAmountP" label="年费基准" width="100" />
+            <el-table-column prop="totalButlerAmountP" label="管家总所得" width="120" />
+            <el-table-column label="结算状态" width="100">
+              <template slot-scope="{ row }">
+                <el-tag :type="row.status === 'paid' ? 'success' : 'warning'">{{ row.status === 'paid' ? '已结算' : '待结算' }}</el-tag>
+              </template>
+            </el-table-column>
+            <el-table-column prop="settledAt" label="结算时间" width="160" />
+          </el-table>
+        </div>
+        <el-pagination
+          @size-change="stPage = $event; loadSettlements()"
+          @current-change="stPage = $event; loadSettlements()"
+          :current-page="stPage"
+          :page-size="stSize"
+          :total="stTotal"
+          layout="total, prev, pager, next"
+          class="pagination-wrap"
+        />
+      </el-tab-pane>
+    </el-tabs>
+  </div>
+</template>
+
+<script>
+import { getButlerList, reviewButler, getButlerServiceRecords, getButlerSettlements } from '@/api/butler'
+
+export default {
+  data() {
+    return {
+      activeTab: 'review',
+      // 审核列表
+      list: [],
+      loading: false,
+      page: 1,
+      size: 20,
+      total: 0,
+      statusFilter: 'pending',
+      rejectDialogVisible: false,
+      rejectTarget: null,
+      rejectReason: '',
+      // 服务记录
+      serviceRecords: [],
+      srLoading: false,
+      srPage: 1,
+      srSize: 20,
+      srTotal: 0,
+      // 结算
+      settlements: [],
+      stLoading: false,
+      stPage: 1,
+      stSize: 20,
+      stTotal: 0
+    }
+  },
+  mounted() {
+    this.loadList()
+  },
+  methods: {
+    statusType(status) {
+      const map = { pending: 'warning', approved: 'success', rejected: 'danger' }
+      return map[status] || 'info'
+    },
+    statusLabel(status) {
+      const map = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
+      return map[status] || status
+    },
+    serviceTypeLabel(type) {
+      const map = { annual: '年费服务', per_session: '按次服务' }
+      return map[type] || type
+    },
+    onTabChange() {
+      this.page = 1
+      if (this.activeTab === 'review') this.loadList()
+      else if (this.activeTab === 'service-records') this.loadServiceRecords()
+      else if (this.activeTab === 'settlements') this.loadSettlements()
+    },
+    async loadList() {
+      this.loading = true
+      try {
+        const params = { page: this.page, size: this.size, status: this.statusFilter }
+        const res = await getButlerList(params)
+        this.list = res.data.list || []
+        this.total = res.data.total || 0
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    async loadServiceRecords() {
+      this.srLoading = true
+      try {
+        const res = await getButlerServiceRecords({ page: this.srPage, size: this.srSize })
+        this.serviceRecords = res.data.list || []
+        this.srTotal = res.data.total || 0
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.srLoading = false
+      }
+    },
+    async loadSettlements() {
+      this.stLoading = true
+      try {
+        const res = await getButlerSettlements({ page: this.stPage, size: this.stSize })
+        this.settlements = res.data.list || []
+        this.stTotal = res.data.total || 0
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.stLoading = false
+      }
+    },
+    onPageChange(val) {
+      this.page = val
+      this.loadList()
+    },
+    async handleReview(row, action) {
+      try {
+        await reviewButler({ userId: row.userId, action: action, reason: this.rejectReason })
+        this.$message.success('审核完成')
+        this.loadList()
+      } catch (e) {
+        this.$message.error('操作失败')
+      }
+    },
+    showRejectDialog(row) {
+      this.rejectTarget = row
+      this.rejectReason = ''
+      this.rejectDialogVisible = true
+    },
+    handleActionCmd(row, cmd) {
+      switch (cmd) {
+        case 'reject': this.showRejectDialog(row); break
+      }
+    },
+    async confirmReject() {
+      if (!this.rejectReason) {
+        this.$message.warning('请填写拒绝原因')
+        return
+      }
+      await this.handleReview(this.rejectTarget, 'reject')
+      this.rejectDialogVisible = false
+    }
+  }
+}
+</script>
+
+<style scoped>
+.no-op {
+  color: #ccc;
+}
+</style>

+ 825 - 0
docs/superpowers/plans/2026-07-14-member-detail-dimension-sections.md

@@ -0,0 +1,825 @@
+# 家庭成员详情页维度专属内容区 — 实施计划
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** 家庭成员详情页根据 `dimensionCode` 参数(body/mind/wisdom/action)展示维度专属内容
+
+**Architecture:** 在现有 `member-detail.vue` 的 FamilyRelationGraph 下方插入动态组件区域,通过 `<component :is="dimensionSection" />` 根据 dimensionCode 切换 4 个独立子组件。共用部分(导航、成员卡、能量条、关系图谱)不变。
+
+**Tech Stack:** uni-app Vue 2 (Options API), 微信小程序
+
+---
+
+### Task 1: 改造 member-detail.vue — 动态组件插槽
+
+**Files:**
+- Modify: `cfc-frontend/pages/member-detail/member-detail.vue`
+
+- [ ] **Step 1: 在 template 中 FamilyRelationGraph 之后插入动态组件区**
+
+在 `member-detail.vue` 的 template 中,FamilyRelationGraph 的 `</view>` 闭合标签之后、`<!-- 底部占位 -->` 之前,添加:
+
+```html
+      <!-- 维度专属内容 -->
+      <view class="dimension-section">
+        <component :is="dimensionSection" />
+      </view>
+```
+
+- [ ] **Step 2: 在 script 中添加 imports**
+
+在现有 import 块末尾添加:
+
+```javascript
+import BodySection from '../../components/member-detail-sections/body-section.vue'
+import MindSection from '../../components/member-detail-sections/mind-section.vue'
+import WisdomSection from '../../components/member-detail-sections/wisdom-section.vue'
+import ActionSection from '../../components/member-detail-sections/action-section.vue'
+```
+
+在 `components: { ... }` 中添加这四个组件。
+
+- [ ] **Step 3: 添加 computed 属性映射 dimensionCode → 组件**
+
+```javascript
+    dimensionSection: function() {
+      var map = {
+        body: BodySection,
+        mind: MindSection,
+        wisdom: WisdomSection,
+        action: ActionSection
+      }
+      return map[this.dimensionCode] || null
+    },
+```
+
+- [ ] **Step 4: 添加样式**
+
+```css
+.dimension-section {
+  margin: 20rpx 30rpx;
+}
+```
+
+- [ ] **Step 5: 确认 member-detail.vue 的 props 传递正确**
+
+在 `<component :is="dimensionSection">` 上添加 props 绑定:
+
+```html
+        <component :is="dimensionSection"
+          :memberId="targetMemberId"
+          :childId="childId"
+          :memberType="memberType"
+          :memberInfo="memberInfo"
+          :sandboxData="sandboxData"
+          :energyMap="energyMapForGraph" />
+```
+
+---
+
+### Task 2: 创建 body-section.vue
+
+**Files:**
+- Create: `cfc-frontend/components/member-detail-sections/body-section.vue`
+- Reference: `cfc-frontend/pages/body/index.vue` 的 `loadHealthReport`, `loadDimensionData`, `getCheckinList`
+
+- [ ] **Step 1: 创建 body-section.vue 基本结构(template + script + style)**
+
+```html
+<template>
+  <view class="body-section">
+    <!-- 最新健康报告 -->
+    <view class="section-card" v-if="healthReport">
+      <view class="section-header">
+        <text class="section-title">📋 最新健康报告</text>
+      </view>
+      <view class="report-card" @click="goReportDetail">
+        <text class="report-score">{{ healthReport.overallScore || '--' }}</text>
+        <text class="report-label">综合健康分</text>
+      </view>
+      <view class="report-meta">
+        <text class="report-date">报告日期:{{ formatDate(healthReport.reportDate) }}</text>
+      </view>
+    </view>
+    <view class="section-card" v-else-if="!loading">
+      <view class="empty-state">
+        <text class="empty-text">暂无健康报告</text>
+        <text class="empty-sub">规划师录入后将自动显示</text>
+      </view>
+    </view>
+
+    <!-- 七维健康图 -->
+    <view class="section-card" v-if="dimensionOverview">
+      <view class="section-header">
+        <text class="section-title">📊 七维健康图</text>
+      </view>
+      <RadarChart
+        :dimensions="dimensionOverview.dimensions.map(function(d) { return { label: d.label, key: d.dimension } })"
+        :scores="dimensionOverview.dimensions.map(function(d) { return d.score })"
+        fillColor="#FF8C42"
+        gridColor="#FED7AA"
+        labelColor="#9A3412"
+        :width="580"
+        :height="400" />
+    </view>
+
+    <!-- 最近打卡 -->
+    <view class="section-card" v-if="checkinList.length > 0">
+      <view class="section-header">
+        <text class="section-title">🏃 最近打卡</text>
+      </view>
+      <view class="checkin-item" v-for="item in checkinList" :key="item.id">
+        <text class="checkin-type">{{ item.typeName || item.type }}</text>
+        <text class="checkin-time">{{ formatDate(item.createTime) }}</text>
+      </view>
+    </view>
+
+    <!-- 仅孩子成员提示 -->
+    <view class="section-card" v-if="!childId && !loading">
+      <view class="empty-state">
+        <text class="empty-text">该成员暂无健康数据</text>
+      </view>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">加载中...</view>
+  </view>
+</template>
+```
+
+- [ ] **Step 2: 添加 script 逻辑**
+
+```html
+<script>
+import RadarChart from '../RadarChart.vue'
+import { getHealthAnalysis, getDimensionOverview, getCheckinList } from '../../utils/api.js'
+
+export default {
+  components: { RadarChart },
+  props: {
+    memberId: [Number, String],
+    childId: [Number, String],
+    memberType: String,
+    memberInfo: Object,
+    sandboxData: Object,
+    energyMap: Object
+  },
+  data: function() {
+    return {
+      loading: true,
+      healthReport: null,
+      dimensionOverview: null,
+      checkinList: []
+    }
+  },
+  mounted: function() {
+    if (this.childId) {
+      this.loadData()
+    } else {
+      this.loading = false
+    }
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      // 并行加载
+      getHealthAnalysis(this.childId).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.healthReport = res.data
+        }
+      }).catch(function(e) { console.log('加载健康分析失败', e) })
+
+      getDimensionOverview({ childId: this.childId }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.dimensionOverview = res.data
+        }
+      }).catch(function(e) { console.log('加载维度概览失败', e) })
+
+      getCheckinList({ childId: this.childId, page: 1, size: 3 }).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.checkinList = Array.isArray(res.data) ? res.data : (res.data.records || [])
+        }
+      }).catch(function(e) { console.log('加载打卡记录失败', e) })
+
+      this.loading = false
+    },
+    formatDate: function(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate()
+    },
+    goReportDetail: function() {
+      uni.navigateTo({ url: '/pages/body-detail/health-report?childId=' + this.childId })
+    }
+  }
+}
+</script>
+```
+
+- [ ] **Step 3: 添加样式**
+
+```html
+<style scoped>
+.body-section {}
+.section-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.section-header {
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.report-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 0;
+}
+.report-score {
+  font-size: 72rpx;
+  font-weight: bold;
+  color: #FF8C42;
+}
+.report-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 8rpx;
+}
+.report-meta {
+  padding-top: 12rpx;
+  border-top: 1rpx solid #f0f0f0;
+}
+.report-date {
+  font-size: 24rpx;
+  color: #999;
+}
+.checkin-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 16rpx 0;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.checkin-type {
+  font-size: 28rpx;
+  color: #333;
+}
+.checkin-time {
+  font-size: 24rpx;
+  color: #999;
+}
+.empty-state {
+  padding: 30rpx 0;
+  text-align: center;
+}
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+}
+.empty-sub {
+  font-size: 24rpx;
+  color: #ccc;
+  margin-top: 8rpx;
+}
+.loading {
+  text-align: center;
+  padding: 40rpx;
+  color: #999;
+}
+</style>
+```
+
+---
+
+### Task 3: 创建 mind-section.vue
+
+**Files:**
+- Create: `cfc-frontend/components/member-detail-sections/mind-section.vue`
+- Reference: `cfc-frontend/pages/mind/index.vue` 的 `loadContacts`, `loadHealthAlerts`, `loadMilestones`
+
+- [ ] **Step 1: 创建 mind-section.vue 模板**
+
+```html
+<template>
+  <view class="mind-section">
+    <!-- 亲密关系评分 -->
+    <view class="section-card" v-if="relationshipScores.length > 0">
+      <view class="section-header">
+        <text class="section-title">💞 亲密关系评分</text>
+      </view>
+      <view class="relation-item" v-for="score in relationshipScores" :key="score.memberId">
+        <text class="relation-name">{{ score.nickname || '成员' }}</text>
+        <view class="relation-bar-wrap">
+          <view class="relation-bar" :style="{ width: score.trust + '%', background: getScoreColor(score.trust) }"></view>
+        </view>
+        <text class="relation-score">{{ score.trust }}</text>
+      </view>
+    </view>
+
+    <!-- 联系人 -->
+    <view class="section-card" v-if="contactList.length > 0">
+      <view class="section-header">
+        <text class="section-title">📞 联系人</text>
+      </view>
+      <view class="contact-item" v-for="item in contactList" :key="item.id">
+        <text class="contact-name">{{ item.name }}</text>
+        <text class="contact-relation">{{ item.relation || '' }}</text>
+      </view>
+    </view>
+
+    <!-- 健康告警 -->
+    <view class="section-card" v-if="healthAlerts.length > 0">
+      <view class="section-header">
+        <text class="section-title">⚠️ 健康告警</text>
+      </view>
+      <view class="alert-item" v-for="alert in healthAlerts" :key="alert.id">
+        <text class="alert-title">{{ alert.title || alert.content }}</text>
+      </view>
+    </view>
+
+    <!-- 里程碑 -->
+    <view class="section-card" v-if="milestones.length > 0">
+      <view class="section-header">
+        <text class="section-title">🎯 里程碑</text>
+      </view>
+      <view class="milestone-item" v-for="ms in milestones" :key="ms.id">
+        <text class="milestone-title">{{ ms.title || ms.name }}</text>
+        <text class="milestone-date">{{ formatDate(ms.dueDate) }}</text>
+      </view>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">加载中...</view>
+  </view>
+</template>
+```
+
+- [ ] **Step 2: 添加 mind-section.vue script**
+
+```html
+<script>
+import { getContactList, getHealthAlerts, getMilestones, getFamilySummary } from '../../utils/api.js'
+
+export default {
+  props: {
+    memberId: [Number, String],
+    childId: [Number, String],
+    memberType: String,
+    memberInfo: Object,
+    sandboxData: Object,
+    energyMap: Object
+  },
+  data: function() {
+    return {
+      loading: true,
+      contactList: [],
+      healthAlerts: [],
+      milestones: [],
+      relationshipScores: []
+    }
+  },
+  mounted: function() {
+    this.loadData()
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      var familyId = uni.getStorageSync('familyId')
+      if (!familyId) {
+        var userInfo = uni.getStorageSync('userInfo')
+        if (userInfo && userInfo.familyId) familyId = userInfo.familyId
+      }
+
+      // 从 energyMap 构建关系评分
+      if (this.energyMap && this.sandboxData && this.sandboxData.members) {
+        var scores = []
+        var selfId = this.memberId
+        for (var i = 0; i < this.sandboxData.members.length; i++) {
+          var m = this.sandboxData.members[i]
+          var mid = m.memberId || m.id
+          if (mid != selfId) {
+            var e = this.energyMap[mid]
+            var trust = (e && e.bodyScore) ? Math.round((e.bodyScore + (e.mindScore || 50)) / 2) : 70
+            scores.push({ memberId: mid, nickname: m.name || m.nickname || '成员', trust: trust })
+          }
+        }
+        self.relationshipScores = scores
+      }
+
+      if (familyId) {
+        getContactList({ memberId: this.memberId }).then(function(res) {
+          if (res.code === 200) {
+            self.contactList = Array.isArray(res.data) ? res.data : []
+          }
+        }).catch(function() {})
+
+        getHealthAlerts(familyId).then(function(res) {
+          if (res.code === 200) {
+            self.healthAlerts = Array.isArray(res.data) ? res.data.slice(0, 5) : []
+          }
+        }).catch(function() {})
+
+        getMilestones(familyId).then(function(res) {
+          if (res.code === 200) {
+            self.milestones = Array.isArray(res.data) ? res.data.slice(0, 5) : []
+          }
+        }).catch(function() {})
+      }
+
+      this.loading = false
+    },
+    getScoreColor: function(score) {
+      if (score >= 80) return '#10B981'
+      if (score >= 60) return '#F59E0B'
+      return '#EF4444'
+    },
+    formatDate: function(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate()
+    }
+  }
+}
+</script>
+```
+
+- [ ] **Step 3: 添加样式(与 body-section 风格一致)**
+
+```html
+<style scoped>
+.mind-section {}
+.section-card { background: #fff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 20rpx; box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06); }
+.section-header { margin-bottom: 16rpx; }
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; }
+.relation-item { display: flex; flex-direction: row; align-items: center; padding: 12rpx 0; }
+.relation-name { width: 100rpx; font-size: 26rpx; color: #333; }
+.relation-bar-wrap { flex: 1; height: 16rpx; background: #f0f0f0; border-radius: 8rpx; margin: 0 16rpx; overflow: hidden; }
+.relation-bar { height: 100%; border-radius: 8rpx; }
+.relation-score { width: 60rpx; text-align: right; font-size: 24rpx; color: #666; }
+.contact-item, .alert-item, .milestone-item { display: flex; flex-direction: row; justify-content: space-between; padding: 16rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.contact-name, .alert-title, .milestone-title { font-size: 28rpx; color: #333; }
+.contact-relation, .milestone-date { font-size: 24rpx; color: #999; }
+.loading { text-align: center; padding: 40rpx; color: #999; }
+</style>
+```
+
+---
+
+### Task 4: 创建 wisdom-section.vue
+
+**Files:**
+- Create: `cfc-frontend/components/member-detail-sections/wisdom-section.vue`
+- Reference: `cfc-frontend/pages/wisdom/index.vue` 的 `loadCognitiveReport`, `getAssessmentHistory`
+
+- [ ] **Step 1: 创建 wisdom-section.vue 模板**
+
+```html
+<template>
+  <view class="wisdom-section">
+    <!-- 最新测评结果 -->
+    <view class="section-card" v-if="latestAssessment">
+      <view class="section-header">
+        <text class="section-title">🧠 最新测评结果</text>
+      </view>
+      <view class="assessment-card" @click="goAssessmentDetail">
+        <text class="assessment-date">测评日期:{{ formatDate(latestAssessment.createTime) }}</text>
+        <view class="assessment-score-row">
+          <view class="score-item" v-for="(score, key) in dimensionScores" :key="key">
+            <text class="score-num" :style="{ color: '#6366F1' }">{{ score }}</text>
+            <text class="score-label">{{ key }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="section-card" v-else-if="!loading && !childId">
+      <view class="empty-state">
+        <text class="empty-text">该成员暂无测评数据</text>
+      </view>
+    </view>
+
+    <!-- 近期测评历史 -->
+    <view class="section-card" v-if="assessmentHistory.length > 0">
+      <view class="section-header">
+        <text class="section-title">📊 近期测评历史</text>
+      </view>
+      <view class="history-item" v-for="item in assessmentHistory" :key="item.id">
+        <text class="history-name">{{ item.assessmentName || '心智测评' }}</text>
+        <text class="history-score">{{ item.totalScore || '--' }}分</text>
+        <text class="history-date">{{ formatDate(item.createTime) }}</text>
+      </view>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">加载中...</view>
+  </view>
+</template>
+```
+
+- [ ] **Step 2: 添加 wisdom-section.vue script**
+
+```html
+<script>
+import { getAssessmentLatestResult, getAssessmentHistory } from '../../utils/api.js'
+
+export default {
+  props: {
+    memberId: [Number, String],
+    childId: [Number, String],
+    memberType: String,
+    memberInfo: Object,
+    sandboxData: Object,
+    energyMap: Object
+  },
+  data: function() {
+    return {
+      loading: true,
+      latestAssessment: null,
+      assessmentHistory: []
+    }
+  },
+  computed: {
+    dimensionScores: function() {
+      if (!this.latestAssessment) return {}
+      var result = {}
+      var keys = ['iq', 'eq', 'memory', 'logic', 'creativity', 'concentration']
+      for (var i = 0; i < keys.length; i++) {
+        var k = keys[i]
+        if (this.latestAssessment[k] != null) {
+          result[k === 'iq' ? '智商' : k === 'eq' ? '情商' : k === 'memory' ? '记忆' : k === 'logic' ? '逻辑' : k === 'creativity' ? '创造' : k === 'concentration' ? '专注' : k] = this.latestAssessment[k]
+        }
+      }
+      return result
+    }
+  },
+  mounted: function() {
+    if (this.childId) {
+      this.loadData()
+    } else {
+      this.loading = false
+    }
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+
+      getAssessmentLatestResult(this.childId).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.latestAssessment = res.data
+        }
+      }).catch(function(e) { console.log('加载测评结果失败', e) })
+
+      getAssessmentHistory(this.childId, 3).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.assessmentHistory = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function(e) { console.log('加载测评历史失败', e) })
+
+      this.loading = false
+    },
+    formatDate: function(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate()
+    },
+    goAssessmentDetail: function() {
+      uni.navigateTo({ url: '/pages/assessment/report?childId=' + this.childId })
+    }
+  }
+}
+</script>
+```
+
+- [ ] **Step 3: 添加样式**
+
+```html
+<style scoped>
+.wisdom-section {}
+.section-card { background: #fff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 20rpx; box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06); }
+.section-header { margin-bottom: 16rpx; }
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; }
+.assessment-card { padding: 10rpx 0; }
+.assessment-date { font-size: 24rpx; color: #999; }
+.assessment-score-row { display: flex; flex-direction: row; flex-wrap: wrap; margin-top: 16rpx; }
+.score-item { display: flex; flex-direction: column; align-items: center; min-width: 80rpx; margin: 8rpx 16rpx 8rpx 0; }
+.score-num { font-size: 36rpx; font-weight: bold; }
+.score-label { font-size: 22rpx; color: #999; margin-top: 4rpx; }
+.history-item { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.history-name { font-size: 28rpx; color: #333; flex: 1; }
+.history-score { font-size: 28rpx; color: #6366F1; font-weight: bold; margin: 0 16rpx; }
+.history-date { font-size: 22rpx; color: #999; }
+.empty-state { padding: 30rpx 0; text-align: center; }
+.empty-text { font-size: 28rpx; color: #999; }
+.loading { text-align: center; padding: 40rpx; color: #999; }
+</style>
+```
+
+---
+
+### Task 5: 创建 action-section.vue
+
+**Files:**
+- Create: `cfc-frontend/components/member-detail-sections/action-section.vue`
+- Reference: `cfc-frontend/pages/action/index.vue`
+
+- [ ] **Step 1: 创建 action-section.vue 模板**
+
+```html
+<template>
+  <view class="action-section">
+    <!-- 今日任务(仅孩子) -->
+    <view class="section-card" v-if="childId && tasks.length > 0">
+      <view class="section-header">
+        <text class="section-title">📋 今日任务</text>
+      </view>
+      <view class="task-item" v-for="task in tasks" :key="task.id" @click="goTaskDetail(task)">
+        <view class="task-left">
+          <text class="task-name">{{ task.title || task.name }}</text>
+          <text class="task-desc">{{ task.description || '' }}</text>
+        </view>
+        <text class="task-status" :class="'status-' + (task.status || 'pending')">{{ task.statusLabel || task.status }}</text>
+      </view>
+    </view>
+
+    <!-- 打卡统计 -->
+    <view class="section-card" v-if="checkinStats">
+      <view class="section-header">
+        <text class="section-title">🏃 打卡统计</text>
+      </view>
+      <view class="stats-row">
+        <view class="stat-item">
+          <text class="stat-num">{{ checkinStats.totalDays || 0 }}</text>
+          <text class="stat-label">累计天数</text>
+        </view>
+        <view class="stat-item">
+          <text class="stat-num">{{ checkinStats.currentStreak || 0 }}</text>
+          <text class="stat-label">连续天数</text>
+        </view>
+        <view class="stat-item">
+          <text class="stat-num">{{ checkinStats.completionRate || '0%' }}</text>
+          <text class="stat-label">完成率</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 最近活动 -->
+    <view class="section-card" v-if="activities.length > 0">
+      <view class="section-header">
+        <text class="section-title">🎪 最近活动</text>
+      </view>
+      <view class="activity-item" v-for="act in activities" :key="act.id" @click="goActivityDetail(act)">
+        <text class="activity-title">{{ act.title || act.name }}</text>
+        <text class="activity-date">{{ formatDate(act.startTime || act.createTime) }}</text>
+      </view>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">加载中...</view>
+  </view>
+</template>
+```
+
+- [ ] **Step 2: 添加 action-section.vue script**
+
+```html
+<script>
+import { getTodayTasksByCategory, getCheckinStats, getActivityList } from '../../utils/api.js'
+
+export default {
+  props: {
+    memberId: [Number, String],
+    childId: [Number, String],
+    memberType: String,
+    memberInfo: Object,
+    sandboxData: Object,
+    energyMap: Object
+  },
+  data: function() {
+    return {
+      loading: true,
+      tasks: [],
+      checkinStats: null,
+      activities: []
+    }
+  },
+  mounted: function() {
+    this.loadData()
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+
+      if (this.childId) {
+        getTodayTasksByCategory(this.childId, 'all').then(function(res) {
+          if (res && res.data) {
+            self.tasks = Array.isArray(res.data) ? res.data.slice(0, 5) : []
+          }
+        }).catch(function() {})
+
+        getCheckinStats({ childId: this.childId }).then(function(res) {
+          if (res.code === 200 && res.data) {
+            self.checkinStats = res.data
+          }
+        }).catch(function() {})
+      }
+
+      getActivityList({ page: 1, size: 3 }).then(function(res) {
+        if (res && res.data) {
+          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          self.activities = list.slice(0, 3)
+        }
+      }).catch(function() {})
+
+      this.loading = false
+    },
+    formatDate: function(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth()+1) + '-' + d.getDate()
+    },
+    goTaskDetail: function(task) {
+      uni.navigateTo({ url: '/pages/tasks/tasks' })
+    },
+    goActivityDetail: function(act) {
+      if (act && act.id) {
+        uni.navigateTo({ url: '/pages/discover-detail/activity-detail/activity-detail?id=' + act.id })
+      }
+    }
+  }
+}
+</script>
+```
+
+- [ ] **Step 3: 添加样式**
+
+```html
+<style scoped>
+.action-section {}
+.section-card { background: #fff; border-radius: 24rpx; padding: 24rpx; margin-bottom: 20rpx; box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06); }
+.section-header { margin-bottom: 16rpx; }
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; }
+.task-item { display: flex; flex-direction: row; justify-content: space-between; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.task-left { flex: 1; }
+.task-name { font-size: 28rpx; color: #333; }
+.task-desc { font-size: 22rpx; color: #999; margin-top: 4rpx; }
+.task-status { font-size: 24rpx; padding: 4rpx 12rpx; border-radius: 8rpx; }
+.status-pending { background: #FEF3C7; color: #D97706; }
+.status-completed { background: #D1FAE5; color: #059669; }
+.stats-row { display: flex; flex-direction: row; justify-content: space-around; padding: 16rpx 0; }
+.stat-item { display: flex; flex-direction: column; align-items: center; }
+.stat-num { font-size: 40rpx; font-weight: bold; color: #10B981; }
+.stat-label { font-size: 22rpx; color: #999; margin-top: 4rpx; }
+.activity-item { display: flex; flex-direction: row; justify-content: space-between; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
+.activity-title { font-size: 28rpx; color: #333; flex: 1; }
+.activity-date { font-size: 22rpx; color: #999; }
+.loading { text-align: center; padding: 40rpx; color: #999; }
+</style>
+```
+
+---
+
+### Task 6: 验证编译
+
+**Files:**
+- Project root: `cfc-backend/` (not needed, frontend only)
+
+- [ ] **Step 1: 检查文件结构**
+
+```bash
+ls -la cfc-frontend/components/member-detail-sections/
+ls -la cfc-frontend/pages/member-detail/
+```
+
+- [ ] **Step 2: 检查 member-detail.vue 无语法错误**
+
+确认 imports、components、computed 等都已正确添加。
+
+- [ ] **Step 3: 确认页面路由不冲突**
+
+```bash
+grep -n 'member-detail' cfc-frontend/pages.json
+```
+
+---
+
+### 自检清单
+
+1. **Spec 覆盖度**:
+   - [ ] 4 个子组件都对应到 spec 中的各维度内容定义
+   - [ ] 动态组件机制已实现(Task 1)
+   - [ ] 共用部分保持不变
+   - [ ] childId 可用性处理(非孩子成员显示空状态)
+
+2. **占位符检查**: 无 "TBD", "TODO", "implement later"
+
+3. **类型一致性**:
+   - [ ] `memberId`, `childId`, `memberType` props 在 4 个子组件中命名一致
+   - [ ] `energyMap` prop 与 member-detail.vue 中的 `energyMapForGraph` 匹配
+
+4. **Scope 检查**: 范围聚焦于 member-detail 页面的内容区改造,不涉及导航、路由、后端改动。

+ 113 - 0
docs/superpowers/specs/2026-07-14-member-detail-dimension-sections.md

@@ -0,0 +1,113 @@
+# 家庭成员详情页 — 维度专属内容区设计
+
+## 概述
+
+当前 `pages/member-detail/member-detail.vue` 从身/心/智/行四个维度页面的 FamilyRelationGraph 点击成员进入,但展示内容不区分入口维度。本设计让该页面根据 `dimensionCode` 参数展示与维度相关的专属内容,实现"从哪个页面进,显示该页面相关的内容"。
+
+## 现状
+
+- `member-detail.vue` 已接收 `dimensionCode` 参数(body/mind/wisdom/action)
+- 页面现有结构:导航栏 + 成员信息卡 + 五维能量条 + 关系图谱 + 通用任务/活动/商品
+- 通用内容(活动/商品推荐)不区分维度,始终展示 body 域内容
+
+## 设计
+
+### 页面结构(所有维度共用)
+
+```
+┌─ 顶部导航栏(返回按钮 + "成员名 · 维度名")
+├─ 成员信息卡(头像 + 昵称/角色 + 综合能量分)
+├─ 五维能量条 (FamilyEnergyBar)
+├─ 家庭成员关系图谱 (FamilyRelationGraph)
+├─ ══ 维度专属内容区(根据 dimensionCode 切换)══
+│   └─ (详见各维度内容定义)
+└─ 底部占位
+```
+
+### 架构
+
+- `member-detail.vue` 作为容器页面,负责所有共用部分的渲染
+- 在关系图谱下方使用 `<component :is="dimensionSection" />` 动态组件
+- 4 个独立子组件放在 `components/member-detail-sections/` 目录下
+
+### 各维度专属内容
+
+#### 身 (body-section)
+
+| 内容块 | 数据来源 | 说明 |
+|--------|---------|------|
+| 最新健康报告卡片 | `getHealthAnalysis(childId)` | 总分 + 关键指标 + 报告日期,无报告时显示"暂无健康报告" |
+| 七维健康雷达图 | `getDimensionOverview({ childId })` | 与 body/index.vue 相同的 RadarChart 组件 |
+| 健康指标列表 | `getHealthIndicatorList(childId)` | 关键指标展示 |
+| 最近打卡记录 | `getCheckinList({ childId, page:1, size:3 })` | 最近3条健康打卡 |
+
+#### 心 (mind-section)
+
+| 内容块 | 数据来源 | 说明 |
+|--------|---------|------|
+| 亲密关系评分 | `intimacyMap` + `energyMap` | 用进度条或评分卡展示该成员与各家庭成员的关系质量 |
+| 联系人列表 | `getContactList({ memberId })` | 该成员的联系人列表 |
+| 健康告警 | `getHealthAlerts(familyId)` | 家庭健康提醒 |
+| 里程碑 | `getMilestones(familyId)` | 近期待办里程碑 |
+
+#### 智 (wisdom-section)
+
+| 内容块 | 数据来源 | 说明 |
+|--------|---------|------|
+| 最新测评结果 | `getAssessmentLatestResult(childId)` | 总分 + 各维度分数,与 wisdom/index.vue 风格一致 |
+| 认知能力维度图 | 从测评结果提取 | 用雷达图或柱状图展示各认知维度得分 |
+| 近期测评历史 | `getAssessmentHistory(childId, 3)` | 最近3次测评简表 |
+| 认知趋势 | `getCognitiveTrend(childId, 5)` | 趋势图(如有需要) |
+
+#### 行 (action-section)
+
+| 内容块 | 数据来源 | 说明 |
+|--------|---------|------|
+| 今日任务 | `getTodayTasksByCategory(childId, 'all')` | 已存在的 DimensionTasks 组件,仅孩子类型显示 |
+| 打卡统计 | `getCheckinStats({ childId })` | 行为打卡汇总统计 |
+| 最近活动 | `getActivityList({ page:1, size:3 })` | 推荐活动 |
+
+### 数据加载策略
+
+- `member-detail.vue` 的 `onLoad` 仍加载共用数据(沙盘、成员信息)
+- 维度专属数据在 `dimensionSection` 子组件的 `onShow`/`mounted` 中各自加载
+- 子组件通过 props 接收:`memberId`, `childId`, `memberType`, `sandboxData`, `energyMap`
+- `familyId` 从 `uni.getStorageSync('familyId')` 获取(项目内统一模式)
+- **childId 可用性**:`childId` 仅在 `memberType === 'child'` 时有值。身/智维度依赖健康数据和测评数据,这些数据仅对孩子成员有意义——非孩子成员时显示"该成员暂无维度数据"的提示。心/行维度的部分内容(联系人、告警、里程碑)对全年龄段成员可用。
+
+### 错误处理
+
+- 各子组件独立处理自己的加载失败,不阻塞共用部分的渲染
+- 加载中显示骨架屏或 loading 文本
+- 无数据时显示空状态提示
+
+### 文件清单(新增)
+
+```
+cfc-frontend/components/member-detail-sections/
+├── body-section.vue       # 身维度专属内容
+├── mind-section.vue       # 心维度专属内容
+├── wisdom-section.vue     # 智维度专属内容
+└── action-section.vue     # 行维度专属内容
+```
+
+### 修改文件
+
+```
+cfc-frontend/pages/member-detail/member-detail.vue  # 引入动态组件区
+```
+
+## 排除范围
+
+- 不修改各维度页面(parent-index、body、mind、wisdom、action)的 `goMemberDetail`——它们已经传了 `dimensionCode`
+- 不修改 FamilyRelationGraph 组件
+- 不改动共用部分(导航、成员卡、能量条、关系图谱)的现有逻辑
+
+## 成功标准
+
+1. 从**身**页面点击成员 → 进入后看到健康相关数据(报告/雷达图/指标)
+2. 从**心**页面点击成员 → 进入后看到关系/联系人/告警数据
+3. 从**智**页面点击成员 → 进入后看到测评/认知数据
+4. 从**行**页面点击成员 → 进入后看到任务/打卡/活动数据
+5. 共用部分在所有维度下表现一致
+6. 各专属内容区独立加载,单个模块失败不影响其他区域