Эх сурвалжийг харах

feat: 健康能量 v1.2 心智增强设计 + 会员分销/财富模块功能完善

- docs: 健康能量设计.md 更新至 v1.2(DAN 70%+先天30%+行为修正三层模型)
- docs: 新增 v1.2-心智增强-版本计划.md(阶段划分/交付物/验收标准)
- docs: 新增 v1.2-心智增强-阶段总结.md(5路代码审计 + 需求确认 + 19项实施任务)
- feat: 会员与分销体系(Membership CRUD/升级/佣金/提现/邀请)
- feat: 财富维度页面(记账打卡/保单管理/财富首页/保险添加)
- feat: 教育后台管理 + 健康报告实体/服务/控制器
- feat: 规划师端测评管理/团队管理 + 认知评分服务
- feat: 前端 TabBar 5标签重构 + 身体/行动/发现/商城/教师页面
- refactor: 前端组件化(PageBanner/RadarChart/TaskItem/BadgeGrid/EnergyWeekCard)
- refactor: 后端服务拆分(EnergyService/HealthReport/Commission/FinanceCheckin)
- chore: 配置更新(application.yml/pom.xml/schema.sql/schema_assessment.sql)
User 3 сар өмнө
parent
commit
1767d4414e
100 өөрчлөгдсөн 9848 нэмэгдсэн , 1799 устгасан
  1. 2 2
      DESIGN.md
  2. 1 1
      cfc-backend/pom.xml
  3. 1 1
      cfc-backend/sql/init.sql
  4. 315 416
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  5. 35 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/CommissionController.java
  6. 171 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java
  7. 172 22
      cfc-backend/src/main/java/com/etotem/cfc/controller/MembershipController.java
  8. 0 46
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminController.java
  9. 91 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminVendorController.java
  10. 52 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/admin/EducationSystemController.java
  11. 112 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/assessment/CognitiveController.java
  12. 2 2
      cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideAssessmentController.java
  13. 110 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideTeamController.java
  14. 70 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/FinanceCheckinController.java
  15. 79 0
      cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/InsurancePolicyController.java
  16. 10 1
      cfc-backend/src/main/java/com/etotem/cfc/entity/CommissionRecord.java
  17. 6 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/DanAssessmentResult.java
  18. 46 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/EducationSystem.java
  19. 38 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/FinanceCheckin.java
  20. 62 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/Guide.java
  21. 47 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/HealthIndicator.java
  22. 64 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/HealthReport.java
  23. 50 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/InsurancePolicy.java
  24. 33 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/MemberUpgradeRecord.java
  25. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/entity/MembershipLevel.java
  26. 21 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/User.java
  27. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/EducationSystemMapper.java
  28. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/FinanceCheckinMapper.java
  29. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/HealthIndicatorMapper.java
  30. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/HealthReportMapper.java
  31. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/InsurancePolicyMapper.java
  32. 9 0
      cfc-backend/src/main/java/com/etotem/cfc/mapper/MemberUpgradeRecordMapper.java
  33. 27 1
      cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentAppointmentService.java
  34. 6 1
      cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentService.java
  35. 126 0
      cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java
  36. 294 29
      cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java
  37. 33 0
      cfc-backend/src/main/java/com/etotem/cfc/service/EducationSystemService.java
  38. 32 3
      cfc-backend/src/main/java/com/etotem/cfc/service/EnergyService.java
  39. 77 0
      cfc-backend/src/main/java/com/etotem/cfc/service/FinanceCheckinService.java
  40. 15 0
      cfc-backend/src/main/java/com/etotem/cfc/service/GameRecordService.java
  41. 177 0
      cfc-backend/src/main/java/com/etotem/cfc/service/HealthReportService.java
  42. 53 0
      cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePolicyService.java
  43. 313 60
      cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java
  44. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/service/PackagePaymentService.java
  45. 30 11
      cfc-backend/src/main/java/com/etotem/cfc/service/StreakService.java
  46. 8 0
      cfc-backend/src/main/java/com/etotem/cfc/service/VendorService.java
  47. 16 0
      cfc-backend/src/main/java/com/etotem/cfc/service/api/MembershipServiceInterface.java
  48. 51 0
      cfc-backend/src/main/java/com/etotem/cfc/task/MembershipScheduledTasks.java
  49. 0 4
      cfc-backend/src/main/resources/application.yml
  50. 5 0
      cfc-backend/src/main/resources/migration_add_cognitive_dimensions.sql
  51. 812 77
      cfc-backend/src/main/resources/schema.sql
  52. 1 1
      cfc-backend/src/main/resources/schema_assessment.sql
  53. 2 19
      cfc-frontend/App.vue
  54. 56 0
      cfc-frontend/components/BadgeGrid.vue
  55. 83 0
      cfc-frontend/components/EnergyWeekCard.vue
  56. 147 0
      cfc-frontend/components/PageBanner.vue
  57. 575 0
      cfc-frontend/components/RadarChart.vue
  58. 57 0
      cfc-frontend/components/TaskItem.vue
  59. 0 89
      cfc-frontend/components/bottom-nav.vue
  60. 46 42
      cfc-frontend/components/wuxing-sandbox-helpers/render.js
  61. 117 43
      cfc-frontend/components/wuxing-sandbox.vue
  62. 5 5
      cfc-frontend/config.js
  63. 1 1
      cfc-frontend/index.html
  64. 1 1
      cfc-frontend/package.json
  65. 44 2
      cfc-frontend/pages.json
  66. 269 303
      cfc-frontend/pages/action/index.vue
  67. 119 54
      cfc-frontend/pages/body/index.vue
  68. 10 46
      cfc-frontend/pages/discover/index.vue
  69. 1 1
      cfc-frontend/pages/guide/packages/share.vue
  70. 78 70
      cfc-frontend/pages/index/child-index.vue
  71. 6 49
      cfc-frontend/pages/index/index.vue
  72. 139 75
      cfc-frontend/pages/index/parent-index.vue
  73. 70 11
      cfc-frontend/pages/login/login.vue
  74. 157 21
      cfc-frontend/pages/membership/index.vue
  75. 461 0
      cfc-frontend/pages/membership/upgrade.vue
  76. 6 42
      cfc-frontend/pages/mind/articles.vue
  77. 361 64
      cfc-frontend/pages/mind/index.vue
  78. 289 0
      cfc-frontend/pages/mind/training.vue
  79. 368 10
      cfc-frontend/pages/profile/profile.vue
  80. 131 33
      cfc-frontend/pages/promotion/commission.vue
  81. 108 49
      cfc-frontend/pages/promotion/index.vue
  82. 112 28
      cfc-frontend/pages/promotion/invite.vue
  83. 107 43
      cfc-frontend/pages/promotion/withdraw.vue
  84. 1 4
      cfc-frontend/pages/shop/index.vue
  85. 153 0
      cfc-frontend/pages/teacher/index.vue
  86. 366 0
      cfc-frontend/pages/wealth/checkin.vue
  87. 363 0
      cfc-frontend/pages/wealth/index.vue
  88. 312 0
      cfc-frontend/pages/wealth/insurance-add.vue
  89. 296 0
      cfc-frontend/pages/wealth/insurance-list.vue
  90. 363 0
      cfc-frontend/pages/wisdom/index.vue
  91. 1 1
      cfc-frontend/project.private.config.json
  92. 10 0
      cfc-frontend/store/index.js
  93. 115 2
      cfc-frontend/utils/api.js
  94. 2 2
      cfc-web/public/index.html
  95. 40 0
      cfc-web/src/api/admin.js
  96. 8 1
      cfc-web/src/router/index.js
  97. 1 1
      cfc-web/src/views/Dashboard.vue
  98. 37 6
      cfc-web/src/views/Layout.vue
  99. 1 1
      cfc-web/src/views/Login.vue
  100. 131 0
      cfc-web/src/views/admin/EducationSystems.vue

+ 2 - 2
DESIGN.md

@@ -1,5 +1,5 @@
 ---
-name: 知行益家设计系统
+name: 浠艾福设计系统
 version: 1.1.0
 colors:
   # === 家长端 - 温暖希望 ===
@@ -122,7 +122,7 @@ components:
     rounded: "{rounded.full}"
     size: "48px"
 ---
-# 知行益家 - 多端设计系统
+# 浠艾福 - 多端设计系统
 
 ## 1. 家长端 (Parent)
 

+ 1 - 1
cfc-backend/pom.xml

@@ -10,7 +10,7 @@
     <packaging>jar</packaging>
 
     <name>cfc-backend</name>
-    <description>知行益家后端服务</description>
+    <description>浠艾福后端服务</description>
 
     <parent>
         <groupId>org.springframework.boot</groupId>

+ 1 - 1
cfc-backend/sql/init.sql

@@ -1,4 +1,4 @@
--- 知行益家数据库初始化脚本
+-- 浠艾福数据库初始化脚本
 -- 创建数据库
 CREATE DATABASE IF NOT EXISTS xzyj DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
 USE xzyj;

+ 315 - 416
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -4,11 +4,16 @@ import com.etotem.cfc.mapper.*;
 import lombok.extern.slf4j.Slf4j;
 import javax.annotation.Resource;
 import org.springframework.boot.CommandLineRunner;
+import org.springframework.core.io.ResourceLoader;
 import org.springframework.jdbc.core.JdbcTemplate;
 import org.springframework.stereotype.Component;
+import org.springframework.util.StreamUtils;
 
-import java.util.Arrays;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
 import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 @Slf4j
 @Component
@@ -17,6 +22,9 @@ public class DatabaseInitializer implements CommandLineRunner {
     @Resource
     private JdbcTemplate jdbcTemplate;
 
+    @Resource
+    private ResourceLoader resourceLoader;
+
     @Override
     public void run(String... args) throws Exception {
         log.info("开始初始化数据库...");
@@ -47,425 +55,49 @@ public class DatabaseInitializer implements CommandLineRunner {
     }
 
     private void initializeTables() {
-        // 表创建SQL - 使用IF NOT EXISTS确保幂等性
-        List<String> createTableSQLs = Arrays.asList(
-            // 家庭表
-            "CREATE TABLE IF NOT EXISTS families (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "name VARCHAR(100), " +
-            "invite_code VARCHAR(8) UNIQUE, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-            "INDEX idx_invite_code (invite_code)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 用户表
-            "CREATE TABLE IF NOT EXISTS users (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "openid VARCHAR(64) NOT NULL, " +
-            "unionid VARCHAR(64), " +
-            "family_id BIGINT NOT NULL, " +
-            "role ENUM('parent','child','admin') NOT NULL, " +
-            "nickname VARCHAR(50), " +
-            "avatar VARCHAR(255), " +
-            "password VARCHAR(128), " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-            "INDEX idx_openid (openid), " +
-            "INDEX idx_family_id (family_id)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-// 孩子详情表
-"CREATE TABLE IF NOT EXISTS children (" +
-"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-"user_id BIGINT NOT NULL, " +
-"family_id BIGINT NOT NULL, " +
-"nickname VARCHAR(50) NOT NULL, " +
-"age TINYINT NOT NULL, " +
-"dan_level VARCHAR(2), " +
-"penalty_enabled TINYINT DEFAULT 1, " +
-"theme VARCHAR(32) DEFAULT \'default\', " +
-"total_points INT DEFAULT 0, " +
-"streak_days INT DEFAULT 0, " +
-"last_task_date DATE, " +
-"focus_max_daily TINYINT DEFAULT 3, " +
-"focus_remaining TINYINT DEFAULT 3, " +
-"focus_reset_date DATE, " +
-"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-"INDEX idx_user_id (user_id), " +
-"INDEX idx_family_id (family_id)" +
-") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 任务表
-            "CREATE TABLE IF NOT EXISTS tasks (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "family_id BIGINT NOT NULL, " +
-            "creator_id BIGINT NOT NULL, " +
-            "child_id BIGINT COMMENT '执行者ID(child任务时必填,parent任务时可空)', " +
-            "executor_type VARCHAR(20) COMMENT '执行者类型: child/parent', " +
-            "executor_id BIGINT COMMENT '执行者ID', " +
-            "title VARCHAR(200) NOT NULL, " +
-            "description VARCHAR(500), " +
-            "points TINYINT DEFAULT 2, " +
-            "deadline DATETIME, " +
-            "repeat_type ENUM('none','daily','weekly') DEFAULT 'none', " +
-            "category VARCHAR(32), " +
-            "need_review TINYINT DEFAULT 0, " +
-            "review_by_category TINYINT DEFAULT 0, " +
-            "status ENUM('pending','completed','overdue','cancelled') DEFAULT 'pending', " +
-            "completed_at DATETIME, " +
-            "bonus_rule VARCHAR(500), " +
-            "points_expire_days INT DEFAULT 0, " +
-            "template_id BIGINT, " +
-            "is_template TINYINT DEFAULT 0, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-            "INDEX idx_child_id_deadline (child_id, deadline), " +
-            "INDEX idx_status (status), " +
-            "INDEX idx_category (category), " +
-            "INDEX idx_executor (executor_type, executor_id)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 积分流水表
-            "CREATE TABLE IF NOT EXISTS points_log (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "child_id BIGINT NOT NULL, " +
-            "task_id BIGINT, " +
-            "amount INT NOT NULL, " +
-            "type ENUM('earn','spend','bonus','penalty','adjust','reset','refund') NOT NULL, " +
-            "description VARCHAR(255), " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "INDEX idx_child_id_created (child_id, created_at), " +
-            "INDEX idx_type (type)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 积分清零记录表
-            "CREATE TABLE IF NOT EXISTS points_reset_log (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "child_id BIGINT NOT NULL, " +
-            "balance_before INT NOT NULL, " +
-            "balance_after INT DEFAULT 0, " +
-            "reset_type ENUM('monthly','manual') NOT NULL, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "INDEX idx_child_id (child_id)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-// 奖励表
-"CREATE TABLE IF NOT EXISTS rewards (" +
-"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-"family_id BIGINT NOT NULL, " +
-"child_id BIGINT NULL COMMENT '孩子ID,模板奖励为NULL', " +
-"title VARCHAR(100) NOT NULL, " +
-"points_required INT NOT NULL, " +
-"category VARCHAR(32), " +
-"status ENUM(\'available\',\'pending\',\'exchanged\',\'rejected\') DEFAULT \'available\', " +
-"exchange_approved TINYINT, " +
-"exchanged_at DATETIME, " +
-"is_template TINYINT DEFAULT 0, " +
-"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-"INDEX idx_child_id_status (child_id, status), " +
-"INDEX idx_is_template (is_template)" +
-") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 奖励分类表
-            "CREATE TABLE IF NOT EXISTS reward_categories (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "family_id BIGINT NOT NULL, " +
-            "name VARCHAR(32) NOT NULL, " +
-            "icon VARCHAR(64), " +
-            "sort_order INT DEFAULT 0, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "INDEX idx_family_id (family_id)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 敏感词表
-            "CREATE TABLE IF NOT EXISTS sensitive_words (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "word VARCHAR(50) NOT NULL, " +
-            "category VARCHAR(32) NOT NULL, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "INDEX idx_word (word)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 任务审核表
-            "CREATE TABLE IF NOT EXISTS task_reviews (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "task_id BIGINT NOT NULL, " +
-            "child_id BIGINT NOT NULL, " +
-            "photo_url VARCHAR(255), " +
-            "voice_note_url VARCHAR(255), " +
-            "ai_result ENUM('pass','review','pending'), " +
-            "ai_confidence DECIMAL(3,2), " +
-            "ai_suggestion VARCHAR(255), " +
-            "ai_threshold ENUM('strict','normal','loose') DEFAULT 'normal', " +
-            "parent_result ENUM('pass','reject'), " +
-            "parent_override TINYINT DEFAULT 0, " +
-            "review_type ENUM('ai','manual') NOT NULL, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "reviewed_at DATETIME, " +
-            "INDEX idx_task_id (task_id), " +
-            "INDEX idx_ai_result (ai_result)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 勋章定义表
-            "CREATE TABLE IF NOT EXISTS badge_definitions (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "badge_type VARCHAR(32) NOT NULL, " +
-            "name VARCHAR(50) NOT NULL, " +
-            "description VARCHAR(200), " +
-            "icon VARCHAR(64), " +
-            "condition_type VARCHAR(32) NOT NULL, " +
-            "condition_value INT NOT NULL, " +
-            "reward_points INT DEFAULT 0, " +
-            "INDEX idx_badge_type (badge_type)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 勋章表
-            "CREATE TABLE IF NOT EXISTS badges (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "child_id BIGINT NOT NULL, " +
-            "badge_type VARCHAR(32) NOT NULL, " +
-            "unlocked_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "INDEX idx_child_id_type (child_id, badge_type)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 打卡里程碑表
-            "CREATE TABLE IF NOT EXISTS streak_milestones (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "days INT NOT NULL, " +
-            "reward_points INT DEFAULT 0, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 专注训练记录表
-            "CREATE TABLE IF NOT EXISTS focus_sessions (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "child_id BIGINT NOT NULL, " +
-            "mode ENUM('simple','pomodoro') NOT NULL, " +
-            "duration TINYINT NOT NULL, " +
-            "stars TINYINT, " +
-            "points_earned INT, " +
-            "voice_note_url VARCHAR(255), " +
-            "started_at DATETIME NOT NULL, " +
-            "completed_at DATETIME, " +
-            "INDEX idx_child_id_completed (child_id, completed_at)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 专注训练配置表
-            "CREATE TABLE IF NOT EXISTS focus_config (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "family_id BIGINT, " +
-            "max_daily_count TINYINT DEFAULT 3, " +
-            "allow_exit TINYINT DEFAULT 1, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // DAN测评表
-            "CREATE TABLE IF NOT EXISTS dan_assessments (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "child_id BIGINT NOT NULL, " +
-            "dan_level VARCHAR(2) NOT NULL, " +
-            "assessment_date DATE NOT NULL, " +
-            "details JSON, " +
-            "source ENUM('api','manual') DEFAULT 'manual', " +
-            "photo_url VARCHAR(255), " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "INDEX idx_child_id_level (child_id, dan_level)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 任务模板表
-            "CREATE TABLE IF NOT EXISTS task_templates (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "dan_level VARCHAR(2) NOT NULL, " +
-            "title VARCHAR(200) NOT NULL, " +
-            "points TINYINT DEFAULT 2, " +
-            "duration_minutes TINYINT, " +
-            "category VARCHAR(32), " +
-            "is_default TINYINT DEFAULT 1, " +
-            "family_id BIGINT, " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "INDEX idx_dan_level (dan_level), " +
-            "INDEX idx_is_default (is_default)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-// 管理员表
-"CREATE TABLE IF NOT EXISTS admins (" +
-"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-"username VARCHAR(50) NOT NULL, " +
-"password VARCHAR(128) NOT NULL, " +
-"real_name VARCHAR(50), " +
-"phone VARCHAR(11), " +
-"email VARCHAR(100), " +
-"role VARCHAR(20) DEFAULT 'admin', " +
-"status TINYINT DEFAULT 1, " +
-"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-"INDEX idx_phone (phone), " +
-"INDEX idx_username (username)" +
-") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-// 验证码表
-"CREATE TABLE IF NOT EXISTS verification_codes (" +
-"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-"phone VARCHAR(11) NOT NULL, " +
-"code VARCHAR(6) NOT NULL, " +
-"type VARCHAR(20) NOT NULL, " +
-"expires_in INT DEFAULT 300, " +
-"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-"expires_at DATETIME NOT NULL, " +
-"used TINYINT DEFAULT 0, " +
-"INDEX idx_phone_type (phone, type), " +
-"INDEX idx_expires_at (expires_at)" +
-") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-
-// 管理后台任务模板表
-"CREATE TABLE IF NOT EXISTS admin_task_templates (" +
-"id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-"title VARCHAR(200) NOT NULL, " +
-"description TEXT, " +
-"points INT DEFAULT 10, " +
-"category VARCHAR(32), " +
-"need_review TINYINT DEFAULT 0, " +
-"review_by_category TINYINT DEFAULT 0, " +
-"recommended_age INT, " +
-"difficulty ENUM('easy', 'medium', 'hard') DEFAULT 'medium', " +
-"is_active TINYINT DEFAULT 1, " +
-"created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-"updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-"INDEX idx_category (category), " +
-            "INDEX idx_difficulty (difficulty), " +
-            "INDEX idx_is_active (is_active)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4",
-            
-            // 测评资料表
-            "CREATE TABLE IF NOT EXISTS assessment_materials (" +
-            "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
-            "title VARCHAR(100) NOT NULL COMMENT '资料标题', " +
-            "content TEXT COMMENT '测评介绍内容', " +
-            "cover_image VARCHAR(255) COMMENT '封面图片', " +
-            "status VARCHAR(20) DEFAULT 'active' COMMENT '状态:active/inactive', " +
-            "created_at DATETIME COMMENT '创建时间', " +
-            "updated_at DATETIME COMMENT '更新时间', " +
-            "INDEX idx_status (status)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评资料表'",
-
-            // 积分配置表
-            "CREATE TABLE IF NOT EXISTS assessment_points_config (" +
-            "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
-            "points_value INT DEFAULT 100 COMMENT '完成测评获得积分', " +
-            "points_value_yuan DECIMAL(10,2) DEFAULT 0.01 COMMENT '1积分等于多少元', " +
-            "is_active TINYINT DEFAULT 1 COMMENT '是否启用', " +
-            "created_at DATETIME COMMENT '创建时间', " +
-            "updated_at DATETIME COMMENT '更新时间'" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分配置表'",
-
-            // 家庭测评配置表
-            "CREATE TABLE IF NOT EXISTS family_assessment_config (" +
-            "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
-            "family_id BIGINT NOT NULL COMMENT '家庭ID', " +
-            "material_id BIGINT COMMENT '测评资料ID', " +
-            "remind_months INT DEFAULT 12 COMMENT '提醒周期(月)', " +
-            "last_remind_at DATETIME COMMENT '上次提醒时间', " +
-            "next_remind_at DATETIME COMMENT '下次提醒时间', " +
-            "is_enabled TINYINT DEFAULT 1 COMMENT '是否启用:1启用0关闭', " +
-            "created_by BIGINT COMMENT '创建人(规划师ID)', " +
-            "created_at DATETIME COMMENT '创建时间', " +
-            "updated_at DATETIME COMMENT '更新时间', " +
-            "UNIQUE KEY uk_family (family_id), " +
-            "INDEX idx_next_remind (next_remind_at), " +
-            "INDEX idx_enabled (is_enabled)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭测评配置表'",
-
-            // 测评记录表
-            "CREATE TABLE IF NOT EXISTS assessment_records (" +
-            "id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', " +
-            "family_id BIGINT COMMENT '家庭ID', " +
-            "child_id BIGINT COMMENT '孩子ID', " +
-            "material_id BIGINT COMMENT '测评资料ID', " +
-            "status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending待完成/completed已完成', " +
-            "completed_at DATETIME COMMENT '完成时间', " +
-            "created_at DATETIME COMMENT '创建时间', " +
-            "INDEX idx_family (family_id), " +
-            "INDEX idx_status (status)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评记录表'",
-
-            // 成长规划师表(已认证的规划师)
-            "CREATE TABLE IF NOT EXISTS guides (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
-            "user_id BIGINT NOT NULL UNIQUE COMMENT '用户ID(唯一)', " +
-            "level VARCHAR(20) NOT NULL DEFAULT 'junior' COMMENT '等级: junior-初级, intermediate-中级, senior-高级', " +
-            "status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active-活跃, inactive-停用', " +
-            "service_count INT DEFAULT 0 COMMENT '当前服务家庭数', " +
-            "package_id BIGINT COMMENT '购买套餐ID', " +
-            "nickname VARCHAR(100) COMMENT '规划师昵称', " +
-            "phone VARCHAR(20) COMMENT '联系电话', " +
-            "intro TEXT COMMENT '个人介绍', " +
-            "avatar VARCHAR(500) COMMENT '头像URL', " +
-            "commission_rate DECIMAL(5,4) DEFAULT 0.1000 COMMENT '佣金比例', " +
-            "approved_at DATETIME COMMENT '审核通过时间', " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
-            "INDEX idx_user_id (user_id), " +
-            "INDEX idx_level (level), " +
-            "INDEX idx_status (status)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师表'",
-
-            // 成长规划师申请表
-            "CREATE TABLE IF NOT EXISTS guide_applications (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID', " +
-            "user_id BIGINT NOT NULL COMMENT '申请人用户ID', " +
-            "type VARCHAR(20) NOT NULL DEFAULT 'new_apply' COMMENT '申请类型: new_apply-新注册, upgrade-升级', " +
-            "target_level VARCHAR(20) NOT NULL COMMENT '目标等级: junior-初级, intermediate-中级, senior-高级', " +
-            "current_level VARCHAR(20) DEFAULT NULL COMMENT '当前等级(升级时填写)', " +
-            "package_id BIGINT COMMENT '申请的套餐ID', " +
-            "payment_proof VARCHAR(500) COMMENT '付款凭证URL', " +
-            "payment_amount DECIMAL(10,2) COMMENT '付款金额', " +
-            "status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending-待审核, approved-已通过, rejected-已拒绝', " +
-            "reject_reason VARCHAR(500) COMMENT '拒绝原因', " +
-            "reviewed_at DATETIME COMMENT '审核时间', " +
-            "reviewed_by BIGINT COMMENT '审核人ID', " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', " +
-            "INDEX idx_user_id (user_id), " +
-            "INDEX idx_status (status), " +
-            "INDEX idx_type (type)" +
-            ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请表'",
-
-            // 小游戏表
-            "CREATE TABLE IF NOT EXISTS mini_games (" +
-            "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
-            "game_code VARCHAR(20) NOT NULL UNIQUE COMMENT '游戏代码', " +
-            "game_name VARCHAR(50) NOT NULL COMMENT '游戏名称', " +
-            "description VARCHAR(255) COMMENT '游戏描述', " +
-            "icon VARCHAR(50) COMMENT '图标emoji', " +
-            "default_duration INT DEFAULT 5 COMMENT '默认时长(分钟)', " +
-            "default_points INT DEFAULT 5 COMMENT '默认奖励积分', " +
-            "game_url VARCHAR(255) COMMENT '游戏H5页面URL', " +
-            "sort_order INT DEFAULT 0 COMMENT '排序', " +
-            "status TINYINT DEFAULT 1 COMMENT '状态:0禁用1启用', " +
-            "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
-            "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
-"INDEX idx_game_code (game_code), " +
-"INDEX idx_status (status)" +
-") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4"
-        );
-
-        // 执行创建表SQL
-        for (String sql : createTableSQLs) {
-            try {
-                jdbcTemplate.execute(sql);
-            } catch (Exception e) {
-                log.warn("创建表失败: {}", e.getMessage());
+        try {
+            org.springframework.core.io.Resource resource = resourceLoader.getResource("classpath:schema.sql");
+            String content;
+            try (java.io.InputStream is = resource.getInputStream()) {
+                content = new String(StreamUtils.copyToByteArray(is), StandardCharsets.UTF_8);
+            }
+
+            // Remove SQL single-line comments
+            content = content.replaceAll("(?m)^\\s*--.*$", "");
+
+            // Extract all CREATE TABLE IF NOT EXISTS statements
+            Pattern pattern = Pattern.compile(
+                "CREATE\\s+TABLE\\s+IF\\s+NOT\\s+EXISTS\\s+.*?;",
+                Pattern.CASE_INSENSITIVE | Pattern.DOTALL
+            );
+            Matcher matcher = pattern.matcher(content);
+
+            List<String> createTableSQLs = new ArrayList<>();
+            while (matcher.find()) {
+                String sql = matcher.group().trim();
+                if (!sql.isEmpty()) {
+                    createTableSQLs.add(sql);
+                }
+            }
+
+            log.info("从 schema.sql 加载了 {} 个建表语句", createTableSQLs.size());
+
+            for (String sql : createTableSQLs) {
+                try {
+                    jdbcTemplate.execute(sql);
+                } catch (Exception e) {
+                    log.warn("创建表失败: {}", e.getMessage());
+                }
             }
+
+            log.info("数据表检查完成");
+        } catch (Exception e) {
+            log.error("读取 schema.sql 失败", e);
         }
-        
-        log.info("数据表检查完成");
     }
 
     private void runMigrations() {
-        // 迁移1: 任务表添加执行者字段
+        // 迁移1: 任务表添加执行者字段
         try {
             jdbcTemplate.execute("ALTER TABLE tasks ADD COLUMN executor_type ENUM('child', 'parent') DEFAULT 'child' COMMENT '执行者类型'");
             log.info("已添加executor_type列到tasks表");
@@ -1315,6 +947,17 @@ log.info("已添加template_id列到tasks表");
             insertSysConfigSeed("renewal_discount_rate", "0.85", "续费折扣率");
             insertSysConfigSeed("member_discount_standard", "0.9", "标准会员折扣率");
             insertSysConfigSeed("member_discount_premium", "0.8", "高级会员折扣率");
+            insertSysConfigSeed("member_fee_family", "36500", "家庭会员年费(分)");
+            insertSysConfigSeed("member_upgrade_threshold", "131400", "自动升级家庭会员累计消费门槛(分)");
+            insertSysConfigSeed("member_discount_family", "9000", "家庭会员商品折扣(bps)");
+            insertSysConfigSeed("commission_member_l1_rate", "2000", "推荐家庭会员L1佣金率(bps)");
+            insertSysConfigSeed("commission_member_l2_rate", "500", "推荐家庭会员L2佣金率(bps)");
+            insertSysConfigSeed("commission_product_profit_share", "1000", "下线商品购买利润分成(bps)");
+            insertSysConfigSeed("commission_service_rate", "1000", "下线服务购买佣金率(bps)");
+            insertSysConfigSeed("commission_event_fixed", "2000", "推荐新用户报名活动固定佣金(分)");
+            insertSysConfigSeed("withdraw_min_amount", "10000", "最低提现金额(分)");
+            insertSysConfigSeed("withdraw_tax_rate", "2000", "佣金税率(bps)");
+            insertSysConfigSeed("withdraw_fee_free_threshold", "80000", "免税额度(分)");
             log.info("SysConfig 种子数据已加载");
         } catch (Exception e) {
             log.warn("SysConfig 种子数据初始化失败: {}", e.getMessage());
@@ -1466,6 +1109,66 @@ log.info("已添加template_id列到tasks表");
             log.warn("创建withdrawal_requests表失败: {}", e.getMessage());
         }
 
+        // ==================== 会员体系 & 分佣扩展 ====================
+
+        // 迁移: users表添加会员字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'users' AND COLUMN_NAME = 'member_level'",
+                Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN member_level VARCHAR(20) DEFAULT 'FREE' COMMENT '会员等级: FREE/FAMILY/PROVIDER'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN member_expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_consumption BIGINT DEFAULT 0 COMMENT '累计消费金额(分)'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN upgrade_from_consumption TINYINT DEFAULT 0 COMMENT '是否通过累计消费自动升级'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN direct_count INT DEFAULT 0 COMMENT '直接推荐人数'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN indirect_count INT DEFAULT 0 COMMENT '间接推荐人数'");
+                jdbcTemplate.execute("ALTER TABLE users ADD COLUMN total_commission_earned BIGINT DEFAULT 0 COMMENT '累计获得佣金(分)'");
+                log.info("已添加会员字段到users表");
+            } else {
+                log.info("会员字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加会员字段失败: {}", e.getMessage());
+        }
+
+        // 迁移: commission_records表添加扩展字段
+        try {
+            Integer exists = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'commission_records' AND COLUMN_NAME = 'level'",
+                Integer.class);
+            if (exists == null || exists == 0) {
+                jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN level INT DEFAULT 1 COMMENT '佣金层级: 1=直接推荐, 2=间接推荐'");
+                jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN settled_at DATETIME DEFAULT NULL COMMENT '结算时间'");
+                jdbcTemplate.execute("ALTER TABLE commission_records ADD COLUMN withdrawn_at DATETIME DEFAULT NULL COMMENT '提现时间'");
+                log.info("已添加扩展字段到commission_records表");
+            } else {
+                log.info("commission_records扩展字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加commission_records扩展字段失败: {}", e.getMessage());
+        }
+
+        // 新表: member_upgrade_records (会员升级记录)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS member_upgrade_records (" +
+                "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                "from_level VARCHAR(20) NOT NULL COMMENT '升级前等级', " +
+                "to_level VARCHAR(20) NOT NULL COMMENT '升级后等级', " +
+                "upgrade_type VARCHAR(20) NOT NULL COMMENT '升级类型: pay/consumption/admin', " +
+                "order_id BIGINT DEFAULT NULL COMMENT '关联支付订单ID', " +
+                "consumption_amount BIGINT DEFAULT NULL COMMENT '触发升级的累计消费金额', " +
+                "expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间', " +
+                "created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, " +
+                "INDEX idx_user (user_id), " +
+                "INDEX idx_order (order_id)" +
+                ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员升级记录'");
+            log.info("已创建member_upgrade_records表");
+        } catch (Exception e) {
+            log.warn("创建member_upgrade_records表失败: {}", e.getMessage());
+        }
+
         // ==================== 文章内容系统 ====================
 
         // 文章分类表
@@ -1662,6 +1365,101 @@ log.info("已添加template_id列到tasks表");
             log.warn("初始化商品能量比例配置失败: {}", e.getMessage());
         }
 
+        // ==================== 迁移: guides 表统一 ====================
+
+        // 迁移: guides 添加 type 字段(区分服务商类型)
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN type VARCHAR(32) DEFAULT 'teacher' COMMENT '服务商类型: teacher/planner/activity_provider/product_supplier'");
+            log.info("已添加type列到guides表");
+        } catch (Exception e) {
+            // 列已存在,忽略错误
+        }
+
+        // 迁移: guides 添加 teachers 合并字段
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN teacher_no VARCHAR(20) COMMENT '服务商编号'");
+            log.info("已添加teacher_no列到guides表");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN title VARCHAR(50) COMMENT '职称/称号'");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN specialty VARCHAR(200) COMMENT '专长领域'");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN bio TEXT COMMENT '简介'");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN experience_years TINYINT DEFAULT 0 COMMENT '从业年限'");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN rating DECIMAL(3,2) DEFAULT 5.00 COMMENT '评分'");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN total_appointments INT DEFAULT 0 COMMENT '总服务次数'");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN street_id BIGINT COMMENT '街道ID'");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN province VARCHAR(50) COMMENT '省份'");
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN city 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 '街道'");
+            log.info("已添加地址字段到guides表");
+        } catch (Exception e) {}
+
+        try {
+            jdbcTemplate.execute("ALTER TABLE guides ADD COLUMN verified_at DATETIME COMMENT '审核通过时间'");
+        } catch (Exception e) {}
+
+        // 迁移: 将 teachers 表数据合并到 guides(type='teacher')
+        try {
+            int migrated = jdbcTemplate.update(
+                "INSERT IGNORE INTO guides (user_id, type, teacher_no, title, specialty, bio, experience_years, rating, total_appointments, " +
+                "street_id, province, city, district, street, verified_at, status, level, created_at, updated_at) " +
+                "SELECT t.user_id, 'teacher', t.teacher_no, t.title, t.specialty, t.bio, t.experience_years, t.rating, t.total_appointments, " +
+                "t.street_id, t.province, t.city, t.district, t.street, t.verified_at, " +
+                "CASE WHEN t.status = 'active' THEN 'active' ELSE 'inactive' END, " +
+                "CASE WHEN t.verification_status = 'approved' THEN 'senior' WHEN t.verification_status = 'pending' THEN 'junior' ELSE 'junior' END, " +
+                "t.created_at, t.updated_at " +
+                "FROM teachers t " +
+                "LEFT JOIN guides g ON g.user_id = t.user_id " +
+                "WHERE g.id IS NULL"
+            );
+            if (migrated > 0) {
+                log.info("已从 teachers 表迁移 {} 条数据到 guides(type=teacher)", migrated);
+            }
+        } catch (Exception e) {
+            log.warn("迁移 teachers → guides 数据失败: {}", e.getMessage());
+        }
+
+        // 迁移: dan_assessment_results 表添加认知维度字段(6维认知体系扩展)
+        try {
+            Integer colExists = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'dan_assessment_results' AND COLUMN_NAME = 'perception_score'",
+                Integer.class);
+            if (colExists == null || colExists == 0) {
+                jdbcTemplate.execute("ALTER TABLE dan_assessment_results " +
+                    "ADD COLUMN perception_score INT COMMENT '感知得分', " +
+                    "ADD COLUMN spatial_score INT COMMENT '空间思维得分', " +
+                    "ADD COLUMN processing_speed_score INT COMMENT '加工速度得分'");
+                log.info("已为 dan_assessment_results 表添加认知维度字段");
+            } else {
+                log.info("dan_assessment_results 认知维度字段已存在,跳过");
+            }
+        } catch (Exception e) {
+            log.warn("检查/添加 dan_assessment_results 认知维度字段失败: {}", e.getMessage());
+        }
+
         log.info("数据库迁移完成");
     }
 
@@ -2121,7 +1919,108 @@ try {
             // 列已存在,忽略错误
         }
 
-        log.info("成长规划师模块数据库表初始化完成");
+        // 财富维度: 财商打卡记录表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS finance_checkins (" +
+                "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
+                "checkin_type VARCHAR(20) NOT NULL COMMENT '打卡类型: daily_plan/saving/expense/review', " +
+                "checkin_date DATE NOT NULL COMMENT '打卡日期', " +
+                "amount DECIMAL(10,2) COMMENT '金额(收支记录)', " +
+                "category VARCHAR(50) COMMENT '收支分类', " +
+                "note VARCHAR(200) COMMENT '备注', " +
+                "mood VARCHAR(20) COMMENT '心情', " +
+                "earned_energy INT DEFAULT 0 COMMENT '获得的能量值', " +
+                "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                "INDEX idx_user_date (user_id, checkin_date), " +
+                "INDEX idx_child (child_id)" +
+                ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='财商打卡记录表'");
+            log.info("已创建finance_checkins表");
+        } catch (Exception e) {
+            log.warn("创建finance_checkins表失败: {}", e.getMessage());
+        }
+
+        // 财富维度: 保单管理表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS insurance_policies (" +
+                "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                "user_id BIGINT NOT NULL COMMENT '用户ID', " +
+                "child_id BIGINT COMMENT '关联孩子ID', " +
+                "policy_name VARCHAR(100) NOT NULL COMMENT '保单名称', " +
+                "insurance_company VARCHAR(100) COMMENT '保险公司', " +
+                "policy_type VARCHAR(50) COMMENT '保单类型: medical/life/accident/education/endowment', " +
+                "insured_person VARCHAR(50) NOT NULL COMMENT '被保人姓名', " +
+                "premium DECIMAL(10,2) COMMENT '保费', " +
+                "sum_insured DECIMAL(10,2) COMMENT '保额', " +
+                "start_date DATE COMMENT '生效日期', " +
+                "end_date DATE COMMENT '到期日期', " +
+                "renewal_type VARCHAR(20) DEFAULT 'annual' COMMENT '续费类型: annual/monthly/single', " +
+                "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/expired/cancelled', " +
+                "policy_no VARCHAR(100) COMMENT '保单号', " +
+                "remark VARCHAR(500) COMMENT '备注', " +
+                "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                "INDEX idx_user (user_id), " +
+                "INDEX idx_child (child_id), " +
+                "INDEX idx_status (status)" +
+                ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='保单管理表'");
+            log.info("已创建insurance_policies表");
+        } catch (Exception e) {
+            log.warn("创建insurance_policies表失败: {}", e.getMessage());
+        }
+
+        // ==================== 健康检测报告模块 ====================
+        // 健康报告表(体检报告/菌群检测报告)
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_reports (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "child_id BIGINT NOT NULL COMMENT '孩子ID', " +
+                    "family_id BIGINT COMMENT '家庭ID', " +
+                    "report_type VARCHAR(32) NOT NULL COMMENT '报告类型: physical_exam(体检)/gut_flora(菌群检测)', " +
+                    "report_date DATE COMMENT '报告日期', " +
+                    "overall_score INT COMMENT '健康总分(0-100)', " +
+                    "gut_health_score INT COMMENT '菌群健康分', " +
+                    "chronic_disease_score INT COMMENT '慢病控制分', " +
+                    "nutrition_score INT COMMENT '营养均衡分', " +
+                    "gut_age VARCHAR(32) COMMENT '肠道预测年龄', " +
+                    "gut_type VARCHAR(50) COMMENT '肠型', " +
+                    "file_url VARCHAR(500) COMMENT '报告文件URL', " +
+                    "status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/archived', " +
+                    "remark VARCHAR(500) COMMENT '备注', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, " +
+                    "INDEX idx_child_id (child_id), " +
+                    "INDEX idx_family_id (family_id), " +
+                    "INDEX idx_report_type (report_type)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康检测报告表'");
+            log.info("已创建health_reports表");
+        } catch (Exception e) {
+            log.warn("创建health_reports表可能已存在: {}", e.getMessage());
+        }
+
+        // 健康指标明细表
+        try {
+            jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS health_indicators (" +
+                    "id BIGINT AUTO_INCREMENT PRIMARY KEY, " +
+                    "report_id BIGINT NOT NULL COMMENT '关联报告ID', " +
+                    "category VARCHAR(50) COMMENT '指标分类: 营养状况/疾病风险/肠道屏障/神经递质/...', " +
+                    "indicator_name VARCHAR(100) NOT NULL COMMENT '指标名称', " +
+                    "indicator_value VARCHAR(100) COMMENT '检测值', " +
+                    "unit VARCHAR(50) COMMENT '单位', " +
+                    "ref_range VARCHAR(200) COMMENT '参考范围', " +
+                    "status VARCHAR(50) COMMENT '评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出', " +
+                    "sort_order INT DEFAULT 0 COMMENT '显示排序', " +
+                    "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+                    "INDEX idx_report_id (report_id), " +
+                    "INDEX idx_category (category)" +
+                    ") ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康指标明细表'");
+            log.info("已创建health_indicators表");
+        } catch (Exception e) {
+            log.warn("创建health_indicators表可能已存在: {}", e.getMessage());
+        }
+
+        log.info("成长规划师模块、财富维度及健康检测报告数据库表初始化完成");
     }
 
     private void insertSysConfigSeed(String key, String value, String desc) {

+ 35 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/CommissionController.java

@@ -8,6 +8,8 @@ import com.etotem.cfc.service.CommissionService;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import java.math.BigDecimal;
+import java.util.HashMap;
 import java.util.Map;
 
 @RestController
@@ -29,4 +31,37 @@ public class CommissionController {
         int size = params.getOrDefault("size", 20);
         return Result.success(commissionService.getList(userId, page, size));
     }
+
+    /**
+     * 获取团队统计(L1/L2人数、团队佣金)
+     */
+    @PostMapping("/team")
+    public Result<Map<String, Object>> team(@RequestAttribute("userId") Long userId) {
+        Map<String, Object> stats = commissionService.getTeamStats(userId);
+        return Result.success(stats);
+    }
+
+    /**
+     * 获取佣金统计面板数据(今日/本月/累计/可提现/L1/L2)
+     */
+    @PostMapping("/stats")
+    public Result<Map<String, Object>> stats(@RequestAttribute("userId") Long userId) {
+        Map<String, Object> stats = commissionService.getCommissionStats(userId);
+        return Result.success(stats);
+    }
+
+    /**
+     * 获取可提现余额详情
+     */
+    @PostMapping("/balance")
+    public Result<Map<String, Object>> balance(@RequestAttribute("userId") Long userId) {
+        CommissionSummaryDTO summary = commissionService.getSummary(userId);
+        Map<String, Object> balanceInfo = new HashMap<>();
+        balanceInfo.put("availableAmount", summary.getAvailableAmount());
+        balanceInfo.put("settledAmount", summary.getSettledAmount());
+        balanceInfo.put("pendingAmount", summary.getPendingAmount());
+        balanceInfo.put("totalCommission", summary.getTotalCommission());
+        balanceInfo.put("withdrawnAmount", summary.getWithdrawnAmount());
+        return Result.success(balanceInfo);
+    }
 }

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

@@ -0,0 +1,171 @@
+package com.etotem.cfc.controller;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.HealthIndicator;
+import com.etotem.cfc.entity.HealthReport;
+import com.etotem.cfc.service.HealthReportService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 健康检测报告接口 — 体检报告/菌群检测报告的录入与查询
+ */
+@Tag(name = "健康检测报告", description = "健康报告上传、查询、指标明细")
+@RestController
+@RequestMapping("/api/health")
+public class HealthReportController {
+
+    @Resource
+    private HealthReportService healthReportService;
+
+    /**
+     * 创建健康报告(含指标明细)
+     */
+    @Operation(summary = "创建健康报告")
+    @PostMapping("/report/create")
+    public Result<HealthReport> createReport(
+            @RequestBody Map<String, Object> params,
+            @RequestAttribute("userId") Long userId) {
+
+        Long childId = params.get("childId") != null
+                ? Long.valueOf(params.get("childId").toString()) : null;
+        if (childId == null) {
+            return Result.error("childId不能为空");
+        }
+
+        String reportType = (String) params.get("reportType");
+        if (reportType == null) {
+            return Result.error("reportType不能为空: physical_exam / gut_flora");
+        }
+
+        // 构建报告主体
+        HealthReport report = new HealthReport();
+        report.setChildId(childId);
+        report.setReportType(reportType);
+        report.setOverallScore(params.get("overallScore") != null
+                ? Integer.valueOf(params.get("overallScore").toString()) : null);
+        report.setGutHealthScore(params.get("gutHealthScore") != null
+                ? Integer.valueOf(params.get("gutHealthScore").toString()) : null);
+        report.setChronicDiseaseScore(params.get("chronicDiseaseScore") != null
+                ? Integer.valueOf(params.get("chronicDiseaseScore").toString()) : null);
+        report.setNutritionScore(params.get("nutritionScore") != null
+                ? Integer.valueOf(params.get("nutritionScore").toString()) : null);
+        report.setGutAge((String) params.get("gutAge"));
+        report.setGutType((String) params.get("gutType"));
+        report.setFileUrl((String) params.get("fileUrl"));
+        report.setRemark((String) params.get("remark"));
+
+        // 报告日期(默认当天)
+        report.setReportDate(new java.util.Date());
+
+        // 指标明细(可选)
+        @SuppressWarnings("unchecked")
+        List<Map<String, Object>> indicatorList =
+                (List<Map<String, Object>>) params.get("indicators");
+        List<HealthIndicator> indicators = null;
+        if (indicatorList != null) {
+            indicators = new java.util.ArrayList<>();
+            for (int i = 0; i < indicatorList.size(); i++) {
+                Map<String, Object> item = indicatorList.get(i);
+                HealthIndicator ind = new HealthIndicator();
+                ind.setCategory((String) item.get("category"));
+                ind.setIndicatorName((String) item.get("indicatorName"));
+                ind.setIndicatorValue(item.get("indicatorValue") != null
+                        ? item.get("indicatorValue").toString() : null);
+                ind.setUnit((String) item.get("unit"));
+                ind.setRefRange((String) item.get("refRange"));
+                ind.setStatus((String) item.get("status"));
+                ind.setSortOrder(i);
+                indicators.add(ind);
+            }
+        }
+
+        HealthReport created = healthReportService.createReport(report, indicators);
+        return Result.success(created);
+    }
+
+    /**
+     * 获取孩子的最新健康报告
+     */
+    @Operation(summary = "获取最新健康报告")
+    @PostMapping("/report/latest")
+    public Result<HealthReport> getLatestReport(
+            @RequestBody Map<String, Object> params) {
+        Long childId = params.get("childId") != null
+                ? Long.valueOf(params.get("childId").toString()) : null;
+        if (childId == null) {
+            return Result.error("childId不能为空");
+        }
+        HealthReport report = healthReportService.getLatestReport(childId);
+        return Result.success(report);
+    }
+
+    /**
+     * 获取孩子的所有健康报告列表
+     */
+    @Operation(summary = "获取健康报告列表")
+    @PostMapping("/report/list")
+    public Result<List<HealthReport>> listReports(
+            @RequestBody Map<String, Object> params) {
+        Long childId = params.get("childId") != null
+                ? Long.valueOf(params.get("childId").toString()) : null;
+        if (childId == null) {
+            return Result.error("childId不能为空");
+        }
+        List<HealthReport> reports = healthReportService.getChildReports(childId);
+        return Result.success(reports);
+    }
+
+    /**
+     * 获取某份报告的所有指标明细
+     */
+    @Operation(summary = "获取报告指标明细")
+    @PostMapping("/indicator/list")
+    public Result<List<HealthIndicator>> listIndicators(
+            @RequestBody Map<String, Object> params) {
+        Long reportId = params.get("reportId") != null
+                ? Long.valueOf(params.get("reportId").toString()) : null;
+        if (reportId == null) {
+            return Result.error("reportId不能为空");
+        }
+        List<HealthIndicator> indicators = healthReportService.getReportIndicators(reportId);
+        return Result.success(indicators);
+    }
+
+    /**
+     * 获取孩子身体维度数据详情(健康指标解读)
+     */
+    @Operation(summary = "获取身体维度数据详情")
+    @PostMapping("/body/detail")
+    public Result<List<HealthIndicator>> getBodyDetail(
+            @RequestBody Map<String, Object> params) {
+        Long childId = params.get("childId") != null
+                ? Long.valueOf(params.get("childId").toString()) : null;
+        if (childId == null) {
+            return Result.error("childId不能为空");
+        }
+        List<HealthIndicator> detail = healthReportService.getBodyDetail(childId);
+        return Result.success(detail);
+    }
+
+    /**
+     * 归档报告
+     */
+    @Operation(summary = "归档健康报告")
+    @PostMapping("/report/archive")
+    public Result<String> archiveReport(
+            @RequestBody Map<String, Object> params) {
+        Long reportId = params.get("reportId") != null
+                ? Long.valueOf(params.get("reportId").toString()) : null;
+        if (reportId == null) {
+            return Result.error("reportId不能为空");
+        }
+        healthReportService.archiveReport(reportId);
+        return Result.success("报告已归档");
+    }
+}

+ 172 - 22
cfc-backend/src/main/java/com/etotem/cfc/controller/MembershipController.java

@@ -1,13 +1,21 @@
 package com.etotem.cfc.controller;
 
+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.dto.*;
+import com.etotem.cfc.entity.MemberUpgradeRecord;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.MemberUpgradeRecordMapper;
+import com.etotem.cfc.mapper.UserMapper;
 import com.etotem.cfc.service.MembershipService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -19,6 +27,12 @@ public class MembershipController {
     @Resource
     private MembershipService membershipService;
 
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private MemberUpgradeRecordMapper memberUpgradeRecordMapper;
+
     /**
      * 获取所有会员等级
      */
@@ -30,42 +44,150 @@ public class MembershipController {
     }
 
     /**
-     * 获取家庭当前会员信息
+     * 获取当前用户的会员信息
      */
-    @Operation(summary = "获取家庭会员信息")
+    @Operation(summary = "获取我的会员信息")
     @PostMapping("/my")
-    public Result<FamilyMembershipDTO> getMyMembership(javax.servlet.http.HttpServletRequest request) {
+    public Result<Map<String, Object>> getMyMembership(HttpServletRequest request) {
         Long userId = getUserId(request);
-        // 获取用户家庭ID
-        // 简化:假设通过其他服务获取
-        Long familyId = 1L; // 临时
-        
-        FamilyMembershipDTO membership = membershipService.getFamilyMembership(familyId);
-        return Result.success(membership);
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        Long familyId = user.getFamilyId();
+        Map<String, Object> result = new HashMap<>();
+
+        // 会员等级信息
+        String memberLevel = membershipService.getMemberLevel(userId);
+        result.put("memberLevel", memberLevel);
+        result.put("memberExpireTime", user.getMemberExpireTime());
+
+        // 家庭会员记录
+        if (familyId != null) {
+            FamilyMembershipDTO membership = membershipService.getFamilyMembership(familyId);
+            result.put("membership", membership);
+        }
+
+        // 累计消费
+        result.put("totalConsumption", user.getTotalConsumption());
+
+        return Result.success(result);
     }
 
     /**
-     * 获取当前会员等级
+     * 获取当前有效会员等级
      */
-    @Operation(summary = "获取当前会员等级")
+    @Operation(summary = "获取当前有效会员等级")
+    @PostMapping("/level")
+    public Result<Map<String, Object>> getLevel(HttpServletRequest request) {
+        Long userId = getUserId(request);
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        String memberLevel = membershipService.getMemberLevel(userId);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("memberLevel", memberLevel);
+        result.put("memberExpireTime", user.getMemberExpireTime());
+
+        return Result.success(result);
+    }
+
+    /**
+     * 获取当前会员等级(含等级详情)
+     */
+    @Operation(summary = "获取当前会员等级详情")
     @PostMapping("/current")
-    public Result<MembershipLevelDTO> getCurrentLevel(javax.servlet.http.HttpServletRequest request) {
-        Long familyId = 1L; // 临时
-        
-        MembershipLevelDTO level = membershipService.getCurrentLevel(familyId);
+    public Result<MembershipLevelDTO> getCurrentLevel(HttpServletRequest request) {
+        Long userId = getUserId(request);
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getFamilyId() == null) {
+            return Result.error("用户无家庭信息");
+        }
+
+        MembershipLevelDTO level = membershipService.getCurrentLevel(user.getFamilyId());
         return Result.success(level);
     }
 
+    /**
+     * 获取会员升级记录
+     */
+    @Operation(summary = "获取升级记录")
+    @PostMapping("/records")
+    public Result<Page<MemberUpgradeRecord>> getUpgradeRecords(HttpServletRequest request,
+                                                                @RequestBody Map<String, Object> params) {
+        Long userId = getUserId(request);
+        if (userId == null) {
+            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;
+
+        Page<MemberUpgradeRecord> pageParam = new Page<>(page, size);
+        Page<MemberUpgradeRecord> result = memberUpgradeRecordMapper.selectPage(pageParam,
+                new LambdaQueryWrapper<MemberUpgradeRecord>()
+                        .eq(MemberUpgradeRecord::getUserId, userId)
+                        .orderByDesc(MemberUpgradeRecord::getCreatedAt));
+        return Result.success(result);
+    }
+
+    /**
+     * 创建升级订单(FAMILY会员)
+     */
+    @Operation(summary = "创建会员升级订单")
+    @PostMapping("/upgrade")
+    public Result<PaymentOrderDTO> upgradeOrder(HttpServletRequest request,
+                                                 @RequestBody Map<String, Object> params) {
+        Long userId = getUserId(request);
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        Long familyId = membershipService.getUserFamilyId(userId);
+        String levelCode = params.getOrDefault("levelCode", "FAMILY").toString();
+        String paymentType = params.getOrDefault("paymentType", "yearly").toString();
+
+        PaymentOrderDTO order = membershipService.createOrder(familyId, levelCode, paymentType);
+        return Result.success(order);
+    }
+
     /**
      * 创建支付订单
      */
     @Operation(summary = "创建订单")
     @PostMapping("/orders")
-    public Result<PaymentOrderDTO> createOrder(@RequestBody Map<String, Object> params) {
+    public Result<PaymentOrderDTO> createOrder(HttpServletRequest request,
+                                                @RequestBody Map<String, Object> params) {
+        Long userId = getUserId(request);
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+
+        Long familyId = membershipService.getUserFamilyId(userId);
         String levelCode = params.get("levelCode").toString();
         String paymentType = params.get("paymentType").toString();
-        Long familyId = 1L; // 临时
-        
+
         PaymentOrderDTO order = membershipService.createOrder(familyId, levelCode, paymentType);
         return Result.success(order);
     }
@@ -88,15 +210,43 @@ public class MembershipController {
      */
     @Operation(summary = "检查功能权限")
     @PostMapping("/can-use")
-    public Result<Boolean> canUseFeature(@RequestBody Map<String, Object> params) {
+    public Result<Boolean> canUseFeature(HttpServletRequest request,
+                                          @RequestBody Map<String, Object> params) {
+        Long userId = getUserId(request);
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+
+        Long familyId = membershipService.getUserFamilyId(userId);
         String feature = params.get("feature").toString();
-        Long familyId = 1L; // 临时
-        
+
         boolean canUse = membershipService.canUseFeature(familyId, feature);
         return Result.success(canUse);
     }
 
-    private Long getUserId(javax.servlet.http.HttpServletRequest request) {
+    /**
+     * 应用会员折扣
+     */
+    @Operation(summary = "应用会员折扣")
+    @PostMapping("/discount")
+    public Result<Map<String, Object>> applyDiscount(HttpServletRequest request,
+                                                      @RequestBody Map<String, Object> params) {
+        Long userId = getUserId(request);
+        if (userId == null) {
+            return Result.error("用户未登录");
+        }
+
+        java.math.BigDecimal originalPrice = new java.math.BigDecimal(params.get("amount").toString());
+        java.math.BigDecimal discountedPrice = membershipService.applyMemberDiscount(userId, originalPrice);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("originalPrice", originalPrice);
+        result.put("discountedPrice", discountedPrice);
+
+        return Result.success(result);
+    }
+
+    private Long getUserId(HttpServletRequest request) {
         Object userIdObj = request.getAttribute("userId");
         if (userIdObj != null) {
             return (Long) userIdObj;

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

@@ -802,52 +802,6 @@ public class AdminController {
         return false;
     }
 
-    // ========== 溪福俱乐部 - 服务商审核接口 ==========
-
-    @PostMapping("/vendor/list")
-    public Result vendorList(@RequestBody Map<String, Object> params) {
-        Integer page = params.get("page") != null ? Integer.valueOf(params.get("page").toString()) : 1;
-        Integer size = params.get("size") != null ? Integer.valueOf(params.get("size").toString()) : 20;
-        String status = (String) params.get("status");
-        com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<User> wrapper = new com.baomidou.mybatisplus.core.conditions.query.QueryWrapper<>();
-        wrapper.isNotNull("vendor_type");
-        if (status != null && !status.isEmpty()) {
-            wrapper.eq("vendor_status", status);
-        }
-        wrapper.orderByDesc("updated_at");
-        com.baomidou.mybatisplus.extension.plugins.pagination.Page<User> pageObj = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>(page, size);
-        com.baomidou.mybatisplus.core.metadata.IPage<User> result = userMapper.selectPage(pageObj, 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("/vendor/review")
-    public Result vendorReview(@RequestBody Map<String, Object> params) {
-        Long userId = Long.valueOf(params.get("userId").toString());
-        String action = (String) params.get("action");
-        String reason = (String) params.getOrDefault("reason", "");
-        User user = userMapper.selectById(userId);
-        if (user == null) {
-            return Result.error("用户不存在");
-        }
-        if ("approve".equals(action)) {
-            user.setVendorStatus("approved");
-            user.setVendorRejectReason(null);
-        } else if ("reject".equals(action)) {
-            user.setVendorStatus("rejected");
-            user.setVendorRejectReason(reason);
-        } else {
-            return Result.error("无效的审核操作");
-        }
-        user.setUpdatedAt(new Date());
-        userMapper.updateById(user);
-        return Result.success("审核完成");
-    }
-
     // ========== 商品/订单管理接口 ==========
 
     @Resource

+ 91 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/AdminVendorController.java

@@ -0,0 +1,91 @@
+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.User;
+import com.etotem.cfc.mapper.UserMapper;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
+@RestController
+@RequestMapping("/api/admin/vendor")
+public class AdminVendorController {
+
+    @Resource
+    private UserMapper userMapper;
+
+    /**
+     * 分页查询服务商入驻申请列表
+     * 过滤条件: vendorStatus, vendorType
+     */
+    @PostMapping("/list")
+    public Result<Map<String, Object>> list(@RequestBody Map<String, Object> params) {
+        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 vendorStatus = (String) params.get("vendorStatus");
+        String vendorType = (String) params.get("vendorType");
+
+        Page<User> pageParam = new Page<>(page, size);
+        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
+                .isNotNull(User::getVendorStatus);
+
+        if (vendorStatus != null && !vendorStatus.isEmpty()) {
+            wrapper.eq(User::getVendorStatus, vendorStatus);
+        }
+        if (vendorType != null && !vendorType.isEmpty()) {
+            wrapper.eq(User::getVendorType, vendorType);
+        }
+
+        wrapper.orderByDesc(User::getUpdatedAt);
+        Page<User> result = userMapper.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);
+    }
+
+    /**
+     * 审核服务商入驻申请
+     * @param params userId, vendorStatus(approved/rejected), rejectReason
+     */
+    @PostMapping("/review")
+    public Result<String> review(@RequestBody Map<String, Object> params) {
+        Long userId = params.get("userId") != null
+                ? Long.valueOf(params.get("userId").toString())
+                : null;
+        String vendorStatus = (String) params.get("vendorStatus");
+        String rejectReason = (String) params.getOrDefault("rejectReason", "");
+
+        if (userId == null) {
+            return Result.error("userId不能为空");
+        }
+        if (vendorStatus == null || (!"approved".equals(vendorStatus) && !"rejected".equals(vendorStatus))) {
+            return Result.error("vendorStatus必须为 approved 或 rejected");
+        }
+
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return Result.error("用户不存在");
+        }
+
+        user.setVendorStatus(vendorStatus);
+        if ("rejected".equals(vendorStatus)) {
+            user.setVendorRejectReason(rejectReason);
+        } else {
+            user.setVendorRejectReason(null);
+        }
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
+
+        String msg = "approved".equals(vendorStatus) ? "审核通过" : "已拒绝";
+        return Result.success(msg);
+    }
+}

+ 52 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/admin/EducationSystemController.java

@@ -0,0 +1,52 @@
+package com.etotem.cfc.controller.admin;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.EducationSystem;
+import com.etotem.cfc.service.EducationSystemService;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@RestController
+@RequestMapping("/api/admin/education-systems")
+public class EducationSystemController {
+
+    @Resource
+    private EducationSystemService educationSystemService;
+
+    @PostMapping("/list")
+    public Result<List<EducationSystem>> list() {
+        List<EducationSystem> list = educationSystemService.getActiveSystems();
+        return Result.success(list);
+    }
+
+    @PostMapping("/all")
+    public Result<List<EducationSystem>> listAll() {
+        List<EducationSystem> list = educationSystemService.list();
+        return Result.success(list);
+    }
+
+    @PostMapping("/create")
+    public Result<EducationSystem> create(@RequestBody EducationSystem system) {
+        system.setStatus("ACTIVE");
+        system.setCreatedAt(new Date());
+        system.setUpdatedAt(new Date());
+        EducationSystem created = educationSystemService.createSystem(system);
+        return Result.success(created);
+    }
+
+    @PostMapping("/update")
+    public Result<EducationSystem> update(@RequestBody EducationSystem system) {
+        system.setUpdatedAt(new Date());
+        boolean success = educationSystemService.updateSystem(system);
+        return success ? Result.success(system) : Result.error("更新失败");
+    }
+
+    @PostMapping("/delete")
+    public Result<Void> delete(@RequestParam Long id) {
+        boolean success = educationSystemService.deleteSystem(id);
+        return success ? Result.success(null) : Result.error("删除失败");
+    }
+}

+ 112 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/assessment/CognitiveController.java

@@ -0,0 +1,112 @@
+package com.etotem.cfc.controller.assessment;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.DanAssessmentResult;
+import com.etotem.cfc.service.CognitiveService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import org.springframework.web.bind.annotation.*;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+@Tag(name = "认知维度", description = "六维认知体系(感知、专注、记忆、逻辑、空间、加工速度)")
+@RestController
+@RequestMapping("/api/cognitive")
+public class CognitiveController {
+
+    @Resource
+    private CognitiveService cognitiveService;
+
+    @Operation(summary = "获取孩子最新六维认知得分")
+    @PostMapping("/child")
+    public Result<Map<String, Object>> getChildCognitiveScores(
+            @RequestBody Map<String, Object> body) {
+        Long childId = body.get("childId") != null
+                ? Long.valueOf(body.get("childId").toString())
+                : null;
+        if (childId == null) {
+            return Result.error("childId不能为空");
+        }
+
+        DanAssessmentResult result = cognitiveService.getChildLatestResult(childId);
+        if (result == null) {
+            return Result.error("未找到测评结果");
+        }
+
+        Map<String, Object> scores = new LinkedHashMap<>();
+        scores.put("childId", result.getChildId());
+        scores.put("childName", result.getChildName());
+        scores.put("danLevel", result.getDanLevel());
+        scores.put("attentionScore", result.getAttentionScore());
+        scores.put("focusScore", result.getFocusScore());
+        scores.put("memoryScore", result.getMemoryScore());
+        scores.put("logicScore", result.getLogicScore());
+        scores.put("perceptionScore", result.getPerceptionScore());
+        scores.put("spatialScore", result.getSpatialScore());
+        scores.put("processingSpeedScore", result.getProcessingSpeedScore());
+        scores.put("overallScore", result.getOverallScore());
+        scores.put("assessmentDate", result.getAssessmentDate());
+
+        return Result.success(scores);
+    }
+
+    @Operation(summary = "获取家庭所有孩子的最新六维认知得分")
+    @PostMapping("/family")
+    public Result<List<Map<String, Object>>> getFamilyCognitiveScores(
+            @RequestAttribute("userId") Long userId) {
+        Map<Long, DanAssessmentResult> results = cognitiveService.getFamilyLatestResults(userId);
+
+        List<Map<String, Object>> list = new ArrayList<>();
+        for (Map.Entry<Long, DanAssessmentResult> entry : results.entrySet()) {
+            DanAssessmentResult r = entry.getValue();
+            Map<String, Object> item = new LinkedHashMap<>();
+            item.put("childId", r.getChildId());
+            item.put("childName", r.getChildName());
+            item.put("danLevel", r.getDanLevel());
+            item.put("attentionScore", r.getAttentionScore());
+            item.put("focusScore", r.getFocusScore());
+            item.put("memoryScore", r.getMemoryScore());
+            item.put("logicScore", r.getLogicScore());
+            item.put("perceptionScore", r.getPerceptionScore());
+            item.put("spatialScore", r.getSpatialScore());
+            item.put("processingSpeedScore", r.getProcessingSpeedScore());
+            item.put("overallScore", r.getOverallScore());
+            item.put("assessmentDate", r.getAssessmentDate());
+            list.add(item);
+        }
+
+        return Result.success(list);
+    }
+
+    @Operation(summary = "更新孩子认知维度得分")
+    @PostMapping("/update")
+    public Result<String> updateCognitiveScore(
+            @RequestBody Map<String, Object> body) {
+        Long childId = body.get("childId") != null
+                ? Long.valueOf(body.get("childId").toString())
+                : null;
+        String dimension = body.get("dimension") != null
+                ? body.get("dimension").toString()
+                : null;
+        Integer score = body.get("score") != null
+                ? Integer.valueOf(body.get("score").toString())
+                : null;
+
+        if (childId == null) {
+            return Result.error("childId不能为空");
+        }
+        if (dimension == null || dimension.isEmpty()) {
+            return Result.error("dimension不能为空");
+        }
+        if (score == null) {
+            return Result.error("score不能为空");
+        }
+
+        boolean success = cognitiveService.updateChildDimensionScore(childId, dimension, score);
+        if (!success) {
+            return Result.error("更新失败,未找到最新测评结果或维度不存在");
+        }
+        return Result.success("更新成功");
+    }
+}

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

@@ -63,8 +63,8 @@ public class GuideAssessmentController {
 
     @Operation(summary = "获取所有家庭测评配置")
     @PostMapping("/families")
-    public Result<List<Map<String, Object>>> getFamilyConfigs() {
-        List<FamilyAssessmentConfig> configs = assessmentService.getFamiliesByTeacher(null);
+    public Result<List<Map<String, Object>>> getFamilyConfigs(@RequestAttribute("userId") Long teacherId) {
+        List<FamilyAssessmentConfig> configs = assessmentService.getFamiliesByTeacher(teacherId);
 
         List<Map<String, Object>> result = configs.stream().map(config -> {
             Map<String, Object> map = new HashMap<>();

+ 110 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/guide/GuideTeamController.java

@@ -2,9 +2,11 @@ package com.etotem.cfc.controller.guide;
 
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.GuideHierarchy;
 import com.etotem.cfc.entity.GuideInviteCode;
 import com.etotem.cfc.entity.User;
 import com.etotem.cfc.mapper.UserMapper;
+import com.etotem.cfc.service.GuideHierarchyService;
 import com.etotem.cfc.service.GuideInviteCodeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -21,6 +23,9 @@ public class GuideTeamController {
     @Resource
     private GuideInviteCodeService guideInviteCodeService;
 
+    @Resource
+    private GuideHierarchyService guideHierarchyService;
+
     @Resource
     private UserMapper userMapper;
 
@@ -73,6 +78,111 @@ public class GuideTeamController {
         return Result.success(members);
     }
 
+    @Operation(summary = "检查用户相对邀请码的状态")
+    @PostMapping("/check-status")
+    public Result<Map<String, Object>> checkStatus(@RequestAttribute("userId") Long userId,
+                                                    @RequestBody Map<String, String> body) {
+        String inviteCode = body.get("inviteCode");
+        Map<String, Object> result = new HashMap<>();
+
+        if (inviteCode == null || inviteCode.isEmpty()) {
+            return Result.error("邀请码不能为空");
+        }
+
+        GuideInviteCode code = guideInviteCodeService.validateInviteCode(inviteCode);
+        if (code == null) {
+            return Result.error("邀请码无效或已过期");
+        }
+
+        User guide = userMapper.selectById(code.getGuideId());
+        result.put("inviteGuideName", guide != null ? guide.getNickname() : "未知");
+        result.put("inviteGuideId", code.getGuideId());
+
+        User currentUser = userMapper.selectById(userId);
+        boolean isPlanner = "teacher".equals(currentUser.getRole())
+            || "planner".equals(currentUser.getVendorType());
+
+        result.put("isLoggedIn", true);
+        result.put("isPlanner", isPlanner);
+
+        if (isPlanner) {
+            // 检查是否已有团队关系(在guide_hierarchy中被添加为child)
+            LambdaQueryWrapper<GuideHierarchy> teamCheck = new LambdaQueryWrapper<>();
+            teamCheck.eq(GuideHierarchy::getChildId, userId)
+                     .eq(GuideHierarchy::getStatus, "active");
+            GuideHierarchy existingTeam = guideHierarchyService.getOne(teamCheck);
+
+            result.put("inTeam", existingTeam != null);
+            if (existingTeam != null) {
+                User parentGuide = userMapper.selectById(existingTeam.getParentId());
+                result.put("currentTeamGuideName", parentGuide != null ? parentGuide.getNickname() : "未知");
+                result.put("currentTeamGuideId", existingTeam.getParentId());
+            }
+        }
+
+        result.put("valid", true);
+        return Result.success(result);
+    }
+
+    @Operation(summary = "通过邀请码加入团队")
+    @PostMapping("/join-by-invite")
+    public Result<Map<String, Object>> joinByInvite(@RequestAttribute("userId") Long userId,
+                                                     @RequestBody Map<String, String> body) {
+        String inviteCode = body.get("inviteCode");
+        if (inviteCode == null || inviteCode.isEmpty()) {
+            return Result.error("邀请码不能为空");
+        }
+
+        // 验证邀请码
+        GuideInviteCode code = guideInviteCodeService.validateInviteCode(inviteCode);
+        if (code == null) {
+            return Result.error("邀请码无效或已过期");
+        }
+
+        Long guideId = code.getGuideId();
+        if (guideId.equals(userId)) {
+            return Result.error("不能通过自己的邀请码加入团队");
+        }
+
+        // 检查是否已在该团队中
+        LambdaQueryWrapper<GuideHierarchy> existCheck = new LambdaQueryWrapper<>();
+        existCheck.eq(GuideHierarchy::getParentId, guideId)
+                  .eq(GuideHierarchy::getChildId, userId)
+                  .eq(GuideHierarchy::getStatus, "active");
+        if (guideHierarchyService.count(existCheck) > 0) {
+            return Result.error("您已经在该团队中");
+        }
+
+        // 建立团队关系
+        guideHierarchyService.addChildGuide(guideId, userId);
+        guideInviteCodeService.useInviteCode(inviteCode, userId);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", true);
+        result.put("guideId", guideId);
+        return Result.success(result);
+    }
+
+    @Operation(summary = "离开当前团队")
+    @PostMapping("/leave")
+    public Result<Map<String, Object>> leaveTeam(@RequestAttribute("userId") Long userId) {
+        LambdaQueryWrapper<GuideHierarchy> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(GuideHierarchy::getChildId, userId)
+               .eq(GuideHierarchy::getStatus, "active");
+        GuideHierarchy hierarchy = guideHierarchyService.getOne(wrapper);
+
+        if (hierarchy == null) {
+            return Result.error("您当前不在任何团队中");
+        }
+
+        hierarchy.setStatus("inactive");
+        guideHierarchyService.updateById(hierarchy);
+
+        Map<String, Object> result = new HashMap<>();
+        result.put("success", true);
+        return Result.success(result);
+    }
+
     @Operation(summary = "获取团队统计")
     @PostMapping("/stats")
     public Result<Map<String, Object>> getTeamStats(@RequestAttribute("userId") Long guideId) {

+ 70 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/FinanceCheckinController.java

@@ -0,0 +1,70 @@
+package com.etotem.cfc.controller.wealth;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.FinanceCheckin;
+import com.etotem.cfc.service.FinanceCheckinService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+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;
+
+@Tag(name = "财商打卡", description = "财富维度日常打卡记录")
+@RestController
+@RequestMapping("/api/wealth/checkin")
+public class FinanceCheckinController {
+
+    @Resource
+    private FinanceCheckinService financeCheckinService;
+
+    @Operation(summary = "获取打卡列表")
+    @PostMapping("/list")
+    public Result<List<FinanceCheckin>> list(
+            @RequestBody Map<String, Object> params,
+            @RequestAttribute("userId") Long userId) {
+        Long childId = params.get("childId") != null
+                ? Long.valueOf(params.get("childId").toString()) : null;
+        String yearMonth = (String) params.get("yearMonth");
+        List<FinanceCheckin> list = financeCheckinService.getCheckins(userId, childId, yearMonth);
+        return Result.success(list);
+    }
+
+    @Operation(summary = "获取打卡统计")
+    @PostMapping("/stats")
+    public Result<Map<String, Object>> stats(
+            @RequestBody Map<String, Object> params,
+            @RequestAttribute("userId") Long userId) {
+        Long childId = params.get("childId") != null
+                ? Long.valueOf(params.get("childId").toString()) : null;
+        Map<String, Object> stats = financeCheckinService.getCheckinStats(userId, childId);
+        return Result.success(stats);
+    }
+
+    @Operation(summary = "创建打卡记录")
+    @PostMapping("/create")
+    public Result<FinanceCheckin> create(
+            @RequestBody FinanceCheckin checkin,
+            @RequestAttribute("userId") Long userId) {
+        checkin.setUserId(userId);
+        FinanceCheckin created = financeCheckinService.createCheckin(checkin);
+        return Result.success(created);
+    }
+
+    @Operation(summary = "删除打卡记录")
+    @PostMapping("/delete")
+    public Result<String> delete(@RequestBody Map<String, Object> params) {
+        Long id = params.get("id") != null
+                ? Long.valueOf(params.get("id").toString()) : null;
+        if (id == null) {
+            return Result.error("id不能为空");
+        }
+        financeCheckinService.deleteCheckin(id);
+        return Result.success("删除成功");
+    }
+}

+ 79 - 0
cfc-backend/src/main/java/com/etotem/cfc/controller/wealth/InsurancePolicyController.java

@@ -0,0 +1,79 @@
+package com.etotem.cfc.controller.wealth;
+
+import com.etotem.cfc.common.Result;
+import com.etotem.cfc.entity.InsurancePolicy;
+import com.etotem.cfc.service.InsurancePolicyService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+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;
+
+@Tag(name = "保单管理", description = "保单信息管理")
+@RestController
+@RequestMapping("/api/wealth/insurance")
+public class InsurancePolicyController {
+
+    @Resource
+    private InsurancePolicyService insurancePolicyService;
+
+    @Operation(summary = "获取保单列表")
+    @PostMapping("/list")
+    public Result<List<InsurancePolicy>> list(
+            @RequestBody Map<String, Object> params,
+            @RequestAttribute("userId") Long userId) {
+        String status = (String) params.get("status");
+        List<InsurancePolicy> list = insurancePolicyService.getPolicies(userId, status);
+        return Result.success(list);
+    }
+
+    @Operation(summary = "获取保单详情")
+    @PostMapping("/detail")
+    public Result<InsurancePolicy> detail(@RequestBody Map<String, Object> params) {
+        Long id = params.get("id") != null
+                ? Long.valueOf(params.get("id").toString()) : null;
+        if (id == null) {
+            return Result.error("id不能为空");
+        }
+        InsurancePolicy policy = insurancePolicyService.getById(id);
+        if (policy == null) {
+            return Result.error("保单不存在");
+        }
+        return Result.success(policy);
+    }
+
+    @Operation(summary = "创建保单")
+    @PostMapping("/create")
+    public Result<InsurancePolicy> create(
+            @RequestBody InsurancePolicy policy,
+            @RequestAttribute("userId") Long userId) {
+        policy.setUserId(userId);
+        InsurancePolicy created = insurancePolicyService.createPolicy(policy);
+        return Result.success(created);
+    }
+
+    @Operation(summary = "更新保单")
+    @PostMapping("/update")
+    public Result<String> update(@RequestBody InsurancePolicy policy) {
+        insurancePolicyService.updatePolicy(policy);
+        return Result.success("更新成功");
+    }
+
+    @Operation(summary = "删除保单")
+    @PostMapping("/delete")
+    public Result<String> delete(@RequestBody Map<String, Object> params) {
+        Long id = params.get("id") != null
+                ? Long.valueOf(params.get("id").toString()) : null;
+        if (id == null) {
+            return Result.error("id不能为空");
+        }
+        insurancePolicyService.deletePolicy(id);
+        return Result.success("删除成功");
+    }
+}

+ 10 - 1
cfc-backend/src/main/java/com/etotem/cfc/entity/CommissionRecord.java

@@ -32,7 +32,16 @@ public class CommissionRecord implements Serializable {
 
     private BigDecimal commissionAmount;
 
-    private String status;           // pending/settled
+    private String status;           // pending/settled/cancelled/withdrawn
+
+    // 佣金层级: 1=直接推荐, 2=间接推荐
+    private Integer level;
+
+    // 结算时间
+    private Date settledAt;
+
+    // 提现时间
+    private Date withdrawnAt;
 
     private String remark;
 

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

@@ -31,6 +31,12 @@ public class DanAssessmentResult implements Serializable {
 
     private Integer logicScore;  // 逻辑思维得分
 
+    private Integer perceptionScore;  // 感知得分
+
+    private Integer spatialScore;  // 空间思维得分
+
+    private Integer processingSpeedScore;  // 加工速度得分
+
     private Integer overallScore;  // 综合得分
 
     private Date assessmentDate;

+ 46 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/EducationSystem.java

@@ -0,0 +1,46 @@
+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("education_systems")
+public class EducationSystem implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /**
+     * 编码(key)
+     */
+    private String name;
+
+    /**
+     * 显示名称
+     */
+    private String label;
+
+    /**
+     * 描述说明
+     */
+    private String description;
+
+    /**
+     * 排序
+     */
+    private Integer sortOrder;
+
+    /**
+     * 状态: ACTIVE/DISABLED
+     */
+    private String status;
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 38 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/FinanceCheckin.java

@@ -0,0 +1,38 @@
+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.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("finance_checkins")
+public class FinanceCheckin implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long userId;
+
+    private Long childId;
+
+    private String checkinType;
+
+    private Date checkinDate;
+
+    private BigDecimal amount;
+
+    private String category;
+
+    private String note;
+
+    private String mood;
+
+    private Integer earnedEnergy;
+
+    private Date createdAt;
+}

+ 62 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/Guide.java

@@ -18,6 +18,56 @@ public class Guide implements Serializable {
 
     private Long userId;
 
+    /**
+     * 服务商类型: teacher/planner/activity_provider/product_supplier
+     */
+    private String type;
+
+    /**
+     * 服务商编号
+     */
+    private String teacherNo;
+
+    /**
+     * 职称/称号
+     */
+    private String title;
+
+    /**
+     * 专长领域
+     */
+    private String specialty;
+
+    /**
+     * 简介
+     */
+    private String bio;
+
+    /**
+     * 从业年限
+     */
+    private Integer experienceYears;
+
+    /**
+     * 毕业院校
+     */
+    private String university;
+
+    /**
+     * 教育体系(参考education_systems表)
+     */
+    private String eduSystem;
+
+    /**
+     * 评分
+     */
+    private BigDecimal rating;
+
+    /**
+     * 总服务次数
+     */
+    private Integer totalAppointments;
+
     private String nickname;
 
     private String phone;
@@ -36,8 +86,20 @@ public class Guide implements Serializable {
 
     private BigDecimal commissionRate;
 
+    private Long streetId;
+
+    private String province;
+
+    private String city;
+
+    private String district;
+
+    private String street;
+
     private Date approvedAt;
 
+    private Date verifiedAt;
+
     private Date createdAt;
 
     private Date updatedAt;

+ 47 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/HealthIndicator.java

@@ -0,0 +1,47 @@
+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;
+
+/**
+ * 健康指标项 — 从检测报告中提取的单项指标
+ * 关联 health_reports,存储每个指标的原始值和评估状态
+ */
+@Data
+@TableName("health_indicators")
+public class HealthIndicator implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 关联报告ID */
+    private Long reportId;
+
+    /** 指标分类: 营养状况/疾病风险/肠道屏障/神经递质/短链脂肪酸/抗生素耐药/菌群状态 */
+    private String category;
+
+    /** 指标名称 */
+    private String indicatorName;
+
+    /** 检测值(字符串存储适应多样格式) */
+    private String indicatorValue;
+
+    /** 单位 */
+    private String unit;
+
+    /** 参考范围 */
+    private String refRange;
+
+    /** 评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出等 */
+    private String status;
+
+    /** 排序 */
+    private Integer sortOrder;
+
+    private Date createdAt;
+}

+ 64 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/HealthReport.java

@@ -0,0 +1,64 @@
+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;
+
+/**
+ * 健康检测报告 — 体检报告/菌群检测报告
+ * 用于计算五维能量中的"身(body)"维度
+ */
+@Data
+@TableName("health_reports")
+public class HealthReport implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    /** 孩子ID */
+    private Long childId;
+
+    /** 家庭ID */
+    private Long familyId;
+
+    /** 报告类型: physical_exam(体检报告), gut_flora(菌群检测报告) */
+    private String reportType;
+
+    /** 报告日期 */
+    private Date reportDate;
+
+    /** 健康总分 (0-100) */
+    private Integer overallScore;
+
+    /** 菌群健康分 (0-100) */
+    private Integer gutHealthScore;
+
+    /** 慢病控制分 (0-100) */
+    private Integer chronicDiseaseScore;
+
+    /** 营养均衡分 (0-100) */
+    private Integer nutritionScore;
+
+    /** 肠道预测年龄 (如 "10.68") */
+    private String gutAge;
+
+    /** 肠型 (如 "拟杆菌型") */
+    private String gutType;
+
+    /** 报告文件URL */
+    private String fileUrl;
+
+    /** 状态: active/archived */
+    private String status;
+
+    /** 标注信息(如生成报告的机构名) */
+    private String remark;
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 50 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/InsurancePolicy.java

@@ -0,0 +1,50 @@
+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.math.BigDecimal;
+import java.util.Date;
+
+@Data
+@TableName("insurance_policies")
+public class InsurancePolicy implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long userId;
+
+    private Long childId;
+
+    private String policyName;
+
+    private String insuranceCompany;
+
+    private String policyType;
+
+    private String insuredPerson;
+
+    private BigDecimal premium;
+
+    private BigDecimal sumInsured;
+
+    private Date startDate;
+
+    private Date endDate;
+
+    private String renewalType;
+
+    private String status;
+
+    private String policyNo;
+
+    private String remark;
+
+    private Date createdAt;
+
+    private Date updatedAt;
+}

+ 33 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/MemberUpgradeRecord.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("member_upgrade_records")
+public class MemberUpgradeRecord implements Serializable {
+
+    @TableId(type = IdType.AUTO)
+    private Long id;
+
+    private Long userId;
+
+    private String fromLevel;
+
+    private String toLevel;
+
+    private String upgradeType; // pay/consumption/admin
+
+    private Long orderId;
+
+    private Long consumptionAmount;
+
+    private Date expireTime;
+
+    private Date createdAt;
+}

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

@@ -15,7 +15,7 @@ public class MembershipLevel implements Serializable {
     @TableId(type = IdType.AUTO)
     private Long id;
 
-    private String levelCode;  // FREE/BASIC/PROFESSIONAL/ENTERPRISE
+    private String levelCode;  // FREE/FAMILY/PROVIDER
 
     private String levelName;  // 等级名称
 

+ 21 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/User.java

@@ -97,6 +97,27 @@ public class User implements Serializable {
     // 分佣系统 - 个人邀请码(6位字母数字)
     private String referralCode;
 
+    // 会员等级: FREE/FAMILY/PROVIDER
+    private String memberLevel;
+
+    // 家庭会员到期时间
+    private Date memberExpireTime;
+
+    // 累计消费金额(分)
+    private Long totalConsumption;
+
+    // 是否通过累计消费自动升级
+    private Boolean upgradeFromConsumption;
+
+    // 直接推荐人数
+    private Integer directCount;
+
+    // 间接推荐人数
+    private Integer indirectCount;
+
+    // 累计获得佣金(分)
+    private Long totalCommissionEarned;
+
     private Date createdAt;
 
     private Date updatedAt;

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

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

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

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

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

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

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

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

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

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

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

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

+ 27 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/AssessmentAppointmentService.java

@@ -13,12 +13,18 @@ import javax.annotation.Resource;
 import java.util.Date;
 import java.util.List;
 
+import lombok.extern.slf4j.Slf4j;
+
+@Slf4j
 @Service
 public class AssessmentAppointmentService extends ServiceImpl<AssessmentAppointmentMapper, AssessmentAppointment> {
 
     @Resource
     private AssessmentAppointmentMapper assessmentAppointmentMapper;
 
+    @Resource
+    private EnergyService energyService;
+
     public AssessmentAppointment createAppointment(Long userId, Long childId, Long guideId,
                                                     Long packageId, String appointmentDate, String appointmentTime, String notes) {
         AssessmentAppointment appointment = new AssessmentAppointment();
@@ -34,6 +40,15 @@ public class AssessmentAppointmentService extends ServiceImpl<AssessmentAppointm
         appointment.setCreatedAt(new Date());
         appointment.setUpdatedAt(new Date());
         this.save(appointment);
+
+        // 测评预约能量发放
+        try {
+            energyService.awardEnergy(childId, "appointment", appointment.getId(), 10,
+                    "提交测评预约", null);
+        } catch (Exception e) {
+            log.warn("测评预约能量发放失败: childId={}, error={}", childId, e.getMessage());
+        }
+
         return appointment;
     }
 
@@ -52,7 +67,18 @@ public class AssessmentAppointmentService extends ServiceImpl<AssessmentAppointm
         if (!teacherId.equals(appointment.getGuideId())) return false;
         appointment.setStatus(2); // completed
         appointment.setUpdatedAt(new Date());
-        return assessmentAppointmentMapper.updateById(appointment) > 0;
+        boolean updated = assessmentAppointmentMapper.updateById(appointment) > 0;
+
+        if (updated) {
+            try {
+                energyService.awardEnergy(appointment.getChildId(), "appointment", appointment.getId(), 30,
+                        "完成测评", null);
+            } catch (Exception e) {
+                log.warn("测评完成能量发放失败: appointmentId={}, error={}", id, e.getMessage());
+            }
+        }
+
+        return updated;
     }
 
     public boolean cancelAppointment(Long id) {

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

@@ -136,7 +136,12 @@ public class AssessmentService extends ServiceImpl<AssessmentMaterialMapper, Ass
     }
 
     public List<FamilyAssessmentConfig> getFamiliesByTeacher(Long teacherId) {
-        return familyConfigMapper.selectList(null);
+        if (teacherId == null) {
+            return familyConfigMapper.selectList(null);
+        }
+        LambdaQueryWrapper<FamilyAssessmentConfig> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(FamilyAssessmentConfig::getCreatedBy, teacherId);
+        return familyConfigMapper.selectList(wrapper);
     }
 
     public AssessmentRecord createRecord(Long familyId, Long childId, Long materialId) {

+ 126 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/CognitiveService.java

@@ -0,0 +1,126 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.Child;
+import com.etotem.cfc.entity.DanAssessmentResult;
+import com.etotem.cfc.entity.User;
+import com.etotem.cfc.mapper.ChildMapper;
+import com.etotem.cfc.mapper.DanAssessmentResultMapper;
+import com.etotem.cfc.mapper.UserMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import javax.annotation.Resource;
+import java.util.*;
+
+/**
+ * 认知维度服务 — 六维认知体系(感知、专注、记忆、逻辑、空间、加工速度)
+ */
+@Slf4j
+@Service
+public class CognitiveService {
+
+    @Resource
+    private DanAssessmentResultMapper danAssessmentResultMapper;
+
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private ChildMapper childMapper;
+
+    /**
+     * 获取指定孩子的最新认知六维得分
+     *
+     * @param childId 孩子ID
+     * @return DanAssessmentResult(最新一条记录)
+     */
+    public DanAssessmentResult getChildLatestResult(Long childId) {
+        LambdaQueryWrapper<DanAssessmentResult> wrapper = new LambdaQueryWrapper<DanAssessmentResult>()
+                .eq(DanAssessmentResult::getChildId, childId)
+                .eq(DanAssessmentResult::getStatus, "completed")
+                .orderByDesc(DanAssessmentResult::getAssessmentDate)
+                .last("LIMIT 1");
+        return danAssessmentResultMapper.selectOne(wrapper);
+    }
+
+    /**
+     * 获取家庭所有孩子的最新认知六维得分
+     *
+     * @param userId 家长用户ID
+     * @return Map<childId, DanAssessmentResult> 孩子ID与最新测评结果的映射
+     */
+    public Map<Long, DanAssessmentResult> getFamilyLatestResults(Long userId) {
+        // 1. 查找用户所属家庭
+        User user = userMapper.selectById(userId);
+        if (user == null || user.getFamilyId() == null) {
+            return Collections.emptyMap();
+        }
+
+        // 2. 查找该家庭下所有孩子
+        List<Child> children = childMapper.selectList(
+                new LambdaQueryWrapper<Child>()
+                        .eq(Child::getFamilyId, user.getFamilyId())
+        );
+
+        // 3. 获取每个孩子的最新测评结果
+        Map<Long, DanAssessmentResult> resultMap = new LinkedHashMap<>();
+        for (Child child : children) {
+            DanAssessmentResult latest = getChildLatestResult(child.getId());
+            if (latest != null) {
+                resultMap.put(child.getId(), latest);
+            }
+        }
+        return resultMap;
+    }
+
+    /**
+     * 更新指定孩子的认知维度得分
+     *
+     * @param childId   孩子ID
+     * @param dimension 维度名称(attentionScore/focusScore/memoryScore/logicScore/perceptionScore/spatialScore/processingSpeedScore/overallScore)
+     * @param score     新得分
+     * @return true=更新成功, false=未找到最新结果或维度不存在
+     */
+    public boolean updateChildDimensionScore(Long childId, String dimension, Integer score) {
+        DanAssessmentResult latest = getChildLatestResult(childId);
+        if (latest == null) {
+            log.warn("未找到孩子 {} 的最新测评结果,无法更新", childId);
+            return false;
+        }
+
+        switch (dimension) {
+            case "attentionScore":
+                latest.setAttentionScore(score);
+                break;
+            case "focusScore":
+                latest.setFocusScore(score);
+                break;
+            case "memoryScore":
+                latest.setMemoryScore(score);
+                break;
+            case "logicScore":
+                latest.setLogicScore(score);
+                break;
+            case "perceptionScore":
+                latest.setPerceptionScore(score);
+                break;
+            case "spatialScore":
+                latest.setSpatialScore(score);
+                break;
+            case "processingSpeedScore":
+                latest.setProcessingSpeedScore(score);
+                break;
+            case "overallScore":
+                latest.setOverallScore(score);
+                break;
+            default:
+                log.warn("未知的认知维度: {}", dimension);
+                return false;
+        }
+
+        danAssessmentResultMapper.updateById(latest);
+        log.info("已更新孩子 {} 的维度 {} 得分为 {}", childId, dimension, score);
+        return true;
+    }
+}

+ 294 - 29
cfc-backend/src/main/java/com/etotem/cfc/service/CommissionService.java

@@ -19,14 +19,19 @@ import org.springframework.stereotype.Service;
 import javax.annotation.Resource;
 import java.math.BigDecimal;
 import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Random;
+import java.util.stream.Collectors;
 
 @Service
 public class CommissionService {
 
     private static final Logger log = LoggerFactory.getLogger(CommissionService.class);
 
-    private static final BigDecimal MEMBER_COMMISSION_RATE = new BigDecimal("0.30");
+    // 产品利润佣金提成比例(已废弃,改用 sys_config commission_product_profit_share)
+    // 保留为静态备用值
     private static final BigDecimal PROFIT_COMMISSION_RATE = new BigDecimal("0.30");
 
     @Resource
@@ -41,6 +46,38 @@ public class CommissionService {
     @Resource
     private WithdrawalRequestMapper withdrawalRequestMapper;
 
+    @Resource
+    private SysConfigService sysConfigService;
+
+    // ==================== 工具方法 ====================
+
+    /**
+     * 从系统配置获取bps费率并转为BigDecimal
+     * 如 2000bps = 0.20 (20%)
+     */
+    private BigDecimal getRateFromConfig(String key, int defaultBps) {
+        String val = sysConfigService.getValue(key);
+        if (val == null) {
+            return BigDecimal.valueOf(defaultBps).divide(new BigDecimal("10000"));
+        }
+        return BigDecimal.valueOf(Long.parseLong(val)).divide(new BigDecimal("10000"));
+    }
+
+    /**
+     * 从系统配置获取bps整数值
+     */
+    private int getIntConfig(String key, int defaultValue) {
+        String val = sysConfigService.getValue(key);
+        if (val == null) {
+            return defaultValue;
+        }
+        try {
+            return Integer.parseInt(val);
+        } catch (NumberFormatException e) {
+            return defaultValue;
+        }
+    }
+
     // ==================== 邀请码相关 ====================
 
     public String getOrCreateReferralCode(Long userId) {
@@ -80,6 +117,10 @@ public class CommissionService {
         }
         user.setReferrerId(referrer.getId());
         userMapper.updateById(user);
+
+        // 更新推荐人直接推荐人数
+        referrer.setDirectCount(referrer.getDirectCount() != null ? referrer.getDirectCount() + 1 : 1);
+        userMapper.updateById(referrer);
     }
 
     public Page<User> getMyReferrals(Long userId, int page, int size) {
@@ -99,6 +140,8 @@ public class CommissionService {
         Long count = userMapper.selectCount(
                 new LambdaQueryWrapper<User>().eq(User::getReferrerId, userId));
         dto.setInvitedCount(count != null ? count.intValue() : 0);
+
+        // 只统计已结算的佣金(排除cancelled)
         LambdaQueryWrapper<CommissionRecord> wrapper = new LambdaQueryWrapper<CommissionRecord>()
                 .eq(CommissionRecord::getReferrerId, userId)
                 .eq(CommissionRecord::getStatus, "settled");
@@ -112,66 +155,180 @@ public class CommissionService {
 
     // ==================== 佣金结算 ====================
 
+    /**
+     * 原有settle方法 - 兼容旧调用
+     * 内部委托给settleTwoLevel实现L1+L2佣金
+     */
     public void settle(Long orderId, String orderType, Long buyerId,
                        BigDecimal orderAmount, Long productId) {
+        settleTwoLevel(orderId, orderType, buyerId, orderAmount, productId);
+    }
+
+    /**
+     * 两级佣金结算(L1+L2)
+     * - L1: 买家的直接推荐人获得佣金
+     * - L2: 如果L1推荐人也有推荐人,且订单类型为membership,L2获得佣金
+     * - 佣金费率从sys_config动态读取
+     * - 默认状态为pending,settledAt为7天后
+     */
+    public void settleTwoLevel(Long orderId, String orderType, Long buyerId,
+                               BigDecimal orderAmount, Long productId) {
         try {
             if (buyerId == null || orderAmount == null || orderAmount.compareTo(BigDecimal.ZERO) <= 0) {
                 return;
             }
+
             User buyer = userMapper.selectById(buyerId);
             if (buyer == null || buyer.getReferrerId() == null) {
                 return;
             }
-            Long referrerId = buyer.getReferrerId();
-            BigDecimal commissionAmount;
+
+            // === L1: 创建一级佣金 ===
+            Long l1ReferrerId = buyer.getReferrerId();
+            User l1Referrer = userMapper.selectById(l1ReferrerId);
+            if (l1Referrer == null) {
+                return;
+            }
+
+            BigDecimal l1Amount = BigDecimal.ZERO;
             String commissionType;
-            BigDecimal profitRate = BigDecimal.ZERO;
 
             if ("membership".equals(orderType)) {
-                commissionAmount = orderAmount.multiply(MEMBER_COMMISSION_RATE);
+                BigDecimal rate = getRateFromConfig("commission_member_l1_rate", 2000);
+                l1Amount = orderAmount.multiply(rate);
                 commissionType = "member";
             } else {
+                // 非会员订单:只有FAMILY用户的推荐人能获得产品利润佣金
+                boolean isFamily = "FAMILY".equals(l1Referrer.getMemberLevel());
+
                 if (productId != null) {
                     Product product = productMapper.selectById(productId);
                     if (product != null && product.getProfitRate() != null) {
-                        profitRate = product.getProfitRate();
+                        BigDecimal profitRate = product.getProfitRate();
+                        if (isFamily) {
+                            // FAMILY推荐人获取利润佣金
+                            BigDecimal profitShare = getRateFromConfig("commission_product_profit_share", 1000);
+                            l1Amount = orderAmount.multiply(profitRate)
+                                    .divide(new BigDecimal("100"))
+                                    .multiply(profitShare);
+                            commissionType = "profit";
+                        } else {
+                            // FREE推荐人只获得member类型佣金(不计利润分成)
+                            l1Amount = BigDecimal.ZERO;
+                            commissionType = "member";
+                        }
+                    } else {
+                        // 没有利润率的产品,只给FAMILY推荐人基础服务佣金
+                        if (isFamily) {
+                            BigDecimal serviceRate = getRateFromConfig("commission_service_rate", 1000);
+                            l1Amount = orderAmount.multiply(serviceRate);
+                            commissionType = "service";
+                        } else {
+                            l1Amount = BigDecimal.ZERO;
+                            commissionType = "member";
+                        }
+                    }
+                } else {
+                    // 没有productId的非会员订单(如评估、套餐)
+                    if (isFamily) {
+                        BigDecimal serviceRate = getRateFromConfig("commission_service_rate", 1000);
+                        l1Amount = orderAmount.multiply(serviceRate);
+                        commissionType = "service";
+                    } else {
+                        l1Amount = BigDecimal.ZERO;
+                        commissionType = "member";
+                    }
+                }
+            }
+
+            // 创建L1佣金记录(金额>0才创建)
+            if (l1Amount.compareTo(BigDecimal.ZERO) > 0) {
+                createCommissionRecord(orderId, orderType, l1ReferrerId, buyerId,
+                        commissionType, orderAmount, BigDecimal.ZERO, l1Amount,
+                        1, "settled", new Date());
+            }
+
+            // 更新推荐人的累计佣金
+            l1Referrer.setTotalCommissionEarned(
+                    (l1Referrer.getTotalCommissionEarned() != null ? l1Referrer.getTotalCommissionEarned() : 0L)
+                            + l1Amount.longValue());
+            userMapper.updateById(l1Referrer);
+
+            log.info("L1佣金结算成功: orderId={}, referrerId={}, amount={}, level=1",
+                    orderId, l1ReferrerId, l1Amount);
+
+            // === L2: 创建二级佣金(仅会员订单) ===
+            if ("membership".equals(orderType) && l1Referrer.getReferrerId() != null) {
+                Long l2ReferrerId = l1Referrer.getReferrerId();
+                User l2Referrer = userMapper.selectById(l2ReferrerId);
+                if (l2Referrer != null) {
+                    BigDecimal l2Rate = getRateFromConfig("commission_member_l2_rate", 500);
+                    BigDecimal l2Amount = orderAmount.multiply(l2Rate);
+
+                    if (l2Amount.compareTo(BigDecimal.ZERO) > 0) {
+                        createCommissionRecord(orderId, orderType, l2ReferrerId, buyerId,
+                                "member", orderAmount, BigDecimal.ZERO, l2Amount,
+                                2, "settled", new Date());
+
+                        l2Referrer.setTotalCommissionEarned(
+                                (l2Referrer.getTotalCommissionEarned() != null ? l2Referrer.getTotalCommissionEarned() : 0L)
+                                        + l2Amount.longValue());
+                        userMapper.updateById(l2Referrer);
+
+                        // 更新L1推荐人的间接推荐人数
+                        l1Referrer.setIndirectCount(
+                                (l1Referrer.getIndirectCount() != null ? l1Referrer.getIndirectCount() : 0) + 1);
+                        userMapper.updateById(l1Referrer);
+
+                        log.info("L2佣金结算成功: orderId={}, referrerId={}, amount={}, level=2",
+                                orderId, l2ReferrerId, l2Amount);
                     }
                 }
-                commissionAmount = orderAmount.multiply(profitRate)
-                        .divide(new BigDecimal("100"))
-                        .multiply(PROFIT_COMMISSION_RATE);
-                commissionType = "profit";
             }
 
-            CommissionRecord record = new CommissionRecord();
-            record.setOrderId(orderId);
-            record.setOrderType(orderType);
-            record.setReferrerId(referrerId);
-            record.setBuyerId(buyerId);
-            record.setCommissionType(commissionType);
-            record.setOrderAmount(orderAmount);
-            record.setProfitRate(profitRate);
-            record.setCommissionAmount(commissionAmount);
-            record.setStatus("settled");
-            record.setCreatedAt(new Date());
-            commissionRecordMapper.insert(record);
-
-            log.info("佣金结算成功: orderId={}, referrerId={}, amount={}", orderId, referrerId, commissionAmount);
         } catch (Exception e) {
             log.error("佣金结算异常: orderId={}", orderId, e);
         }
     }
 
+    /**
+     * 创建佣金记录
+     */
+    private void createCommissionRecord(Long orderId, String orderType, Long referrerId,
+                                        Long buyerId, String commissionType, BigDecimal orderAmount,
+                                        BigDecimal profitRate, BigDecimal commissionAmount,
+                                        Integer level, String status, Date settledAt) {
+        CommissionRecord record = new CommissionRecord();
+        record.setOrderId(orderId);
+        record.setOrderType(orderType);
+        record.setReferrerId(referrerId);
+        record.setBuyerId(buyerId);
+        record.setCommissionType(commissionType);
+        record.setOrderAmount(orderAmount);
+        record.setProfitRate(profitRate);
+        record.setCommissionAmount(commissionAmount);
+        record.setLevel(level);
+        record.setStatus(status);
+        record.setSettledAt(settledAt);
+        record.setCreatedAt(new Date());
+        commissionRecordMapper.insert(record);
+    }
+
+    // ==================== 查询统计 ====================
+
     public CommissionSummaryDTO getSummary(Long userId) {
         CommissionSummaryDTO dto = new CommissionSummaryDTO();
 
+        // 总佣金(排除cancelled)
         LambdaQueryWrapper<CommissionRecord> allWrapper = new LambdaQueryWrapper<CommissionRecord>()
-                .eq(CommissionRecord::getReferrerId, userId);
+                .eq(CommissionRecord::getReferrerId, userId)
+                .ne(CommissionRecord::getStatus, "cancelled");
         BigDecimal totalCommission = commissionRecordMapper.selectList(allWrapper).stream()
                 .map(r -> r.getCommissionAmount() != null ? r.getCommissionAmount() : BigDecimal.ZERO)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
         dto.setTotalCommission(totalCommission);
 
+        // 已结算(settled)
         LambdaQueryWrapper<CommissionRecord> settledWrapper = new LambdaQueryWrapper<CommissionRecord>()
                 .eq(CommissionRecord::getReferrerId, userId)
                 .eq(CommissionRecord::getStatus, "settled");
@@ -180,6 +337,7 @@ public class CommissionService {
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
         dto.setSettledAmount(settledAmount);
 
+        // 待结算(pending)
         LambdaQueryWrapper<CommissionRecord> pendingWrapper = new LambdaQueryWrapper<CommissionRecord>()
                 .eq(CommissionRecord::getReferrerId, userId)
                 .eq(CommissionRecord::getStatus, "pending");
@@ -202,17 +360,102 @@ public class CommissionService {
                         .orderByDesc(CommissionRecord::getCreatedAt));
     }
 
+    /**
+     * 获取可提现金额(已结算 - 已提现,排除cancelled)
+     */
     public BigDecimal getAvailableAmount(Long userId) {
-        LambdaQueryWrapper<CommissionRecord> settledWrapper = new LambdaQueryWrapper<CommissionRecord>()
+        // 已结算(settled + pending中已过settledAt的)
+        LambdaQueryWrapper<CommissionRecord> availableWrapper = new LambdaQueryWrapper<CommissionRecord>()
                 .eq(CommissionRecord::getReferrerId, userId)
-                .eq(CommissionRecord::getStatus, "settled");
-        BigDecimal settled = commissionRecordMapper.selectList(settledWrapper).stream()
+                .ne(CommissionRecord::getStatus, "cancelled")
+                .ne(CommissionRecord::getStatus, "withdrawn")
+                .and(w -> w.eq(CommissionRecord::getStatus, "settled")
+                        .or(i -> i.eq(CommissionRecord::getStatus, "pending")
+                                .isNotNull(CommissionRecord::getSettledAt)
+                                .le(CommissionRecord::getSettledAt, new Date())));
+        BigDecimal available = commissionRecordMapper.selectList(availableWrapper).stream()
                 .map(r -> r.getCommissionAmount() != null ? r.getCommissionAmount() : BigDecimal.ZERO)
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
 
         BigDecimal withdrawn = getWithdrawnAmount(userId);
 
-        return settled.subtract(withdrawn).max(BigDecimal.ZERO);
+        return available.subtract(withdrawn).max(BigDecimal.ZERO);
+    }
+
+    /**
+     * 获取团队统计数据
+     */
+    public Map<String, Object> getTeamStats(Long userId) {
+        Map<String, Object> stats = new HashMap<>();
+
+        // L1直接推荐人数
+        Long l1Count = userMapper.selectCount(
+                new LambdaQueryWrapper<User>().eq(User::getReferrerId, userId));
+        stats.put("l1Count", l1Count != null ? l1Count.intValue() : 0);
+
+        // L2间接推荐人数(L1推荐的人的推荐人数)
+        List<User> l1Users = userMapper.selectList(
+                new LambdaQueryWrapper<User>().eq(User::getReferrerId, userId));
+        int l2Count = 0;
+        for (User l1User : l1Users) {
+            Long count = userMapper.selectCount(
+                    new LambdaQueryWrapper<User>().eq(User::getReferrerId, l1User.getId()));
+            l2Count += count != null ? count.intValue() : 0;
+        }
+        stats.put("l2Count", l2Count);
+
+        // 团队总佣金
+        LambdaQueryWrapper<CommissionRecord> teamWrapper = new LambdaQueryWrapper<CommissionRecord>()
+                .eq(CommissionRecord::getReferrerId, userId)
+                .ne(CommissionRecord::getStatus, "cancelled");
+        BigDecimal totalTeamCommission = commissionRecordMapper.selectList(teamWrapper).stream()
+                .map(r -> r.getCommissionAmount() != null ? r.getCommissionAmount() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        stats.put("totalCommission", totalTeamCommission);
+
+        return stats;
+    }
+
+    /**
+     * 获取佣金统计(首页面板数据)
+     */
+    public Map<String, Object> getCommissionStats(Long userId) {
+        Map<String, Object> stats = new HashMap<>();
+
+        Date now = new Date();
+
+        // 今日佣金
+        LambdaQueryWrapper<CommissionRecord> todayWrapper = new LambdaQueryWrapper<CommissionRecord>()
+                .eq(CommissionRecord::getReferrerId, userId)
+                .ne(CommissionRecord::getStatus, "cancelled")
+                .ge(CommissionRecord::getCreatedAt, getStartOfDay(now))
+                .le(CommissionRecord::getCreatedAt, now);
+        BigDecimal todayCommission = commissionRecordMapper.selectList(todayWrapper).stream()
+                .map(r -> r.getCommissionAmount() != null ? r.getCommissionAmount() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        stats.put("todayCommission", todayCommission);
+
+        // 本月佣金
+        LambdaQueryWrapper<CommissionRecord> monthWrapper = new LambdaQueryWrapper<CommissionRecord>()
+                .eq(CommissionRecord::getReferrerId, userId)
+                .ne(CommissionRecord::getStatus, "cancelled")
+                .ge(CommissionRecord::getCreatedAt, getStartOfMonth(now))
+                .le(CommissionRecord::getCreatedAt, now);
+        BigDecimal monthCommission = commissionRecordMapper.selectList(monthWrapper).stream()
+                .map(r -> r.getCommissionAmount() != null ? r.getCommissionAmount() : BigDecimal.ZERO)
+                .reduce(BigDecimal.ZERO, BigDecimal::add);
+        stats.put("monthCommission", monthCommission);
+
+        // 累计佣金
+        stats.put("totalCommission", getSummary(userId).getTotalCommission());
+
+        // 可提现余额
+        stats.put("availableBalance", getAvailableAmount(userId));
+
+        // L1/L2人数
+        stats.putAll(getTeamStats(userId));
+
+        return stats;
     }
 
     private BigDecimal getWithdrawnAmount(Long userId) {
@@ -225,6 +468,28 @@ public class CommissionService {
                 .reduce(BigDecimal.ZERO, BigDecimal::add);
     }
 
+    private Date getStartOfDay(Date date) {
+        // 简单实现:当天00:00
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.setTime(date);
+        cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+        cal.set(java.util.Calendar.MINUTE, 0);
+        cal.set(java.util.Calendar.SECOND, 0);
+        cal.set(java.util.Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
+    private Date getStartOfMonth(Date date) {
+        java.util.Calendar cal = java.util.Calendar.getInstance();
+        cal.setTime(date);
+        cal.set(java.util.Calendar.DAY_OF_MONTH, 1);
+        cal.set(java.util.Calendar.HOUR_OF_DAY, 0);
+        cal.set(java.util.Calendar.MINUTE, 0);
+        cal.set(java.util.Calendar.SECOND, 0);
+        cal.set(java.util.Calendar.MILLISECOND, 0);
+        return cal.getTime();
+    }
+
     private String generateReferralCode(Long userId) {
         String chars = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
         Random random = new Random();

+ 33 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/EducationSystemService.java

@@ -0,0 +1,33 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import com.etotem.cfc.entity.EducationSystem;
+import com.etotem.cfc.mapper.EducationSystemMapper;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+@Service
+public class EducationSystemService extends ServiceImpl<EducationSystemMapper, EducationSystem> {
+
+    public List<EducationSystem> getActiveSystems() {
+        LambdaQueryWrapper<EducationSystem> wrapper = new LambdaQueryWrapper<>();
+        wrapper.eq(EducationSystem::getStatus, "ACTIVE")
+               .orderByAsc(EducationSystem::getSortOrder);
+        return this.list(wrapper);
+    }
+
+    public EducationSystem createSystem(EducationSystem system) {
+        this.save(system);
+        return system;
+    }
+
+    public boolean updateSystem(EducationSystem system) {
+        return this.updateById(system);
+    }
+
+    public boolean deleteSystem(Long id) {
+        return this.removeById(id);
+    }
+}

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

@@ -49,6 +49,9 @@ public class EnergyService {
     @Resource
     private StreakMilestoneMapper streakMilestoneMapper;
 
+    @Resource
+    private HealthReportService healthReportService;
+
     /**
      * 计算整个家庭的能量沙盘数据
      *
@@ -144,8 +147,25 @@ public class EnergyService {
         return dto;
     }
 
-    /** 家长 身 — 运动健康类任务完成率 */
+    /**
+     * 家长 身 — 运动健康类任务完成率
+     * 注:家长如绑定了孩子(child ID),可通过该孩子的健康报告间接获取身体数据
+     */
     private int calcParentBody(User parent) {
+        // 尝试通过家庭中任意孩子的健康报告取分(家长关注的是家庭整体健康)
+        if (parent.getFamilyId() != null) {
+            List<Child> children = childMapper.selectList(
+                    new LambdaQueryWrapper<Child>()
+                            .eq(Child::getFamilyId, parent.getFamilyId())
+            );
+            for (Child child : children) {
+                Integer healthScore = healthReportService.calculateBodyScore(child.getId());
+                if (healthScore != null) {
+                    return healthScore;
+                }
+            }
+        }
+        // 降级:运动健康类任务完成率
         return calcTaskCompletionRate(parent.getId(), "parent",
                 Arrays.asList("运动", "体育", "户外", "健康"), 365);
     }
@@ -203,8 +223,17 @@ public class EnergyService {
         return dto;
     }
 
-    /** 孩子 身 — 运动/户外类任务完成率 */
+    /**
+     * 孩子 身 — 优先使用健康检测报告得分
+     * 数据源优先级:健康报告overallScore > 子项加权 > 运动/户外类任务完成率(降级)
+     */
     private int calcChildBody(Child child) {
+        // 1. 优先使用健康报告中的身体评分
+        Integer healthScore = healthReportService.calculateBodyScore(child.getId());
+        if (healthScore != null) {
+            return healthScore;
+        }
+        // 2. 降级:运动/户外类任务完成率
         return calcTaskCompletionRate(child.getId(), "child",
                 Arrays.asList("运动", "体育", "户外", "健康"), 365);
     }
@@ -389,7 +418,7 @@ public class EnergyService {
         DAILY_LIMIT_MAP.put("mind", 40);
         DAILY_LIMIT_MAP.put("wisdom", 80);
         DAILY_LIMIT_MAP.put("action", 60);
-        DAILY_LIMIT_MAP.put("wealth", 30);
+        DAILY_LIMIT_MAP.put("wealth", 50);
     }
 
     private static final int GLOBAL_DAILY_LIMIT = 200;

+ 77 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/FinanceCheckinService.java

@@ -0,0 +1,77 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.FinanceCheckin;
+import com.etotem.cfc.mapper.FinanceCheckinMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+@Slf4j
+@Service
+public class FinanceCheckinService {
+
+    @Resource
+    private FinanceCheckinMapper financeCheckinMapper;
+
+    public List<FinanceCheckin> getCheckins(Long userId, Long childId, String yearMonth) {
+        LambdaQueryWrapper<FinanceCheckin> wrapper = new LambdaQueryWrapper<FinanceCheckin>()
+                .eq(FinanceCheckin::getUserId, userId);
+        if (childId != null) {
+            wrapper.eq(FinanceCheckin::getChildId, childId);
+        }
+        wrapper.orderByDesc(FinanceCheckin::getCheckinDate);
+        return financeCheckinMapper.selectList(wrapper);
+    }
+
+    public FinanceCheckin getById(Long id) {
+        return financeCheckinMapper.selectById(id);
+    }
+
+    @Transactional
+    public FinanceCheckin createCheckin(FinanceCheckin checkin) {
+        checkin.setCreatedAt(new Date());
+        financeCheckinMapper.insert(checkin);
+        return checkin;
+    }
+
+    @Transactional
+    public void updateCheckin(FinanceCheckin checkin) {
+        financeCheckinMapper.updateById(checkin);
+    }
+
+    @Transactional
+    public void deleteCheckin(Long id) {
+        financeCheckinMapper.deleteById(id);
+    }
+
+    public Map<String, Object> getCheckinStats(Long userId, Long childId) {
+        LambdaQueryWrapper<FinanceCheckin> wrapper = new LambdaQueryWrapper<FinanceCheckin>()
+                .eq(FinanceCheckin::getUserId, userId);
+        if (childId != null) {
+            wrapper.eq(FinanceCheckin::getChildId, childId);
+        }
+
+        List<FinanceCheckin> all = financeCheckinMapper.selectList(wrapper);
+        long totalCheckins = all.size();
+        long totalEnergy = all.stream()
+                .filter(c -> c.getEarnedEnergy() != null)
+                .mapToLong(FinanceCheckin::getEarnedEnergy)
+                .sum();
+
+        Map<String, Object> stats = new HashMap<>();
+        stats.put("totalCheckins", totalCheckins);
+        stats.put("totalEnergy", totalEnergy);
+        stats.put("dailyPlanCount", all.stream().filter(c -> "daily_plan".equals(c.getCheckinType())).count());
+        stats.put("savingCount", all.stream().filter(c -> "saving".equals(c.getCheckinType())).count());
+        stats.put("expenseCount", all.stream().filter(c -> "expense".equals(c.getCheckinType())).count());
+        stats.put("reviewCount", all.stream().filter(c -> "review".equals(c.getCheckinType())).count());
+        return stats;
+    }
+}

+ 15 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/GameRecordService.java

@@ -24,6 +24,9 @@ public class GameRecordService {
     @Resource
     private MiniGameMapper miniGameMapper;
 
+    @Resource
+    private EnergyService energyService;
+
     public List<GameRecord> listByChildId(Long childId) {
         return gameRecordMapper.selectList(
                 new LambdaQueryWrapper<GameRecord>()
@@ -43,6 +46,18 @@ public class GameRecordService {
         record.setCreatedAt(new Date());
         record.setUpdatedAt(new Date());
         gameRecordMapper.insert(record);
+
+        // 发放智能量(完成游戏奖励,不影响主流程)
+        try {
+            int energyAmount = Math.abs(pointsEarned != null ? pointsEarned : 0);
+            if (energyAmount > 0) {
+                energyService.awardEnergy(childId, "game", record.getId(), energyAmount,
+                        "完成游戏: " + gameCode, null);
+            }
+        } catch (Exception e) {
+            log.warn("游戏能量发放失败: childId={}, gameCode={}, error={}",
+                    childId, gameCode, e.getMessage());
+        }
     }
 
     public Map<String, Object> getHistory(Long childId, String gameCode,

+ 177 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/HealthReportService.java

@@ -0,0 +1,177 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.HealthIndicator;
+import com.etotem.cfc.entity.HealthReport;
+import com.etotem.cfc.mapper.HealthIndicatorMapper;
+import com.etotem.cfc.mapper.HealthReportMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * 健康检测报告服务 — 管理体检报告/菌群检测报告的录入与查询
+ * 为五维能量中的"身(body)"维度提供数据源
+ */
+@Slf4j
+@Service
+public class HealthReportService {
+
+    @Resource
+    private HealthReportMapper healthReportMapper;
+
+    @Resource
+    private HealthIndicatorMapper healthIndicatorMapper;
+
+    /**
+     * 创建健康报告(含指标列表)
+     */
+    @Transactional
+    public HealthReport createReport(HealthReport report, List<HealthIndicator> indicators) {
+        report.setCreatedAt(new Date());
+        report.setUpdatedAt(new Date());
+        if (report.getStatus() == null) {
+            report.setStatus("active");
+        }
+        healthReportMapper.insert(report);
+
+        if (indicators != null && !indicators.isEmpty()) {
+            for (HealthIndicator ind : indicators) {
+                ind.setReportId(report.getId());
+                ind.setCreatedAt(new Date());
+                healthIndicatorMapper.insert(ind);
+            }
+        }
+
+        log.info("健康报告已创建: id={}, childId={}, type={}, score={}",
+                report.getId(), report.getChildId(), report.getReportType(), report.getOverallScore());
+        return report;
+    }
+
+    /**
+     * 获取孩子最新的健康报告
+     */
+    public HealthReport getLatestReport(Long childId) {
+        return healthReportMapper.selectOne(
+                new LambdaQueryWrapper<HealthReport>()
+                        .eq(HealthReport::getChildId, childId)
+                        .eq(HealthReport::getStatus, "active")
+                        .orderByDesc(HealthReport::getReportDate)
+                        .last("LIMIT 1")
+        );
+    }
+
+    /**
+     * 获取孩子的所有健康报告
+     */
+    public List<HealthReport> getChildReports(Long childId) {
+        return healthReportMapper.selectList(
+                new LambdaQueryWrapper<HealthReport>()
+                        .eq(HealthReport::getChildId, childId)
+                        .eq(HealthReport::getStatus, "active")
+                        .orderByDesc(HealthReport::getReportDate)
+        );
+    }
+
+    /**
+     * 获取某份报告的所有指标
+     */
+    public List<HealthIndicator> getReportIndicators(Long reportId) {
+        return healthIndicatorMapper.selectList(
+                new LambdaQueryWrapper<HealthIndicator>()
+                        .eq(HealthIndicator::getReportId, reportId)
+                        .orderByAsc(HealthIndicator::getSortOrder)
+        );
+    }
+
+    /**
+     * 计算孩子身体维度得分 (0-100)
+     * <p>
+     * 数据源优先级:
+     * 1. 最新的健康检测报告 overallScore(如果1年内有效)
+     * 2. 多份报告加权平均(如有体检报告+菌群报告)
+     * 3. 无报告时返回null(由调用方决定降级策略)
+     *
+     * @param childId 孩子ID
+     * @return 身体维度得分 (0-100), null表示无数据
+     */
+    public Integer calculateBodyScore(Long childId) {
+        List<HealthReport> reports = healthReportMapper.selectList(
+                new LambdaQueryWrapper<HealthReport>()
+                        .eq(HealthReport::getChildId, childId)
+                        .eq(HealthReport::getStatus, "active")
+                        .orderByDesc(HealthReport::getReportDate)
+        );
+
+        if (reports.isEmpty()) {
+            return null;
+        }
+
+        // 取最新一份有overallScore的报告
+        for (HealthReport r : reports) {
+            if (r.getOverallScore() != null) {
+                return clampScore(r.getOverallScore());
+            }
+        }
+
+        // 所有报告都没有总分时,取子分值的加权平均
+        int totalWeight = 0;
+        int weightedSum = 0;
+        for (HealthReport r : reports) {
+            int weight = 1; // 默认权重
+            // 越新的报告权重越高
+            if (r.getOverallScore() != null) {
+                weightedSum += r.getOverallScore() * weight;
+                totalWeight += weight;
+            }
+            // 也可以综合子项
+            if (r.getGutHealthScore() != null) {
+                weightedSum += r.getGutHealthScore() * weight;
+                totalWeight += weight;
+            }
+            if (r.getChronicDiseaseScore() != null) {
+                weightedSum += r.getChronicDiseaseScore() * weight;
+                totalWeight += weight;
+            }
+            if (r.getNutritionScore() != null) {
+                weightedSum += r.getNutritionScore() * weight;
+                totalWeight += weight;
+            }
+        }
+
+        if (totalWeight == 0) return null;
+        return clampScore(weightedSum / totalWeight);
+    }
+
+    /**
+     * 获取孩子身体维度的数据详情(健康指标解读)
+     */
+    public List<HealthIndicator> getBodyDetail(Long childId) {
+        HealthReport latest = getLatestReport(childId);
+        if (latest == null) return Collections.emptyList();
+        return getReportIndicators(latest.getId());
+    }
+
+    /**
+     * 将报告标记为已归档
+     */
+    @Transactional
+    public void archiveReport(Long reportId) {
+        HealthReport report = healthReportMapper.selectById(reportId);
+        if (report != null) {
+            report.setStatus("archived");
+            report.setUpdatedAt(new Date());
+            healthReportMapper.updateById(report);
+        }
+    }
+
+    private int clampScore(Integer score) {
+        if (score == null) return 0;
+        return Math.min(Math.max(score, 0), 100);
+    }
+}

+ 53 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePolicyService.java

@@ -0,0 +1,53 @@
+package com.etotem.cfc.service;
+
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.etotem.cfc.entity.InsurancePolicy;
+import com.etotem.cfc.mapper.InsurancePolicyMapper;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import javax.annotation.Resource;
+import java.util.Date;
+import java.util.List;
+
+@Slf4j
+@Service
+public class InsurancePolicyService {
+
+    @Resource
+    private InsurancePolicyMapper insurancePolicyMapper;
+
+    public List<InsurancePolicy> getPolicies(Long userId, String status) {
+        LambdaQueryWrapper<InsurancePolicy> wrapper = new LambdaQueryWrapper<InsurancePolicy>()
+                .eq(InsurancePolicy::getUserId, userId);
+        if (status != null && !status.isEmpty()) {
+            wrapper.eq(InsurancePolicy::getStatus, status);
+        }
+        wrapper.orderByDesc(InsurancePolicy::getCreatedAt);
+        return insurancePolicyMapper.selectList(wrapper);
+    }
+
+    public InsurancePolicy getById(Long id) {
+        return insurancePolicyMapper.selectById(id);
+    }
+
+    @Transactional
+    public InsurancePolicy createPolicy(InsurancePolicy policy) {
+        policy.setCreatedAt(new Date());
+        policy.setUpdatedAt(new Date());
+        insurancePolicyMapper.insert(policy);
+        return policy;
+    }
+
+    @Transactional
+    public void updatePolicy(InsurancePolicy policy) {
+        policy.setUpdatedAt(new Date());
+        insurancePolicyMapper.updateById(policy);
+    }
+
+    @Transactional
+    public void deletePolicy(Long id) {
+        insurancePolicyMapper.deleteById(id);
+    }
+}

+ 313 - 60
cfc-backend/src/main/java/com/etotem/cfc/service/MembershipService.java

@@ -26,6 +26,23 @@ public class MembershipService implements MembershipServiceInterface {
     @Resource
     private FamilyMapper familyMapper;
 
+    @Resource
+    private UserMapper userMapper;
+
+    @Resource
+    private PaymentOrderMapper paymentOrderMapper;
+
+    @Resource
+    private CommissionService commissionService;
+
+    @Resource
+    private SysConfigService sysConfigService;
+
+    @Resource
+    private MemberUpgradeRecordMapper memberUpgradeRecordMapper;
+
+    // ==================== 基础查询 ====================
+
     /**
      * 获取所有会员等级
      */
@@ -34,6 +51,17 @@ public class MembershipService implements MembershipServiceInterface {
         return levels.stream().map(this::toLevelDTO).collect(Collectors.toList());
     }
 
+    /**
+     * 获取用户的家庭ID
+     */
+    public Long getUserFamilyId(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+        return user.getFamilyId();
+    }
+
     /**
      * 获取家庭的会员信息
      */
@@ -44,11 +72,11 @@ public class MembershipService implements MembershipServiceInterface {
                         .orderByDesc(FamilyMembership::getEndDate)
                         .last("LIMIT 1")
         );
-        
+
         if (membership == null) {
             return null;
         }
-        
+
         return toMembershipDTO(membership);
     }
 
@@ -65,12 +93,12 @@ public class MembershipService implements MembershipServiceInterface {
             );
             return freeLevel != null ? toLevelDTO(freeLevel) : null;
         }
-        
+
         MembershipLevel level = levelMapper.selectOne(
                 new LambdaQueryWrapper<MembershipLevel>()
                         .eq(MembershipLevel::getLevelCode, membership.getLevelCode())
         );
-        
+
         return level != null ? toLevelDTO(level) : null;
     }
 
@@ -82,11 +110,52 @@ public class MembershipService implements MembershipServiceInterface {
         if (level == null || level.getFeatures() == null) {
             return false;
         }
-        
+
         // 解析features JSON,判断是否包含该功能
         return level.getFeatures().contains(feature);
     }
 
+    /**
+     * 检查会员是否可使用某功能(基于等级代码直接判断)
+     */
+    public boolean canUseFeature(Long familyId, String feature) {
+        // 先查用户获取实际会员等级
+        // 先通过family的creator获取用户
+        Family family = familyMapper.selectById(familyId);
+        if (family == null) {
+            return false;
+        }
+        User adminUser = userMapper.selectById(family.getCreatorId());
+        String level = adminUser != null && adminUser.getMemberLevel() != null
+                ? adminUser.getMemberLevel() : "FREE";
+
+        // FREE用户可用功能
+        if ("FREE".equals(level)) {
+            return "free_activities".equals(feature)
+                    || "free_courses".equals(feature)
+                    || "full_price_purchase".equals(feature)
+                    || "referral_commission".equals(feature);
+        }
+
+        // FAMILY用户可用功能
+        if ("FAMILY".equals(level)) {
+            return "free_activities".equals(feature)
+                    || "free_courses".equals(feature)
+                    || "full_price_purchase".equals(feature)
+                    || "referral_commission".equals(feature)
+                    || "discount_purchase".equals(feature)
+                    || "member_activities".equals(feature)
+                    || "create_family".equals(feature)
+                    || "invite_family".equals(feature)
+                    || "purchase_commission".equals(feature);
+        }
+
+        // PROVIDER也有全部功能
+        return true;
+    }
+
+    // ==================== 订单与支付 ====================
+
     /**
      * 创建订单
      */
@@ -95,13 +164,25 @@ public class MembershipService implements MembershipServiceInterface {
                 new LambdaQueryWrapper<MembershipLevel>()
                         .eq(MembershipLevel::getLevelCode, levelCode)
         );
-        
+
         if (level == null) {
             throw new RuntimeException("会员等级不存在");
         }
-        
-        BigDecimal amount = "monthly".equals(paymentType) ? level.getPriceMonthly() : level.getPriceYearly();
-        
+
+        // 从系统配置读取会员费用
+        BigDecimal amount;
+        if ("FAMILY".equals(levelCode)) {
+            String feeStr = sysConfigService.getValue("member_fee_family");
+            long fee = feeStr != null ? Long.parseLong(feeStr) : 36500L; // 默认36500分=365元
+            amount = BigDecimal.valueOf(fee);
+        } else if ("PROVIDER".equals(levelCode)) {
+            String feeStr = sysConfigService.getValue("member_fee_standard");
+            long fee = feeStr != null ? Long.parseLong(feeStr) : 1314L;
+            amount = BigDecimal.valueOf(fee);
+        } else {
+            amount = "yearly".equals(paymentType) ? level.getPriceYearly() : level.getPriceMonthly();
+        }
+
         PaymentOrder order = new PaymentOrder();
         order.setOrderNo(generateOrderNo());
         order.setFamilyId(familyId);
@@ -111,15 +192,18 @@ public class MembershipService implements MembershipServiceInterface {
         order.setStatus("pending");
         order.setCreatedAt(new Date());
         order.setUpdatedAt(new Date());
-        
-        // 这里需要注入PaymentOrderMapper
-        // orderMapper.insert(order);
-        
+
+        paymentOrderMapper.insert(order);
+
         PaymentOrderDTO dto = new PaymentOrderDTO();
         dto.setOrderNo(order.getOrderNo());
         dto.setAmount(order.getAmount());
         dto.setStatus(order.getStatus());
-        
+        dto.setFamilyId(order.getFamilyId());
+        dto.setLevelCode(order.getLevelCode());
+        dto.setPaymentType(order.getPaymentType());
+        dto.setCreatedAt(order.getCreatedAt());
+
         return dto;
     }
 
@@ -127,54 +211,223 @@ public class MembershipService implements MembershipServiceInterface {
      * 处理支付回调
      */
     public boolean processPaymentCallback(String orderNo, String transactionId, String payMethod) {
-        // 查找订单
-        // PaymentOrder order = orderMapper.selectOne(...)
-        // if (order == null) return false;
-        // 
-        // order.setStatus("paid");
-        // order.setTransactionId(transactionId);
-        // order.setPayMethod(payMethod);
-        // order.setPaidAt(new Date());
-        // order.setUpdatedAt(new Date());
-        // orderMapper.updateById(order);
-        // 
-        // // 开通/续费会员
-        // MembershipLevel level = levelMapper.selectOne(...)
-        // 
-        // FamilyMembership membership = new FamilyMembership();
-        // membership.setFamilyId(order.getFamilyId());
-        // membership.setLevelCode(order.getLevelCode());
-        // membership.setStartDate(new Date());
-        // membership.setEndDate(calculateEndDate(order.getPaymentType()));
-        // membership.setPaymentStatus("paid");
-        // membership.setOrderNo(orderNo);
-        // membership.setTransactionId(transactionId);
-        // membership.setAmount(order.getAmount());
-        // membershipMapper.insert(membership);
-        // 
-        // return true;
-        
-        return true; // 简化实现
+        try {
+            // 查找订单
+            PaymentOrder order = paymentOrderMapper.selectOne(
+                    new LambdaQueryWrapper<PaymentOrder>()
+                            .eq(PaymentOrder::getOrderNo, orderNo)
+            );
+            if (order == null) {
+                return false;
+            }
+
+            // 防止重复处理
+            if ("paid".equals(order.getStatus())) {
+                return true;
+            }
+
+            order.setStatus("paid");
+            order.setTransactionId(transactionId);
+            order.setPayMethod(payMethod);
+            order.setPaidAt(new Date());
+            order.setUpdatedAt(new Date());
+            paymentOrderMapper.updateById(order);
+
+            // 查找该家庭的创建者(家庭管理员)
+            Family family = familyMapper.selectById(order.getFamilyId());
+            if (family == null || family.getCreatorId() == null) {
+                return false;
+            }
+            Long adminUserId = family.getCreatorId();
+            User adminUser = userMapper.selectById(adminUserId);
+            if (adminUser == null) {
+                return false;
+            }
+
+            // 插入FamilyMembership记录
+            FamilyMembership membership = new FamilyMembership();
+            membership.setFamilyId(order.getFamilyId());
+            membership.setLevelCode(order.getLevelCode());
+            membership.setStartDate(new Date());
+            membership.setEndDate(calculateEndDate(order.getPaymentType()));
+            membership.setPaymentMethod(payMethod);
+            membership.setPaymentStatus("paid");
+            membership.setOrderNo(orderNo);
+            membership.setTransactionId(transactionId);
+            membership.setAmount(order.getAmount());
+            membershipMapper.insert(membership);
+
+            // 升级用户等级
+            Date expireTime = new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000);
+            adminUser.setMemberLevel("FAMILY");
+            adminUser.setMemberExpireTime(expireTime);
+            adminUser.setUpdatedAt(new Date());
+            userMapper.updateById(adminUser);
+
+            // 创建升级记录
+            MemberUpgradeRecord upgradeRecord = new MemberUpgradeRecord();
+            upgradeRecord.setUserId(adminUserId);
+            upgradeRecord.setFromLevel("FREE");
+            upgradeRecord.setToLevel("FAMILY");
+            upgradeRecord.setUpgradeType("pay");
+            upgradeRecord.setOrderId(order.getId());
+            upgradeRecord.setExpireTime(expireTime);
+            upgradeRecord.setCreatedAt(new Date());
+            memberUpgradeRecordMapper.insert(upgradeRecord);
+
+            // 结算佣金(L1+L2)
+            commissionService.settleTwoLevel(
+                    order.getId(),
+                    "membership",
+                    adminUserId,
+                    order.getAmount(),
+                    null
+            );
+
+            return true;
+        } catch (Exception e) {
+            return false;
+        }
     }
 
+    // ==================== 会员升级与过期 ====================
+
     /**
-     * 检查会员是否可使用某功能
+     * 将用户升级为FAMILY会员
      */
-    public boolean canUseFeature(Long familyId, String feature) {
-        MembershipLevelDTO currentLevel = getCurrentLevel(familyId);
-        
-        if (currentLevel == null) {
-            return false;
+    public void upgradeToFamily(Long userId, String upgradeType, Long orderId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            throw new RuntimeException("用户不存在");
+        }
+
+        String fromLevel = user.getMemberLevel() != null ? user.getMemberLevel() : "FREE";
+
+        Date expireTime = new Date(System.currentTimeMillis() + 365L * 24 * 60 * 60 * 1000);
+        user.setMemberLevel("FAMILY");
+        user.setMemberExpireTime(expireTime);
+        user.setUpdatedAt(new Date());
+        userMapper.updateById(user);
+
+        MemberUpgradeRecord record = new MemberUpgradeRecord();
+        record.setUserId(userId);
+        record.setFromLevel(fromLevel);
+        record.setToLevel("FAMILY");
+        record.setUpgradeType(upgradeType);
+        record.setOrderId(orderId);
+        record.setExpireTime(expireTime);
+        record.setCreatedAt(new Date());
+        memberUpgradeRecordMapper.insert(record);
+    }
+
+    /**
+     * 使会员过期(降级为FREE)
+     */
+    public void expireMember(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return;
+        }
+
+        if ("FAMILY".equals(user.getMemberLevel())
+                && user.getMemberExpireTime() != null
+                && user.getMemberExpireTime().before(new Date())) {
+            user.setMemberLevel("FREE");
+            user.setMemberExpireTime(null);
+            user.setUpdatedAt(new Date());
+            userMapper.updateById(user);
+        }
+    }
+
+    /**
+     * 获取用户当前有效会员等级(检查有效期)
+     */
+    public String getMemberLevel(Long userId) {
+        User user = userMapper.selectById(userId);
+        if (user == null) {
+            return "FREE";
+        }
+
+        String level = user.getMemberLevel() != null ? user.getMemberLevel() : "FREE";
+        if ("FAMILY".equals(level) && user.getMemberExpireTime() != null) {
+            if (user.getMemberExpireTime().before(new Date())) {
+                return "FREE";
+            }
+        }
+        return level;
+    }
+
+    /**
+     * 应用会员折扣
+     */
+    public BigDecimal applyMemberDiscount(Long userId, BigDecimal originalPrice) {
+        if (originalPrice == null || originalPrice.compareTo(BigDecimal.ZERO) <= 0) {
+            return BigDecimal.ZERO;
         }
-        
-        // 免费版基础功能
-        if ("FREE".equals(currentLevel.getLevelCode())) {
-            return "basic_tasks".equals(feature) || "points_system".equals(feature);
+
+        String level = getMemberLevel(userId);
+        if ("FAMILY".equals(level)) {
+            String discountStr = sysConfigService.getValue("member_discount_family");
+            if (discountStr != null) {
+                // discount is in bps (9000 = 90% = 10% off)
+                BigDecimal discountBps = new BigDecimal(discountStr);
+                BigDecimal discountRate = discountBps.divide(new BigDecimal("10000"));
+                return originalPrice.multiply(discountRate);
+            }
+        }
+        return originalPrice;
+    }
+
+    // ==================== 定时任务支持 ====================
+
+    /**
+     * 检查和自动升级(消费达标的FREE用户升级为FAMILY)
+     * 由定时任务 daily 3:30AM 调用
+     */
+    public void checkAndAutoUpgrade() {
+        String thresholdStr = sysConfigService.getValue("member_upgrade_threshold");
+        long threshold = thresholdStr != null ? Long.parseLong(thresholdStr) : 131400L;
+
+        List<User> freeUsers = userMapper.selectList(
+                new LambdaQueryWrapper<User>()
+                        .and(w -> w.eq(User::getMemberLevel, "FREE")
+                                .or().isNull(User::getMemberLevel))
+                        .and(w -> w.isNotNull(User::getTotalConsumption)
+                                .ge(User::getTotalConsumption, threshold))
+        );
+
+        for (User user : freeUsers) {
+            try {
+                upgradeToFamily(user.getId(), "consumption", null);
+            } catch (Exception e) {
+                // log error and continue
+            }
         }
-        
-        return hasFeature(familyId, feature);
     }
 
+    /**
+     * 降级已过期的FAMILY会员为FREE
+     * 由定时任务 daily 4:00AM 调用
+     */
+    public void downgradeExpiredMembers() {
+        List<User> familyUsers = userMapper.selectList(
+                new LambdaQueryWrapper<User>()
+                        .eq(User::getMemberLevel, "FAMILY")
+                        .isNotNull(User::getMemberExpireTime)
+                        .lt(User::getMemberExpireTime, new Date())
+        );
+
+        for (User user : familyUsers) {
+            try {
+                expireMember(user.getId());
+            } catch (Exception e) {
+                // log error and continue
+            }
+        }
+    }
+
+    // ==================== 私有方法 ====================
+
     private String generateOrderNo() {
         return "ORD" + System.currentTimeMillis();
     }
@@ -216,19 +469,19 @@ public class MembershipService implements MembershipServiceInterface {
         dto.setOrderNo(membership.getOrderNo());
         dto.setAmount(membership.getAmount());
         dto.setAutoRenew(membership.getAutoRenew());
-        
+
         // 计算是否在会员期内
         Date now = new Date();
-        boolean isActive = "paid".equals(membership.getPaymentStatus()) && 
-                now.after(membership.getStartDate()) && 
+        boolean isActive = "paid".equals(membership.getPaymentStatus()) &&
+                now.after(membership.getStartDate()) &&
                 now.before(membership.getEndDate());
         dto.setIsActive(isActive);
-        
+
         if (isActive) {
             long daysRemaining = (membership.getEndDate().getTime() - now.getTime()) / (24 * 60 * 60 * 1000);
             dto.setDaysRemaining(daysRemaining);
         }
-        
+
         // 获取等级名称
         MembershipLevel level = levelMapper.selectOne(
                 new LambdaQueryWrapper<MembershipLevel>()
@@ -237,7 +490,7 @@ public class MembershipService implements MembershipServiceInterface {
         if (level != null) {
             dto.setLevelName(level.getLevelName());
         }
-        
+
         return dto;
     }
 }

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/service/PackagePaymentService.java

@@ -115,7 +115,7 @@ public class PackagePaymentService implements PackagePaymentServiceInterface {
         params.put("appid", appid);
         params.put("mch_id", mchId);
         params.put("nonce_str", UUID.randomUUID().toString().replace("-", "").substring(0, 16));
-        params.put("body", "知行益家-" + order.getPackageName());
+        params.put("body", "浠艾福-" + order.getPackageName());
         params.put("out_trade_no", order.getOrderNo());
         params.put("total_fee", order.getPrice().multiply(new BigDecimal("100")).intValue());
         params.put("spbill_create_ip", "127.0.0.1");

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

@@ -28,6 +28,9 @@ public class StreakService implements StreakServiceInterface {
     @Resource
     private PointsLogMapper pointsLogMapper;
 
+    @Resource
+    private EnergyService energyService;
+
     /**
      * 检查并发放打卡里程碑奖励
      * 完成任务时调用,检查是否达到新的里程碑并发放奖励
@@ -63,22 +66,38 @@ public class StreakService implements StreakServiceInterface {
         // 检查是否达到新的里程碑
         Integer rewardPoints = null;
         for (StreakMilestone milestone : milestones) {
-            if (currentStreakDays >= milestone.getDays() 
-                && !awardedDays.contains(milestone.getDays())) {
-            // 发放奖励
-            rewardPoints = milestone.getRewardPoints();
+            if (currentStreakDays >= milestone.getDays()
+                    && !awardedDays.contains(milestone.getDays())) {
+                // 发放奖励
+                rewardPoints = milestone.getRewardPoints();
+
+                // 记录积分
+                Child child = childMapper.selectById(childId);
+                if (child != null) {
+                    recordBonusPoints(child, milestone);
+                }
+
+                // 发放行能量 - 达到里程碑
+                try {
+                    energyService.awardEnergy(childId, "streak", milestone.getId().longValue(), rewardPoints,
+                            "打卡里程碑-" + milestone.getDays() + "天奖励", null);
+                } catch (Exception e) {
+                    log.warn("打卡里程碑能量发放失败: childId={}, error={}", childId, e.getMessage());
+                }
 
-            // 记录积分
-            Child child = childMapper.selectById(childId);
-            if (child != null) {
-                recordBonusPoints(child, milestone);
-            }
-                
                 log.info("孩子{}达到连续打卡{}天,奖励{}积分", childId, milestone.getDays(), rewardPoints);
                 break; // 每次只发放一个里程碑奖励
             }
         }
-        
+
+        // 每日连续打卡能量奖励(即使没达到里程碑也奖励)
+        try {
+            energyService.awardEnergy(childId, "streak", 0L, 5,
+                    "连续打卡第" + currentStreakDays + "天", null);
+        } catch (Exception e) {
+            log.warn("每日打卡能量发放失败: childId={}, error={}", childId, e.getMessage());
+        }
+
         return rewardPoints != null ? rewardPoints : 0;
     }
     

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

@@ -24,6 +24,14 @@ public class VendorService {
         if (user.getVendorStatus() != null && !"rejected".equals(user.getVendorStatus())) {
             return Result.error("已有入驻申请,请勿重复提交");
         }
+        // 验证服务商类型
+        if (vendorType != null) {
+            java.util.List<String> validTypes = java.util.Arrays.asList(
+                    "planner", "activity_provider", "product_supplier", "consultant");
+            if (!validTypes.contains(vendorType)) {
+                return Result.error("无效的服务商类型");
+            }
+        }
         user.setVendorType(vendorType);
         user.setVendorStatus("pending");
         user.setVendorRejectReason(null);

+ 16 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/api/MembershipServiceInterface.java

@@ -4,6 +4,7 @@ import com.etotem.cfc.dto.FamilyMembershipDTO;
 import com.etotem.cfc.dto.MembershipLevelDTO;
 import com.etotem.cfc.dto.PaymentOrderDTO;
 
+import java.math.BigDecimal;
 import java.util.List;
 
 public interface MembershipServiceInterface {
@@ -14,4 +15,19 @@ public interface MembershipServiceInterface {
     PaymentOrderDTO createOrder(Long familyId, String levelCode, String paymentType);
     boolean processPaymentCallback(String orderNo, String transactionId, String payMethod);
     boolean canUseFeature(Long familyId, String feature);
+
+    /** 获取用户家庭ID */
+    Long getUserFamilyId(Long userId);
+
+    /** 将用户升级为FAMILY会员 */
+    void upgradeToFamily(Long userId, String upgradeType, Long orderId);
+
+    /** 使会员过期(降级为FREE) */
+    void expireMember(Long userId);
+
+    /** 获取当前有效会员等级 */
+    String getMemberLevel(Long userId);
+
+    /** 应用会员折扣 */
+    BigDecimal applyMemberDiscount(Long userId, BigDecimal originalPrice);
 }

+ 51 - 0
cfc-backend/src/main/java/com/etotem/cfc/task/MembershipScheduledTasks.java

@@ -0,0 +1,51 @@
+package com.etotem.cfc.task;
+
+import com.etotem.cfc.service.MembershipService;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import javax.annotation.Resource;
+
+/**
+ * 会员定时任务
+ * - 消费达标自动升级(每日3:30)
+ * - 过期会员降级(每日4:00)
+ */
+@Slf4j
+@Component
+public class MembershipScheduledTasks {
+
+    @Resource
+    private MembershipService membershipService;
+
+    /**
+     * 每天 3:30 AM 执行
+     * 检查消费达标的FREE用户,自动升级为FAMILY会员
+     */
+    @Scheduled(cron = "0 30 3 * * ?")
+    public void checkAutoUpgrade() {
+        log.info("开始执行会员自动升级检查定时任务");
+        try {
+            membershipService.checkAndAutoUpgrade();
+            log.info("会员自动升级检查定时任务执行完成");
+        } catch (Exception e) {
+            log.error("会员自动升级检查定时任务执行失败", e);
+        }
+    }
+
+    /**
+     * 每天 4:00 AM 执行
+     * 检查已过期的FAMILY会员,降级为FREE
+     */
+    @Scheduled(cron = "0 0 4 * * ?")
+    public void downgradeExpired() {
+        log.info("开始执行过期会员降级定时任务");
+        try {
+            membershipService.downgradeExpiredMembers();
+            log.info("过期会员降级定时任务执行完成");
+        } catch (Exception e) {
+            log.error("过期会员降级定时任务执行失败", e);
+        }
+    }
+}

+ 0 - 4
cfc-backend/src/main/resources/application.yml

@@ -27,10 +27,6 @@ sfms:
     url: jdbc:mysql://bianwoyou.mysql.rds.aliyuncs.com:3305/sfms?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true
     username: sfms
     password: sfms@123
-  sql:
-    init:
-      mode: always
-      schema-locations: classpath:schema.sql
 
 mybatis-plus:
   mapper-locations: classpath*:/mapper/**/*.xml

+ 5 - 0
cfc-backend/src/main/resources/migration_add_cognitive_dimensions.sql

@@ -0,0 +1,5 @@
+-- 为 dan_assessment_results 表添加认知维度字段(6维认知体系扩展)
+ALTER TABLE dan_assessment_results
+    ADD COLUMN IF NOT EXISTS perception_score INT COMMENT '感知得分',
+    ADD COLUMN IF NOT EXISTS spatial_score INT COMMENT '空间思维得分',
+    ADD COLUMN IF NOT EXISTS processing_speed_score INT COMMENT '加工速度得分';

+ 812 - 77
cfc-backend/src/main/resources/schema.sql

@@ -1,4 +1,4 @@
--- 知行益家数据库初始化脚本
+-- 浠艾福数据库初始化脚本
 -- 自动创建表
 
 -- 家庭表
@@ -185,28 +185,6 @@ CREATE TABLE IF NOT EXISTS task_reviews (
     INDEX idx_ai_result (ai_result)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
--- 勋章定义表
-CREATE TABLE IF NOT EXISTS badge_definitions (
-    id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    badge_type VARCHAR(32) NOT NULL,
-    name VARCHAR(50) NOT NULL,
-    description VARCHAR(200),
-    icon VARCHAR(64),
-    condition_type VARCHAR(32) NOT NULL,
-    condition_value INT NOT NULL,
-    reward_points INT DEFAULT 0,
-    INDEX idx_badge_type (badge_type)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
--- 勋章表
-CREATE TABLE IF NOT EXISTS badges (
-    id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    child_id BIGINT NOT NULL,
-    badge_type VARCHAR(32) NOT NULL,
-    unlocked_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-    INDEX idx_child_id_type (child_id, badge_type)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
 -- 打卡里程碑表
 CREATE TABLE IF NOT EXISTS streak_milestones (
     id BIGINT AUTO_INCREMENT PRIMARY KEY,
@@ -293,29 +271,46 @@ FOREIGN KEY (creator_id) REFERENCES users(id) ON DELETE CASCADE
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
 -- =============================================
--- 老师/双培成长规划师相关表
+-- 服务商(成长规划师)相关表
 -- =============================================
 
--- 老师信息表
-CREATE TABLE IF NOT EXISTS teachers (
-    id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    user_id BIGINT NOT NULL COMMENT '关联users表ID',
-    teacher_no VARCHAR(20) UNIQUE COMMENT '成长规划师编号',
+-- 服务商表(统一管理 teacher/planner/activity_provider/product_supplier)
+CREATE TABLE IF NOT EXISTS guides (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL UNIQUE COMMENT '用户ID(唯一)',
+    type VARCHAR(32) DEFAULT 'teacher' COMMENT '服务商类型: teacher/planner/activity_provider/product_supplier',
+    teacher_no VARCHAR(20) COMMENT '服务商编号',
     title VARCHAR(50) COMMENT '职称/称号',
     specialty VARCHAR(200) COMMENT '专长领域',
     bio TEXT COMMENT '简介',
+    university VARCHAR(200) COMMENT '毕业院校',
+    education_system VARCHAR(32) COMMENT '教育体系(参考education_systems表)',
     experience_years TINYINT DEFAULT 0 COMMENT '从业年限',
     rating DECIMAL(3,2) DEFAULT 5.00 COMMENT '评分',
-    total_appointments INT DEFAULT 0 COMMENT '总测评次数',
-    status ENUM('active', 'inactive', 'suspended') DEFAULT 'active',
-    verification_status ENUM('pending', 'approved', 'rejected') DEFAULT 'pending',
-    verified_at DATETIME,
-    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    total_appointments INT DEFAULT 0 COMMENT '总服务次数',
+    level VARCHAR(20) NOT NULL DEFAULT 'junior' COMMENT '等级: junior-初级, intermediate-中级, senior-高级',
+    status VARCHAR(20) NOT NULL DEFAULT 'active' COMMENT '状态: active-活跃, inactive-停用',
+    service_count INT DEFAULT 0 COMMENT '当前服务家庭数',
+    package_id BIGINT COMMENT '购买套餐ID',
+    nickname VARCHAR(100) COMMENT '申请昵称',
+    phone VARCHAR(20) COMMENT '联系电话',
+    intro TEXT COMMENT '个人介绍',
+    avatar VARCHAR(500) COMMENT '头像URL',
+    commission_rate DECIMAL(5,4) DEFAULT 0.1000 COMMENT '佣金比例',
+    street_id BIGINT COMMENT '街道ID',
+    province VARCHAR(50) COMMENT '省份',
+    city VARCHAR(50) COMMENT '城市',
+    district VARCHAR(50) COMMENT '区县',
+    street VARCHAR(100) COMMENT '街道',
+    approved_at DATETIME COMMENT '审核通过时间',
+    verified_at DATETIME COMMENT '认证通过时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
     INDEX idx_user_id (user_id),
-    INDEX idx_teacher_no (teacher_no),
-    INDEX idx_status (status)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+    INDEX idx_level (level),
+    INDEX idx_status (status),
+    INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务商表(规划师/活动方/供应商)';
 
 -- 测评预约表
 CREATE TABLE IF NOT EXISTS assessment_appointments (
@@ -393,45 +388,6 @@ CREATE TABLE IF NOT EXISTS growth_plans (
     INDEX idx_status (status)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
 
--- 任务模板推荐表(老师推荐给家长)
-CREATE TABLE IF NOT EXISTS teacher_task_recommendations (
-    id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    teacher_id BIGINT NOT NULL,
-    family_id BIGINT NOT NULL,
-    child_id BIGINT,
-    task_template_id BIGINT,
-    title VARCHAR(200) NOT NULL,
-    description TEXT,
-    points TINYINT DEFAULT 2,
-    duration_minutes TINYINT,
-    category VARCHAR(32),
-    reason TEXT COMMENT '推荐理由',
-    status ENUM('pending', 'accepted', 'rejected', 'expired') DEFAULT 'pending',
-    expires_at DATETIME,
-    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    INDEX idx_teacher_id (teacher_id),
-    INDEX idx_family_id (family_id),
-    INDEX idx_status (status)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
--- 家长授权表(授权老师查看孩子任务)
-CREATE TABLE IF NOT EXISTS parent_authorizations (
-    id BIGINT AUTO_INCREMENT PRIMARY KEY,
-    family_id BIGINT NOT NULL,
-    child_id BIGINT NOT NULL,
-    teacher_id BIGINT NOT NULL,
-    permissions JSON COMMENT '权限集: task_view,task_review,message_send',
-    status ENUM('active', 'revoked', 'expired') DEFAULT 'active',
-    expires_at DATETIME,
-    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-    INDEX idx_family_id (family_id),
-    INDEX idx_child_id (child_id),
-    INDEX idx_teacher_id (teacher_id),
-INDEX idx_status (status)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-
 -- =============================================
 -- 成长档案表
 -- =============================================
@@ -686,3 +642,782 @@ CREATE TABLE IF NOT EXISTS child_badge (
     INDEX idx_badge_id (badge_id),
     INDEX idx_status (status)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
+
+-- =============================================
+-- 以下表从 DatabaseInitializer.java 提取
+-- 管理后台表
+-- =============================================
+
+-- 管理员表
+CREATE TABLE IF NOT EXISTS admins (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    username VARCHAR(50) NOT NULL COMMENT '用户名',
+    password VARCHAR(128) NOT NULL COMMENT '密码(MD5加密)',
+    nickname VARCHAR(50) COMMENT '昵称',
+    real_name VARCHAR(50) COMMENT '真实姓名',
+    phone VARCHAR(11) COMMENT '手机号',
+    email VARCHAR(100) COMMENT '邮箱',
+    role VARCHAR(20) DEFAULT 'admin' COMMENT '角色: admin/teacher',
+    status TINYINT DEFAULT 1 COMMENT '状态:1启用0禁用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_phone (phone),
+    INDEX idx_username (username)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';
+
+-- 验证码表
+CREATE TABLE IF NOT EXISTS verification_codes (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    phone VARCHAR(11) NOT NULL COMMENT '手机号',
+    code VARCHAR(6) NOT NULL COMMENT '验证码',
+    type VARCHAR(20) NOT NULL COMMENT '验证码类型',
+    expires_in INT DEFAULT 300 COMMENT '过期时间(秒)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    expires_at DATETIME NOT NULL COMMENT '过期时间',
+    used TINYINT DEFAULT 0 COMMENT '是否已使用:0否1是',
+    INDEX idx_phone_type (phone, type),
+    INDEX idx_expires_at (expires_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='验证码表';
+
+-- 管理后台任务模板表
+CREATE TABLE IF NOT EXISTS admin_task_templates (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    title VARCHAR(200) NOT NULL COMMENT '模板标题',
+    description TEXT COMMENT '模板描述',
+    points INT DEFAULT 10 COMMENT '默认积分',
+    category VARCHAR(32) COMMENT '任务分类',
+    need_review TINYINT DEFAULT 0 COMMENT '是否需要审核:0否1是',
+    review_by_category TINYINT DEFAULT 0 COMMENT '是否按分类审核:0否1是',
+    recommended_age INT COMMENT '推荐年龄',
+    difficulty ENUM('easy', 'medium', 'hard') DEFAULT 'medium' COMMENT '难度: easy/medium/hard',
+    teacher_id BIGINT COMMENT '成长规划师ID',
+    complete_types VARCHAR(100) COMMENT '完成方式JSON',
+    duration INT COMMENT '预计完成时长(分钟)',
+    review_type VARCHAR(20) COMMENT '审核人类型: creator/parent/teacher/ai',
+    task_type VARCHAR(20) COMMENT '任务性质: onetime/recurring',
+    frequency VARCHAR(20) COMMENT '频率: daily/weekly/monthly',
+    max_frequency INT COMMENT '周期内最多完成次数',
+    is_active TINYINT DEFAULT 1 COMMENT '是否启用:0禁用1启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_category (category),
+    INDEX idx_difficulty (difficulty),
+    INDEX idx_is_active (is_active)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理后台任务模板表';
+
+-- =============================================
+-- 测评模块表
+-- =============================================
+
+-- 测评资料表
+CREATE TABLE IF NOT EXISTS assessment_materials (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    title VARCHAR(100) NOT NULL COMMENT '资料标题',
+    content TEXT COMMENT '测评介绍内容',
+    cover_image VARCHAR(255) COMMENT '封面图片',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态:active/inactive',
+    created_at DATETIME COMMENT '创建时间',
+    updated_at DATETIME COMMENT '更新时间',
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评资料表';
+
+-- 积分配置表
+CREATE TABLE IF NOT EXISTS assessment_points_config (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    points_value INT DEFAULT 100 COMMENT '完成测评获得积分',
+    points_value_yuan DECIMAL(10,2) DEFAULT 0.01 COMMENT '1积分等于多少元',
+    is_active TINYINT DEFAULT 1 COMMENT '是否启用',
+    created_at DATETIME COMMENT '创建时间',
+    updated_at DATETIME COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='积分配置表';
+
+-- 家庭测评配置表
+CREATE TABLE IF NOT EXISTS family_assessment_config (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    material_id BIGINT COMMENT '测评资料ID',
+    remind_months INT DEFAULT 12 COMMENT '提醒周期(月)',
+    last_remind_at DATETIME COMMENT '上次提醒时间',
+    next_remind_at DATETIME COMMENT '下次提醒时间',
+    is_enabled TINYINT DEFAULT 1 COMMENT '是否启用:1启用0关闭',
+    created_by BIGINT COMMENT '创建人(规划师ID)',
+    created_at DATETIME COMMENT '创建时间',
+    updated_at DATETIME COMMENT '更新时间',
+    UNIQUE KEY uk_family (family_id),
+    INDEX idx_next_remind (next_remind_at),
+    INDEX idx_enabled (is_enabled)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='家庭测评配置表';
+
+-- 测评记录表
+CREATE TABLE IF NOT EXISTS assessment_records (
+    id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键',
+    family_id BIGINT COMMENT '家庭ID',
+    child_id BIGINT COMMENT '孩子ID',
+    material_id BIGINT COMMENT '测评资料ID',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态:pending待完成/completed已完成',
+    completed_at DATETIME COMMENT '完成时间',
+    created_at DATETIME COMMENT '创建时间',
+    INDEX idx_family (family_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='测评记录表';
+
+-- 评估订单表(测评订单)
+CREATE TABLE IF NOT EXISTS assessment_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单编号',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT COMMENT '孩子ID',
+    guide_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    package_id BIGINT NOT NULL COMMENT '套餐ID',
+    appointment_id BIGINT COMMENT '关联预约ID',
+    guide_name VARCHAR(50) COMMENT '规划师名称',
+    package_name VARCHAR(100) COMMENT '套餐名称',
+    total_price BIGINT DEFAULT 0 COMMENT '总价(分)',
+    discount_amount BIGINT DEFAULT 0 COMMENT '优惠金额(分)',
+    actual_price BIGINT DEFAULT 0 COMMENT '实付金额(分)',
+    status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/paid/completed/cancelled/refunded',
+    pay_time VARCHAR(50) COMMENT '支付时间',
+    pay_type VARCHAR(20) COMMENT '支付方式',
+    transaction_id VARCHAR(64) COMMENT '交易流水号',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_family (family_id),
+    INDEX idx_guide (guide_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='评估订单表';
+
+-- =============================================
+-- 成长规划师相关表
+-- =============================================
+
+-- 成长规划师申请表
+CREATE TABLE IF NOT EXISTS guide_applications (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '申请人用户ID',
+    type VARCHAR(20) NOT NULL DEFAULT 'new_apply' COMMENT '申请类型: new_apply-新注册, upgrade-升级',
+    target_level VARCHAR(20) NOT NULL COMMENT '目标等级: junior-初级, intermediate-中级, senior-高级',
+    current_level VARCHAR(20) DEFAULT NULL COMMENT '当前等级(升级时填写)',
+    package_id BIGINT COMMENT '申请的套餐ID',
+    payment_proof VARCHAR(500) COMMENT '付款凭证URL',
+    payment_amount DECIMAL(10,2) COMMENT '付款金额',
+    address VARCHAR(500) COMMENT '地址',
+    bank_card VARCHAR(50) COMMENT '银行卡号',
+    bank_name VARCHAR(100) COMMENT '开户行',
+    status VARCHAR(20) NOT NULL DEFAULT 'pending' COMMENT '状态: pending-待审核, approved-已通过, rejected-已拒绝',
+    reject_reason VARCHAR(500) COMMENT '拒绝原因',
+    reviewed_at DATETIME COMMENT '审核时间',
+    reviewed_by BIGINT COMMENT '审核人ID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user_id (user_id),
+    INDEX idx_status (status),
+    INDEX idx_type (type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请表';
+
+-- 成长规划师申请过程记录表
+CREATE TABLE IF NOT EXISTS guide_application_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    application_id BIGINT NOT NULL COMMENT '关联申请ID',
+    user_id BIGINT NOT NULL COMMENT '操作人用户ID',
+    action VARCHAR(50) NOT NULL COMMENT '操作: submit/approve/reject/withdraw/resubmit',
+    from_status VARCHAR(20) COMMENT '操作前状态',
+    to_status VARCHAR(20) NOT NULL COMMENT '操作后状态',
+    remark VARCHAR(500) COMMENT '备注/原因',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_application_id (application_id),
+    INDEX idx_user_id (user_id),
+    INDEX idx_action (action)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师申请过程记录表';
+
+-- =============================================
+-- 小游戏表
+-- =============================================
+
+-- 小游戏表
+CREATE TABLE IF NOT EXISTS mini_games (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    game_code VARCHAR(20) NOT NULL UNIQUE COMMENT '游戏代码',
+    game_name VARCHAR(50) NOT NULL COMMENT '游戏名称',
+    description VARCHAR(255) COMMENT '游戏描述',
+    icon VARCHAR(50) COMMENT '图标emoji',
+    default_duration INT DEFAULT 5 COMMENT '默认时长(分钟)',
+    default_points INT DEFAULT 5 COMMENT '默认奖励积分',
+    game_url VARCHAR(255) COMMENT '游戏H5页面URL',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status TINYINT DEFAULT 1 COMMENT '状态:0禁用1启用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_game_code (game_code),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='小游戏表';
+
+-- =============================================
+-- 地址维度表
+-- =============================================
+
+-- 街道地址维度表
+CREATE TABLE IF NOT EXISTS streets (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    province VARCHAR(50) COMMENT '省份',
+    province_code VARCHAR(20) COMMENT '省份编码',
+    city VARCHAR(50) COMMENT '城市',
+    city_code VARCHAR(20) COMMENT '城市编码',
+    district VARCHAR(50) COMMENT '区县',
+    district_code VARCHAR(20) COMMENT '区县编码',
+    street VARCHAR(100) COMMENT '街道',
+    street_code VARCHAR(20) COMMENT '街道编码',
+    full_name VARCHAR(200) COMMENT '完整地址',
+    level INT COMMENT '层级:1省2市3区4街道',
+    parent_id BIGINT COMMENT '父级ID',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_level (level),
+    INDEX idx_parent_id (parent_id),
+    INDEX idx_street_code (street_code),
+    INDEX idx_full_name (full_name(50))
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='街道地址维度表';
+
+-- =============================================
+-- 系统日志表
+-- =============================================
+
+-- 用户操作日志表
+CREATE TABLE IF NOT EXISTS user_operation_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT COMMENT '用户ID',
+    nickname VARCHAR(50) COMMENT '用户昵称',
+    operation_type VARCHAR(20) NOT NULL COMMENT '操作类型: login/logout/create/update/delete/query等',
+    description VARCHAR(500) COMMENT '操作描述',
+    request_path VARCHAR(200) COMMENT '请求路径',
+    request_method VARCHAR(10) COMMENT '请求方法',
+    request_params TEXT COMMENT '请求参数(脱敏)',
+    response_status INT COMMENT '响应状态码',
+    client_ip VARCHAR(50) COMMENT '客户端IP',
+    user_agent VARCHAR(500) COMMENT 'User-Agent',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user_id (user_id),
+    INDEX idx_operation_type (operation_type),
+    INDEX idx_created_at (created_at)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户操作日志表';
+
+-- =============================================
+-- 邀请与心愿表
+-- =============================================
+
+-- 邀请卡片表
+CREATE TABLE IF NOT EXISTS invite_card (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    type VARCHAR(20) NOT NULL COMMENT '类型: child/family/guide',
+    code VARCHAR(64) NOT NULL UNIQUE COMMENT '邀请码',
+    ref_id BIGINT NOT NULL COMMENT '关联ID(childId/familyId)',
+    creator_id BIGINT NOT NULL COMMENT '创建人',
+    expires_at DATETIME NOT NULL COMMENT '过期时间',
+    used TINYINT DEFAULT 0 COMMENT '是否已使用:0否1是',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_code (code),
+    INDEX idx_ref_type (ref_id, type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请卡片表';
+
+-- 心愿单表
+CREATE TABLE IF NOT EXISTS wishes (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    family_id BIGINT COMMENT '家庭ID',
+    child_id BIGINT COMMENT '目标孩子ID',
+    created_by BIGINT COMMENT '创建者用户ID',
+    creator_type VARCHAR(20) COMMENT '创建者类型: child/parent',
+    title VARCHAR(200) NOT NULL COMMENT '心愿标题',
+    description VARCHAR(500) COMMENT '心愿描述',
+    category VARCHAR(32) COMMENT '心愿分类',
+    points_required INT COMMENT '所需积分',
+    points_set_by BIGINT COMMENT '定价者ID',
+    points_set_at DATETIME COMMENT '定价时间',
+    status VARCHAR(32) DEFAULT 'active' COMMENT '状态: draft/active/pending_exchange/approved/exchanging/fulfilled/cancelled/rejected',
+    exchange_requested_at DATETIME COMMENT '申请兑换时间',
+    exchange_approved_by BIGINT COMMENT '批准者ID',
+    exchange_approved_at DATETIME COMMENT '批准时间',
+    points_deducted_at DATETIME COMMENT '扣积分时间',
+    fulfilled_by BIGINT COMMENT '兑现者ID',
+    fulfilled_at DATETIME COMMENT '兑现时间',
+    child_confirmed_at DATETIME COMMENT '孩子确认收货时间',
+    rejected_by BIGINT COMMENT '拒绝者ID',
+    rejected_at DATETIME COMMENT '拒绝时间',
+    reject_reason VARCHAR(500) COMMENT '拒绝原因',
+    reject_count INT DEFAULT 0 COMMENT '被拒绝次数',
+    cancelled_by BIGINT COMMENT '取消者ID',
+    cancelled_at DATETIME COMMENT '取消时间',
+    cancel_reason VARCHAR(500) COMMENT '取消原因',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_family_id (family_id),
+    INDEX idx_child_id (child_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='心愿单表';
+
+-- =============================================
+-- 商品与订单表
+-- =============================================
+
+-- 商品表
+CREATE TABLE IF NOT EXISTS products (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(200) NOT NULL COMMENT '商品名称',
+    description VARCHAR(1000) COMMENT '商品描述',
+    intro TEXT COMMENT '详细介绍',
+    cover_image VARCHAR(500) COMMENT '封面图片',
+    images TEXT COMMENT '多张图片JSON',
+    price DECIMAL(10,2) NOT NULL DEFAULT 0 COMMENT '价格',
+    stock INT NOT NULL DEFAULT 0 COMMENT '库存',
+    sales_count INT NOT NULL DEFAULT 0 COMMENT '销量',
+    product_type VARCHAR(32) NOT NULL COMMENT '商品类型: activity/course/physical/digital/service',
+    vendor_id BIGINT COMMENT '供应商ID',
+    vendor_name VARCHAR(100) COMMENT '供应商名称',
+    status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT '状态: draft/pending/approved/rejected/on_shelf/off_shelf',
+    reject_reason VARCHAR(500) COMMENT '审核拒绝原因',
+    member_price DECIMAL(10,2) COMMENT '会员价',
+    member_eligible VARCHAR(32) DEFAULT 'none' COMMENT '会员资格: all/budget/none',
+    domain VARCHAR(32) COMMENT '五维领域: action/mind/body/wisdom/wealth',
+    profit_rate DECIMAL(5,2) DEFAULT 0.00 COMMENT '平台利润率(%)',
+    external_source VARCHAR(64) COMMENT '外部来源系统',
+    external_id VARCHAR(128) COMMENT '外部来源ID',
+    external_data TEXT COMMENT '外部来源数据JSON',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
+
+-- 商品订单表
+CREATE TABLE IF NOT EXISTS product_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_no VARCHAR(64) NOT NULL UNIQUE COMMENT '订单编号',
+    product_id BIGINT NOT NULL COMMENT '商品ID',
+    product_name VARCHAR(200) COMMENT '商品名称',
+    product_type VARCHAR(32) COMMENT '商品类型',
+    buyer_id BIGINT NOT NULL COMMENT '购买者ID',
+    family_id BIGINT COMMENT '家庭ID',
+    quantity INT NOT NULL DEFAULT 1 COMMENT '数量',
+    unit_price DECIMAL(10,2) NOT NULL COMMENT '单价',
+    total_amount DECIMAL(10,2) NOT NULL COMMENT '总金额',
+    status VARCHAR(32) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/paid/completed/cancelled/refunded',
+    payment_method VARCHAR(32) COMMENT '支付方式: wechat/points/membership',
+    remark VARCHAR(500) COMMENT '备注',
+    paid_at DATETIME COMMENT '支付时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品订单表';
+
+-- =============================================
+-- 系统配置与内容表
+-- =============================================
+
+-- 系统配置表
+CREATE TABLE IF NOT EXISTS sys_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    config_key VARCHAR(64) NOT NULL UNIQUE COMMENT '配置键',
+    config_value TEXT COMMENT '配置值',
+    description VARCHAR(500) COMMENT '配置说明',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
+
+-- 内容区块配置表
+CREATE TABLE IF NOT EXISTS content_sections (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    page_key VARCHAR(50) NOT NULL COMMENT '页面标识',
+    section_key VARCHAR(50) NOT NULL COMMENT '区块标识',
+    title VARCHAR(100) COMMENT '区块标题',
+    allowed_roles VARCHAR(255) DEFAULT '["anonymous"]' COMMENT '允许查看的角色JSON数组',
+    sort_order INT DEFAULT 0 COMMENT '显示顺序',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/inactive',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='内容区块配置表';
+
+-- 文章分类表
+CREATE TABLE IF NOT EXISTS article_categories (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(50) NOT NULL COMMENT '分类名',
+    icon VARCHAR(20) DEFAULT '' COMMENT '图标emoji',
+    color VARCHAR(20) DEFAULT '#5B9BD5' COMMENT '标识色',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status TINYINT DEFAULT 1 COMMENT '状态:1启用/0禁用'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章分类表';
+
+-- 文章表
+CREATE TABLE IF NOT EXISTS articles (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    category_id BIGINT DEFAULT 0 COMMENT '所属分类ID',
+    title VARCHAR(200) NOT NULL COMMENT '标题',
+    summary VARCHAR(500) DEFAULT '' COMMENT '摘要',
+    cover_image VARCHAR(500) DEFAULT '' COMMENT '封面图URL',
+    content LONGTEXT COMMENT '富文本内容',
+    tags VARCHAR(200) DEFAULT '' COMMENT '标签JSON数组',
+    author VARCHAR(100) DEFAULT '浠艾福' COMMENT '作者',
+    read_time INT DEFAULT 0 COMMENT '预计阅读分钟数',
+    related_dimensions VARCHAR(100) DEFAULT '' COMMENT '关联五维JSON数组',
+    visibility VARCHAR(20) DEFAULT 'public' COMMENT '浏览权限: public/login/private',
+    visible_to TEXT COMMENT '私密指定人群JSON',
+    status VARCHAR(20) DEFAULT 'draft' COMMENT '状态: draft/published/archived',
+    is_featured TINYINT DEFAULT 0 COMMENT '是否精选:1精选/0普通',
+    published_at DATETIME DEFAULT NULL COMMENT '发布时间',
+    view_count INT DEFAULT 0 COMMENT '浏览次数',
+    created_by BIGINT DEFAULT 0 COMMENT '发布人adminID',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='文章表';
+
+-- =============================================
+-- 五维能量系统表
+-- =============================================
+
+-- 能量维度定义表
+CREATE TABLE IF NOT EXISTS energy_dimension (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    code VARCHAR(16) NOT NULL UNIQUE COMMENT '维度代码: body/mind/wisdom/action/wealth',
+    name VARCHAR(20) NOT NULL COMMENT '维度名称: 身/心/智/行/富',
+    icon VARCHAR(16) COMMENT '图标emoji',
+    element VARCHAR(10) COMMENT '五行元素: 土/火/金/木/水',
+    sort_order INT DEFAULT 0 COMMENT '显示顺序',
+    status TINYINT DEFAULT 1 COMMENT '状态:1启用/0禁用',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量维度定义表';
+
+-- 服务-维度比例配置表
+CREATE TABLE IF NOT EXISTS energy_source_config (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    source_type VARCHAR(32) NOT NULL COMMENT '来源类型: task/activity/course/product/consultation',
+    source_id BIGINT NOT NULL COMMENT '来源业务表ID',
+    source_name VARCHAR(200) COMMENT '冗余名称',
+    dimension_id BIGINT NOT NULL COMMENT '维度ID',
+    ratio DECIMAL(5,4) NOT NULL COMMENT '占比如0.6000=60%',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_source (source_type, source_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务-维度比例配置表';
+
+-- 能量流水表
+CREATE TABLE IF NOT EXISTS energy_log (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    dimension_id BIGINT NOT NULL COMMENT '维度ID',
+    amount INT NOT NULL COMMENT '变动量(正=获得,负=消耗)',
+    balance_after INT NOT NULL COMMENT '变动后余额',
+    source_type VARCHAR(32) COMMENT '来源类型: task/product/medical_report/daily_record',
+    source_id BIGINT COMMENT '来源ID',
+    ref_id BIGINT COMMENT '关联旧流水ID(覆盖时标记)',
+    expires_at DATETIME COMMENT '过期时间(空=永不过期)',
+    description VARCHAR(500) COMMENT '描述',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_child_dim (child_id, dimension_id),
+    INDEX idx_created (created_at),
+    INDEX idx_source (source_type, source_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='能量流水表';
+
+-- 维度余额表
+CREATE TABLE IF NOT EXISTS energy_balance (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    dimension_id BIGINT NOT NULL COMMENT '维度ID',
+    balance INT DEFAULT 0 COMMENT '当前能量值(可为负)',
+    total_earned INT DEFAULT 0 COMMENT '累计获得',
+    total_spent INT DEFAULT 0 COMMENT '累计消耗',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    UNIQUE KEY uk_child_dim (child_id, dimension_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='维度余额表';
+
+-- =============================================
+-- 分佣裂变系统表
+-- =============================================
+
+-- 佣金记录表
+CREATE TABLE IF NOT EXISTS commission_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    order_type VARCHAR(32) NOT NULL COMMENT '订单类型: membership/product/assessment/package',
+    referrer_id BIGINT NOT NULL COMMENT '推荐人ID',
+    buyer_id BIGINT NOT NULL COMMENT '购买人ID',
+    commission_type VARCHAR(16) NOT NULL COMMENT '佣金类型: member/profit',
+    level INT DEFAULT 1 COMMENT '佣金层级: 1=直接推荐, 2=间接推荐',
+    order_amount DECIMAL(10,2) NOT NULL COMMENT '订单金额',
+    profit_rate DECIMAL(5,2) DEFAULT 0.00 COMMENT '利润率(%)',
+    commission_amount DECIMAL(10,2) NOT NULL COMMENT '佣金金额',
+    status VARCHAR(16) NOT NULL DEFAULT 'settled' COMMENT '状态: pending/settled',
+    remark VARCHAR(255) DEFAULT NULL COMMENT '备注',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    settled_at DATETIME DEFAULT NULL COMMENT '结算时间',
+    withdrawn_at DATETIME DEFAULT NULL COMMENT '提现时间',
+    INDEX idx_referrer (referrer_id),
+    INDEX idx_buyer (buyer_id),
+    INDEX idx_order (order_id, order_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金记录表';
+
+-- 提现申请表
+CREATE TABLE IF NOT EXISTS withdrawal_requests (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    amount DECIMAL(10,2) NOT NULL COMMENT '提现金额',
+    account_info TEXT COMMENT '账户信息(JSON)',
+    status VARCHAR(16) NOT NULL DEFAULT 'pending' COMMENT '状态: pending/approved/rejected',
+    audit_by BIGINT DEFAULT NULL COMMENT '审核人ID',
+    audit_at DATETIME DEFAULT NULL COMMENT '审核时间',
+    remark VARCHAR(255) DEFAULT NULL COMMENT '审核备注',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user (user_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='提现申请表';
+
+-- 会员升级记录表
+CREATE TABLE IF NOT EXISTS member_upgrade_records (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    from_level VARCHAR(20) NOT NULL COMMENT '升级前等级',
+    to_level VARCHAR(20) NOT NULL COMMENT '升级后等级',
+    upgrade_type VARCHAR(20) NOT NULL COMMENT '升级类型: pay/consumption/admin',
+    order_id BIGINT DEFAULT NULL COMMENT '关联支付订单ID',
+    consumption_amount BIGINT DEFAULT NULL COMMENT '触发升级的累计消费金额',
+    expire_time DATETIME DEFAULT NULL COMMENT '家庭会员到期时间',
+    created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user (user_id),
+    INDEX idx_order (order_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='会员升级记录表';
+
+-- =============================================
+-- 规划师服务模块表
+-- =============================================
+
+-- 服务类型表
+CREATE TABLE IF NOT EXISTS service_types (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(100) NOT NULL COMMENT '类型名称',
+    icon VARCHAR(50) COMMENT '图标',
+    description VARCHAR(500) COMMENT '描述',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE/DISABLED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务类型表';
+
+-- 服务内容表
+CREATE TABLE IF NOT EXISTS service_contents (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    type_id BIGINT NOT NULL COMMENT '关联服务类型',
+    title VARCHAR(200) NOT NULL COMMENT '内容标题',
+    content TEXT COMMENT '富文本内容',
+    duration INT COMMENT '建议时长(分钟)',
+    age_range VARCHAR(50) COMMENT '适用年龄',
+    price DECIMAL(10,2) COMMENT '参考价格',
+    status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态: DRAFT/ACTIVE',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务内容表';
+
+-- 套餐模板表
+CREATE TABLE IF NOT EXISTS guide_package_templates (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(100) NOT NULL COMMENT '模板名称',
+    validity_months INT COMMENT '有效期(月)',
+    reference_price DECIMAL(10,2) COMMENT '参考价格',
+    min_price DECIMAL(10,2) COMMENT '最低售价',
+    description VARCHAR(500) COMMENT '说明',
+    status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE/DISABLED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='套餐模板表';
+
+-- 套餐模板服务项表
+CREATE TABLE IF NOT EXISTS package_template_services (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    template_id BIGINT NOT NULL COMMENT '关联模板',
+    content_id BIGINT NOT NULL COMMENT '关联服务内容',
+    quantity INT DEFAULT 1 COMMENT '数量',
+    unit VARCHAR(20) COMMENT '单位'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='套餐模板服务项表';
+
+-- 成长规划师套餐表
+CREATE TABLE IF NOT EXISTS guide_packages (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    guide_id BIGINT COMMENT '成长规划师ID',
+    package_id BIGINT COMMENT '关联模板',
+    commission_rate DECIMAL(5,2) COMMENT '佣金比例',
+    teacher_id BIGINT COMMENT '成长规划师ID(旧字段)',
+    template_id BIGINT COMMENT '关联模板(旧字段)',
+    name VARCHAR(100) NOT NULL COMMENT '套餐名称',
+    level VARCHAR(20) COMMENT '套餐等级',
+    price DECIMAL(10,2) NOT NULL COMMENT '销售价格',
+    validity_months INT COMMENT '有效期(月)',
+    validity_days INT DEFAULT 0 COMMENT '有效期天数',
+    description VARCHAR(500) COMMENT '套餐说明',
+    status VARCHAR(20) DEFAULT 'DRAFT' COMMENT '状态: DRAFT/PUBLISHED/PAUSED',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    sales_count INT DEFAULT 0 COMMENT '销量',
+    share_code VARCHAR(50) COMMENT '分享码',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师套餐表';
+
+-- 成长规划师套餐服务项表
+CREATE TABLE IF NOT EXISTS guide_package_services (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    package_id BIGINT NOT NULL COMMENT '关联套餐',
+    content_id BIGINT NOT NULL COMMENT '关联服务内容',
+    quantity INT DEFAULT 1 COMMENT '数量',
+    unit VARCHAR(20) COMMENT '单位'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师套餐服务项表';
+
+-- 成长规划师订单表
+CREATE TABLE IF NOT EXISTS guide_orders (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    order_no VARCHAR(50) NOT NULL COMMENT '订单号',
+    teacher_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    package_id BIGINT NOT NULL COMMENT '套餐ID',
+    amount DECIMAL(10,2) NOT NULL COMMENT '金额',
+    status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态: PENDING/PAID/COMPLETED/REFUNDED',
+    pay_time DATETIME COMMENT '支付时间',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师订单表';
+
+-- 佣金明细表
+CREATE TABLE IF NOT EXISTS guide_commissions (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    teacher_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    order_id BIGINT NOT NULL COMMENT '订单ID',
+    amount DECIMAL(10,2) NOT NULL COMMENT '佣金金额',
+    rate DECIMAL(5,2) COMMENT '佣金比例',
+    status VARCHAR(20) DEFAULT 'PENDING' COMMENT '状态: PENDING/COMPLETED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='佣金明细表';
+
+-- 服务记录表
+CREATE TABLE IF NOT EXISTS guide_service_logs (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    teacher_id BIGINT NOT NULL COMMENT '成长规划师ID',
+    family_id BIGINT NOT NULL COMMENT '家庭ID',
+    order_id BIGINT COMMENT '订单ID',
+    service_content_id BIGINT NOT NULL COMMENT '服务内容ID',
+    notes VARCHAR(500) COMMENT '服务记录',
+    duration INT COMMENT '服务时长(分钟)',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务记录表';
+
+-- 成长规划师邀请码表
+CREATE TABLE IF NOT EXISTS guide_invite_codes (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    guide_id BIGINT NOT NULL COMMENT '成长规划师用户ID',
+    code VARCHAR(50) NOT NULL UNIQUE COMMENT '邀请码',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态',
+    used_by_user_id BIGINT COMMENT '使用该邀请码的用户ID',
+    used_at DATETIME COMMENT '使用时间',
+    expire_at DATETIME COMMENT '过期时间',
+    use_count INT DEFAULT 0 COMMENT '使用次数',
+    max_use_count INT COMMENT '最大使用次数',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='成长规划师邀请码表';
+
+-- =============================================
+-- 财富维度表
+-- =============================================
+
+-- 财商打卡记录表
+CREATE TABLE IF NOT EXISTS finance_checkins (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    checkin_type VARCHAR(20) NOT NULL COMMENT '打卡类型: daily_plan/saving/expense/review',
+    checkin_date DATE NOT NULL COMMENT '打卡日期',
+    amount DECIMAL(10,2) COMMENT '金额(收支记录)',
+    category VARCHAR(50) COMMENT '收支分类',
+    note VARCHAR(200) COMMENT '备注',
+    mood VARCHAR(20) COMMENT '心情',
+    earned_energy INT DEFAULT 0 COMMENT '获得的能量值',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_user_date (user_id, checkin_date),
+    INDEX idx_child (child_id)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='财商打卡记录表';
+
+-- 保单管理表
+CREATE TABLE IF NOT EXISTS insurance_policies (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    user_id BIGINT NOT NULL COMMENT '用户ID',
+    child_id BIGINT COMMENT '关联孩子ID',
+    policy_name VARCHAR(100) NOT NULL COMMENT '保单名称',
+    insurance_company VARCHAR(100) COMMENT '保险公司',
+    policy_type VARCHAR(50) COMMENT '保单类型: medical/life/accident/education/endowment',
+    insured_person VARCHAR(50) NOT NULL COMMENT '被保人姓名',
+    premium DECIMAL(10,2) COMMENT '保费',
+    sum_insured DECIMAL(10,2) COMMENT '保额',
+    start_date DATE COMMENT '生效日期',
+    end_date DATE COMMENT '到期日期',
+    renewal_type VARCHAR(20) DEFAULT 'annual' COMMENT '续费类型: annual/monthly/single',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/expired/cancelled',
+    policy_no VARCHAR(100) COMMENT '保单号',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_user (user_id),
+    INDEX idx_child (child_id),
+    INDEX idx_status (status)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='保单管理表';
+
+-- =============================================
+-- 健康检测报告模块表
+-- =============================================
+
+-- 健康检测报告表
+CREATE TABLE IF NOT EXISTS health_reports (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    child_id BIGINT NOT NULL COMMENT '孩子ID',
+    family_id BIGINT COMMENT '家庭ID',
+    report_type VARCHAR(32) NOT NULL COMMENT '报告类型: physical_exam(体检)/gut_flora(菌群检测)',
+    report_date DATE COMMENT '报告日期',
+    overall_score INT COMMENT '健康总分(0-100)',
+    gut_health_score INT COMMENT '菌群健康分',
+    chronic_disease_score INT COMMENT '慢病控制分',
+    nutrition_score INT COMMENT '营养均衡分',
+    gut_age VARCHAR(32) COMMENT '肠道预测年龄',
+    gut_type VARCHAR(50) COMMENT '肠型',
+    file_url VARCHAR(500) COMMENT '报告文件URL',
+    status VARCHAR(20) DEFAULT 'active' COMMENT '状态: active/archived',
+    remark VARCHAR(500) COMMENT '备注',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
+    INDEX idx_child_id (child_id),
+    INDEX idx_family_id (family_id),
+    INDEX idx_report_type (report_type)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康检测报告表';
+
+-- 健康指标明细表
+CREATE TABLE IF NOT EXISTS health_indicators (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    report_id BIGINT NOT NULL COMMENT '关联报告ID',
+    category VARCHAR(50) COMMENT '指标分类: 营养状况/疾病风险/肠道屏障/神经递质/...',
+    indicator_name VARCHAR(100) NOT NULL COMMENT '指标名称',
+    indicator_value VARCHAR(100) COMMENT '检测值',
+    unit VARCHAR(50) COMMENT '单位',
+    ref_range VARCHAR(200) COMMENT '参考范围',
+    status VARCHAR(50) COMMENT '评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出',
+    sort_order INT DEFAULT 0 COMMENT '显示排序',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    INDEX idx_report_id (report_id),
+    INDEX idx_category (category)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康指标明细表';
+
+-- 教育体系配置表
+CREATE TABLE IF NOT EXISTS education_systems (
+    id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
+    name VARCHAR(32) NOT NULL UNIQUE COMMENT '编码(key)',
+    label VARCHAR(50) NOT NULL COMMENT '显示名称',
+    description VARCHAR(200) COMMENT '描述说明',
+    sort_order INT DEFAULT 0 COMMENT '排序',
+    status VARCHAR(20) DEFAULT 'ACTIVE' COMMENT '状态: ACTIVE/DISABLED',
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间'
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='教育体系配置表';
+
+INSERT INTO education_systems (name, label, description, sort_order) VALUES
+('high_school', '中专/高中', '中等教育学历', 1),
+('associate', '大专', '高等专科学历', 2),
+('bachelor', '本科', '大学本科学历', 3),
+('master', '硕士', '硕士研究生学历', 4),
+('doctor', '博士', '博士研究生学历', 5);

+ 1 - 1
cfc-backend/src/main/resources/schema_assessment.sql

@@ -1,5 +1,5 @@
 -- DAN测评任务系统数据库
--- 知行益家
+-- 浠艾福
 
 -- 1. 测评资料表
 CREATE TABLE IF NOT EXISTS assessment_materials (

+ 2 - 19
cfc-frontend/App.vue

@@ -6,35 +6,18 @@ Vue.prototype.$config = config
 export default {
   onLaunch: function() {
     console.log('App Launch')
-    // 检查登录状态
+    // TabBar 始终显示 5 个底签,不因登录状态隐藏
     const token = uni.getStorageSync('token')
     if (token) {
       this.$store.commit('setToken', token)
-      // 已登录:确保原生 TabBar 显示
-      uni.showTabBar()
     } else {
-      // 未登录:隐藏原生 TabBar
-      uni.hideTabBar()
-      // 如果当前不在允许的未登录页面,则跳转发现页
-      const pages = getCurrentPages()
-      const currentRoute = pages.length > 0 ? pages[pages.length - 1].route : ''
-      const allowedRoutes = ['pages/discover/index', 'pages/index/index', 'pages/body/index', 'pages/mind/index', 'pages/action/index']
-      if (!allowedRoutes.includes(currentRoute)) {
-        uni.reLaunch({ url: '/pages/discover/index' })
-      }
       // 无token时尝试自动登录
       this.tryAutoLogin()
     }
   },
   onShow: function() {
     console.log('App Show')
-    // 每次显示时检查登录状态,同步 TabBar
-    const token = uni.getStorageSync('token')
-    if (token) {
-      uni.showTabBar()
-    } else {
-      uni.hideTabBar()
-    }
+    // TabBar 始终可见,无需根据登录状态 hide/show
   },
   onHide: function() {
     console.log('App Hide')

+ 56 - 0
cfc-frontend/components/BadgeGrid.vue

@@ -0,0 +1,56 @@
+<template>
+  <view class="badge-grid" v-if="badges.length > 0">
+    <view class="badge-grid-inner">
+      <view class="badge-cell" v-for="badge in badges" :key="badge.id"
+        :class="{ 'badge-cell--locked': !badge.unlocked }">
+        <view class="badge-icon-wrap" :class="{ 'badge-icon-wrap--locked': !badge.unlocked }">
+          <text class="badge-icon">{{ badge.unlocked ? badge.icon : '🔒' }}</text>
+        </view>
+        <text class="badge-name">{{ badge.name }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    badges: { type: Array, default: function() { return [] } }
+  }
+}
+</script>
+
+<style scoped>
+.badge-grid {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.badge-grid-inner {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16rpx;
+}
+.badge-cell {
+  width: calc(33.33% - 11rpx);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 12rpx 0;
+}
+.badge-cell--locked { opacity: 0.5; }
+.badge-icon-wrap {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8rpx;
+}
+.badge-icon-wrap--locked { background: #E2E8F0; }
+.badge-icon { font-size: 36rpx; }
+.badge-name { font-size: 22rpx; color: #333; text-align: center; }
+</style>

+ 83 - 0
cfc-frontend/components/EnergyWeekCard.vue

@@ -0,0 +1,83 @@
+<template>
+  <view class="energy-week-card">
+    <view class="ewc-header">
+      <text class="ewc-title">📈 {{ title }}</text>
+    </view>
+    <view class="ewc-body">
+      <view class="ewc-stat-row">
+        <text class="ewc-stat-label">本周获取</text>
+        <text class="ewc-stat-value accent">+{{ weekGain }}</text>
+        <text class="ewc-stat-label" style="margin-left: 24rpx;">上周</text>
+        <text class="ewc-stat-value" :class="gainTrend >= 0 ? 'up' : 'down'">{{ lastWeekGain }}</text>
+        <text class="ewc-trend" :class="gainTrend >= 0 ? 'up' : 'down'">
+          {{ gainTrend >= 0 ? '↑' : '↓' }}{{ Math.abs(gainTrend) }}%
+        </text>
+      </view>
+      <view class="ewc-mini-chart">
+        <view class="ewc-bar" v-for="(val, i) in weekData" :key="i"
+          :style="{ height: (val / maxVal * 80) + 'rpx' }"
+          :class="{ 'ewc-bar--today': i === weekData.length - 1 }">
+        </view>
+      </view>
+      <text class="ewc-tip" v-if="tip">{{ tip }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    title: { type: String, default: '能量周报' },
+    weekGain: { type: Number, default: 0 },
+    lastWeekGain: { type: Number, default: 0 },
+    gainTrend: { type: Number, default: 0 },
+    weekData: { type: Array, default: function() { return [10, 20, 30, 40, 50, 60, 70] } },
+    tip: { type: String, default: '' }
+  },
+  computed: {
+    maxVal() {
+      return Math.max.apply(null, this.weekData.concat([1]))
+    }
+  }
+}
+</script>
+
+<style scoped>
+.energy-week-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.ewc-header { margin-bottom: 16rpx; }
+.ewc-title { font-size: 28rpx; font-weight: 700; color: #333; }
+.ewc-stat-row {
+  display: flex;
+  align-items: baseline;
+  flex-wrap: wrap;
+  margin-bottom: 16rpx;
+}
+.ewc-stat-label { font-size: 22rpx; color: #999; }
+.ewc-stat-value { font-size: 28rpx; font-weight: 700; }
+.ewc-stat-value.accent { color: #F97316; }
+.ewc-stat-value.up { color: #10B981; }
+.ewc-stat-value.down { color: #EF4444; }
+.ewc-trend { font-size: 22rpx; margin-left: 8rpx; }
+.ewc-trend.up { color: #10B981; }
+.ewc-trend.down { color: #EF4444; }
+.ewc-mini-chart {
+  display: flex;
+  align-items: flex-end;
+  gap: 8rpx;
+  height: 90rpx;
+  margin-bottom: 12rpx;
+}
+.ewc-bar {
+  flex: 1;
+  background: #E2E8F0;
+  border-radius: 4rpx 4rpx 0 0;
+  min-height: 8rpx;
+}
+.ewc-bar--today { background: #F97316; }
+.ewc-tip { font-size: 22rpx; color: #F97316; display: block; }
+</style>

+ 147 - 0
cfc-frontend/components/PageBanner.vue

@@ -0,0 +1,147 @@
+<template>
+  <view class="page-banner" :style="bannerStyle">
+    <view class="banner-left">
+      <image class="banner-logo" src="/static/logo.png" mode="aspectFit" />
+      <view class="banner-texts">
+        <text class="banner-name">浠艾福</text>
+        <text class="banner-tagline">{{ tagline }}</text>
+      </view>
+    </view>
+    <view class="banner-right" v-if="quote">
+      <text class="banner-quote">{{ quote }}</text>
+    </view>
+  </view>
+</template>
+
+<script>
+/**
+ * 各主题对应沙盘颜色的渐变背景
+ * 五行色来源:wuxing-sandbox 组件和 render.js
+ *   身(body/土): #8D6E63
+ *   心(mind/火): #FF6B35
+ *   智(wisdom/金): #FFD700
+ *   行(action/木): #4CAF50
+ *   富(wealth/水): #42A5F5
+ */
+var THEME_STYLES = {
+  home: {
+    background: 'linear-gradient(135deg, #FF8A65 0%, #FFB74D 100%)',
+    color: '#FFFFFF'
+  },
+  body: {
+    background: 'linear-gradient(135deg, #8D6E63 0%, #A1887F 100%)',
+    color: '#FFFFFF'
+  },
+  mind: {
+    background: 'linear-gradient(135deg, #FF6B35 0%, #E65100 100%)',
+    color: '#FFFFFF'
+  },
+  wisdom: {
+    background: 'linear-gradient(135deg, #FFD700 0%, #FFA000 100%)',
+    color: '#5D4037'
+  },
+  'mind-wisdom': {
+    background: 'linear-gradient(135deg, #FF6B35 0%, #FFD700 100%)',
+    color: '#FFFFFF'
+  },
+  action: {
+    background: 'linear-gradient(135deg, #4CAF50 0%, #66BB6A 100%)',
+    color: '#FFFFFF'
+  },
+  wealth: {
+    background: 'linear-gradient(135deg, #42A5F5 0%, #64B5F6 100%)',
+    color: '#FFFFFF'
+  }
+}
+
+export default {
+  props: {
+    /** banner 主题:home | body | mind | wisdom | mind-wisdom | action | wealth */
+    theme: {
+      type: String,
+      default: 'home'
+    },
+    /** 平台一句介绍(显示在名称下方) */
+    tagline: {
+      type: String,
+      default: '家庭健康成长俱乐部'
+    },
+    /** 右侧金句 */
+    quote: {
+      type: String,
+      default: ''
+    }
+  },
+  computed: {
+    bannerStyle: function() {
+      var style = THEME_STYLES[this.theme] || THEME_STYLES.home
+      return {
+        background: style.background,
+        color: style.color
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page-banner {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 24rpx 30rpx;
+  min-height: 100rpx;
+  border-radius: 0;
+  margin: 0;
+}
+
+.banner-left {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  flex-shrink: 0;
+}
+
+.banner-logo {
+  width: 56rpx;
+  height: 56rpx;
+  border-radius: 12rpx;
+  flex-shrink: 0;
+  margin-right: 16rpx;
+}
+
+.banner-texts {
+  display: flex;
+  flex-direction: column;
+}
+
+.banner-name {
+  font-size: 32rpx;
+  font-weight: 700;
+  letter-spacing: 4rpx;
+  line-height: 1.3;
+}
+
+.banner-tagline {
+  font-size: 20rpx;
+  opacity: 0.85;
+  line-height: 1.4;
+  margin-top: 2rpx;
+}
+
+.banner-right {
+  flex: 1;
+  text-align: right;
+  margin-left: 20rpx;
+}
+
+.banner-quote {
+  font-size: 22rpx;
+  opacity: 0.92;
+  line-height: 1.4;
+  display: inline-block;
+  text-align: right;
+  letter-spacing: 1rpx;
+}
+</style>

+ 575 - 0
cfc-frontend/components/RadarChart.vue

@@ -0,0 +1,575 @@
+<template>
+  <view class="radar-chart-wrap">
+    <view
+      class="radar-container"
+      :style="containerStyle"
+    >
+      <canvas
+        id="radarCanvas"
+        type="2d"
+        class="radar-canvas"
+      ></canvas>
+      <!-- 透明点击感应层 -->
+      <view class="radar-tap-layer" @click="onCanvasTap"></view>
+    </view>
+  </view>
+</template>
+
+<script>
+/**
+ * RadarChart - 六维雷达图 Canvas 组件
+ *
+ * 基于 HTML5 Canvas 2D 渲染,支持 DPR 适配、入场动画、点击事件。
+ * 用于「心智」页面展示六维认知能力评分。
+ *
+ * 用法:
+ *   <RadarChart
+ *     :dimensions="dimList"
+ *     :scores="[85, 72, 68, 90, 55, 78]"
+ *     childName="小明"
+ *     @dimensionClick="onDimClick"
+ *   />
+ */
+export default {
+  props: {
+    /** 六维标签 [{label, key}, ...],顺序:感知/专注/记忆/逻辑/空间/加工速度 */
+    dimensions: {
+      type: Array,
+      default: function() {
+        return []
+      }
+    },
+    /** 六维分值 0-100,与 dimensions 顺序一一对应 */
+    scores: {
+      type: Array,
+      default: function() {
+        return []
+      }
+    },
+    /** 图表标题(显示在中心) */
+    childName: {
+      type: String,
+      default: ''
+    },
+    /** 图表宽度(rpx) */
+    width: {
+      type: Number,
+      default: 600
+    },
+    /** 图表高度(rpx) */
+    height: {
+      type: Number,
+      default: 600
+    }
+  },
+  data: function() {
+    return {
+      canvasReady: false,
+      canvasWidth: 0,
+      canvasHeight: 0,
+      ctx: null,
+      dpr: 1,
+      _canvasNode: null,
+      _canvasLeft: 0,
+      _canvasTop: 0,
+      /** 动画插值中的分数 [0..100] */
+      _animScores: [0, 0, 0, 0, 0, 0],
+      _animId: null
+    }
+  },
+  computed: {
+    /** 容器内联样式 */
+    containerStyle: function() {
+      return {
+        width: this.width + 'rpx',
+        height: this.height + 'rpx'
+      }
+    },
+    /** 六边形 6 个顶点的标准角度(弧度),从顶部顺时针 */
+    vertexAngles: function() {
+      var angles = []
+      for (var i = 0; i < 6; i++) {
+        angles.push(-Math.PI / 2 + (i * 2 * Math.PI) / 6)
+      }
+      return angles
+    }
+  },
+  watch: {
+    scores: {
+      handler: function() {
+        if (this.canvasReady) {
+          this._startAnimation()
+        }
+      },
+      deep: true
+    }
+  },
+  mounted: function() {
+    var self = this
+    // 等 DOM 布局完成后初始化 Canvas
+    setTimeout(function() {
+      self.initCanvas()
+    }, 100)
+  },
+  beforeDestroy: function() {
+    if (this._animId) {
+      this._cancelAnim(this._animId)
+      this._animId = null
+    }
+  },
+  methods: {
+    /* ===========================
+     * Canvas 初始化
+     * =========================== */
+
+    initCanvas: function() {
+      var self = this
+      uni.createSelectorQuery().in(this)
+        .select('#radarCanvas')
+        .fields({ node: true, size: true, rect: true })
+        .exec(function(res) {
+          if (!res || !res[0]) { return }
+          var info = res[0]
+          if (!info.node) { return }
+
+          var canvas = info.node
+          var width = info.width
+          var height = info.height
+          if (!width || !height) { return }
+
+          var system = uni.getSystemInfoSync()
+          var pixelRatio = system.pixelRatio || 2
+
+          canvas.width = width * pixelRatio
+          canvas.height = height * pixelRatio
+
+          var ctx = canvas.getContext('2d')
+          ctx.scale(pixelRatio, pixelRatio)
+
+          self.ctx = ctx
+          self._canvasNode = canvas
+          self.canvasWidth = width
+          self.canvasHeight = height
+          self.dpr = pixelRatio
+          self._canvasLeft = info.left || 0
+          self._canvasTop = info.top || 0
+          self.canvasReady = true
+
+          // 有分数则播放动画,否则只画网格
+          if (self.scores && self.scores.length >= 6) {
+            self._startAnimation()
+          } else {
+            self._doRender()
+          }
+        })
+    },
+
+    /* ===========================
+     * 渲染
+     * =========================== */
+
+    _doRender: function() {
+      if (!this.ctx) { return }
+      var ctx = this.ctx
+      var w = this.canvasWidth
+      var h = this.canvasHeight
+      var cx = w / 2
+      var cy = h / 2
+      var outerR = Math.min(w, h) * 0.38
+      var angles = this.vertexAngles
+
+      ctx.clearRect(0, 0, w, h)
+
+      // 1. 绘制背景网格
+      this._drawGrid(ctx, cx, cy, outerR, angles)
+
+      // 2. 绘制分数区域
+      this._drawScoreArea(ctx, cx, cy, outerR, angles)
+
+      // 3. 绘制顶点标记
+      this._drawVertices(ctx, cx, cy, outerR, angles)
+
+      // 4. 绘制分数文字
+      this._drawScoreText(ctx, cx, cy, outerR, angles)
+
+      // 5. 绘制维度标签
+      this._drawLabels(ctx, cx, cy, outerR, angles)
+
+      // 6. 绘制中心标题
+      this._drawCenterText(ctx, cx, cy)
+    },
+
+    /**
+     * 绘制同心六边形网格 + 辐射线
+     */
+    _drawGrid: function(ctx, cx, cy, outerR, angles) {
+      var levels = [0.25, 0.5, 0.75, 1.0]
+
+      // 同心六边形
+      ctx.strokeStyle = '#E2E8F0'
+      ctx.lineWidth = 1
+
+      for (var li = 0; li < levels.length; li++) {
+        var r = outerR * levels[li]
+        ctx.beginPath()
+        for (var ai2 = 0; ai2 < 6; ai2++) {
+          var vx_ = cx + r * Math.cos(angles[ai2])
+          var vy_ = cy + r * Math.sin(angles[ai2])
+          if (ai2 === 0) {
+            ctx.moveTo(vx_, vy_)
+          } else {
+            ctx.lineTo(vx_, vy_)
+          }
+        }
+        ctx.closePath()
+        ctx.stroke()
+      }
+
+      // 辐射线(中心到各顶点)
+      ctx.strokeStyle = '#E2E8F0'
+      ctx.lineWidth = 1
+      ctx.beginPath()
+      for (var ai3 = 0; ai3 < 6; ai3++) {
+        ctx.moveTo(cx, cy)
+        ctx.lineTo(
+          cx + outerR * Math.cos(angles[ai3]),
+          cy + outerR * Math.sin(angles[ai3])
+        )
+      }
+      ctx.stroke()
+    },
+
+    /**
+     * 绘制分数填充多边形
+     */
+    _drawScoreArea: function(ctx, cx, cy, outerR, angles) {
+      var scores = this._animScores
+      if (!scores || scores.length < 6) { return }
+
+      // 构建顶点数据
+      var points = []
+      for (var i = 0; i < 6; i++) {
+        var score = scores[i] || 0
+        var r = outerR * Math.max(0, Math.min(1, score / 100))
+        points.push({
+          x: cx + r * Math.cos(angles[i]),
+          y: cy + r * Math.sin(angles[i])
+        })
+      }
+
+      // 填充多边形
+      ctx.beginPath()
+      for (var pi = 0; pi < points.length; pi++) {
+        if (pi === 0) {
+          ctx.moveTo(points[pi].x, points[pi].y)
+        } else {
+          ctx.lineTo(points[pi].x, points[pi].y)
+        }
+      }
+      ctx.closePath()
+
+      // 径向渐变填充
+      var gradient = ctx.createRadialGradient(cx, cy, 0, cx, cy, outerR)
+      gradient.addColorStop(0, 'rgba(249, 115, 22, 0.12)')
+      gradient.addColorStop(0.5, 'rgba(249, 115, 22, 0.20)')
+      gradient.addColorStop(1, 'rgba(249, 115, 22, 0.32)')
+      ctx.fillStyle = gradient
+      ctx.fill()
+
+      // 描边
+      ctx.strokeStyle = '#F97316'
+      ctx.lineWidth = 2.5
+      ctx.stroke()
+    },
+
+    /**
+     * 在各顶点绘制小圆点
+     */
+    _drawVertices: function(ctx, cx, cy, outerR, angles) {
+      var scores = this._animScores
+      if (!scores || scores.length < 6) { return }
+
+      ctx.fillStyle = '#F97316'
+      ctx.shadowColor = 'rgba(249, 115, 22, 0.3)'
+      ctx.shadowBlur = 6
+
+      for (var i = 0; i < 6; i++) {
+        var score = scores[i] || 0
+        var r = outerR * Math.max(0, Math.min(1, score / 100))
+        var vx = cx + r * Math.cos(angles[i])
+        var vy = cy + r * Math.sin(angles[i])
+        ctx.beginPath()
+        ctx.arc(vx, vy, 4.5, 0, 2 * Math.PI)
+        ctx.fill()
+      }
+
+      ctx.shadowColor = 'transparent'
+      ctx.shadowBlur = 0
+    },
+
+    /**
+     * 在各顶点附近显示分值(百分比)
+     */
+    _drawScoreText: function(ctx, cx, cy, outerR, angles) {
+      var scores = this._animScores
+      if (!scores || scores.length < 6) { return }
+
+      ctx.font = '11px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif'
+      ctx.fillStyle = '#F97316'
+      ctx.textAlign = 'center'
+      ctx.textBaseline = 'middle'
+
+      for (var i = 0; i < 6; i++) {
+        var score = scores[i] || 0
+        // 分数文字放在顶点向外偏移 16px 处
+        var vertexR = outerR * Math.max(0, Math.min(1, score / 100))
+        var textR = vertexR + 16
+        var tx = cx + textR * Math.cos(angles[i])
+        var ty = cy + textR * Math.sin(angles[i])
+
+        var intScore = Math.round(score)
+        ctx.fillText(intScore + '%', tx, ty)
+      }
+    },
+
+    /**
+     * 绘制维度名称标签(在六边形外围)
+     */
+    _drawLabels: function(ctx, cx, cy, outerR, angles) {
+      var dims = this.dimensions
+      if (!dims || dims.length < 6) { return }
+
+      var labelOffset = 36
+
+      for (var i = 0; i < 6; i++) {
+        var dim = dims[i]
+        if (!dim) { continue }
+
+        var angle = angles[i]
+        var labelR = outerR + labelOffset
+        var lx = cx + labelR * Math.cos(angle)
+        var ly = cy + labelR * Math.sin(angle)
+
+        // 根据角度确定文字对齐方式
+        var align = 'center'
+        var baseline = 'middle'
+
+        // 将角度归一化到 0 ~ 2PI
+        var a = ((angle % (2 * Math.PI)) + 2 * Math.PI) % (2 * Math.PI)
+
+        if (a < (1 * Math.PI) / 6 || a >= (11 * Math.PI) / 6) {
+          // 右侧(-30°~30°):上右 / 下右
+          if (a < Math.PI / 2) {
+            // 下右
+            align = 'left'
+            baseline = 'top'
+          } else {
+            // 上右
+            align = 'left'
+            baseline = 'bottom'
+          }
+        } else if (a < (5 * Math.PI) / 6) {
+          // 底部(30°~150°)
+          align = 'center'
+          baseline = 'top'
+        } else if (a < (7 * Math.PI) / 6) {
+          // 左侧(150°~210°)
+          if (a < Math.PI) {
+            // 下左
+            align = 'right'
+            baseline = 'top'
+          } else {
+            // 上左
+            align = 'right'
+            baseline = 'bottom'
+          }
+        } else {
+          // 顶部(210°~330°)
+          align = 'center'
+          baseline = 'bottom'
+        }
+
+        ctx.font = 'bold 13px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif'
+        ctx.fillStyle = '#1E293B'
+        ctx.textAlign = align
+        ctx.textBaseline = baseline
+        ctx.fillText(dim.label || '', lx, ly)
+      }
+    },
+
+    /**
+     * 在图表中心绘制孩子名字
+     */
+    _drawCenterText: function(ctx, cx, cy) {
+      var name = this.childName
+      if (!name) { return }
+
+      ctx.font = 'bold 15px -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif'
+      ctx.fillStyle = '#F97316'
+      ctx.textAlign = 'center'
+      ctx.textBaseline = 'middle'
+      ctx.fillText(name, cx, cy)
+    },
+
+    /* ===========================
+     * 动画控制
+     * =========================== */
+
+    _startAnimation: function() {
+      var self = this
+      var target = this.scores.slice(0, 6)
+      var start = [0, 0, 0, 0, 0, 0]
+      var startTime = Date.now()
+      var duration = 600
+
+      if (this._animId) {
+        this._cancelAnim(this._animId)
+        this._animId = null
+      }
+
+      function tick() {
+        var elapsed = Date.now() - startTime
+        var progress = Math.min(1, elapsed / duration)
+        // ease-out cubic
+        var eased = 1 - Math.pow(1 - progress, 3)
+
+        for (var i = 0; i < 6; i++) {
+          self._animScores[i] = start[i] + (target[i] - start[i]) * eased
+        }
+
+        self._doRender()
+
+        if (progress < 1) {
+          self._animId = self._requestAnim(tick)
+        } else {
+          // 确保最终为精确值
+          for (var j = 0; j < 6; j++) {
+            self._animScores[j] = target[j]
+          }
+          self._doRender()
+          self._animId = null
+        }
+      }
+
+      self._animId = self._requestAnim(tick)
+    },
+
+    /**
+     * 跨环境 requestAnimationFrame
+     */
+    _requestAnim: function(callback) {
+      if (this._canvasNode && typeof this._canvasNode.requestAnimationFrame === 'function') {
+        return this._canvasNode.requestAnimationFrame(callback)
+      }
+      // fallback
+      return setTimeout(callback, 16)
+    },
+
+    /**
+     * 跨环境 cancelAnimationFrame
+     */
+    _cancelAnim: function(id) {
+      if (this._canvasNode && typeof this._canvasNode.cancelAnimationFrame === 'function') {
+        this._canvasNode.cancelAnimationFrame(id)
+      } else {
+        clearTimeout(id)
+      }
+    },
+
+    /* ===========================
+     * 点击检测
+     * =========================== */
+
+    onCanvasTap: function(e) {
+      if (!this.canvasReady) { return }
+
+      var pageX
+      var pageY
+
+      // 从事件中获取页面坐标
+      if (e.detail && typeof e.detail.x === 'number') {
+        pageX = e.detail.x
+        pageY = e.detail.y
+      } else if (e.touches && e.touches[0]) {
+        pageX = e.touches[0].x || e.touches[0].clientX || 0
+        pageY = e.touches[0].y || e.touches[0].clientY || 0
+      } else if (e.changedTouches && e.changedTouches[0]) {
+        pageX = e.changedTouches[0].x || e.changedTouches[0].clientX || 0
+        pageY = e.changedTouches[0].y || e.changedTouches[0].clientY || 0
+      } else {
+        return
+      }
+
+      // 转为 canvas 相对坐标
+      var relX = pageX - (this._canvasLeft || 0)
+      var relY = pageY - (this._canvasTop || 0)
+      if (relX < 0 || relY < 0 || relX > this.canvasWidth || relY > this.canvasHeight) {
+        return
+      }
+
+      var cx = this.canvasWidth / 2
+      var cy = this.canvasHeight / 2
+      var outerR = Math.min(this.canvasWidth, this.canvasHeight) * 0.38
+      var angles = this.vertexAngles
+      var labelOffset = 36
+      var hitRadius = 44
+
+      // 检测点击是否落在某个维度标签附近
+      for (var i = 0; i < 6; i++) {
+        var dim = this.dimensions[i]
+        if (!dim) { continue }
+
+        var angle = angles[i]
+        var labelR = outerR + labelOffset
+        var lx = cx + labelR * Math.cos(angle)
+        var ly = cy + labelR * Math.sin(angle)
+
+        var dx = relX - lx
+        var dy = relY - ly
+        var dist = Math.sqrt(dx * dx + dy * dy)
+
+        if (dist < hitRadius) {
+          this.$emit('dimensionClick', dim.key || '')
+          return
+        }
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.radar-chart-wrap {
+  margin: 20rpx 30rpx;
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
+  overflow: hidden;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 16rpx 0;
+}
+
+.radar-container {
+  position: relative;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+
+.radar-canvas {
+  display: block;
+  width: 100%;
+  height: 100%;
+}
+
+.radar-tap-layer {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  z-index: 2;
+}
+</style>

+ 57 - 0
cfc-frontend/components/TaskItem.vue

@@ -0,0 +1,57 @@
+<template>
+  <view class="task-item" @click="handleClick">
+    <view class="task-item-left">
+      <text class="task-item-name">{{ task.title }}</text>
+      <text class="task-item-deadline" v-if="task.deadline">⏰ {{ task.deadline }}</text>
+    </view>
+    <view class="task-item-right">
+      <text class="task-item-points" v-if="task.points">+{{ task.points }}⭐</text>
+      <text class="task-item-action" @click.stop="handleAction">去完成</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  props: {
+    task: { type: Object, required: true }
+  },
+  methods: {
+    handleClick() {
+      this.$emit('click', this.task)
+    },
+    handleAction() {
+      this.$emit('action', this.task)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.task-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.task-item:last-child { border-bottom: none; }
+.task-item-left { flex: 1; min-width: 0; }
+.task-item-name { font-size: 26rpx; color: #333; display: block; }
+.task-item-deadline { font-size: 22rpx; color: #999; margin-top: 4rpx; display: block; }
+.task-item-right {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+  margin-left: 16rpx;
+  flex-shrink: 0;
+}
+.task-item-points { font-size: 24rpx; font-weight: 600; color: #F97316; }
+.task-item-action {
+  font-size: 24rpx;
+  color: #5B9BD5;
+  padding: 8rpx 16rpx;
+  border: 2rpx solid #5B9BD5;
+  border-radius: 20rpx;
+}
+</style>

+ 0 - 89
cfc-frontend/components/bottom-nav.vue

@@ -1,89 +0,0 @@
-<template>
-  <view class="bottom-nav">
-    <view
-      class="nav-item"
-      :class="{ active: current === 'index' }"
-      @click="goNav('index')"
-    >
-      <text class="nav-icon">🏠</text>
-      <text class="nav-text">首页</text>
-    </view>
-    <view
-      class="nav-item"
-      :class="{ active: current === 'action' }"
-      @click="goNav('action')"
-    >
-      <text class="nav-icon">🎯</text>
-      <text class="nav-text">行动</text>
-    </view>
-    <view
-      class="nav-item"
-      :class="{ active: current === 'mind' }"
-      @click="goNav('mind')"
-    >
-      <text class="nav-icon">🧠</text>
-      <text class="nav-text">心智</text>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  props: {
-    current: { type: String, default: 'index' }
-  },
-  methods: {
-    goNav(tab) {
-      if (tab === this.current) return
-
-      if (tab === 'index') {
-        // 首页是 TabBar 页面
-        if (uni.getStorageSync('token')) {
-          uni.switchTab({ url: '/pages/index/index' })
-        } else {
-          uni.navigateTo({ url: '/pages/index/index' })
-        }
-      } else if (tab === 'action') {
-        // 行动是 TabBar 页面
-        if (uni.getStorageSync('token')) {
-          uni.switchTab({ url: '/pages/action/index' })
-        } else {
-          uni.navigateTo({ url: '/pages/action/index' })
-        }
-      } else if (tab === 'mind') {
-        // 心智文章页(非 TabBar)
-        uni.navigateTo({ url: '/pages/mind/articles' })
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.bottom-nav {
-  position: fixed;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  height: 100rpx;
-  background: #fff;
-  border-top: 1rpx solid #eee;
-  display: flex;
-  align-items: center;
-  justify-content: space-around;
-  padding-bottom: env(safe-area-inset-bottom);
-  z-index: 100;
-}
-.nav-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  flex: 1;
-  height: 100%;
-}
-.nav-item.active .nav-text { color: #FF6B6B; }
-.nav-icon { font-size: 40rpx; }
-.nav-text { font-size: 20rpx; color: #999; margin-top: 4rpx; }
-.nav-item.active .nav-text { font-weight: bold; }
-</style>

+ 46 - 42
cfc-frontend/components/wuxing-sandbox-helpers/render.js

@@ -31,13 +31,13 @@ export function calcStarVertices(cx, cy, outerR) {
   return { outer: outer, inner: inner }
 }
 
-/** 五色配置(与 CSS 一致) */
+/** 五色配置(与 CSS 一致,ts 顺序 = [body, mind, wisdom, action, wealth]) */
 var COLORS = [
-  { r: 76, g: 175, b: 80 },   // 木·身
-  { r: 255, g: 107, b: 53 },  // 火·心
-  { r: 141, g: 110, b: 99 },  // 土·智
-  { r: 255, g: 215, b: 0 },   // 金·行
-  { r: 66, g: 165, b: 245 }   // 水·富
+  { r: 141, g: 110, b: 99 },  // 土·身 (body)
+  { r: 255, g: 107, b: 53 },  // 火·心 (mind)
+  { r: 255, g: 215, b: 0 },   // 金·智 (wisdom)
+  { r: 76, g: 175, b: 80 },   // 木·行 (action)
+  { r: 66, g: 165, b: 245 }   // 水·富 (wealth)
 ]
 
 /**
@@ -48,12 +48,16 @@ var COLORS = [
  * @param {Array<number>} ts - 5 维归一化值 (0~1),顺序 [body,mind,wisdom,action,wealth]
  * @param {Object} [options]
  * @param {boolean} [options.showTaiji=true] - 是否绘制太极
+ * @param {number} [options.familyEnergy] - 家庭能量值(显示在阳面)
+ * @param {number} [options.personalEnergy] - 个人能量值(显示在阴面)
  */
 export function renderStar(ctx, w, h, ts, options) {
   if (!ctx || !w || !h) return
 
   var opts = options || {}
   var showTaiji = opts.showTaiji !== false
+  var familyEnergy = opts.familyEnergy
+  var personalEnergy = opts.personalEnergy
 
   // 清空
   ctx.clearRect(0, 0, w, h)
@@ -61,7 +65,7 @@ export function renderStar(ctx, w, h, ts, options) {
   // 星型几何
   var cx = w / 2
   var cy = h / 2
-  var outerR = Math.min(w, h) * 0.42
+  var outerR = Math.min(w, h) * 0.46
   var vertices = calcStarVertices(cx, cy, outerR)
   var outer = vertices.outer
   var inner = vertices.inner
@@ -156,89 +160,89 @@ export function renderStar(ctx, w, h, ts, options) {
 
   // --- 中心太极阴阳鱼(在 Canvas 最上层绘制) ---
   if (showTaiji) {
-    var taijiR = outerR * 0.22
-    drawTaiji(ctx, cx, cy, taijiR)
+    var taijiR = outerR * 0.30
+    drawTaiji(ctx, cx, cy, taijiR, familyEnergy, personalEnergy)
   }
 }
 
 /**
  * 绘制太极阴阳鱼
+ * 阳(亮面·白)= 家庭能量,阴(暗面·黑)= 个人能量
+ * 数值显示在两个鱼眼上
  * @param {CanvasRenderingContext2D} ctx
  * @param {number} cx - 中心 x
  * @param {number} cy - 中心 y
  * @param {number} r - 半径
+ * @param {number} [familyEnergy] - 家庭能量值
+ * @param {number} [personalEnergy] - 个人能量值
  */
-export function drawTaiji(ctx, cx, cy, r) {
-  var yangColor = 'rgba(91, 155, 213, 0.35)'
-  var yinColor = 'rgba(26, 42, 74, 0.75)'
-  var strokeColor = 'rgba(143, 197, 232, 0.2)'
-  var yangEyeColor = 'rgba(26, 42, 74, 0.85)'
-  var yinEyeColor = 'rgba(143, 197, 232, 0.75)'
-
+export function drawTaiji(ctx, cx, cy, r, familyEnergy, personalEnergy) {
   ctx.save()
 
-  // 1. 外圆背景及边框
+  // 1. 外圆背景(白色底)
   ctx.beginPath()
   ctx.arc(cx, cy, r, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = yangColor
+  ctx.fillStyle = '#fff'
   ctx.fill()
-  ctx.strokeStyle = strokeColor
+  ctx.strokeStyle = 'rgba(0,0,0,0.15)'
   ctx.lineWidth = 1
   ctx.stroke()
 
-  // 2. 左半边:阴(深色)
+  // 2. 左半边(阴,黑色)
   ctx.beginPath()
   ctx.moveTo(cx, cy)
   ctx.arc(cx, cy, r, Math.PI / 2, -Math.PI / 2)
   ctx.closePath()
-  ctx.fillStyle = yinColor
+  ctx.fillStyle = '#000'
   ctx.fill()
 
-  // 3. 上半小圆左半弧:阳色
+  // 3. 上半小圆左半弧(白色,在黑色半圆中)
   ctx.beginPath()
   ctx.moveTo(cx, cy - r)
   ctx.arc(cx, cy - r / 2, r / 2, Math.PI / 2, -Math.PI / 2)
   ctx.closePath()
-  ctx.fillStyle = yangColor
+  ctx.fillStyle = '#fff'
   ctx.fill()
 
-  // 4. 下半小圆右半弧:阴色
+  // 4. 下半小圆右半弧(黑色,在白色半圆中)
   ctx.beginPath()
   ctx.moveTo(cx, cy + r)
   ctx.arc(cx, cy + r / 2, r / 2, -Math.PI / 2, Math.PI / 2)
   ctx.closePath()
-  ctx.fillStyle = yinColor
+  ctx.fillStyle = '#000'
   ctx.fill()
 
-  // 5. 阳眼(深色点,上方)
+  // 鱼眼半径(够大,容纳数值文字)
+  var eyeR = r * 0.16
+
+  // 5. 阳鱼眼(白色区域中的黑点 → 写白色文字)
   ctx.beginPath()
-  ctx.arc(cx, cy - r / 2, r * 0.12, 0, Math.PI * 2)
+  ctx.arc(cx, cy + r / 2, eyeR, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = yangEyeColor
+  ctx.fillStyle = '#000'
   ctx.fill()
 
-  // 6. 阴眼(浅色点,下方
+  // 6. 阴鱼眼(黑色区域中的白点 → 写黑色文字
   ctx.beginPath()
-  ctx.arc(cx, cy + r / 2, r * 0.12, 0, Math.PI * 2)
+  ctx.arc(cx, cy - r / 2, eyeR, 0, Math.PI * 2)
   ctx.closePath()
-  ctx.fillStyle = yinEyeColor
+  ctx.fillStyle = '#fff'
   ctx.fill()
 
-  ctx.restore()
-
-  // 7. 文字标签
-  ctx.save()
-  var fontSize = Math.round(r * 0.3)
-  ctx.font = 'bold ' + fontSize + 'px sans-serif'
+  // 7. 阳眼文字(黑底上写白色)— 家庭能量
+  var familyVal = (familyEnergy != null && !isNaN(familyEnergy)) ? Math.round(familyEnergy) : '--'
+  var eyeFontSize = Math.round(eyeR * 1.2)
+  ctx.font = 'bold ' + eyeFontSize + 'px sans-serif'
   ctx.textAlign = 'center'
   ctx.textBaseline = 'middle'
+  ctx.fillStyle = '#fff'
+  ctx.fillText(familyVal + '', cx, cy + r / 2)
 
-  ctx.fillStyle = 'rgba(143, 197, 232, 0.6)'
-  ctx.fillText('家', cx - r * 0.3, cy + r * 0.15)
-
-  ctx.fillStyle = 'rgba(91, 155, 213, 0.8)'
-  ctx.fillText('个', cx + r * 0.3, cy - r * 0.15)
+  // 8. 阴眼文字(白底上写黑色)— 个人能量
+  var personalVal = (personalEnergy != null && !isNaN(personalEnergy)) ? Math.round(personalEnergy) : '--'
+  ctx.fillStyle = '#000'
+  ctx.fillText(personalVal + '', cx, cy - r / 2)
 
   ctx.restore()
 }

+ 117 - 43
cfc-frontend/components/wuxing-sandbox.vue

@@ -3,7 +3,8 @@
   <!-- header slot -->
   <slot name="header">
     <view class="sandbox-header">
-      <text class="sandbox-title">🌟 家庭能量沙盘</text>
+      <image class="sandbox-logo" src="/static/logo.png" mode="aspectFit"></image>
+      <text class="sandbox-title">浠艾福能量沙盘</text>
       <text class="sandbox-badge">{{ displayBadgeText }}</text>
     </view>
   </slot>
@@ -17,37 +18,37 @@
     <!-- Canvas 点击感应层 -->
     <view class="canvas-tap-area" @click="onCanvasTap"></view>
 
-    <!-- 顶点:从 sortedDimensions 数据驱动 (心/行/富/智/身) -->
-    <view class="star-point star-point--top" @click="onPointClick(sortedDimensions[0].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[0].element)">
+    <!-- 顶点:从 sortedDimensions 顺时针排 [顶, 右, 右下, 左下, 左] -->
+    <view class="star-point star-point--top star-point--row" @click="onPointClick(sortedDimensions[0].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[0].code)">
         <text class="point-icon">{{ sortedDimensions[0].icon }}</text>
       </view>
       <text class="point-label">{{ sortedDimensions[0].name }}</text>
       <text class="point-energy">{{ sortedDimensions[0].energy }}</text>
     </view>
-    <view class="star-point star-point--left" @click="onPointClick(sortedDimensions[1].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[1].element)">
+    <view class="star-point star-point--right" @click="onPointClick(sortedDimensions[1].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[1].code)">
         <text class="point-icon">{{ sortedDimensions[1].icon }}</text>
       </view>
       <text class="point-label">{{ sortedDimensions[1].name }}</text>
       <text class="point-energy">{{ sortedDimensions[1].energy }}</text>
     </view>
-    <view class="star-point star-point--right" @click="onPointClick(sortedDimensions[2].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[2].element)">
+    <view class="star-point star-point--lr star-point--row" @click="onPointClick(sortedDimensions[2].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[2].code)">
         <text class="point-icon">{{ sortedDimensions[2].icon }}</text>
       </view>
       <text class="point-label">{{ sortedDimensions[2].name }}</text>
       <text class="point-energy">{{ sortedDimensions[2].energy }}</text>
     </view>
-    <view class="star-point star-point--ll" @click="onPointClick(sortedDimensions[3].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[3].element)">
+    <view class="star-point star-point--ll star-point--rev" @click="onPointClick(sortedDimensions[3].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[3].code)">
         <text class="point-icon">{{ sortedDimensions[3].icon }}</text>
       </view>
       <text class="point-label">{{ sortedDimensions[3].name }}</text>
       <text class="point-energy">{{ sortedDimensions[3].energy }}</text>
     </view>
-    <view class="star-point star-point--lr" @click="onPointClick(sortedDimensions[4].code)">
-      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[4].element)">
+    <view class="star-point star-point--left" @click="onPointClick(sortedDimensions[4].code)">
+      <view class="point-dot" :class="'point-dot--' + dimElementColor(sortedDimensions[4].code)">
         <text class="point-icon">{{ sortedDimensions[4].icon }}</text>
       </view>
       <text class="point-label">{{ sortedDimensions[4].name }}</text>
@@ -55,18 +56,16 @@
     </view>
 
     <!-- 中心点击区域(太极由 Canvas 绘制) -->
-    <view class="star-center" @click="onCenterClick">
-      <text class="energy-hint">{{ viewMode === 'family' ? '家庭能量' : (currentMember ? currentMember.name : '个人') }}</text>
-    </view>
+    <view class="star-center" @click="onCenterClick"></view>
   </view>
 
   <!-- dimension-bar slot -->
   <slot name="dimension-bar" v-if="showDimensionBar && mode !== 'preview'">
     <view class="dimension-bar">
-      <view class="dim-item dim-item--wood">🌿身 {{ activeScores[0] }}%</view>
+      <view class="dim-item dim-item--earth">🌏身 {{ activeScores[0] }}%</view>
       <view class="dim-item dim-item--fire">🔥心 {{ activeScores[1] }}%</view>
-      <view class="dim-item dim-item--earth">🌏智 {{ activeScores[2] }}%</view>
-      <view class="dim-item dim-item--metal">⚡行 {{ activeScores[3] }}%</view>
+      <view class="dim-item dim-item--metal">⚡智 {{ activeScores[2] }}%</view>
+      <view class="dim-item dim-item--wood">🌿行 {{ activeScores[3] }}%</view>
       <view class="dim-item dim-item--water">💧富 {{ activeScores[4] }}%</view>
     </view>
   </slot>
@@ -80,6 +79,15 @@ import { detectHitArm, armIndexToDomain } from './wuxing-sandbox-helpers/hitdete
 
 var DIM_KEYS = ['body', 'mind', 'wisdom', 'action', 'wealth']
 
+/** 维度 → 五行映射(API 缺字段时的兜底) */
+var DIM_META = {
+  mind:   { name: '心', icon: '🔥', element: '火', color: 'fire' },
+  action: { name: '行', icon: '🌿', element: '木', color: 'wood' },
+  wealth: { name: '富', icon: '💧', element: '水', color: 'water' },
+  wisdom: { name: '智', icon: '⚡', element: '金', color: 'metal' },
+  body:   { name: '身', icon: '🌏', element: '土', color: 'earth' }
+}
+
 export default {
   props: {
     /** 模式:'preview' 随机数据 / 'live' 真实数据 */
@@ -129,7 +137,7 @@ export default {
     /** 星图区域高度 */
     starHeight: {
       type: String,
-      default: '480rpx'
+      default: '540rpx'
     }
   },
   data: function() {
@@ -215,20 +223,71 @@ export default {
     hasMultipleMembers: function() {
       return this.members && this.members.length > 1
     },
-    /** 按五行布局排序的维度数据(心·火(顶)/行·木(左)/富·水(右)/智·金(左下)/身·土(右下)) */
+    /** 家庭能量值(用于太极阳面) */
+    familyEnergy: function() {
+      if (this.isPreview) {
+        var s = this.previewScores
+        return Math.round((s[0] + s[1] + s[2] + s[3] + s[4]) / 5)
+      }
+      return this.viewMode === 'family' ? (this.overallScore || 0) : (this.overallScore || 0)
+    },
+    /** 个人能量值(用于太极阴面) */
+    personalEnergy: function() {
+      if (this.isPreview) return 0
+      if (this.viewMode === 'member') {
+        var m = this.currentMember
+        return m ? (m.overallScore || 0) : 0
+      }
+      // 家庭模式下取当前选中成员
+      var m = this.currentMember
+      return m ? (m.overallScore || 0) : 0
+    },
+    /** 按五行顺序绕圈排列
+     *  五行固定序:身(土)→智(金)→富(水)→行(木)→心(火)
+     *  最小值放最上面,其余按五行序顺时针依次排:左边→右边→左下→右下
+     *  预览/无数据时以身为起点
+     */
     sortedDimensions: function() {
-      var order = ['mind', 'action', 'wealth', 'wisdom', 'body']
+      var order = ['body', 'wisdom', 'wealth', 'action', 'mind']
       var dimMap = {}
       for (var i = 0; i < this.dimensions.length; i++) {
         var d = this.dimensions[i]
         if (d && d.code) dimMap[d.code] = d
       }
-      var result = []
+      // 构建完整数据(含兜底)
+      var all = []
       for (var j = 0; j < order.length; j++) {
         var code = order[j]
-        result.push(dimMap[code] || { code: code, name: '', icon: '', element: '', energy: 0, healthIndex: 0 })
+        var d = dimMap[code]
+        var meta = DIM_META[code] || {}
+        if (d) {
+          if (!d.name) d.name = meta.name || ''
+          if (!d.icon) d.icon = meta.icon || ''
+          if (!d.element) d.element = meta.element || ''
+          all.push(d)
+        } else {
+          all.push({ code: code, name: meta.name || '', icon: meta.icon || '', element: meta.element || '', energy: 0, healthIndex: 0 })
+        }
+      }
+      // all 此时为 [身, 智, 富, 行, 心](五行固定序)
+      // 找最小值所在的索引
+      var startIdx = 0  // 默认以身为起点
+      if (this.mode === 'live' && all.some(function(d) { return d.energy > 0 })) {
+        var minEnergy = all[0].energy
+        for (var i = 1; i < all.length; i++) {
+          if (all[i].energy < minEnergy) {
+            minEnergy = all[i].energy
+            startIdx = i
+          }
+        }
+      }
+      // 从最小值开始,按五行序顺时针绕圈填入 [顶, 右, 右下, 左下, 左]
+      var result = []
+      for (var k = 0; k < 5; k++) {
+        result.push(all[(startIdx + k) % 5])
       }
       return result
+      // result = [最小值→顶, 下一位→右, 再→右下, 再→左下, 再→左]
     }
   },
   watch: {
@@ -306,10 +365,10 @@ export default {
       })
     },
 
-    /** 五行元素 → CSS 颜色类名 */
-    dimElementColor: function(element) {
-      var map = { '火': 'fire', '水': 'water', '土': 'earth', '金': 'metal', '木': 'wood' }
-      return map[element] || 'fire'
+    /** 维度 code → CSS 颜色类名(不依赖 API 的 element 字段) */
+    dimElementColor: function(code) {
+      var meta = DIM_META[code]
+      return meta ? meta.color : 'fire'
     },
 
     /* ===========================
@@ -351,7 +410,10 @@ export default {
     _doRender: function() {
       if (!this.ctx) return
       var ts = [this.animBody, this.animMind, this.animWisdom, this.animAction, this.animWealth]
-      renderStar(this.ctx, this.canvasWidth, this.canvasHeight, ts)
+      renderStar(this.ctx, this.canvasWidth, this.canvasHeight, ts, {
+        familyEnergy: this.familyEnergy,
+        personalEnergy: this.personalEnergy
+      })
     },
 
     /* ===========================
@@ -409,7 +471,7 @@ export default {
 
       var cx = this.canvasWidth / 2
       var cy = this.canvasHeight / 2
-      var outerR = Math.min(this.canvasWidth, this.canvasHeight) * 0.42
+      var outerR = Math.min(this.canvasWidth, this.canvasHeight) * 0.46
       var armIdx = detectHitArm(relX, relY, cx, cy, outerR, calcStarVertices)
       if (armIdx >= 0) {
         this.onPointClick(armIndexToDomain(armIdx))
@@ -446,19 +508,21 @@ export default {
 /* ---- 标题 ---- */
 .sandbox-header {
   display: flex;
-  justify-content: space-between;
+  justify-content: flex-start;
   align-items: center;
   margin-bottom: 12rpx;
   position: relative;
   z-index: 5;
 }
-.sandbox-title { font-size: 26rpx; font-weight: bold; color: #8FC5E8; }
+.sandbox-title { font-size: 26rpx; font-weight: bold; color: #8FC5E8; margin-left: 8rpx; }
+.sandbox-logo { width: 32rpx; height: 32rpx; flex-shrink: 0; }
 .sandbox-badge {
   font-size: 20rpx;
   color: rgba(143,197,232,0.7);
   background: rgba(143,197,232,0.1);
   padding: 4rpx 14rpx;
   border-radius: 20rpx;
+  margin-left: auto;
 }
 
 /* ---- 五角星区域 ---- */
@@ -472,7 +536,7 @@ export default {
 
 /* 背景光晕 */
 .star-glow-bg {
-  position: absolute; width: 440rpx; height: 440rpx;
+  position: absolute; width: 500rpx; height: 500rpx;
   top: 50%; left: 50%; transform: translate(-50%, -50%);
   background: radial-gradient(circle, rgba(91,155,213,0.10), transparent 70%);
   border-radius: 50%; pointer-events: none;
@@ -492,11 +556,20 @@ export default {
   position: absolute; transform: translate(-50%, -50%); z-index: 3; cursor: pointer;
   display: flex; flex-direction: column; align-items: center;
 }
-.star-point--top { top: 3%; left: 50%; }
+/* 横排 */
+.star-point--row {
+  flex-direction: row;
+  gap: 6rpx;
+}
+.star-point--rev {
+  flex-direction: row-reverse;
+  gap: 6rpx;
+}
+.star-point--top { top: 1%; left: 50%; }
 .star-point--left { top: 38%; left: 5%; }
 .star-point--right { top: 38%; left: 95%; }
-.star-point--lr { top: 73%; left: 75%; }
-.star-point--ll { top: 73%; left: 25%; }
+.star-point--lr { top: 89%; left: 85%; }
+.star-point--ll { top: 89%; left: 15%; }
 
 .point-dot {
   width: 52rpx; height: 52rpx; border-radius: 50%;
@@ -516,12 +589,20 @@ export default {
   margin-top: 6rpx; text-shadow: 0 1rpx 4rpx rgba(0,0,0,0.5);
   letter-spacing: 2rpx;
 }
+.star-point--row .point-label,
+.star-point--rev .point-label {
+  margin-top: 0;
+}
 .point-energy {
   font-size: 18rpx; color: rgba(143,197,232,0.8);
   background: rgba(143,197,232,0.12);
   padding: 1rpx 10rpx; border-radius: 12rpx;
   margin-top: 2rpx;
 }
+.star-point--row .point-energy,
+.star-point--rev .point-energy {
+  margin-top: 0;
+}
 
 /* ============================================================
 中心点击区域
@@ -529,17 +610,10 @@ export default {
 .star-center {
   position: absolute; top: 50%; left: 50%;
   transform: translate(-50%, -50%); z-index: 4;
-  width: 130rpx; height: 130rpx; border-radius: 50%;
+  width: 180rpx; height: 180rpx; border-radius: 50%;
   cursor: pointer;
 }
 
-.energy-hint {
-  position: absolute; bottom: -30rpx; left: 50%;
-  transform: translateX(-50%);
-  font-size: 18rpx; color: rgba(143,197,232,0.4);
-  white-space: nowrap;
-}
-
 /* ============================================================
 维度详情条
 ============================================================ */

+ 5 - 5
cfc-frontend/config.js

@@ -11,7 +11,7 @@
  */
 
 // 默认值(兜底)
-let API_BASE_URL = 'http://localhost:9080'
+let API_BASE_URL = 'http://localhost:9082'
 let DANSHOP_BASE_URL = 'http://localhost:8081'
 
 try {
@@ -22,18 +22,18 @@ try {
     switch (env) {
       case 'develop':
         // 开发版 - 本地开发
-        API_BASE_URL = 'http://localhost:9080'
+        API_BASE_URL = 'http://localhost:9082'
         DANSHOP_BASE_URL = 'http://localhost:8081'
         break
       case 'trial':
         // 体验版
-        API_BASE_URL = 'https://dev.iwintrue.com/num'
+        API_BASE_URL = 'https://cfc.iwintrue.com'
         DANSHOP_BASE_URL = 'https://dev.iwintrue.com/danshop'
         break
       case 'release':
         // 正式版
-        API_BASE_URL = 'https://num.etotem.com'
-        DANSHOP_BASE_URL = 'https://danshop.etotem.com'
+        API_BASE_URL = 'https://cfc.etotem.com.cn'
+        DANSHOP_BASE_URL = 'https://danshop.etotem.com.cn'
         break
     }
   }

+ 1 - 1
cfc-frontend/index.html

@@ -3,7 +3,7 @@
 <head>
     <meta charset="UTF-8">
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0">
-    <title>知行益家</title>
+    <title>浠艾福</title>
     <!--preload-links-->
     <!--app-context-->
     <link rel="stylesheet" href="/static/index.css" />

+ 1 - 1
cfc-frontend/package.json

@@ -1,7 +1,7 @@
 {
   "name": "cfc-frontend",
   "version": "1.0.0",
-  "description": "知行益家小程序前端",
+  "description": "浠艾福小程序前端",
   "main": "main.js",
   "scripts": {
     "postbuild": "node -e \"try{require('fs').unlinkSync('dist/dev/mp-weixin/custom-tab-bar/index.vue')}catch(e){};try{const c=require('./dist/dev/mp-weixin/project.config.json');c.setting=c.setting||{};c.setting.lazyCodeLoading='requiredComponents';require('fs').writeFileSync('./dist/dev/mp-weixin/project.config.json',JSON.stringify(c,null,2))}catch(e){}\""

+ 44 - 2
cfc-frontend/pages.json

@@ -63,6 +63,12 @@
         "navigationBarTitleText": "文章详情"
       }
     },
+    {
+      "path": "pages/mind/training",
+      "style": {
+        "navigationBarTitleText": "能力训练"
+      }
+    },
     {
       "path": "pages/action/index",
       "style": {
@@ -403,7 +409,7 @@
       "pages": [
         {
           "path": "index",
-          "style": { "navigationBarTitleText": "推广中心" }
+          "style": { "navigationBarTitleText": "财富中心" }
         },
         {
           "path": "commission",
@@ -429,6 +435,16 @@
         }
       ]
     },
+    // ===== 智慧维度 =====
+    {
+      "root": "pages/wisdom",
+      "pages": [
+        {
+          "path": "index",
+          "style": { "navigationBarTitleText": "智慧" }
+        }
+      ]
+    },
     // ===== 会员服务 =====
     {
       "root": "pages/membership",
@@ -436,13 +452,39 @@
         {
           "path": "index",
           "style": { "navigationBarTitleText": "会员服务" }
+        },
+        {
+          "path": "upgrade",
+          "style": { "navigationBarTitleText": "开通会员" }
+        }
+      ]
+    },
+    // ===== 财富维度 =====
+    {
+      "root": "pages/wealth",
+      "pages": [
+        {
+          "path": "index",
+          "style": { "navigationBarTitleText": "财富" }
+        },
+        {
+          "path": "checkin",
+          "style": { "navigationBarTitleText": "财商打卡" }
+        },
+        {
+          "path": "insurance-list",
+          "style": { "navigationBarTitleText": "保单管理" }
+        },
+        {
+          "path": "insurance-add",
+          "style": { "navigationBarTitleText": "新增保单" }
         }
       ]
     }
   ],
   "globalStyle": {
     "navigationBarTextStyle": "black",
-    "navigationBarTitleText": "知行益家",
+    "navigationBarTitleText": "浠艾福",
     "navigationBarBackgroundColor": "#FFFFFF",
     "backgroundColor": "#F8F8F8"
   },

+ 269 - 303
cfc-frontend/pages/action/index.vue

@@ -1,409 +1,375 @@
 <template>
   <view class="container">
-    <!-- Level 1 分类:2列横排网格 -->
-    <view class="cat-level1">
-      <view
-        v-for="cat in categories"
-        :key="cat.id"
-        :class="['cat1-item', currentCat1 && currentCat1.id === cat.id ? 'active' : '']"
-        @click="onCat1Select(cat)"
-      >
-        <image v-if="cat.image" class="cat1-icon" :src="cat.image" mode="aspectFit" />
-        <view v-else class="cat1-icon-placeholder">
-          <text class="cat1-icon-text">{{ cat.name.substr(0, 1) }}</text>
+    <!-- 品牌头部 -->
+    <PageBanner theme="action"
+      tagline="知行合一 · 快乐成长"
+      quote="千里之行,始于足下" />
+
+    <!-- 我的任务(登录后可见,放在最上面) -->
+    <view class="section" v-if="sectionVisible('today_tasks') && isLoggedIn">
+      <view class="section-header">
+        <text class="section-title">📋 我的任务</text>
+        <text class="section-more" @click="goTasks">全部 ›</text>
+      </view>
+      <view class="task-wrap" v-if="todayTasks.length > 0">
+        <view class="task-item" v-for="task in todayTasks" :key="task.id">
+          <text class="task-name">{{ task.title }}</text>
+          <text class="task-points">+{{ task.points || 0 }}分</text>
         </view>
-        <text class="cat1-name">{{ cat.name }}</text>
+      </view>
+      <view class="task-empty" v-else>
+        <text class="task-empty-text">今日暂无任务</text>
       </view>
     </view>
 
-    <!-- Level 2 子分类:横向滚动chips -->
-    <scroll-view
-      v-if="currentCat1 && currentCat1.children && currentCat1.children.length"
-      scroll-x
-      enable-flex
-      show-scrollbar="false"
-      class="cat-level2-scroll"
-    >
-      <view class="cat2-chips">
-        <view
-          :class="['cat2-chip', !currentCat2 ? 'active' : '']"
-          @click="onCat2Select(null)"
-        >全部</view>
-        <view
-          v-for="child in currentCat1.children"
-          :key="child.id"
-          :class="['cat2-chip', currentCat2 && currentCat2.id === child.id ? 'active' : '']"
-          @click="onCat2Select(child)"
-        >{{ child.name }}</view>
+    <!-- 功能入口(4个:活动、商品、课程) -->
+    <view class="func-section" v-if="sectionVisible('func_entries')">
+      <view class="func-grid">
+        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
+          <view class="func-icon-wrap">
+            <text class="func-icon">{{ item.icon }}</text>
+          </view>
+          <text class="func-label">{{ item.label }}</text>
+        </view>
       </view>
-    </scroll-view>
+    </view>
 
-    <!-- 商品列表 -->
-    <scroll-view
-      scroll-y
-      class="product-list"
-      @scrolltolower="onLoadMore"
-      refresher-enabled
-      :refresher-triggered="refreshing"
-      @refresherrefresh="onRefresh"
-    >
-      <view v-if="loading && products.length === 0" class="loading-wrap">
-        <text class="loading-text">加载中...</text>
+    <!-- 热门活动 -->
+    <view class="section" v-if="sectionVisible('hot_activities')">
+      <view class="section-header">
+        <text class="section-title">🔥 热门活动</text>
+        <text class="section-more" @click="goMoreActivities">更多 ›</text>
       </view>
-      <view v-else-if="products.length === 0" class="empty-wrap">
-        <text class="empty-icon">📦</text>
-        <text class="empty-text">暂无商品</text>
-      </view>
-      <view v-else class="grid">
-        <view
-          v-for="item in products"
-          :key="item.id"
-          class="product-card"
-          @click="goDetail(item.id)"
-        >
-          <image
-            class="cover"
-            :src="item.image || item.coverImage || '/static/default-product.png'"
-            mode="aspectFill"
-          />
-          <view class="card-body">
-            <text class="product-name">{{ item.name }}</text>
-            <view class="price-row">
-              <text class="price">¥{{ item.price }}</text>
-              <text v-if="item.memberPrice" class="member-price">会员¥{{ item.memberPrice }}</text>
-            </view>
-            <text class="vendor-name">{{ item.brandName || '平台官方' }}</text>
+      <scroll-view scroll-x class="activity-scroll" show-scrollbar="false">
+        <view class="activity-card" v-for="act in hotActivities" :key="act.id" @click="goActivityDetail(act.id)">
+          <image class="activity-img" :src="act.image || '/static/default-activity.png'" mode="aspectFill" />
+          <view class="activity-info">
+            <text class="activity-name">{{ act.name }}</text>
+            <text class="activity-meta">{{ act.date }} · {{ act.location }}</text>
           </view>
         </view>
+      </scroll-view>
+    </view>
+
+    <!-- 推荐商品 -->
+    <view class="section" v-if="sectionVisible('recommended_products')">
+      <view class="section-header">
+        <text class="section-title">🛍️ 推荐商品</text>
+        <text class="section-more" @click="goMoreProducts">更多 ›</text>
       </view>
-      <view v-if="loadingMore" class="loading-more">
-        <text class="loading-text">加载中...</text>
-      </view>
-      <view v-if="noMore && products.length > 0" class="no-more">
-        <text class="no-more-text">— 没有更多了 —</text>
-      </view>
-      <view v-if="!isLoggedIn" class="login-hint-bar">
-        <text class="login-hint-text">🔒 登录查看全部商品和会员价</text>
+      <view class="product-list">
+        <view class="product-card" v-for="prod in recommendedProducts" :key="prod.id" @click="goProductDetail(prod.id)">
+          <image class="product-img" :src="prod.image || '/static/default-product.png'" mode="aspectFill" />
+          <text class="product-name line-clamp-2">{{ prod.name }}</text>
+          <text class="product-price">¥{{ prod.price }}</text>
+        </view>
       </view>
-      <view class="bottom-spacer"></view>
-    </scroll-view>
+    </view>
 
-    <bottom-nav v-if="!isLoggedIn" current="action" />
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
   </view>
 </template>
 
 <script>
-import { goodsCategory, productList } from '@/utils/api.js'
-import BottomNav from '../../components/bottom-nav.vue'
+import PageBanner from '../../components/PageBanner.vue'
+import { getVisibleSections, getEnergyOverview, getTodayTasks } from '../../utils/api.js'
 
 export default {
-  components: { BottomNav },
+  components: { PageBanner },
   data() {
     return {
-      categories: [],
-      currentCat1: null,
-      currentCat2: null,
-      products: [],
-      page: 1,
-      size: 20,
-      loading: false,
-      loadingMore: false,
-      refreshing: false,
-      noMore: false,
-      isLoggedIn: false
+      isLoggedIn: false,
+      role: null,
+      currentChildId: null,
+      totalEnergy: null,
+      visibleSections: [],
+      funcList: [
+        { icon: '\u{1F3CB}', label: '任务', needLogin: true, page: '/pages/tasks/tasks' },
+        { icon: '\u{1F3C0}', label: '活动', needLogin: false, page: '/pages/discover/index?type=activity' },
+        { icon: '\u{1F6CD}', label: '商城', needLogin: false, page: '/pages/shop/index' },
+        { icon: '\u{1F3EB}', label: '课程', needLogin: false, page: '/pages/discover/index?type=course' }
+      ],
+      hotActivities: [
+        { id: 1, name: '亲子户外徒步', date: '周六', location: '奥林匹克公园', image: '' },
+        { id: 2, name: '家庭健康跑', date: '周日', location: '朝阳公园', image: '' },
+        { id: 3, name: '儿童瑜伽体验', date: '下周六', location: '浠艾福中心', image: '' }
+      ],
+      recommendedProducts: [
+        { id: 1, name: '儿童智能手环', price: 299, image: '' },
+        { id: 2, name: '家庭健康管理套装', price: 599, image: '' },
+        { id: 3, name: '亲子运动礼盒', price: 399, image: '' },
+        { id: 4, name: '儿童营养补充品', price: 199, image: '' }
+      ],
+      todayTasks: []
     }
   },
-  onLoad() {
-    this.isLoggedIn = !!uni.getStorageSync('token')
-    this.loadCategories()
-    this.loadProducts()
-  },
   onShow() {
-    this.isLoggedIn = !!uni.getStorageSync('token')
+    var token = uni.getStorageSync('token')
+    this.isLoggedIn = !!token
+    if (this.isLoggedIn) {
+      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      this.currentChildId = uni.getStorageSync('currentChildId') || null
+    }
+    this.loadSectionConfig()
+    if (this.isLoggedIn) {
+      this.loadEnergyData()
+      this.loadTodayTasks()
+    }
   },
   methods: {
-    loadCategories() {
-      goodsCategory('0').then(res => {
+    async loadSectionConfig() {
+      if (!uni.getStorageSync('token')) {
+        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
+        return
+      }
+      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      try {
+        var res = await getVisibleSections({ pageKey: 'action', role: role })
         if (res.code === 200 && res.data) {
-          this.categories = res.data || []
+          this.visibleSections = res.data.map(function(s) { return s.sectionKey })
         }
-      }).catch(() => {})
-    },
-    onCat1Select(cat) {
-      if (this.currentCat1 && this.currentCat1.id === cat.id) return
-      this.currentCat1 = cat
-      this.currentCat2 = null
-      this.page = 1
-      this.products = []
-      this.noMore = false
-      this.loadProducts()
+      } catch (e) {
+        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
+      }
     },
-    onCat2Select(cat) {
-      this.currentCat2 = cat
-      this.page = 1
-      this.products = []
-      this.noMore = false
-      this.loadProducts()
+    async loadEnergyData() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getEnergyOverview(childId)
+        if (res && res.data) {
+          this.totalEnergy = res.data.totalEnergy || 0
+        }
+      } catch (e) { console.log('获取能量概览失败', e) }
     },
-    loadProducts() {
-      if (this.loading) return
-      this.loading = true
-      const params = { page: this.page, size: this.size }
-      if (this.currentCat2) {
-        params.categoryId = this.currentCat2.id
-      } else if (this.currentCat1) {
-        params.categoryId = this.currentCat1.id
-      }
-      productList(params).then(res => {
-        this.loading = false
-        this.refreshing = false
-        if (res.code === 200 && res.data) {
-          const records = res.data.records || []
-          if (this.page === 1) {
-            this.products = records
-          } else {
-            this.products = this.products.concat(records)
-          }
-          const total = res.data.total || 0
-          this.noMore = this.products.length >= total
+    async loadTodayTasks() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getTodayTasks(childId)
+        if (res && res.data) {
+          this.todayTasks = res.data.slice(0, 5)
         }
-      }).catch(() => {
-        this.loading = false
-        this.refreshing = false
-      })
+      } catch (e) { console.log('获取今日任务失败', e) }
     },
-    onRefresh() {
-      this.refreshing = true
-      this.page = 1
-      this.noMore = false
-      this.loadProducts()
+    sectionVisible(key) {
+      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
     },
-    onLoadMore() {
-      if (this.noMore || this.loadingMore) return
-      this.page++
-      this.loadingMore = true
-      const params = { page: this.page, size: this.size }
-      if (this.currentCat2) {
-        params.categoryId = this.currentCat2.id
-      } else if (this.currentCat1) {
-        params.categoryId = this.currentCat1.id
+    onFuncClick(item) {
+      if (item.needLogin && !this.isLoggedIn) {
+        uni.showModal({
+          title: '提示',
+          content: '请先登录后使用此功能',
+          success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
+        })
+        return
       }
-      productList(params).then(res => {
-        this.loadingMore = false
-        if (res.code === 200 && res.data) {
-          const records = res.data.records || []
-          this.products = this.products.concat(records)
-          const total = res.data.total || 0
-          this.noMore = this.products.length >= total
+      if (item.page) {
+        // TabBar page uses switchTab, others use navigateTo
+        var nativeTabs = ['/pages/tasks/tasks', '/pages/index/index', '/pages/body/index', '/pages/mind/index', '/pages/action/index', '/pages/profile/profile']
+        if (nativeTabs.indexOf(item.page) !== -1) {
+          uni.switchTab({ url: item.page })
+        } else {
+          uni.navigateTo({ url: item.page })
         }
-      }).catch(() => {
-        this.loadingMore = false
-      })
+      }
     },
-    goDetail(id) {
-      const token = uni.getStorageSync('token')
+    goMoreActivities() {},
+    goActivityDetail(id) {},
+    goMoreProducts() {},
+    goProductDetail(id) {
+      var token = uni.getStorageSync('token')
       if (token) {
         uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
       } else {
         uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/action/index') })
       }
-    }
+    },
+    goTasks() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
+    goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
   }
 }
 </script>
 
 <style scoped>
 .container {
-  display: flex;
-  flex-direction: column;
-  height: 100vh;
-  background: #f5f5f5;
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
 }
-/* ==================== 分类布局 ==================== */
-/* Level 1 分类:2列横排网格 */
-.cat-level1 {
-  display: flex;
-  flex-wrap: wrap;
-  padding: 16rpx 16rpx 8rpx;
+
+/* ===== 功能入口 ===== */
+.func-section {
+  margin: -40rpx 20rpx 0;
+  position: relative;
+  z-index: 2;
+}
+.func-grid {
   background: #fff;
-  border-bottom: 1rpx solid #eee;
+  border-radius: 24rpx;
+  padding: 30rpx 16rpx;
+  display: flex;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
 }
-.cat1-item {
-  width: 50%;
-  box-sizing: border-box;
-  padding: 8rpx;
+.func-item {
+  flex: 1;
   display: flex;
   flex-direction: column;
   align-items: center;
-  text-align: center;
-  border-radius: 12rpx;
-  margin-bottom: 8rpx;
-}
-.cat1-item.active {
-  background: rgba(249, 115, 22, 0.08);
 }
-.cat1-icon {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  margin-bottom: 6rpx;
-  background: #f5f5f5;
+.func-item:active {
+  opacity: 0.7;
 }
-.cat1-icon-placeholder {
-  width: 72rpx;
-  height: 72rpx;
+.func-icon-wrap {
+  width: 88rpx;
+  height: 88rpx;
   border-radius: 50%;
-  margin-bottom: 6rpx;
-  background: linear-gradient(135deg, #667eea, #764ba2);
+  background: #FFF7ED;
   display: flex;
   align-items: center;
   justify-content: center;
+  margin-bottom: 10rpx;
 }
-.cat1-icon-text {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #fff;
+.func-icon {
+  font-size: 40rpx;
 }
-.cat1-name {
+.func-label {
   font-size: 24rpx;
   color: #333;
-  max-width: 140rpx;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
+  font-weight: 500;
 }
-.cat1-item.active .cat1-name {
-  color: #F97316;
+
+/* ===== 通用区块 ===== */
+.section {
+  margin: 20rpx 20rpx;
+}
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
   font-weight: bold;
+  color: #333;
+}
+.section-more {
+  font-size: 24rpx;
+  color: #999;
 }
 
-/* Level 2 子分类:横向滚动chips */
-.cat-level2-scroll {
-  background: #fff;
+/* ===== 热门活动 ===== */
+.activity-scroll {
   white-space: nowrap;
-  padding: 12rpx 16rpx;
-  border-bottom: 1rpx solid #f0f0f0;
-}
-.cat2-chips {
-  display: inline-flex;
-  gap: 12rpx;
-  padding: 0 4rpx;
 }
-.cat2-chip {
+.activity-card {
   display: inline-block;
-  padding: 8rpx 24rpx;
-  font-size: 24rpx;
-  color: #666;
-  background: #f0f0f0;
-  border-radius: 30rpx;
-  flex-shrink: 0;
-}
-.cat2-chip.active {
-  background: #F97316;
-  color: #fff;
+  width: 280rpx;
+  margin-right: 20rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-
-/* ==================== 商品列表 ==================== */
-.product-list {
-  flex: 1;
-  height: calc(100vh - 220rpx);
+.activity-img {
+  width: 280rpx;
+  height: 180rpx;
+  background: #f0f0f0;
 }
-.login-hint-bar {
-  text-align: center;
-  padding: 20rpx;
-  background: #fef9e7;
-  border-top: 1rpx solid #f0e6c0;
+.activity-info {
+  padding: 16rpx;
 }
-.login-hint-text {
-  font-size: 24rpx;
-  color: #b8860b;
+.activity-name {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
 }
-.bottom-spacer {
-  height: 120rpx;
+.activity-meta {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 8rpx;
+  display: block;
 }
-.grid {
+
+/* ===== 推荐商品 ===== */
+.product-list {
   display: flex;
   flex-wrap: wrap;
-  padding: 20rpx;
-  justify-content: space-between;
+  gap: 20rpx;
 }
 .product-card {
-  width: 49%;
+  width: calc(50% - 10rpx);
   background: #fff;
   border-radius: 16rpx;
   overflow: hidden;
-  margin-bottom: 20rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.cover {
+.product-img {
   width: 100%;
-  height: 320rpx;
+  height: 240rpx;
   background: #f0f0f0;
 }
-.card-body {
-  padding: 16rpx;
-}
 .product-name {
-  display: block;
-  font-size: 28rpx;
+  font-size: 26rpx;
   color: #333;
-  lines: 2;
+  padding: 12rpx 16rpx 0;
+}
+.product-price {
+  font-size: 28rpx;
+  color: #F97316;
+  font-weight: bold;
+  padding: 8rpx 16rpx 16rpx;
+  display: block;
+}
+.line-clamp-2 {
   overflow: hidden;
   text-overflow: ellipsis;
   display: -webkit-box;
   -webkit-line-clamp: 2;
   -webkit-box-orient: vertical;
-  margin-bottom: 12rpx;
-}
-.price-row {
-  display: flex;
-  align-items: baseline;
-  margin-bottom: 8rpx;
 }
-.price {
-  font-size: 32rpx;
-  color: #F97316;
-  font-weight: bold;
-  margin-right: 12rpx;
-}
-.member-price {
-  font-size: 22rpx;
-  color: #999;
-}
-.vendor-name {
-  font-size: 22rpx;
-  color: #999;
+
+/* ===== 今日任务 ===== */
+.task-wrap {
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.loading-wrap,
-.empty-wrap {
+.task-item {
   display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding-top: 200rpx;
+  justify-content: space-between;
+  padding: 28rpx 24rpx;
+  border-bottom: 1rpx solid #f0f0f0;
 }
-.loading-text {
-  font-size: 26rpx;
-  color: #999;
+.task-item:last-child {
+  border-bottom: none;
 }
-.empty-icon {
-  font-size: 100rpx;
-  margin-bottom: 30rpx;
+.task-name {
+  font-size: 26rpx;
+  color: #333;
 }
-.empty-text {
-  font-size: 28rpx;
-  color: #999;
+.task-points {
+  font-size: 24rpx;
+  color: #F97316;
 }
-.loading-more,
-.no-more {
+.task-empty {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 60rpx;
   text-align: center;
-  padding: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.loading-text {
-  font-size: 24rpx;
+.task-empty-text {
+  font-size: 26rpx;
   color: #999;
 }
-.no-more-text {
-  font-size: 24rpx;
-  color: #ccc;
+
+/* ===== 底部 ===== */
+.bottom-spacer {
+  height: 20rpx;
 }
-</style>
+</style>

+ 119 - 54
cfc-frontend/pages/body/index.vue

@@ -1,16 +1,14 @@
 <template>
   <view class="body-container">
     <!-- 品牌头部 -->
-    <view class="brand-header">
-      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
-      <text class="brand-title">身体能量</text>
-      <text class="brand-slogan">关注儿童身心健康,从每一天开始</text>
-    </view>
+    <PageBanner theme="body"
+      tagline="科学管理全家健康"
+      quote="身体健康是幸福的基础" />
 
     <!-- 功能入口 -->
     <view class="func-section" v-if="sectionVisible('func_entries')">
       <view class="func-grid">
-        <view class="func-item" v-for="item in funcList" :key="item.label">
+        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
           <view class="func-icon-wrap">
             <text class="func-icon">{{ item.icon }}</text>
           </view>
@@ -19,8 +17,8 @@
       </view>
     </view>
 
-    <!-- 今日身体数据 -->
-    <view class="section" v-if="sectionVisible('daily_stats')">
+    <!-- 今日身体数据(登录后可见) -->
+    <view class="section" v-if="sectionVisible('daily_stats') && isLoggedIn">
       <view class="section-header">
         <text class="section-title">今日身体数据</text>
       </view>
@@ -62,6 +60,19 @@
       </view>
     </view>
 
+    <!-- 能量一周(登录后可见) -->
+    <view class="section" v-if="sectionVisible('energy_week') && isLoggedIn">
+      <view class="section-header">
+        <text class="section-title">能量一周</text>
+      </view>
+      <view class="energy-week-bar">
+        <view class="week-column" v-for="(day, idx) in energyWeek" :key="idx">
+          <view class="bar-fill" :style="{ height: day.percent + '%' }"></view>
+          <text class="bar-label">{{ day.label }}</text>
+        </view>
+      </view>
+    </view>
+
     <!-- 规划师推荐(服务商可见,后台可配置) -->
     <view class="section" v-if="sectionVisible('teacher_tips')">
       <view class="section-header">
@@ -85,18 +96,25 @@
 </template>
 
 <script>
-import { getVisibleSections } from '../../utils/api.js'
+import PageBanner from '../../components/PageBanner.vue'
+import { getVisibleSections, getEnergyOverview } from '../../utils/api.js'
 
 export default {
+  components: { PageBanner },
   data() {
     return {
+      isLoggedIn: false,
+      role: null,
+      currentChildId: null,
+      totalEnergy: null,
+      totalHealthIndex: null,
       visibleSections: [],
       funcList: [
-        { icon: '\u{1F3C3}', label: '运动' },
-        { icon: '\u{1F957}', label: '饮食' },
-        { icon: '\u{1F634}', label: '作息' },
-        { icon: '\u{1F9A0}', label: '菌群' },
-        { icon: '\u{1F9D8}', label: '冥想' }
+        { icon: '\u{1F3C3}', label: '运动', needLogin: false, page: '' },
+        { icon: '\u{1F957}', label: '饮食', needLogin: false, page: '' },
+        { icon: '\u{1F634}', label: '作息', needLogin: false, page: '' },
+        { icon: '\u{1F9A0}', label: '菌群', needLogin: false, page: '' },
+        { icon: '\u{1F9D8}', label: '冥想', needLogin: false, page: '' }
       ],
       dailyStats: {
         steps: '6,582',
@@ -112,36 +130,76 @@ export default {
       teacherTips: [
         { id: 1, title: '儿童体态评估与纠正', summary: '专业体态评估方法,及早发现并纠正不良体态。', date: '2024-01-14' },
         { id: 2, title: '感统训练家庭方案', summary: '针对不同年龄段的感觉统合训练建议,在家也能做。', date: '2024-01-11' }
+      ],
+      energyWeek: [
+        { label: '一', percent: 60 },
+        { label: '二', percent: 75 },
+        { label: '三', percent: 45 },
+        { label: '四', percent: 80 },
+        { label: '五', percent: 65 },
+        { label: '六', percent: 90 },
+        { label: '日', percent: 70 }
       ]
     }
   },
   onShow() {
-    this.resetTabBar()
+    var token = uni.getStorageSync('token')
+    this.isLoggedIn = !!token
+    if (this.isLoggedIn) {
+      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      this.currentChildId = uni.getStorageSync('currentChildId') || null
+    }
     this.loadSectionConfig()
+    if (this.isLoggedIn) {
+      this.loadEnergyData()
+    }
   },
   methods: {
-    resetTabBar() {
-      uni.setTabBarItem({ index: 1, text: '身体' })
-      uni.setTabBarItem({ index: 2, text: '心智' })
-    },
     async loadSectionConfig() {
-      var token = uni.getStorageSync('token')
-      var role = null
-      if (token) {
-        role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      if (!uni.getStorageSync('token')) {
+        this.visibleSections = ['func_entries']
+        return
       }
-      var res
+      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
       try {
-        res = await getVisibleSections({ pageKey: 'body', role: role })
+        var res = await getVisibleSections({ pageKey: 'body', role: role })
         if (res.code === 200 && res.data) {
           this.visibleSections = res.data.map(function(s) { return s.sectionKey })
         }
       } catch (e) {
-        this.visibleSections = ['func_entries', 'daily_stats', 'health_tips']
+        this.visibleSections = ['func_entries']
+      }
+    },
+    async loadEnergyData() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getEnergyOverview(childId)
+        if (res && res.data) {
+          this.visibleSections = res.data.dimensions || []
+          this.totalEnergy = res.data.totalEnergy || 0
+          this.totalHealthIndex = res.data.totalHealthIndex || 0
+        }
+      } catch (e) {
+        console.log('获取能量概览失败', e)
+      }
+    },
+    sectionVisible(key) {
+      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
+    },
+    onFuncClick(item) {
+      if (item.needLogin && !this.isLoggedIn) {
+        uni.showModal({
+          title: '提示',
+          content: '请先登录后使用此功能',
+          success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
+        })
+        return
       }
+      if (item.page) uni.navigateTo({ url: item.page })
     },
-    sectionVisible(sectionKey) {
-      return this.visibleSections.indexOf(sectionKey) !== -1
+    goLogin() {
+      uni.navigateTo({ url: '/pages/login/login' })
     }
   }
 }
@@ -154,32 +212,6 @@ export default {
   padding-bottom: 120rpx;
 }
 
-/* ===== 品牌头部 ===== */
-.brand-header {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 30rpx 30rpx 16rpx;
-  background: linear-gradient(180deg, #1A2A4A 0%, #1E3A5F 60%, #f5f7fa 100%);
-}
-.brand-logo {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 18rpx;
-  margin-bottom: 8rpx;
-}
-.brand-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #8FC5E8;
-  letter-spacing: 4rpx;
-}
-.brand-slogan {
-  font-size: 22rpx;
-  color: rgba(143, 197, 232, 0.6);
-  margin-top: 6rpx;
-}
-
 /* ===== 功能入口 ===== */
 .func-section {
   margin: 20rpx 30rpx;
@@ -310,6 +342,39 @@ export default {
   font-weight: 500;
 }
 
+/* ===== 能量一周 ===== */
+.energy-week-bar {
+  display: flex;
+  align-items: flex-end;
+  justify-content: space-around;
+  height: 200rpx;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.week-column {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 60rpx;
+}
+.bar-fill {
+  width: 40rpx;
+  background: linear-gradient(180deg, #F97316, #FBBF24);
+  border-radius: 20rpx 20rpx 4rpx 4rpx;
+  min-height: 8rpx;
+}
+.bar-label {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 12rpx;
+}
+
+.func-item:active {
+  opacity: 0.7;
+}
+
 /* ===== 底部 ===== */
 .bottom-spacer {
   height: 120rpx;

+ 10 - 46
cfc-frontend/pages/discover/index.vue

@@ -1,13 +1,9 @@
 <template>
   <view class="discover-container">
     <!-- 品牌头部 -->
-    <view class="brand-header">
-      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
-      <view class="brand-text-wrap">
-        <text class="brand-title">浠艾福</text>
-        <text class="brand-subtitle">家庭健康成长俱乐部</text>
-      </view>
-    </view>
+    <PageBanner theme="home"
+      tagline="家庭健康成长俱乐部"
+      quote="给全家的一站式幸福提案" />
 
     <!-- 五行沙盘预览 -->
     <wuxing-sandbox mode="preview" badgeText="登录查看完整报告" @point-click="handleLogin" />
@@ -108,21 +104,17 @@
       <text class="login-footer-text">登录即表示同意《用户协议》和《隐私政策》</text>
     </view>
 
-    <!-- 未登录态自定义底部导航 -->
-    <bottom-nav current="index" />
-
     <!-- 底部占位 -->
     <view style="height: 20rpx;"></view>
   </view>
 </template>
 
 <script>
+import PageBanner from '../../components/PageBanner.vue'
 import { productList, getFeaturedArticles } from '@/utils/api.js'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
-import BottomNav from '../../components/bottom-nav.vue'
-
 export default {
-  components: { WuxingSandbox, BottomNav },
+  components: { PageBanner, WuxingSandbox },
   data() {
     return {
       typeList: [
@@ -147,6 +139,11 @@ export default {
       coverIcons: ['🌱', '🏃', '🎯', '🌟', '📚']
     }
   },
+  onLoad(options) {
+    if (options && options.type) {
+      this.currentType = options.type
+    }
+  },
   onShow() {
     this.loadProducts()
     this.loadFeaturedArticles()
@@ -218,39 +215,6 @@ export default {
   padding-bottom: 100rpx;
 }
 
-/* ===== 品牌头部 ===== */
-.brand-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding: 20rpx 30rpx 12rpx;
-  background: linear-gradient(180deg, #EBF2FA 0%, #f5f9fc 100%);
-}
-.brand-logo {
-  width: 64rpx;
-  height: 64rpx;
-  border-radius: 14rpx;
-  flex-shrink: 0;
-}
-.brand-text-wrap {
-  display: flex;
-  flex-direction: column;
-  margin-left: 14rpx;
-}
-.brand-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #2C5282;
-  letter-spacing: 4rpx;
-  line-height: 1.2;
-}
-.brand-subtitle {
-  font-size: 20rpx;
-  color: #5B9BD5;
-  margin-top: 2rpx;
-  line-height: 1.2;
-}
-
 /* ===== 推荐 ===== */
 .recommend-section {
   margin: 20rpx 30rpx;

+ 1 - 1
cfc-frontend/pages/guide/packages/share.vue

@@ -123,7 +123,7 @@ export default {
       // 品牌标识
       ctx.setFillStyle('#cccccc')
       ctx.setFontSize(16)
-      ctx.fillText('知行益家', 480, 880)
+      ctx.fillText('浠艾福', 480, 880)
       
       ctx.draw()
       

+ 78 - 70
cfc-frontend/pages/index/child-index.vue

@@ -22,21 +22,10 @@
 
 <!-- ====== 主内容(有孩子时) ====== -->
 <view class="main-content" v-if="!loading && hasChildren">
-<!-- ============================================================
-A) 五维理念区
-============================================================ -->
-<view class="philosophy-section animate-fade-in animate-stagger-1">
-<view class="philosophy-banner">
-<text class="philosophy-title">浠艾福</text>
-<view class="philosophy-five">
-<text class="five-item five-body">🌏 身泰</text>
-<text class="five-item five-wisdom">⚔️ 智达</text>
-<text class="five-item five-mind">🔥 心怡</text>
-<text class="five-item five-action">🌿 行远</text>
-<text class="five-item five-wealth">💧 富沛</text>
-</view>
-</view>
-</view>
+<!-- ===== A) 顶部 Banner ===== -->
+<PageBanner theme="home"
+  tagline="家庭健康成长俱乐部"
+  quote="给全家的一站式幸福提案" />
 
 <!-- 五维能量沙盘 -->
 <view class="animate-fade-in animate-stagger-2">
@@ -246,6 +235,30 @@ B) 积分卡片 — Claymorphism 暖橙黏土风格
         </scroll-view>
       </view>
 
+      <!-- ============================================================
+      F) 最近能量变动
+      ============================================================ -->
+      <view class="energy-log-section animate-slide-up animate-stagger-6" v-if="energyLogs.length > 0">
+        <view class="section-header">
+          <text class="section-title">💧 最近能量变动</text>
+          <text class="section-link" @click="goToEnergyDetail">查看详情 →</text>
+        </view>
+        <view class="energy-log-list">
+          <view class="energy-log-item" v-for="log in energyLogs" :key="log.id">
+            <view class="log-icon-wrap" :class="'log-icon--' + ((log.amount || 0) >= 0 ? 'up' : 'down')">
+              <text class="log-icon">{{ (log.amount || 0) >= 0 ? '⬆' : '⬇' }}</text>
+            </view>
+            <view class="log-info">
+              <text class="log-title">{{ log.reason || log.description }}</text>
+              <text class="log-time">{{ log.createdAt || log.time }}</text>
+            </view>
+            <text class="log-amount" :class="(log.amount || 0) >= 0 ? 'log-amount--up' : 'log-amount--down'">
+              {{ (log.amount || 0) >= 0 ? '+' : '' }}{{ log.amount }}
+            </text>
+          </view>
+        </view>
+      </view>
+
       <!-- 底部安全留白 -->
       <view class="bottom-spacer"></view>
     </view>
@@ -253,7 +266,8 @@ B) 积分卡片 — Claymorphism 暖橙黏土风格
 </template>
 
 <script>
-import { getChildren, getTodayTasks, getEnergyOverview } from '../../utils/api.js'
+import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs } from '../../utils/api.js'
+import PageBanner from '../../components/PageBanner.vue'
 import PlayfulCard from '../../components/PlayfulCard.vue'
 import PlayfulButton from '../../components/PlayfulButton.vue'
 import BaseBadge from '../../components/BaseBadge.vue'
@@ -288,6 +302,7 @@ var TASK_ICONS = {
 
 export default {
   components: {
+    PageBanner,
     PlayfulCard,
     PlayfulButton,
     BaseBadge,
@@ -317,7 +332,8 @@ export default {
       _mounted: false,
       energyDimensions: [],
       totalEnergy: 0,
-      totalHealthIndex: 0
+      totalHealthIndex: 0,
+      energyLogs: []
     }
   },
   computed: {
@@ -459,6 +475,19 @@ export default {
             console.log('获取能量概览失败', e)
           }
         }
+
+        // 加载最近能量变动
+        if (energyChildId) {
+          try {
+            var logsRes = await getEnergyLogs(energyChildId, '', 1, 5)
+            if (logsRes && logsRes.data) {
+              this.energyLogs = logsRes.data.records || logsRes.data || []
+            }
+          } catch (e) {
+            console.log('获取能量日志失败', e)
+            this.energyLogs = []
+          }
+        }
       } catch (e) {
         console.error('加载数据失败', e)
         uni.showToast({ title: '加载失败', icon: 'none' })
@@ -496,6 +525,11 @@ export default {
       this.$emit('navigate', { page: 'streak-detail' })
       uni.showToast({ title: '连续打卡 ' + this.streakDays + ' 天啦!太棒了!', icon: 'none' })
     },
+    goToEnergyDetail() {
+      if (this.childId) {
+        uni.navigateTo({ url: '/pages/energy/detail?childId=' + this.childId + '&code=all' })
+      }
+    },
     showAchievements() {
       uni.showToast({ title: '成就页面开发中', icon: 'none' })
     },
@@ -612,59 +646,6 @@ export default {
   height: 48rpx;
 }
 
-/* ----------------------------------------------------------
-A) 五维理念区
----------------------------------------------------------- */
-.philosophy-section {
-margin-bottom: 32rpx;
-}
-
-.philosophy-banner {
-background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
-border-radius: 28rpx;
-padding: 32rpx 28rpx 24rpx;
-text-align: center;
-border: 3rpx solid rgba(147, 197, 253, 0.2);
-box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
-}
-
-.philosophy-title {
-font-size: 36rpx;
-font-weight: 800;
-color: #FFD700;
-letter-spacing: 8rpx;
-display: block;
-margin-bottom: 24rpx;
-text-shadow: 0 2rpx 12rpx rgba(255, 215, 0, 0.3);
-}
-
-.philosophy-five {
-display: flex;
-justify-content: space-around;
-align-items: center;
-flex-wrap: wrap;
-gap: 12rpx 8rpx;
-}
-
-.five-item {
-font-size: 26rpx;
-font-weight: 700;
-color: #FFFFFF;
-display: inline-flex;
-align-items: center;
-gap: 6rpx;
-padding: 10rpx 16rpx;
-border-radius: 24rpx;
-background: rgba(255, 255, 255, 0.08);
-border: 2rpx solid rgba(255, 255, 255, 0.12);
-}
-
-.five-body { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
-.five-wisdom { border-color: rgba(251, 191, 36, 0.4); color: #FDE68A; }
-.five-mind { border-color: rgba(251, 146, 60, 0.4); color: #FDBA74; }
-.five-action { border-color: rgba(134, 239, 172, 0.4); color: #BBF7D0; }
-.five-wealth { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
-
 /* 连续打卡徽章 — 独立行 */
 .streak-badge-wrap {
 display: flex;
@@ -1159,6 +1140,33 @@ padding-top: 4rpx;
   padding: 200rpx 0;
 }
 
+/* ----------------------------------------------------------
+   能量变动日志
+   ---------------------------------------------------------- */
+.energy-log-section { margin-bottom: 28rpx; }
+.energy-log-list { background: #fff; border-radius: 20rpx; overflow: hidden; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.energy-log-item {
+  display: flex; align-items: center;
+  padding: 20rpx 24rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.energy-log-item:last-child { border-bottom: none; }
+.log-icon-wrap {
+  width: 56rpx; height: 56rpx;
+  border-radius: 50%;
+  display: flex; align-items: center; justify-content: center;
+  margin-right: 16rpx; flex-shrink: 0;
+}
+.log-icon--up { background: rgba(16,185,129,0.12); }
+.log-icon--down { background: rgba(239,68,68,0.12); }
+.log-icon { font-size: 24rpx; }
+.log-info { flex: 1; min-width: 0; }
+.log-title { font-size: 26rpx; color: #333; display: block; }
+.log-time { font-size: 20rpx; color: #999; margin-top: 4rpx; display: block; }
+.log-amount { font-size: 28rpx; font-weight: 700; margin-left: 12rpx; }
+.log-amount--up { color: #10B981; }
+.log-amount--down { color: #EF4444; }
+
 /* 连续打卡徽章的浮动动画 — 使用 uni.scss 全局 float keyframe */
 .streak-badge {
   animation: float 3s ease-in-out infinite;

+ 6 - 49
cfc-frontend/pages/index/index.vue

@@ -3,13 +3,9 @@
   <!-- 未登录态 — 发现页/引流内容,直接内嵌,不跳转 -->
   <view v-if="!currentRole" class="discover-container">
     <!-- 品牌头部 -->
-    <view class="brand-header">
-      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
-      <view class="brand-text-wrap">
-        <text class="brand-title">浠艾福</text>
-        <text class="brand-subtitle">家庭健康成长俱乐部</text>
-      </view>
-    </view>
+    <PageBanner theme="home"
+      tagline="家庭健康成长俱乐部"
+      quote="给全家的一站式幸福提案" />
 
     <!-- 五行沙盘预览 -->
     <wuxing-sandbox mode="preview" badgeText="登录查看完整报告" @point-click="handleLogin" />
@@ -92,9 +88,6 @@
 
     <!-- 底部占位 -->
     <view style="height: 120rpx;"></view>
-
-    <!-- 未登录态自定义底栏(仅 首页/行动) -->
-    <bottom-nav v-if="!currentRole" current="index" />
   </view>
 
   <!-- 登录态 — 按角色渲染 -->
@@ -113,8 +106,8 @@
 import ParentIndex from './parent-index.vue'
 import ChildIndex from './child-index.vue'
 import TeacherIndex from '../teacher/index.vue'
+import PageBanner from '../../components/PageBanner.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
-import BottomNav from '../../components/bottom-nav.vue'
 import { acceptParentInvite, productList } from '../../utils/api.js'
 
 export default {
@@ -122,8 +115,8 @@ export default {
     ParentIndex,
     ChildIndex,
     TeacherIndex,
-    WuxingSandbox,
-    BottomNav
+    PageBanner,
+    WuxingSandbox
   },
   data() {
     return {
@@ -144,7 +137,6 @@ export default {
   onLoad(options) {
     if (!this.isLoggedIn()) {
       this.currentRole = ''
-      setTimeout(() => { uni.hideTabBar() }, 100)
       return
     }
     this.syncRoleAndTabBar()
@@ -166,11 +158,9 @@ export default {
   onShow() {
     if (!this.isLoggedIn()) {
       this.currentRole = ''
-      setTimeout(() => { uni.hideTabBar() }, 100)
       return
     }
     this.syncRoleAndTabBar()
-    setTimeout(() => { uni.showTabBar() }, 100)
   },
   methods: {
     isLoggedIn() {
@@ -281,39 +271,6 @@ export default {
   padding-bottom: 100rpx;
 }
 
-/* ===== 品牌头部 ===== */
-.brand-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding: 20rpx 30rpx 12rpx;
-  background: linear-gradient(180deg, #EBF2FA 0%, #f5f9fc 100%);
-}
-.brand-logo {
-  width: 64rpx;
-  height: 64rpx;
-  border-radius: 14rpx;
-  flex-shrink: 0;
-}
-.brand-text-wrap {
-  display: flex;
-  flex-direction: column;
-  margin-left: 14rpx;
-}
-.brand-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #2C5282;
-  letter-spacing: 4rpx;
-  line-height: 1.2;
-}
-.brand-subtitle {
-  font-size: 20rpx;
-  color: #5B9BD5;
-  margin-top: 2rpx;
-  line-height: 1.2;
-}
-
 /* ===== 推荐 ===== */
 .recommend-section {
   margin: 20rpx 30rpx;

+ 139 - 75
cfc-frontend/pages/index/parent-index.vue

@@ -4,19 +4,10 @@
 <BaseLoading :loading="loading" text="正在加载..." />
 
 <template v-if="!loading">
-<!-- ===== a) 五维理念区 ===== -->
-<view class="philosophy-section animate-fade-in animate-stagger-1">
-<view class="philosophy-banner">
-<text class="philosophy-title">浠艾福</text>
-<view class="philosophy-five">
-<text class="five-item five-body">🌏 身泰</text>
-<text class="five-item five-wisdom">⚔️ 智达</text>
-<text class="five-item five-mind">🔥 心怡</text>
-<text class="five-item five-action">🌿 行远</text>
-<text class="five-item five-wealth">💧 富沛</text>
-</view>
-</view>
-</view>
+<!-- ===== a) 顶部 Banner ===== -->
+<PageBanner theme="home"
+  tagline="家庭健康成长俱乐部"
+  quote="给全家的一站式幸福提案" />
 
 <!-- ===== b) 孩子卡片 ===== -->
       <view class="children-row animate-fade-in animate-stagger-2">
@@ -225,8 +216,68 @@
         </PlayfulCard>
       </view>
 
-      <!-- ===== f) 待审核任务 ===== -->
-      <view class="review-section animate-fade-in animate-stagger-8" v-if="pendingReviewTasks.length > 0">
+      <!-- ===== f) 待处理事项 ===== -->
+      <view class="pending-section animate-fade-in animate-stagger-8">
+        <view class="section-title">📋 待处理事项</view>
+        <view class="pending-grid">
+          <view class="pending-card" @click="goToReview">
+            <view class="pending-icon pending-icon--blue">
+              <text class="pending-emoji">📝</text>
+            </view>
+            <view class="pending-info">
+              <text class="pending-count accent-color">{{ pendingReviewTasks.length }}</text>
+              <text class="pending-label">待审核任务</text>
+            </view>
+          </view>
+          <view class="pending-card" @click="goToWishApprove">
+            <view class="pending-icon pending-icon--pink">
+              <text class="pending-emoji">💝</text>
+            </view>
+            <view class="pending-info">
+              <text class="pending-count pink-color">{{ pendingReviewWishes.length }}</text>
+              <text class="pending-label">待审批心愿</text>
+            </view>
+          </view>
+          <view class="pending-card" @click="goToAssessmentReport">
+            <view class="pending-icon pending-icon--green">
+              <text class="pending-emoji">📊</text>
+            </view>
+            <view class="pending-info">
+              <text class="pending-count teal-color">{{ hasNewReport ? '有新' : '暂无' }}</text>
+              <text class="pending-label">最新测评报告</text>
+            </view>
+          </view>
+          <view class="pending-card" @click="goToActivities">
+            <view class="pending-icon pending-icon--orange">
+              <text class="pending-emoji">🎉</text>
+            </view>
+            <view class="pending-info">
+              <text class="pending-count orange-color">{{ hotActivityCount > 0 ? hotActivityCount : '暂无' }}</text>
+              <text class="pending-label">活动推荐</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- ===== g) 成长建议 ===== -->
+      <view class="advice-section animate-fade-in animate-stagger-9" v-if="growthAdvice.length > 0">
+        <view class="section-title">💡 成长建议</view>
+        <PlayfulCard variant="flat" class="advice-card" v-for="advice in growthAdvice" :key="advice.id" padding="0">
+          <view class="advice-content">
+            <view class="advice-icon-wrap">
+              <text class="advice-icon">💡</text>
+            </view>
+            <view class="advice-text-wrap">
+              <text class="advice-title">{{ advice.title }}</text>
+              <text class="advice-desc">{{ advice.summary || advice.description }}</text>
+            </view>
+            <text class="advice-arrow">›</text>
+          </view>
+        </PlayfulCard>
+      </view>
+
+      <!-- ===== h) 待审核任务 ===== -->
+      <view class="review-section animate-fade-in animate-stagger-10" v-if="pendingReviewTasks.length > 0">
         <view class="section-title">待审核任务</view>
         <PlayfulCard
           variant="flat"
@@ -293,6 +344,7 @@
 
 <script>
 import { getFamilyMembers, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergyOverview } from '../../utils/api.js'
+import PageBanner from '../../components/PageBanner.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import PlayfulCard from '../../components/PlayfulCard.vue'
 import PlayfulButton from '../../components/PlayfulButton.vue'
@@ -301,7 +353,7 @@ import BaseEmpty from '../../components/BaseEmpty.vue'
 import BaseLoading from '../../components/BaseLoading.vue'
 
 export default {
-  components: { WuxingSandbox, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading },
+  components: { PageBanner, WuxingSandbox, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading },
   data() {
     return {
       nickname: '',
@@ -310,7 +362,11 @@ export default {
       selectedChild: null,
       pendingReviewTasks: [],
       pendingReviewWishes: [],
+      pendingWishesCount: 0,
       pendingTasks: [],
+      hotActivityCount: 0,
+      hasNewReport: false,
+      growthAdvice: [],
       taskCompletionRate: 0,
       habitCount: 0,
       membershipText: '开通家庭会员 畅享全部权益',
@@ -389,9 +445,14 @@ export default {
           const dashRes = await getParentDashboard()
           const dashData = dashRes.data || {}
           const dashChildren = dashData.children || []
-          if (dashChildren.length > 0) {
-            this.children = dashChildren
-            this.selectedChild = dashChildren[0]
+          // 过滤:仅保留孩子角色成员,排除家长/本人
+          var currentUserId = uni.getStorageSync('userId')
+          var filteredChildren = dashChildren.filter(function(c) {
+            return c && c.id && c.id !== currentUserId && (c.role === 'child' || c.memberType === 'child' || !c.role)
+          })
+          if (filteredChildren.length > 0) {
+            this.children = filteredChildren
+            this.selectedChild = filteredChildren[0]
             this.taskCompletionRate = dashData.avgCompletionRate || 0
             this.habitCount = dashData.totalHabitCount || 0
             // 从仪表盘获取 familyId
@@ -571,6 +632,9 @@ export default {
     },
     addTask() { uni.navigateTo({ url: '/pages/tasks/create-task' }) },
     goToReview() { uni.navigateTo({ url: '/pages/tasks/review' }) },
+    goToWishApprove() { uni.navigateTo({ url: '/pages/wishes/approve' }) },
+    goToAssessmentReport() { uni.navigateTo({ url: '/pages/assessment/report' }) },
+    goToActivities() { uni.switchTab({ url: '/pages/discover/index' }) },
     goToRewards() { uni.switchTab({ url: '/pages/rewards/rewards' }) },
     goToTaskList() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
     manageChildren() { uni.navigateTo({ url: '/pages/profile/children' }) },
@@ -601,59 +665,6 @@ export default {
   margin-bottom: var(--space-md);
 }
 
-/* ========================================
-a) 五维理念区
-======================================== */
-.philosophy-section {
-margin-bottom: 40rpx;
-}
-
-.philosophy-banner {
-background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
-border-radius: var(--radius-lg, 28rpx);
-padding: 32rpx 28rpx 24rpx;
-text-align: center;
-border: 3rpx solid rgba(147, 197, 253, 0.2);
-box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
-}
-
-.philosophy-title {
-font-size: 36rpx;
-font-weight: 800;
-color: #FFD700;
-letter-spacing: 8rpx;
-display: block;
-margin-bottom: 24rpx;
-text-shadow: 0 2rpx 12rpx rgba(255, 215, 0, 0.3);
-}
-
-.philosophy-five {
-display: flex;
-justify-content: space-around;
-align-items: center;
-flex-wrap: wrap;
-gap: 12rpx 8rpx;
-}
-
-.five-item {
-font-size: 26rpx;
-font-weight: 700;
-color: #FFFFFF;
-display: inline-flex;
-align-items: center;
-gap: 6rpx;
-padding: 10rpx 16rpx;
-border-radius: 24rpx;
-background: rgba(255, 255, 255, 0.08);
-border: 2rpx solid rgba(255, 255, 255, 0.12);
-}
-
-.five-body { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
-.five-wisdom { border-color: rgba(251, 191, 36, 0.4); color: #FDE68A; }
-.five-mind { border-color: rgba(251, 146, 60, 0.4); color: #FDBA74; }
-.five-action { border-color: rgba(134, 239, 172, 0.4); color: #BBF7D0; }
-.five-wealth { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
-
 /* ========================================
 b) 孩子卡片
 ======================================== */
@@ -670,8 +681,8 @@ b) 孩子卡片
 
 .child-card {
   display: inline-flex !important;
-  margin-right: 20rpx;
-  min-width: 440rpx;
+  margin-right: 16rpx;
+  min-width: 320rpx;
   width: auto;
 }
 
@@ -1081,7 +1092,60 @@ b) 孩子卡片
 }
 
 /* ========================================
-   f) 待审核任务
+   f) 待处理事项
+   ======================================== */
+.pending-section { margin-bottom: 48rpx; }
+.pending-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
+.pending-card {
+  width: calc(50% - 8rpx);
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  display: flex;
+  align-items: center;
+}
+.pending-card:active { opacity: 0.8; }
+.pending-icon {
+  width: 72rpx; height: 72rpx;
+  border-radius: 16rpx;
+  display: flex; align-items: center; justify-content: center;
+  margin-right: 16rpx; flex-shrink: 0;
+}
+.pending-icon--blue { background: rgba(91,155,213,0.12); }
+.pending-icon--pink { background: rgba(236,72,153,0.12); }
+.pending-icon--green { background: rgba(16,185,129,0.12); }
+.pending-icon--orange { background: rgba(249,115,22,0.12); }
+.pending-emoji { font-size: 32rpx; }
+.pending-info { flex: 1; min-width: 0; }
+.pending-count { font-size: 36rpx; font-weight: 800; display: block; }
+.pending-label { font-size: 22rpx; color: #666; margin-top: 4rpx; display: block; }
+.pink-color { color: #EC4899; }
+.orange-color { color: #F97316; }
+
+/* ========================================
+   g) 成长建议
+   ======================================== */
+.advice-section { margin-bottom: 48rpx; }
+.advice-card { margin-bottom: 12rpx; }
+.advice-content {
+  display: flex; align-items: center; padding: 24rpx;
+}
+.advice-icon-wrap {
+  width: 56rpx; height: 56rpx;
+  border-radius: 14rpx;
+  background: rgba(91,155,213,0.1);
+  display: flex; align-items: center; justify-content: center;
+  margin-right: 16rpx; flex-shrink: 0;
+}
+.advice-icon { font-size: 28rpx; }
+.advice-text-wrap { flex: 1; min-width: 0; }
+.advice-title { font-size: 28rpx; font-weight: 600; color: #333; display: block; }
+.advice-desc { font-size: 24rpx; color: #999; margin-top: 4rpx; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.advice-arrow { font-size: 36rpx; color: #ccc; margin-left: 12rpx; }
+
+/* ========================================
+   h) 待审核任务
    ======================================== */
 .review-section {
   margin-bottom: 48rpx;

+ 70 - 11
cfc-frontend/pages/login/login.vue

@@ -2,7 +2,7 @@
   <view class="login-container">
     <view class="logo-section">
       <image class="logo" src="/static/logo.png" mode="aspectFit" @error="handleImageError"></image>
-      <text class="app-name">知行益家</text>
+      <text class="app-name">浠艾福</text>
       <text class="slogan">家庭教育任务管理系统</text>
     </view>
 
@@ -70,7 +70,7 @@
 </template>
 
 <script>
-import { sendCode, phoneLogin as phoneLoginApi, wechatPhoneLogin, checkPhone } from '../../utils/api.js'
+import { sendCode, phoneLogin as phoneLoginApi, wechatPhoneLogin, checkPhone, checkTeamInviteStatus, joinTeamByInvite } from '../../utils/api.js'
 
 export default {
   data() {
@@ -193,13 +193,11 @@ export default {
         // 根据角色跳转到对应首页,或进入信息编辑页面
         setTimeout(() => {
           if (res.data.needsRoleSelect) {
-            // 新用户需要选择角色
             uni.redirectTo({
               url: `/pages/user-edit/user-edit?token=${res.data.token}&userId=${res.data.userId}&needsRoleSelect=true`
             })
           } else {
-            // 老用户直接进入首页
-            this.navigateToHome(res.data.role)
+            this.handlePostLoginRouting()
           }
         }, 1000)
       } catch (e) {
@@ -272,13 +270,11 @@ async handleWechatPhoneLogin(e) {
           // 根据角色跳转到对应首页,或进入信息编辑页面
           setTimeout(() => {
             if (res.data.needsRoleSelect) {
-              // 新用户需要选择角色
               uni.redirectTo({
                 url: `/pages/user-edit/user-edit?token=${res.data.token}&userId=${res.data.userId}&needsRoleSelect=true`
               })
             } else {
-              // 老用户直接进入首页
-              this.navigateToHome(res.data.role)
+              this.handlePostLoginRouting()
             }
           }, 1000)
         } catch (e) {
@@ -288,14 +284,77 @@ async handleWechatPhoneLogin(e) {
         }
     }
     },
-    navigateToHome(role) {
+    handlePostLoginRouting() {
+      const inviteCode = uni.getStorageSync('inviteCode')
+      const inviteType = uni.getStorageSync('inviteType')
+
+      if (inviteCode && inviteType === 'team') {
+        this.handleTeamInviteFlow(inviteCode)
+      } else {
+        this.navigateToHome()
+      }
+    },
+    async handleTeamInviteFlow(inviteCode) {
+      try {
+        const res = await checkTeamInviteStatus(inviteCode)
+        const status = res.data.status
+
+        if (status === 'not_planner') {
+          uni.redirectTo({ url: `/pages/guide/register/index?inviteCode=${inviteCode}` })
+        } else if (status === 'not_in_team') {
+          uni.showModal({
+            title: '加入团队',
+            content: '是否加入该规划师团队?',
+            success: async (confirmRes) => {
+              if (confirmRes.confirm) {
+                try {
+                  await joinTeamByInvite(inviteCode)
+                  uni.showToast({ title: '加入成功', icon: 'success' })
+                  uni.removeStorageSync('inviteCode')
+                  uni.removeStorageSync('inviteType')
+                  this.navigateToHome()
+                } catch (e) {
+                  uni.showToast({ title: e.message || '加入失败', icon: 'none' })
+                }
+              } else {
+                this.navigateToHome()
+              }
+            }
+          })
+        } else if (status === 'already_in_team') {
+          uni.showModal({
+            title: '加入新团队',
+            content: '您已在其他团队中,离开当前团队并加入新团队?',
+            success: async (confirmRes) => {
+              if (confirmRes.confirm) {
+                try {
+                  await joinTeamByInvite(inviteCode)
+                  uni.showToast({ title: '加入成功', icon: 'success' })
+                  uni.removeStorageSync('inviteCode')
+                  uni.removeStorageSync('inviteType')
+                  this.navigateToHome()
+                } catch (e) {
+                  uni.showToast({ title: e.message || '加入失败', icon: 'none' })
+                }
+              } else {
+                this.navigateToHome()
+              }
+            }
+          })
+        } else {
+          this.navigateToHome()
+        }
+      } catch (e) {
+        uni.showToast({ title: e.message || '处理邀请失败', icon: 'none' })
+        this.navigateToHome()
+      }
+    },
+    navigateToHome() {
       if (this.redirectUrl) {
-        // 有 redirect 则跳回来源页(登录后已恢复原生 TabBar)
         const redirect = this.redirectUrl
         this.redirectUrl = ''
         uni.reLaunch({ url: redirect })
       } else {
-        // 默认通过 TabBar 首页跳转
         uni.reLaunch({ url: '/pages/index/index' })
       }
     }

+ 157 - 21
cfc-frontend/pages/membership/index.vue

@@ -1,10 +1,15 @@
 <template>
-  <view class="container">
+  <scroll-view class="container" scroll-y>
     <!-- 当前会员状态 -->
     <view class="membership-card">
       <view class="card-header">
-        <text class="card-title">我的会员</text>
-        <text class="card-status" :class="currentLevel.levelCode">{{ currentLevel.levelName }}</text>
+        <view class="card-header-left">
+          <text class="card-title">我的会员</text>
+          <text class="card-subtitle">Membership</text>
+        </view>
+        <text class="card-level-badge" :class="currentLevel.levelCode === 'FAMILY' ? 'badge-family' : 'badge-free'">
+          {{ currentLevel.levelName || '免费用户' }}
+        </text>
       </view>
       <view class="card-body" v-if="membership">
         <view class="expiry-info">
@@ -22,12 +27,35 @@
       <view class="card-body" v-else>
         <text class="free-tip">您当前为免费版,升级享受更多服务</text>
       </view>
-      <button class="btn-upgrade" @click="goToUpgrade">立即升级</button>
+      <button class="btn-upgrade" @click="goToUpgrade">
+        {{ currentLevel.levelCode === 'FAMILY' ? '续费升级' : '立即升级' }}
+      </button>
+    </view>
+
+    <!-- 会员权益亮点 -->
+    <view class="highlights-section">
+      <text class="section-title">会员特权</text>
+      <view class="highlights-grid">
+        <view class="highlight-item" v-for="(item, idx) in highlights" :key="idx">
+          <view class="highlight-icon" :style="'background:' + item.bg">
+            <text class="highlight-emoji">{{ item.icon }}</text>
+          </view>
+          <text class="highlight-name">{{ item.name }}</text>
+          <text class="highlight-desc">{{ item.desc }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 开通CTA -->
+    <view class="cta-section" v-if="!membership || !membership.isActive">
+      <text class="cta-title">开通家庭会员,享受全部特权</text>
+      <text class="cta-price">¥365/年</text>
+      <button class="cta-btn" @click="goToUpgrade">立即开通</button>
     </view>
 
     <!-- 会员权益 -->
     <view class="benefits-section">
-      <text class="section-title">会员权益</text>
+      <text class="section-title">会员等级</text>
       <view class="benefits-grid">
         <view class="benefit-item" v-for="level in levels" :key="level.levelCode">
           <view class="benefit-header">
@@ -61,7 +89,7 @@
         <text class="desc">联系老师获取授权后,可查看孩子任务执行情况</text>
       </view>
     </view>
-  </view>
+  </scroll-view>
 </template>
 
 <script>
@@ -73,7 +101,13 @@ export default {
       levels: [],
       membership: null,
       currentLevel: {},
-      authorizations: []
+      authorizations: [],
+      highlights: [
+        { icon: '🛒', name: '折扣购物', desc: '商品会员专享价', bg: '#FEF3C7' },
+        { icon: '👨‍👩‍👧‍👦', name: '家庭管理', desc: '多成员统一管理', bg: '#DBEAFE' },
+        { icon: '💰', name: '推荐佣金', desc: '分享赚取佣金', bg: '#D1FAE5' },
+        { icon: '🎪', name: '专属活动', desc: '会员优先参与', bg: '#FCE7F3' }
+      ]
     }
   },
   onShow() {
@@ -92,17 +126,19 @@ export default {
         
         // 确定当前等级
         if (this.membership && this.membership.levelCode) {
-          this.currentLevel = this.levels.find(l => l.levelCode === this.membership.levelCode) || {}
+          this.currentLevel = this.levels.find(function(l) { return l.levelCode === this.membership.levelCode }.bind(this)) || {}
         } else {
-          this.currentLevel = this.levels.find(l => l.levelCode === 'FREE') || {}
+          this.currentLevel = this.levels.find(function(l) { return l.levelCode === 'FREE' }) || {}
         }
         
         // 获取授权列表
         const authRes = await getAuthorizations()
-        this.authorizations = (authRes.data || []).map(a => ({
-          ...a,
-          statusText: a.status === 'active' ? '已授权' : '已撤销'
-        }))
+        const authData = authRes.data || []
+        this.authorizations = authData.map(function(a) {
+          return Object.assign({}, a, {
+            statusText: a.status === 'active' ? '已授权' : '已撤销'
+          })
+        })
       } catch (e) {
         console.error('加载数据失败', e)
       }
@@ -110,13 +146,13 @@ export default {
     formatDate(date) {
       if (!date) return ''
       const d = new Date(date)
-      return `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}`
+      return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0')
     },
     parseFeatures(features) {
       if (!features) return []
       try {
         return JSON.parse(features)
-      } catch {
+      } catch (e) {
         return []
       }
     },
@@ -129,13 +165,12 @@ export default {
       uni.showModal({
         title: '撤销授权',
         content: '确定要撤销该老师的查看权限吗?',
-        success: (res) => {
+        success: function(res) {
           if (res.confirm) {
-            // TODO: 调用撤销API
             uni.showToast({ title: '已撤销', icon: 'success' })
             this.loadData()
           }
-        }
+        }.bind(this)
       })
     }
   }
@@ -160,20 +195,40 @@ export default {
   align-items: center;
 }
 
+.card-header-left {
+  display: flex;
+  flex-direction: column;
+}
+
 .card-title {
   font-size: 32rpx;
   color: #fff;
   font-weight: bold;
 }
 
-.card-status {
-  font-size: 24rpx;
+.card-subtitle {
+  font-size: 20rpx;
+  color: rgba(255,255,255,0.6);
+  margin-top: 4rpx;
+}
+
+.card-level-badge {
+  font-size: 22rpx;
   padding: 8rpx 20rpx;
-  border-radius: 20rpx;
+  border-radius: 9999rpx;
+  font-weight: 600;
+}
+
+.badge-free {
   background: rgba(255,255,255,0.2);
   color: #fff;
 }
 
+.badge-family {
+  background: #FEF3C7;
+  color: #D97706;
+}
+
 .card-body {
   margin-top: 30rpx;
 }
@@ -360,4 +415,85 @@ export default {
   margin-top: 10rpx;
   display: block;
 }
+
+/* ===== 权益亮点 ===== */
+.highlights-section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+.highlights-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+.highlight-item {
+  width: calc(50% - 10rpx);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 24rpx 16rpx;
+  background: #FFF7ED;
+  border-radius: 16rpx;
+  box-sizing: border-box;
+}
+.highlight-icon {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 12rpx;
+}
+.highlight-emoji {
+  font-size: 36rpx;
+}
+.highlight-name {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1E293B;
+  margin-bottom: 4rpx;
+}
+.highlight-desc {
+  font-size: 22rpx;
+  color: #94A3B8;
+}
+
+/* ===== CTA开通 ===== */
+.cta-section {
+  background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
+  border-radius: 20rpx;
+  padding: 40rpx;
+  text-align: center;
+  margin-bottom: 30rpx;
+}
+.cta-title {
+  font-size: 30rpx;
+  color: #fff;
+  font-weight: 600;
+  display: block;
+}
+.cta-price {
+  font-size: 44rpx;
+  color: #FEF3C7;
+  font-weight: bold;
+  margin: 16rpx 0 24rpx;
+  display: block;
+}
+.cta-btn {
+  width: 280rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  background: #fff;
+  color: #F97316;
+  font-size: 28rpx;
+  font-weight: 600;
+  border-radius: 36rpx;
+  border: none;
+}
+.cta-btn::after {
+  border: none;
+}
 </style>

+ 461 - 0
cfc-frontend/pages/membership/upgrade.vue

@@ -0,0 +1,461 @@
+<template>
+  <scroll-view class="page" scroll-y>
+    <!-- 当前会员状态卡片 -->
+    <view class="status-card">
+      <view class="status-badge" :class="isFree ? 'badge-free' : 'badge-family'">
+        <text class="badge-icon">{{ isFree ? '○' : '●' }}</text>
+        <text class="badge-text">{{ currentLevelName }}</text>
+      </view>
+      <view class="status-body" v-if="membership && membership.levelCode !== 'FREE'">
+        <view class="expiry-row">
+          <text class="expiry-label">到期时间</text>
+          <text class="expiry-value">{{ formatDate(membership.endDate) }}</text>
+        </view>
+        <view class="days-remaining" v-if="membership.isActive">
+          <text class="days-num">{{ membership.daysRemaining || 0 }}</text>
+          <text class="days-unit">天剩余</text>
+        </view>
+        <view class="expired-msg" v-else>
+          <text class="expired-text">会员已过期,续费恢复权益</text>
+        </view>
+      </view>
+      <view class="status-body" v-else>
+        <text class="free-hint">当前为免费版,开通家庭会员解锁全部特权</text>
+      </view>
+    </view>
+
+    <!-- 家庭会员开通卡片 -->
+    <view class="upgrade-card" v-if="isFree">
+      <view class="upgrade-header">
+        <text class="upgrade-title">家庭会员</text>
+        <text class="upgrade-subtitle">FAMILY</text>
+      </view>
+      <view class="upgrade-price">
+        <text class="price-symbol">¥</text>
+        <text class="price-amount">365</text>
+        <text class="price-unit">/年</text>
+      </view>
+      <view class="feature-list">
+        <view class="feature-item" v-for="(feature, idx) in features" :key="idx">
+          <text class="feature-check">✓</text>
+          <text class="feature-text">{{ feature }}</text>
+        </view>
+      </view>
+      <button class="btn-upgrade" @click="handleUpgrade">立即开通</button>
+    </view>
+
+    <!-- 续费卡片 -->
+    <view class="upgrade-card" v-else>
+      <view class="upgrade-header">
+        <text class="upgrade-title">续费会员</text>
+        <text class="upgrade-subtitle">保持权益不中断</text>
+      </view>
+      <view class="upgrade-price">
+        <text class="price-symbol">¥</text>
+        <text class="price-amount">365</text>
+        <text class="price-unit">/年</text>
+      </view>
+      <button class="btn-upgrade btn-renew" @click="handleRenew">立即续费</button>
+    </view>
+
+    <!-- 权益对比表 -->
+    <view class="compare-section">
+      <text class="section-title">权益对比</text>
+      <view class="compare-table">
+        <view class="compare-row header-row">
+          <text class="compare-cell cell-feature">权益项</text>
+          <text class="compare-cell cell-free">免费</text>
+          <text class="compare-cell cell-family active">家庭会员</text>
+        </view>
+        <view class="compare-row" v-for="(item, idx) in compareData" :key="idx">
+          <text class="compare-cell cell-feature">{{ item.name }}</text>
+          <text class="compare-cell cell-free">{{ item.free }}</text>
+          <text class="compare-cell cell-family active">{{ item.family }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 升级记录 -->
+    <view class="records-section" v-if="records.length > 0">
+      <text class="section-title">升级记录</text>
+      <view class="record-item" v-for="record in records" :key="record.id">
+        <view class="record-left">
+          <text class="record-level">{{ record.levelName }}</text>
+          <text class="record-time">{{ formatDate(record.createdAt) }}</text>
+        </view>
+        <text class="record-amount">¥{{ record.amount || '0.00' }}</text>
+      </view>
+    </view>
+  </scroll-view>
+</template>
+
+<script>
+import { getMyMembership, getMembershipLevels, getUpgradeRecords, upgradeMember, createOrder } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      membership: null,
+      levels: [],
+      records: [],
+      features: ['折扣购物', '家庭管理', '推荐佣金', '专属活动', '任务特权', '成长报告'],
+      isFree: true,
+      currentLevelName: '免费用户',
+      compareData: [
+        { name: '商品折扣', free: '普通价格', family: '会员专享价' },
+        { name: '家庭管理', free: '基础功能', family: '全部功能' },
+        { name: '推荐佣金', free: '不可参与', family: '最高15%' },
+        { name: '专属活动', free: '部分参与', family: '全部参与' },
+        { name: '成长报告', free: '月度', family: '每周+深度' },
+        { name: '任务配额', free: '每日5个', family: '不限量' }
+      ]
+    }
+  },
+  onLoad() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      try {
+        const membershipRes = await getMyMembership()
+        this.membership = membershipRes.data
+        if (this.membership && this.membership.levelCode && this.membership.levelCode !== 'FREE') {
+          this.isFree = false
+          this.currentLevelName = this.membership.levelName || '家庭会员'
+        } else {
+          this.isFree = true
+          this.currentLevelName = '免费用户'
+        }
+      } catch (e) {
+        console.error('加载会员信息失败', e)
+      }
+      try {
+        const levelsRes = await getMembershipLevels()
+        this.levels = levelsRes.data || []
+      } catch (e) {
+        console.error('加载会员等级失败', e)
+      }
+      try {
+        const recordsRes = await getUpgradeRecords()
+        if (recordsRes.data && recordsRes.data.records) {
+          this.records = recordsRes.data.records
+        }
+      } catch (e) {
+        console.error('加载升级记录失败', e)
+      }
+    },
+    handleUpgrade() {
+      uni.showModal({
+        title: '确认开通',
+        content: '确认开通家庭会员(¥365/年)?',
+        success: async (res) => {
+          if (!res.confirm) return
+          try {
+            await upgradeMember('FAMILY')
+            uni.showToast({ title: '开通成功', icon: 'success' })
+            this.loadData()
+          } catch (e) {
+            uni.showToast({ title: e.message || '开通失败', icon: 'none' })
+          }
+        }
+      })
+    },
+    handleRenew() {
+      uni.showModal({
+        title: '确认续费',
+        content: '确认续费家庭会员(¥365/年)?',
+        success: async (res) => {
+          if (!res.confirm) return
+          try {
+            await upgradeMember('FAMILY')
+            uni.showToast({ title: '续费成功', icon: 'success' })
+            this.loadData()
+          } catch (e) {
+            uni.showToast({ title: e.message || '续费失败', icon: 'none' })
+          }
+        }
+      })
+    },
+    formatDate(date) {
+      if (!date) return ''
+      var d = new Date(date)
+      return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.page {
+  min-height: 100vh;
+  background: #FFF7ED;
+  padding: 30rpx;
+  box-sizing: border-box;
+}
+
+/* ===== 状态卡片 ===== */
+.status-card {
+  background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 30rpx;
+}
+.status-badge {
+  display: inline-flex;
+  align-items: center;
+  padding: 10rpx 24rpx;
+  border-radius: 9999rpx;
+  font-size: 24rpx;
+}
+.badge-free {
+  background: rgba(255,255,255,0.2);
+  color: #fff;
+}
+.badge-family {
+  background: rgba(255,255,255,0.3);
+  color: #fff;
+}
+.badge-icon {
+  font-size: 20rpx;
+  margin-right: 8rpx;
+}
+.badge-text {
+  font-weight: 600;
+}
+.status-body {
+  margin-top: 24rpx;
+}
+.expiry-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.expiry-label {
+  font-size: 26rpx;
+  color: rgba(255,255,255,0.8);
+}
+.expiry-value {
+  font-size: 28rpx;
+  color: #fff;
+  font-weight: 500;
+}
+.days-remaining {
+  display: flex;
+  align-items: baseline;
+  justify-content: center;
+  margin-top: 20rpx;
+}
+.days-num {
+  font-size: 56rpx;
+  font-weight: bold;
+  color: #FEF3C7;
+}
+.days-unit {
+  font-size: 26rpx;
+  color: rgba(255,255,255,0.7);
+  margin-left: 8rpx;
+}
+.expired-msg {
+  text-align: center;
+  margin-top: 16rpx;
+}
+.expired-text {
+  font-size: 26rpx;
+  color: #FEF3C7;
+}
+.free-hint {
+  font-size: 26rpx;
+  color: rgba(255,255,255,0.85);
+  text-align: center;
+  display: block;
+}
+
+/* ===== 升级/续费卡片 ===== */
+.upgrade-card {
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 24rpx rgba(249, 115, 22, 0.08);
+}
+.upgrade-header {
+  text-align: center;
+  margin-bottom: 20rpx;
+}
+.upgrade-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #1E293B;
+  display: block;
+}
+.upgrade-subtitle {
+  font-size: 24rpx;
+  color: #94A3B8;
+  margin-top: 6rpx;
+  display: block;
+}
+.upgrade-price {
+  text-align: center;
+  margin: 24rpx 0;
+}
+.price-symbol {
+  font-size: 28rpx;
+  color: #F97316;
+  vertical-align: top;
+}
+.price-amount {
+  font-size: 64rpx;
+  font-weight: bold;
+  color: #F97316;
+  line-height: 1;
+}
+.price-unit {
+  font-size: 26rpx;
+  color: #94A3B8;
+  margin-left: 6rpx;
+}
+.feature-list {
+  margin: 24rpx 0;
+}
+.feature-item {
+  display: flex;
+  align-items: center;
+  padding: 12rpx 0;
+}
+.feature-check {
+  width: 40rpx;
+  height: 40rpx;
+  line-height: 40rpx;
+  text-align: center;
+  background: #F97316;
+  color: #fff;
+  border-radius: 50%;
+  font-size: 22rpx;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+.feature-text {
+  font-size: 28rpx;
+  color: #1E293B;
+}
+.btn-upgrade {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: 600;
+  border-radius: 44rpx;
+  margin-top: 20rpx;
+  border: none;
+}
+.btn-upgrade::after {
+  border: none;
+}
+.btn-upgrade:active {
+  opacity: 0.9;
+}
+.btn-renew {
+  background: linear-gradient(135deg, #0EA5E9 0%, #0284C7 100%);
+}
+
+/* ===== 权益对比 ===== */
+.compare-section {
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 24rpx rgba(249, 115, 22, 0.08);
+}
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #1E293B;
+  display: block;
+  margin-bottom: 24rpx;
+}
+.compare-table {
+  border-radius: 16rpx;
+  overflow: hidden;
+  border: 1rpx solid #FED7AA;
+}
+.compare-row {
+  display: flex;
+  border-bottom: 1rpx solid #FED7AA;
+}
+.compare-row:last-child {
+  border-bottom: none;
+}
+.header-row {
+  background: #FFF7ED;
+}
+.compare-cell {
+  flex: 1;
+  padding: 20rpx 16rpx;
+  font-size: 26rpx;
+  text-align: center;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.cell-feature {
+  flex: 1.5;
+  color: #1E293B;
+  font-weight: 500;
+  justify-content: flex-start;
+}
+.cell-free {
+  color: #94A3B8;
+}
+.cell-family.active {
+  color: #F97316;
+  font-weight: 600;
+}
+.header-row .compare-cell {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #64748B;
+}
+.header-row .cell-family.active {
+  color: #F97316;
+}
+
+/* ===== 升级记录 ===== */
+.records-section {
+  background: #FFFFFF;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 4rpx 24rpx rgba(249, 115, 22, 0.08);
+}
+.record-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #FED7AA;
+}
+.record-item:last-child {
+  border-bottom: none;
+}
+.record-left {
+  flex: 1;
+}
+.record-level {
+  font-size: 28rpx;
+  color: #1E293B;
+  display: block;
+}
+.record-time {
+  font-size: 22rpx;
+  color: #94A3B8;
+  margin-top: 4rpx;
+  display: block;
+}
+.record-amount {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #F97316;
+}
+</style>

+ 6 - 42
cfc-frontend/pages/mind/articles.vue

@@ -1,13 +1,9 @@
 <template>
   <view class="container">
     <!-- 品牌头部 -->
-    <view class="brand-header">
-      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
-      <view class="brand-text-wrap">
-        <text class="brand-title">心智成长</text>
-        <text class="brand-subtitle">探索内心世界,培养健康心智</text>
-      </view>
-    </view>
+    <PageBanner theme="mind-wisdom"
+      tagline="探索内心世界,培养健康心智"
+      quote="知己知彼,百战不殆" />
 
     <!-- 分类筛选 -->
     <view class="filter-section">
@@ -74,17 +70,16 @@
       <view class="bottom-spacer"></view>
     </scroll-view>
 
-    <!-- 自定义底栏(3个tab:首页/行动/心智) -->
-    <bottom-nav v-if="!isLoggedIn" current="mind" />
+
   </view>
 </template>
 
 <script>
-import BottomNav from '../../components/bottom-nav.vue'
+import PageBanner from '../../components/PageBanner.vue'
 import { getArticleCategories, getArticleList } from '../../utils/api.js'
 
 export default {
-  components: { BottomNav },
+  components: { PageBanner },
   data() {
     return {
       categoryList: [{ id: '', name: '全部' }],
@@ -202,37 +197,6 @@ export default {
   background: #f5f7fa;
 }
 
-/* 品牌头部 */
-.brand-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding: 24rpx 30rpx 16rpx;
-  background: linear-gradient(180deg, #EBF2FA 0%, #f5f9fc 100%);
-}
-.brand-logo {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: 12rpx;
-  flex-shrink: 0;
-}
-.brand-text-wrap {
-  display: flex;
-  flex-direction: column;
-  margin-left: 14rpx;
-}
-.brand-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #2C5282;
-  letter-spacing: 2rpx;
-}
-.brand-subtitle {
-  font-size: 20rpx;
-  color: #5B9BD5;
-  margin-top: 2rpx;
-}
-
 /* 分类筛选 */
 .filter-section {
   background: #fff;

+ 361 - 64
cfc-frontend/pages/mind/index.vue

@@ -1,16 +1,51 @@
 <template>
   <view class="container">
     <!-- 品牌头部 -->
-    <view class="brand-header">
-      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
-      <text class="brand-title">心智成长</text>
-      <text class="brand-slogan">探索内心世界,培养健康心智</text>
+    <PageBanner theme="mind-wisdom"
+      tagline="心智成长 · 智慧人生"
+      quote="知己知彼,百战不殆" />
+
+    <!-- ===== 六维雷达图(登录后可见) ===== -->
+    <view class="radar-section" v-if="isLoggedIn && familyMembers.length > 0">
+      <!-- 家庭成员切换 -->
+      <scroll-view class="member-scroll" scroll-x show-scrollbar="false">
+        <view class="member-row">
+          <view
+            v-for="member in familyMembers"
+            :key="member.childId"
+            :class="['member-tab', member.childId === selectedChildId ? 'active' : '']"
+            @click="selectMember(member.childId)"
+          >
+            <text class="member-name">{{ member.childName }}</text>
+            <text class="member-overall">{{ member.overallScore || 0 }}分</text>
+          </view>
+        </view>
+      </scroll-view>
+
+      <!-- 雷达图 -->
+      <view class="radar-card">
+        <RadarChart
+          :dimensions="dimensions"
+          :scores="currentScores"
+          :childName="currentChildName"
+          @dimensionClick="goTraining"
+        />
+        <text class="radar-hint">点击维度标签进入能力训练</text>
+      </view>
+    </view>
+
+    <!-- 未登录雷达图占位 -->
+    <view class="radar-placeholder" v-else-if="!isLoggedIn">
+      <view class="placeholder-icon">&#x1F9E0;</view>
+      <text class="placeholder-title">认知能力评估</text>
+      <text class="placeholder-desc">登录后查看六维认知雷达图,了解孩子的感知、专注、记忆等能力发展</text>
+      <view class="placeholder-btn" @click="goLogin">立即登录</view>
     </view>
 
     <!-- 功能入口 -->
     <view class="func-section" v-if="sectionVisible('func_entries')">
       <view class="func-grid">
-        <view class="func-item" v-for="item in funcList" :key="item.label">
+        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
           <view class="func-icon-wrap">
             <text class="func-icon">{{ item.icon }}</text>
           </view>
@@ -50,11 +85,11 @@
           <text class="article-summary">{{ article.summary }}</text>
           <view class="article-meta">
             <view class="meta-item">
-              <text class="meta-icon">📖</text>
+              <text class="meta-icon">&#x1F4D6;</text>
               <text class="meta-text">{{ article.views }}</text>
             </view>
             <view class="meta-item">
-              <text class="meta-icon">👍</text>
+              <text class="meta-icon">&#x1F44D;</text>
               <text class="meta-text">{{ article.likes }}</text>
             </view>
             <text class="article-date">{{ article.date }}</text>
@@ -63,13 +98,39 @@
       </view>
     </view>
 
-    <!-- 会员专属内容(后台可配置,登录后可见) -->
-    <view class="section" v-if="sectionVisible('premium_content')">
+    <!-- 心智任务 -->
+    <view class="section" v-if="sectionVisible('mind_tasks') && isLoggedIn">
+      <view class="section-header">
+        <text class="section-title">&#x1F9E0; 心智任务</text>
+      </view>
+      <view class="task-list" v-if="mindTasks.length > 0">
+        <view class="task-card" v-for="task in mindTasks" :key="task.id">
+          <text class="task-title">{{ task.title }}</text>
+          <text class="task-status">{{ task.status === 'completed' ? '已完成' : '进行中' }}</text>
+        </view>
+      </view>
+      <view class="empty-placeholder" v-else>
+        <text class="empty-text">暂无相关任务</text>
+      </view>
+    </view>
+
+    <!-- 相关活动 -->
+    <view class="section" v-if="sectionVisible('related_activities') && isLoggedIn">
+      <view class="section-header">
+        <text class="section-title">&#x1F3C3; 相关活动</text>
+      </view>
+      <view class="empty-placeholder">
+        <text class="empty-text">暂无推荐活动</text>
+      </view>
+    </view>
+
+    <!-- 已购课程 -->
+    <view class="section" v-if="sectionVisible('purchased_courses') && isLoggedIn">
       <view class="section-header">
-        <text class="section-title">👑 会员专属</text>
+        <text class="section-title">&#x1F4DA; 已购课程</text>
       </view>
-      <view class="premium-card">
-        <text class="premium-text">深度心智测评报告、个性化成长方案等会员专属内容</text>
+      <view class="empty-placeholder">
+        <text class="empty-text">暂无已购课程</text>
       </view>
     </view>
 
@@ -79,57 +140,157 @@
 </template>
 
 <script>
-import { getVisibleSections, getFeaturedArticles } from '../../utils/api.js'
+import PageBanner from '../../components/PageBanner.vue'
+import RadarChart from '../../components/RadarChart.vue'
+import { getVisibleSections, getFeaturedArticles, getEnergyOverview } from '../../utils/api.js'
+
+var BASE_URL = 'http://localhost:9082'
+try {
+  var accountInfo = uni.getAccountInfoSync()
+  var env = accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.envVersion
+  if (env === 'trial') BASE_URL = 'https://cfc.iwintrue.com'
+  else if (env === 'release') BASE_URL = 'https://cfc.etotem.com.cn'
+} catch (_e) {}
 
 export default {
+  components: { PageBanner, RadarChart },
   data() {
     return {
+      isLoggedIn: false,
+      role: null,
+      currentChildId: null,
       visibleSections: [],
+
+      // 六维雷达图
+      dimensions: [
+        { label: '感知', key: 'perception' },
+        { label: '专注', key: 'focus' },
+        { label: '记忆', key: 'memory' },
+        { label: '逻辑', key: 'logic' },
+        { label: '空间', key: 'spatial' },
+        { label: '加工速度', key: 'processingSpeed' }
+      ],
+      familyMembers: [],
+      selectedChildId: null,
+      currentScores: [0, 0, 0, 0, 0, 0],
+      currentChildName: '',
+
       funcList: [
-        { icon: '\u{1F4D6}', label: '阅读' },
-        { icon: '\u{1F9E9}', label: '认知' },
-        { icon: '\u{1F4AA}', label: '专注' },
-        { icon: '\u{1F3AF}', label: '决策' },
-        { icon: '\u{1F31F}', label: '心智测评' }
+        { icon: '\u{1F4D6}', label: '阅读', needLogin: false, page: '' },
+        { icon: '\u{1F9E9}', label: '认知', needLogin: false, page: '' },
+        { icon: '\u{1F4AA}', label: '专注', needLogin: false, page: '' },
+        { icon: '\u{1F3AF}', label: '决策', needLogin: false, page: '' },
+        { icon: '\u{1F31F}', label: '心智测评', needLogin: true, page: '' }
       ],
       dailyTip: {
         title: '成长型思维',
         content: '拥有成长型思维的孩子相信自己的能力可以通过努力提升,他们更愿意接受挑战,从失败中学习。作为家长,多鼓励孩子的努力而非天赋,可以帮助孩子建立成长型思维。',
         source: '—— 卡罗尔·德韦克《终身成长》'
       },
-      articles: []
+      articles: [],
+      mindTasks: []
     }
   },
   onShow() {
-    this.resetTabBar()
+    var token = uni.getStorageSync('token')
+    this.isLoggedIn = !!token
+    if (this.isLoggedIn) {
+      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      this.currentChildId = uni.getStorageSync('currentChildId') || null
+    }
     this.loadSectionConfig()
     this.loadFeaturedArticles()
+    if (this.isLoggedIn) {
+      this.loadEnergyData()
+      this.loadFamilyCognitiveData()
+    }
   },
   methods: {
-    resetTabBar() {
-      uni.setTabBarItem({ index: 1, text: '身体' })
-      uni.setTabBarItem({ index: 2, text: '心智' })
-    },
+    // ===== 区块可见性 =====
     async loadSectionConfig() {
-      var token = uni.getStorageSync('token')
-      var role = null
-      if (token) {
-        role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      if (!uni.getStorageSync('token')) {
+        this.visibleSections = ['func_entries', 'daily_tip', 'recommended_reading']
+        return
       }
-      var res
+      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
       try {
-        res = await getVisibleSections({ pageKey: 'mind', role: role })
+        var res = await getVisibleSections({ pageKey: 'mind', role: role })
         if (res.code === 200 && res.data) {
           this.visibleSections = res.data.map(function(s) { return s.sectionKey })
         }
       } catch (e) {
-        // 默认显示所有公开区块
-        this.visibleSections = ['func_entries', 'daily_tip', 'recommended_reading']
+        this.visibleSections = ['func_entries', 'daily_tip', 'recommended_reading', 'mind_tasks', 'related_activities', 'purchased_courses']
       }
     },
-    sectionVisible(sectionKey) {
-      return this.visibleSections.indexOf(sectionKey) !== -1
+    sectionVisible(key) {
+      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
     },
+
+    // ===== 六维认知数据 =====
+    async loadFamilyCognitiveData() {
+      var self = this
+      var url = BASE_URL + '/api/cognitive/family'
+      var token = uni.getStorageSync('token')
+      var userId = uni.getStorageSync('userId') || ''
+      try {
+        var res = await new Promise(function(resolve, reject) {
+          uni.request({
+            url: url,
+            method: 'POST',
+            header: {
+              'Content-Type': 'application/json',
+              'Authorization': 'Bearer ' + token,
+              'X-User-Id': userId
+            },
+            success: function(r) { resolve(r) },
+            fail: function(e) { reject(e) }
+          })
+        })
+        if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data) {
+          var data = res.data.data
+          if (Array.isArray(data) && data.length > 0) {
+            self.familyMembers = data
+            var first = data[0]
+            self.selectedChildId = first.childId
+            self.updateCurrentScores(first)
+          }
+        } else if (res.data && res.data.code === 200) {
+          // empty array
+          self.familyMembers = []
+        }
+      } catch (e) {
+        console.log('获取认知数据失败', e)
+      }
+    },
+    selectMember(childId) {
+      if (childId === this.selectedChildId) return
+      this.selectedChildId = childId
+      var member = this.familyMembers.find(function(m) { return m.childId === childId })
+      if (member) {
+        this.updateCurrentScores(member)
+      }
+    },
+    updateCurrentScores(member) {
+      this.currentChildName = member.childName || ''
+      this.currentScores = [
+        member.perceptionScore || 0,
+        member.focusScore || 0,
+        member.memoryScore || 0,
+        member.logicScore || 0,
+        member.spatialScore || 0,
+        member.processingSpeedScore || 0
+      ]
+    },
+    goTraining(dimensionKey) {
+      if (!this.selectedChildId) {
+        uni.showToast({ title: '请先选择家庭成员', icon: 'none' })
+        return
+      }
+      var url = '/pages/mind/training?dimension=' + dimensionKey + '&childId=' + this.selectedChildId
+      uni.navigateTo({ url: url })
+    },
+
+    // ===== 文章 =====
     async loadFeaturedArticles() {
       try {
         const res = await getFeaturedArticles({ size: 5 })
@@ -155,9 +316,32 @@ export default {
             }
           })
         }
-      } catch (e) {
-        // 静默处理,保持空列表
+      } catch (e) {}
+    },
+
+    // ===== 能量数据 =====
+    async loadEnergyData() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getEnergyOverview(childId)
+        if (res && res.data) {
+          // energy data loaded (no longer used in UI but kept for compatibility)
+        }
+      } catch (e) { console.log('获取能量概览失败', e) }
+    },
+
+    // ===== 功能入口点击 =====
+    onFuncClick(item) {
+      if (item.needLogin && !this.isLoggedIn) {
+        uni.showModal({
+          title: '提示',
+          content: '请先登录后使用此功能',
+          success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
+        })
+        return
       }
+      if (item.page) uni.navigateTo({ url: item.page })
     },
     goArticleDetail(id) {
       var token = uni.getStorageSync('token')
@@ -166,7 +350,10 @@ export default {
       } else {
         uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/index') })
       }
-    }
+    },
+    goReportDetail() {},
+    goAssessment() { uni.navigateTo({ url: '/pages/assessment/index' }) },
+    goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
   }
 }
 </script>
@@ -178,33 +365,110 @@ export default {
   padding-bottom: 120rpx;
 }
 
-/* ===== 品牌头部 ===== */
-.brand-header {
+/* ===== 雷达图区域 ===== */
+.radar-section {
+  margin: 20rpx 30rpx;
+}
+
+/* 成员切换标签 */
+.member-scroll {
+  width: 100%;
+  margin-bottom: 16rpx;
+}
+.member-row {
   display: flex;
+  flex-direction: row;
+  padding: 4rpx 0;
+  white-space: nowrap;
+}
+.member-tab {
+  display: inline-flex;
   flex-direction: column;
   align-items: center;
-  padding: 30rpx 30rpx 16rpx;
-  background: linear-gradient(180deg, #1A2A4A 0%, #1E3A5F 60%, #f5f7fa 100%);
+  padding: 16rpx 32rpx;
+  margin-right: 16rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.05);
+  min-width: 120rpx;
+  flex-shrink: 0;
 }
-.brand-logo {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 18rpx;
-  margin-bottom: 8rpx;
+.member-tab.active {
+  background: linear-gradient(135deg, #FF6B35, #FFD700);
+  box-shadow: 0 4rpx 16rpx rgba(255,107,53,0.3);
+}
+.member-tab.active .member-name {
+  color: #fff;
+}
+.member-tab.active .member-overall {
+  color: rgba(255,255,255,0.9);
 }
-.brand-title {
-  font-size: 36rpx;
+.member-name {
+  font-size: 26rpx;
   font-weight: bold;
-  color: #8FC5E8;
-  letter-spacing: 4rpx;
+  color: #333;
+}
+.member-overall {
+  font-size: 20rpx;
+  color: #999;
+  margin-top: 4rpx;
 }
-.brand-slogan {
+
+/* 雷达图卡片 */
+.radar-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 30rpx 20rpx 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.radar-hint {
   font-size: 22rpx;
-  color: rgba(143, 197, 232, 0.6);
-  margin-top: 6rpx;
+  color: #bbb;
+  margin-top: 16rpx;
+}
+
+/* 雷达图未登录占位 */
+.radar-placeholder {
+  margin: 30rpx 30rpx;
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 60rpx 40rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.placeholder-icon {
+  font-size: 80rpx;
+  margin-bottom: 20rpx;
+}
+.placeholder-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+.placeholder-desc {
+  font-size: 26rpx;
+  color: #999;
+  text-align: center;
+  line-height: 1.6;
+  margin-bottom: 30rpx;
+}
+.placeholder-btn {
+  background: linear-gradient(135deg, #FF6B35, #FFD700);
+  color: #fff;
+  font-size: 28rpx;
+  font-weight: bold;
+  padding: 16rpx 64rpx;
+  border-radius: 40rpx;
+  box-shadow: 0 4rpx 16rpx rgba(255,107,53,0.3);
 }
 
-/* ===== 功能入口 ===== */
+/* ===== 功能入口 ===== */
 .func-section {
   margin: 20rpx 30rpx;
 }
@@ -241,6 +505,9 @@ export default {
   color: #666;
   font-weight: 500;
 }
+.func-item:active {
+  opacity: 0.7;
+}
 
 /* ===== 通用区块 ===== */
 .section {
@@ -360,21 +627,51 @@ export default {
   margin-left: auto;
 }
 
-/* ===== 会员专属 ===== */
-.premium-card {
-  background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
-  border-radius: 20rpx;
-  padding: 30rpx 24rpx;
-  text-align: center;
+/* ===== 任务列表 ===== */
+.task-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
 }
-.premium-text {
+.task-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx 20rpx;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.task-title {
   font-size: 26rpx;
-  color: #3A7CC4;
-  line-height: 1.5;
+  color: #333;
+  font-weight: 500;
+  flex: 1;
+}
+.task-status {
+  font-size: 22rpx;
+  color: #999;
+  margin-left: 16rpx;
+}
+
+/* ===== 空状态占位 ===== */
+.empty-placeholder {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 40rpx 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.empty-text {
+  font-size: 24rpx;
+  color: #ccc;
 }
 
 /* ===== 底部 ===== */
 .bottom-spacer {
   height: 120rpx;
 }
-</style>
+</style>

+ 289 - 0
cfc-frontend/pages/mind/training.vue

@@ -0,0 +1,289 @@
+<template>
+  <view class="container">
+    <PageBanner theme="mind-wisdom"
+      tagline="能力训练"
+      :quote="currentQuote" />
+
+    <!-- 训练描述卡片 -->
+    <view class="dimension-card" v-if="currentDim">
+      <view class="dimension-icon-wrap" :style="{ background: currentDim.lightColor }">
+        <text class="dimension-icon">{{ currentDim.icon }}</text>
+      </view>
+      <text class="dimension-name">{{ currentDim.name }}</text>
+      <text class="dimension-desc">{{ currentDim.desc }}</text>
+    </view>
+
+    <!-- 开始训练按钮 -->
+    <view class="action-section">
+      <button class="start-btn" :style="{ background: currentDim ? currentDim.color : '#8B5CF6' }"
+        :disabled="isTraining" @click="startTraining">
+        <text class="btn-text" v-if="!isTraining">开始训练</text>
+        <text class="btn-text" v-else>训练中...</text>
+      </button>
+    </view>
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import PageBanner from '../../components/PageBanner.vue'
+
+var BASE_URL = 'http://localhost:9082'
+
+var accountInfo = null
+try {
+  accountInfo = uni.getAccountInfoSync()
+} catch (_e) {}
+var env = accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.envVersion
+if (env) {
+  switch (env) {
+    case 'develop':
+      BASE_URL = 'http://localhost:9082'
+      break
+    case 'trial':
+      BASE_URL = 'https://cfc.iwintrue.com'
+      break
+    case 'release':
+      BASE_URL = 'https://cfc.etotem.com.cn'
+      break
+  }
+}
+
+var DIMENSION_MAP = {
+  perception: {
+    key: 'perception',
+    name: '感知训练',
+    icon: '\u{1F441}',
+    color: '#8B5CF6',
+    lightColor: '#EDE9FE',
+    desc: '通过图形匹配、颜色分辨等练习提升感知能力',
+    quote: '观千剑而后识器,操千曲而后晓声'
+  },
+  focus: {
+    key: 'focus',
+    name: '专注训练',
+    icon: '\u{1F3AF}',
+    color: '#F97316',
+    lightColor: '#FFF7ED',
+    desc: '通过舒尔特方格、数字追踪等练习提升专注力',
+    quote: '专心致志,惟弈秋之为听'
+  },
+  memory: {
+    key: 'memory',
+    name: '记忆训练',
+    icon: '\u{1F9E0}',
+    color: '#10B981',
+    lightColor: '#ECFDF5',
+    desc: '通过记忆卡片、数字记忆等练习提升记忆力',
+    quote: '温故而知新,可以为师矣'
+  },
+  logic: {
+    key: 'logic',
+    name: '逻辑训练',
+    icon: '\u{1F9E9}',
+    color: '#3B82F6',
+    lightColor: '#EFF6FF',
+    desc: '通过推理题、模式识别等练习提升逻辑思维',
+    quote: '学而不思则罔,思而不学则殆'
+  },
+  spatial: {
+    key: 'spatial',
+    name: '空间训练',
+    icon: '\u{1F9CA}',
+    color: '#8D6E63',
+    lightColor: '#EFEBE9',
+    desc: '通过空间旋转、立体构建等练习提升空间思维能力',
+    quote: '横看成岭侧成峰,远近高低各不同'
+  },
+  processingSpeed: {
+    key: 'processingSpeed',
+    name: '加工速度训练',
+    icon: '\u26A1',
+    color: '#FFD700',
+    lightColor: '#FFFBEB',
+    desc: '通过快速反应、速度测试等练习提升加工速度',
+    quote: '静如处子,动如脱兔'
+  }
+}
+
+export default {
+  components: { PageBanner },
+  data() {
+    return {
+      dimension: null,
+      childId: null,
+      isTraining: false
+    }
+  },
+  computed: {
+    currentDim: function() {
+      var dim = DIMENSION_MAP[this.dimension]
+      return dim || null
+    },
+    currentQuote: function() {
+      var dim = this.currentDim
+      return dim ? dim.quote : '知己知彼,百战不殆'
+    }
+  },
+  onLoad: function(options) {
+    if (options && options.dimension) {
+      this.dimension = options.dimension
+    }
+    if (options && options.childId) {
+      this.childId = options.childId
+    } else {
+      this.childId = uni.getStorageSync('currentChildId') || null
+    }
+    uni.setNavigationBarTitle({
+      title: this.currentDim ? this.currentDim.name : '能力训练'
+    })
+  },
+  methods: {
+    startTraining: function() {
+      if (this.isTraining) return
+      this.isTraining = true
+
+      var self = this
+      setTimeout(function() {
+        var newScore = Math.floor(Math.random() * 31) + 70
+        self.updateScore(newScore)
+      }, 2000)
+    },
+    updateScore: function(score) {
+      var self = this
+      var token = uni.getStorageSync('token')
+      var userId = uni.getStorageSync('userId') || ''
+
+      uni.request({
+        url: BASE_URL + '/api/cognitive/update',
+        method: 'POST',
+        data: {
+          childId: self.childId,
+          dimension: self.dimension + 'Score',
+          score: score
+        },
+        header: {
+          'Content-Type': 'application/json',
+          'X-User-Id': userId,
+          'Authorization': token ? 'Bearer ' + token : ''
+        },
+        success: function(res) {
+          self.isTraining = false
+          if (res.data && res.data.code === 200) {
+            uni.showToast({
+              title: '训练完成!',
+              icon: 'success'
+            })
+            setTimeout(function() {
+              uni.navigateBack({
+                delta: 1
+              })
+            }, 1500)
+          } else {
+            uni.showToast({
+              title: res.data && res.data.message ? res.data.message : '提交失败',
+              icon: 'none'
+            })
+          }
+        },
+        fail: function() {
+          self.isTraining = false
+          uni.showToast({
+            title: '网络请求失败,请重试',
+            icon: 'none'
+          })
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+/* ===== 训练描述卡片 ===== */
+.dimension-card {
+  margin: 30rpx;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx 32rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+
+.dimension-icon-wrap {
+  width: 120rpx;
+  height: 120rpx;
+  border-radius: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 20rpx;
+}
+
+.dimension-icon {
+  font-size: 56rpx;
+}
+
+.dimension-name {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 16rpx;
+}
+
+.dimension-desc {
+  font-size: 26rpx;
+  color: #888;
+  text-align: center;
+  line-height: 1.6;
+}
+
+/* ===== 按钮区域 ===== */
+.action-section {
+  margin: 20rpx 30rpx;
+}
+
+.start-btn {
+  width: 100%;
+  height: 96rpx;
+  border-radius: 48rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  border: none;
+  outline: none;
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: 600;
+  box-shadow: 0 4rpx 16rpx rgba(0,0,0,0.15);
+}
+
+.start-btn[disabled] {
+  opacity: 0.6;
+}
+
+.start-btn:active {
+  opacity: 0.85;
+}
+
+.btn-text {
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: 600;
+}
+
+/* ===== 底部 ===== */
+.bottom-spacer {
+  height: 120rpx;
+}
+</style>

+ 368 - 10
cfc-frontend/pages/profile/profile.vue

@@ -10,6 +10,11 @@
 
     <!-- ===== 已登录状态 ===== -->
     <template v-else>
+    <!-- 品牌头部 -->
+    <PageBanner theme="wealth"
+      tagline="财富管理 · 价值创造"
+      quote="富足源于利他与坚持" />
+
     <!-- 退出切换按钮(仅家长切换到孩子状态时显示) -->
     <view class="exit-switch" v-if="isSwitchedChild" @click="exitSwitch">
       <text>🔄 退出切换</text>
@@ -26,6 +31,103 @@
       <button class="btn-switch" v-if="!isSwitchedChild" @click="switchMode">切换</button>
     </view>
 
+    <!-- 行动数据 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">🎯 行动数据</text>
+      </view>
+      <view class="wealth-card">
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.taskCount }}</text>
+          <text class="wealth-label">完成任务</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.purchaseCount }}</text>
+          <text class="wealth-label">购买商品</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.activityCount }}</text>
+          <text class="wealth-label">参加活动</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ actionData.courseCount }}</text>
+          <text class="wealth-label">学习课程</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 财富数据(推广收益) -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">💰 财富数据</text>
+        <text class="section-more" @click="goToPromotion">全部 ›</text>
+      </view>
+      <view class="wealth-card">
+        <view class="wealth-item">
+          <text class="wealth-value">¥{{ wealthData.totalEarnings }}</text>
+          <text class="wealth-label">累计收益</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">¥{{ wealthData.availableAmount }}</text>
+          <text class="wealth-label">可提现</text>
+        </view>
+        <view class="wealth-divider"></view>
+        <view class="wealth-item">
+          <text class="wealth-value">{{ wealthData.referralCount }}</text>
+          <text class="wealth-label">邀请人数</text>
+        </view>
+      </view>
+      <view class="referral-code-bar" @click="copyReferralCode" v-if="wealthData.referralCode">
+        <text class="referral-code-label">邀请码</text>
+        <text class="referral-code-value">{{ wealthData.referralCode }}</text>
+        <text class="referral-code-copy">复制</text>
+      </view>
+    </view>
+
+    <!-- 成就徽章 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">🏅 成就徽章</text>
+        <text class="section-more" @click="goAllBadges">全部 ›</text>
+      </view>
+      <view class="badge-grid">
+        <view class="badge-item" v-for="badge in badges" :key="badge.id">
+          <view :class="['badge-icon-wrap', badge.unlocked ? 'unlocked' : 'locked']">
+            <text class="badge-icon">{{ badge.icon }}</text>
+          </view>
+          <text class="badge-name">{{ badge.name }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 成长报告 -->
+    <view class="section">
+      <view class="section-header">
+        <text class="section-title">📈 成长报告</text>
+        <text class="section-more" @click="goGrowthReport">查看 ›</text>
+      </view>
+      <view class="report-card" @click="goGrowthReport">
+        <view class="report-stat-item">
+          <text class="report-stat">{{ growthReport.monthActiveDays || 0 }}天</text>
+          <text class="report-label">本月活跃</text>
+        </view>
+        <view class="report-divider"></view>
+        <view class="report-stat-item">
+          <text class="report-stat">{{ growthReport.totalTasks || 0 }}</text>
+          <text class="report-label">累计任务</text>
+        </view>
+        <view class="report-divider"></view>
+        <view class="report-stat-item">
+          <text class="report-stat">{{ growthReport.completionRate || 0 }}%</text>
+          <text class="report-label">完成率</text>
+        </view>
+      </view>
+    </view>
+
     <view class="menu-list">
       <!-- 修改个人资料:所有角色可见 -->
       <view class="menu-item" @click="goToEditInfo">
@@ -69,10 +171,6 @@
         <text>🏪 服务商中心</text>
         <text class="arrow">›</text>
       </view>
-      <view class="menu-item" @click="goToPromotion">
-        <text>📢 推广中心</text>
-        <text class="arrow">›</text>
-      </view>
       <view class="menu-item" @click="logout">
         <text>🚪 退出登录</text>
         <text class="arrow">›</text>
@@ -103,9 +201,11 @@
 </template>
 
 <script>
-import { setPassword, verifyPassword, getChildren, vendorStatus } from '../../utils/api.js'
+import PageBanner from '../../components/PageBanner.vue'
+import { setPassword, verifyPassword, getChildren, vendorStatus, getEnergyOverview, getUserActionStats, getReferralCode, getReferralSummary, getCommissionSummary } from '../../utils/api.js'
 
 export default {
+  components: { PageBanner },
   data() {
     return {
       nickname: '',
@@ -118,7 +218,31 @@ export default {
       children: [],
       isSwitchedChild: false,
       shareData: null,
-      isVendor: false
+      isVendor: false,
+      totalEnergy: null,
+      badges: [
+        { id: 1, icon: '\u{1F3C6}', name: '连续7天', unlocked: false },
+        { id: 2, icon: '\u{2B50}', name: '积分达人', unlocked: false },
+        { id: 3, icon: '\u{1F525}', name: '执行之星', unlocked: false },
+        { id: 4, icon: '\u{1F31F}', name: '学习先锋', unlocked: false }
+      ],
+      actionData: {
+        purchaseCount: 0,
+        activityCount: 0,
+        taskCount: 0,
+        courseCount: 0
+      },
+      wealthData: {
+        totalEarnings: '0.00',
+        availableAmount: '0.00',
+        referralCount: 0,
+        referralCode: ''
+      },
+      growthReport: {
+        monthActiveDays: 0,
+        totalTasks: 0,
+        completionRate: 0
+      }
     }
   },
   computed: {
@@ -150,8 +274,12 @@ export default {
     // 获取切换标记
     this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
     this.loadChildren()
-    this.checkVendorStatus()
-  },
+    this.checkVendorStatus()
+    this.loadEnergyData()
+    this.loadBadges()
+    this.loadActionData()
+    this.loadWealthData()
+  },
   methods: {
     goLogin() {
       uni.navigateTo({ url: '/pages/login/login' })
@@ -285,7 +413,8 @@ export default {
         success: (res) => {
           if (res.confirm) {
             this.$store.commit('logout')
-            uni.reLaunch({ url: '/pages/login/login' })
+            // 退出后返回首页 Tab(预登录状态,5 个底签可见)
+            uni.reLaunch({ url: '/pages/index/index' })
           }
         }
       })
@@ -345,6 +474,83 @@ export default {
     goToPromotion() {
       uni.navigateTo({ url: '/pages/promotion/index' })
     },
+    async loadEnergyData() {
+      var childId = uni.getStorageSync('currentChildId') || null
+      if (!childId) {
+        // Try to get first child
+        if (this.children.length > 0) {
+          childId = this.children[0].id
+        }
+      }
+      if (!childId) return
+      try {
+        var res = await getEnergyOverview(childId)
+        if (res && res.data) {
+          this.totalEnergy = res.data.totalEnergy || 0
+        }
+      } catch (e) { console.log('获取能量概览失败', e) }
+    },
+    loadBadges() {
+      // Unlock based on stored stats
+      var streakDays = parseInt(uni.getStorageSync('streakDays') || 0)
+      var totalPoints = parseInt(uni.getStorageSync('totalPoints') || 0)
+      if (streakDays >= 7) this.badges[0].unlocked = true
+      if (totalPoints >= 1000) this.badges[1].unlocked = true
+
+      // Growth report mock
+      this.growthReport.monthActiveDays = streakDays > 0 ? Math.min(streakDays, 30) : 0
+      this.growthReport.totalTasks = parseInt(uni.getStorageSync('completedTasks') || 0)
+      this.growthReport.completionRate = 85
+    },
+    async loadActionData() {
+      try {
+        const res = await getUserActionStats()
+        if (res.code === 200 && res.data) {
+          this.actionData.purchaseCount = res.data.purchaseCount || 0
+          this.actionData.activityCount = res.data.activityCount || 0
+          this.actionData.taskCount = res.data.taskCount || 0
+          this.actionData.courseCount = res.data.courseCount || 0
+        }
+      } catch (e) {
+        // Fallback to local computed stats
+        this.actionData.taskCount = parseInt(uni.getStorageSync('completedTasks') || 0)
+        console.log('获取行动统计失败,使用本地数据', e)
+      }
+    },
+    async loadWealthData() {
+      try {
+        const codeRes = await getReferralCode()
+        if (codeRes.data) {
+          this.wealthData.referralCode = codeRes.data.referralCode || codeRes.data.code || ''
+        }
+      } catch (e) { console.log('获取邀请码失败', e) }
+      try {
+        const summaryRes = await getReferralSummary()
+        if (summaryRes.data) {
+          this.wealthData.totalEarnings = summaryRes.data.totalEarnings || '0.00'
+          this.wealthData.referralCount = summaryRes.data.referredCount || summaryRes.data.totalCount || 0
+        }
+      } catch (e) { console.log('获取邀请统计失败', e) }
+      try {
+        const comRes = await getCommissionSummary()
+        if (comRes.data) {
+          this.wealthData.availableAmount = comRes.data.availableAmount || '0.00'
+        }
+      } catch (e) { console.log('获取佣金总览失败', e) }
+    },
+    copyReferralCode() {
+      if (!this.wealthData.referralCode) return
+      uni.setClipboardData({
+        data: this.wealthData.referralCode,
+        success: () => { uni.showToast({ title: '邀请码已复制', icon: 'success' }) }
+      })
+    },
+    goAllBadges() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
+    goGrowthReport() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
     async checkVendorStatus() {
       try {
         const res = await vendorStatus()
@@ -386,7 +592,159 @@ export default {
 
 <style scoped>
 .container {
-  padding: 30rpx;
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+/* ===== 通用区块 ===== */
+.section {
+  margin: 20rpx 30rpx;
+}
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.section-more {
+  font-size: 24rpx;
+  color: #999;
+}
+
+/* ===== 推广邀请码 ===== */
+.referral-code-bar {
+  display: flex;
+  align-items: center;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 20rpx 24rpx;
+  margin-top: 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.referral-code-label {
+  font-size: 26rpx;
+  color: #999;
+  margin-right: 16rpx;
+}
+.referral-code-value {
+  flex: 1;
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #F97316;
+  letter-spacing: 4rpx;
+}
+.referral-code-copy {
+  font-size: 24rpx;
+  color: #5B9BD5;
+  padding: 4rpx 16rpx;
+  border: 1rpx solid #5B9BD5;
+  border-radius: 8rpx;
+}
+
+/* ===== 行动数据(推广收益) ===== */
+.wealth-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx 20rpx;
+  display: flex;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.wealth-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.wealth-value {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #F97316;
+}
+.wealth-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 8rpx;
+}
+.wealth-divider {
+  width: 1rpx;
+  height: 60rpx;
+  background: #f0f0f0;
+}
+
+/* ===== 成就徽章 ===== */
+.badge-grid {
+  display: flex;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.badge-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.badge-icon-wrap {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8rpx;
+}
+.badge-icon-wrap.unlocked {
+  background: linear-gradient(135deg, #FBBF24, #F97316);
+}
+.badge-icon-wrap.locked {
+  background: #f0f0f0;
+  opacity: 0.5;
+}
+.badge-icon {
+  font-size: 36rpx;
+}
+.badge-name {
+  font-size: 22rpx;
+  color: #666;
+}
+
+/* ===== 成长报告 ===== */
+.report-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx 20rpx;
+  display: flex;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.report-stat-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.report-stat {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #5B9BD5;
+}
+.report-label {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 6rpx;
+}
+.report-divider {
+  width: 1rpx;
+  height: 50rpx;
+  background: #f0f0f0;
 }
 .user-card {
   background: linear-gradient(135deg, #5B9BD5 0%, #3A7CC4 100%);

+ 131 - 33
cfc-frontend/pages/promotion/commission.vue

@@ -5,12 +5,32 @@
       <view class="header-row">
         <view class="header-item">
           <text class="header-label">累计佣金</text>
-          <text class="header-value orange">¥{{ summary.totalCommission || '0.00' }}</text>
+          <text class="header-value orange" v-if="summary">{{ '¥' + (summary.totalCommission || '0.00') }}</text>
+          <text class="header-value orange" v-else>¥0.00</text>
         </view>
         <view class="header-item">
           <text class="header-label">可提现</text>
-          <text class="header-value blue">¥{{ summary.availableAmount || '0.00' }}</text>
+          <text class="header-value blue" v-if="summary">{{ '¥' + (summary.availableAmount || '0.00') }}</text>
+          <text class="header-value blue" v-else>¥0.00</text>
         </view>
+        <view class="header-item">
+          <text class="header-label">待结算</text>
+          <text class="header-value green" v-if="summary">{{ '¥' + (summary.pendingAmount || '0.00') }}</text>
+          <text class="header-value green" v-else>¥0.00</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- Tab 切换 -->
+    <view class="tab-bar">
+      <view class="tab-item" :class="currentTab === 'all' ? 'tab-active' : ''" @click="switchTab('all')">
+        <text class="tab-text">全部</text>
+      </view>
+      <view class="tab-item" :class="currentTab === 'settled' ? 'tab-active' : ''" @click="switchTab('settled')">
+        <text class="tab-text">已结算</text>
+      </view>
+      <view class="tab-item" :class="currentTab === 'pending' ? 'tab-active' : ''" @click="switchTab('pending')">
+        <text class="tab-text">待结算</text>
       </view>
     </view>
 
@@ -19,12 +39,13 @@
       <view class="list-item" v-for="item in list" :key="item.id">
         <view class="item-left">
           <text :class="['type-tag', item.commissionType === 'member' ? 'tag-orange' : 'tag-blue']">
-            {{ item.commissionType === 'member' ? '会员佣金' : '利润佣金' }}
+            {{ item.commissionType === 'member' ? '会员佣金' : '商品佣金' }}
           </text>
           <text class="item-desc">{{ getOrderDesc(item) }}</text>
         </view>
         <view class="item-right">
           <text class="item-amount">+¥{{ item.commissionAmount || '0.00' }}</text>
+          <text class="item-status" :class="item.status === 'settled' ? 'status-settled' : item.status === 'cancelled' ? 'status-cancelled' : 'status-pending'">{{ getStatusText(item.status) }}</text>
           <text class="item-time">{{ formatTime(item.createdAt) }}</text>
         </view>
       </view>
@@ -39,7 +60,7 @@
 </template>
 
 <script>
-import { getCommissionList, getCommissionSummary } from '../../utils/api.js'
+import { getCommissionList, getCommissionSummary, getCommissionStats } from '../../utils/api.js'
 
 export default {
   data() {
@@ -48,7 +69,8 @@ export default {
       summary: {},
       page: 1,
       hasMore: true,
-      loading: false
+      loading: false,
+      currentTab: 'all'
     }
   },
   onLoad() {
@@ -62,6 +84,13 @@ export default {
     }
   },
   methods: {
+    switchTab(tab) {
+      if (this.currentTab === tab) return
+      this.currentTab = tab
+      this.page = 1
+      this.list = []
+      this.loadList()
+    },
     async loadSummary() {
       try {
         const res = await getCommissionSummary()
@@ -75,9 +104,17 @@ export default {
     async loadList() {
       this.loading = true
       try {
-        const res = await getCommissionList(this.page)
+        var page = this.page
+        var size = 20
+        var status = ''
+        if (this.currentTab === 'settled') {
+          status = 'settled'
+        } else if (this.currentTab === 'pending') {
+          status = 'pending'
+        }
+        const res = await getCommissionList(page, size, status)
         if (res.data && res.data.records) {
-          this.list = this.page === 1 ? res.data.records : [...this.list, ...res.data.records]
+          this.list = this.page === 1 ? res.data.records : this.list.concat(res.data.records)
           this.hasMore = res.data.records.length >= 20
         } else {
           this.hasMore = false
@@ -88,7 +125,7 @@ export default {
       this.loading = false
     },
     getOrderDesc(item) {
-      const typeMap = {
+      var typeMap = {
         membership: '会员',
         product: '商品',
         assessment: '测评',
@@ -96,10 +133,19 @@ export default {
       }
       return (typeMap[item.orderType] || item.orderType) + '订单 #' + item.orderId
     },
+    getStatusText(status) {
+      var map = { settled: '已结算', pending: '待结算', cancelled: '已取消' }
+      return map[status] || status || '待结算'
+    },
+    getStatusClass(status) {
+      if (status === 'settled') return 'status-settled'
+      if (status === 'cancelled') return 'status-cancelled'
+      return 'status-pending'
+    },
     formatTime(time) {
       if (!time) return ''
-      const t = new Date(time)
-      return t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0')
+      var t = new Date(time)
+      return t.getFullYear() + '-' + String(t.getMonth() + 1).padStart(2, '0') + '-' + String(t.getDate()).padStart(2, '0')
     }
   }
 }
@@ -109,14 +155,14 @@ export default {
 .container {
   padding: 30rpx;
   min-height: 100vh;
-  background: #f5f7fa;
+  background: #FFF7ED;
 }
 .header-card {
   background: #fff;
   border-radius: 20rpx;
-  padding: 40rpx;
-  margin-bottom: 30rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
+  padding: 32rpx 24rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
 }
 .header-row {
   display: flex;
@@ -126,70 +172,122 @@ export default {
   text-align: center;
 }
 .header-label {
-  font-size: 26rpx;
-  color: #999;
+  font-size: 22rpx;
+  color: #94A3B8;
   display: block;
-  margin-bottom: 12rpx;
+  margin-bottom: 8rpx;
 }
 .header-value {
-  font-size: 40rpx;
+  font-size: 36rpx;
   font-weight: bold;
 }
 .header-value.orange { color: #F97316; }
-.header-value.blue { color: #3A7CC4; }
+.header-value.blue { color: #0EA5E9; }
+.header-value.green { color: #10B981; }
+
+/* ===== Tab切换 ===== */
+.tab-bar {
+  display: flex;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 8rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 2rpx 8rpx rgba(249,115,22,0.06);
+}
+.tab-item {
+  flex: 1;
+  text-align: center;
+  padding: 16rpx 0;
+  border-radius: 12rpx;
+}
+.tab-active {
+  background: #F97316;
+}
+.tab-active .tab-text {
+  color: #fff;
+  font-weight: 600;
+}
+.tab-text {
+  font-size: 26rpx;
+  color: #64748B;
+}
+
 .list-section {
   background: #fff;
   border-radius: 20rpx;
   overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
 }
 .list-item {
   display: flex;
   justify-content: space-between;
-  padding: 30rpx;
-  border-bottom: 1rpx solid #f0f0f0;
+  padding: 28rpx 24rpx;
+  border-bottom: 1rpx solid #FED7AA;
+}
+.list-item:last-child {
+  border-bottom: none;
 }
 .item-left {
   flex: 1;
 }
 .type-tag {
-  font-size: 22rpx;
+  font-size: 20rpx;
   padding: 4rpx 12rpx;
   border-radius: 6rpx;
   display: inline-block;
   margin-bottom: 8rpx;
 }
 .tag-orange {
-  background: #FEF3E2;
-  color: #F97316;
+  background: #FEF3C7;
+  color: #D97706;
 }
 .tag-blue {
-  background: #E8F0FE;
-  color: #3A7CC4;
+  background: #DBEAFE;
+  color: #2563EB;
 }
 .item-desc {
-  font-size: 26rpx;
-  color: #666;
+  font-size: 24rpx;
+  color: #64748B;
   display: block;
 }
 .item-right {
   text-align: right;
 }
 .item-amount {
-  font-size: 32rpx;
+  font-size: 30rpx;
   font-weight: bold;
   color: #F97316;
   display: block;
 }
+.item-status {
+  font-size: 20rpx;
+  padding: 2rpx 10rpx;
+  border-radius: 6rpx;
+  display: inline-block;
+  margin-top: 4rpx;
+}
+.status-settled {
+  color: #10B981;
+  background: #D1FAE5;
+}
+.status-pending {
+  color: #D97706;
+  background: #FEF3C7;
+}
+.status-cancelled {
+  color: #94A3B8;
+  background: #F1F5F9;
+}
 .item-time {
-  font-size: 22rpx;
-  color: #999;
+  font-size: 20rpx;
+  color: #94A3B8;
   display: block;
-  margin-top: 6rpx;
+  margin-top: 4rpx;
 }
 .empty-state, .loading-state {
   text-align: center;
   padding: 60rpx;
-  color: #999;
+  color: #94A3B8;
   font-size: 28rpx;
 }
 </style>

+ 108 - 49
cfc-frontend/pages/promotion/index.vue

@@ -1,63 +1,85 @@
 <template>
-  <view class="container">
+  <scroll-view class="container" scroll-y>
     <!-- 邀请码区域 -->
     <view class="invite-card">
       <view class="code-section">
         <text class="code-label">我的邀请码</text>
         <view class="code-value" @click="copyCode">
           <text class="code-text">{{ referralCode || '加载中...' }}</text>
-          <text class="copy-icon">📋</text>
+          <text class="copy-icon"></text>
         </view>
+        <text class="code-hint">点击复制邀请码,分享给好友</text>
       </view>
-      <button class="share-btn" @click="shareCard">📤 分享邀请卡</button>
+      <button class="share-btn" @click="shareCard">分享邀请卡</button>
     </view>
 
     <!-- 收益概览 -->
     <view class="earnings-card">
-      <view class="earning-item">
-        <text class="earning-label">累计收益</text>
-        <text class="earning-value orange">¥{{ totalEarnings || '0.00' }}</text>
+      <view class="earnings-header">
+        <text class="earnings-title">收益总览</text>
       </view>
-      <view class="earning-divider"></view>
-      <view class="earning-item">
-        <text class="earning-label">可提现</text>
-        <text class="earning-value blue">¥{{ availableAmount || '0.00' }}</text>
+      <view class="earnings-body">
+        <view class="earning-item">
+          <text class="earning-label">累计佣金</text>
+          <text class="earning-value orange" v-if="summary">{{ '¥' + (summary.totalCommission || '0.00') }}</text>
+          <text class="earning-value orange" v-else>¥0.00</text>
+        </view>
+        <view class="earning-divider"></view>
+        <view class="earning-item">
+          <text class="earning-label">可提现</text>
+          <text class="earning-value blue" v-if="summary">{{ '¥' + (summary.availableAmount || '0.00') }}</text>
+          <text class="earning-value blue" v-else>¥0.00</text>
+        </view>
+        <view class="earning-divider"></view>
+        <view class="earning-item">
+          <text class="earning-label">待结算</text>
+          <text class="earning-value green" v-if="summary">{{ '¥' + (summary.pendingAmount || '0.00') }}</text>
+          <text class="earning-value green" v-else>¥0.00</text>
+        </view>
       </view>
     </view>
 
     <!-- 功能入口 -->
     <view class="menu-grid">
       <view class="menu-item" @click="goCommission">
-        <text class="menu-icon">📋</text>
+        <view class="menu-icon-wrap icon-commission">
+          <text class="menu-icon-text">📋</text>
+        </view>
         <text class="menu-text">佣金明细</text>
       </view>
       <view class="menu-item" @click="goWithdraw">
-        <text class="menu-icon">💰</text>
+        <view class="menu-icon-wrap icon-withdraw">
+          <text class="menu-icon-text">💰</text>
+        </view>
         <text class="menu-text">立即提现</text>
       </view>
       <view class="menu-item" @click="goInvite">
-        <text class="menu-icon">👥</text>
-        <text class="menu-text">邀请记录</text>
+        <view class="menu-icon-wrap icon-invite">
+          <text class="menu-icon-text">👥</text>
+        </view>
+        <text class="menu-text">邀请好友</text>
       </view>
-      <view class="menu-item" @click="showTutorial">
-        <text class="menu-icon">📖</text>
-        <text class="menu-text">推广教程</text>
+      <view class="menu-item" @click="goTeam">
+        <view class="menu-icon-wrap icon-team">
+          <text class="menu-icon-text">🤝</text>
+        </view>
+        <text class="menu-text">推广团队</text>
       </view>
     </view>
 
-    <bottom-nav v-if="!isLoggedIn" current="promotion" />
-  </view>
+  </scroll-view>
 </template>
 
 <script>
-import { getReferralCode, getReferralSummary } from '../../utils/api.js'
+import { getReferralCode, getReferralSummary, getCommissionSummary } from '../../utils/api.js'
 
 export default {
   data() {
     return {
       referralCode: '',
       totalEarnings: '0.00',
-      availableAmount: '0.00'
+      availableAmount: '0.00',
+      summary: null
     }
   },
   computed: {
@@ -87,9 +109,9 @@ export default {
         console.error('获取邀请统计失败', e)
       }
       try {
-        const { getCommissionSummary } = require('../../utils/api.js')
         const comRes = await getCommissionSummary()
         if (comRes.data) {
+          this.summary = comRes.data
           this.availableAmount = comRes.data.availableAmount || '0.00'
         }
       } catch (e) {
@@ -100,7 +122,7 @@ export default {
       if (!this.referralCode) return
       uni.setClipboardData({
         data: this.referralCode,
-        success: () => {
+        success: function() {
           uni.showToast({ title: '邀请码已复制', icon: 'success' })
         }
       })
@@ -117,7 +139,7 @@ export default {
     goInvite() {
       uni.navigateTo({ url: '/pages/promotion/invite' })
     },
-    showTutorial() {
+    goTeam() {
       uni.showToast({ title: '即将上线', icon: 'none' })
     }
   }
@@ -128,10 +150,10 @@ export default {
 .container {
   padding: 30rpx;
   min-height: 100vh;
-  background: #f5f7fa;
+  background: #FFF7ED;
 }
 .invite-card {
-  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  background: linear-gradient(135deg, #F97316, #EA580C);
   border-radius: 20rpx;
   padding: 40rpx;
   color: #fff;
@@ -141,22 +163,29 @@ export default {
   margin-bottom: 30rpx;
 }
 .code-label {
-  font-size: 26rpx;
-  opacity: 0.9;
+  font-size: 24rpx;
+  opacity: 0.85;
 }
 .code-value {
   display: flex;
   align-items: center;
-  margin-top: 16rpx;
+  margin-top: 12rpx;
 }
 .code-text {
-  font-size: 40rpx;
+  font-size: 44rpx;
   font-weight: bold;
-  letter-spacing: 4rpx;
+  letter-spacing: 6rpx;
 }
 .copy-icon {
-  font-size: 32rpx;
+  font-size: 36rpx;
   margin-left: 16rpx;
+  opacity: 0.9;
+}
+.code-hint {
+  font-size: 22rpx;
+  opacity: 0.7;
+  margin-top: 8rpx;
+  display: block;
 }
 .share-btn {
   background: rgba(255,255,255,0.2);
@@ -168,58 +197,88 @@ export default {
   border: 1rpx solid rgba(255,255,255,0.3);
 }
 .share-btn::after { border: none; }
+.share-btn:active { opacity: 0.8; }
+
+/* ===== 收益总览 ===== */
 .earnings-card {
   background: #fff;
   border-radius: 20rpx;
-  padding: 40rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
+  overflow: hidden;
+}
+.earnings-header {
+  padding: 24rpx 32rpx 0;
+}
+.earnings-title {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #1E293B;
+}
+.earnings-body {
   display: flex;
   align-items: center;
-  margin-bottom: 30rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
+  padding: 24rpx 32rpx 32rpx;
 }
 .earning-item {
   flex: 1;
   text-align: center;
 }
 .earning-label {
-  font-size: 26rpx;
-  color: #999;
+  font-size: 22rpx;
+  color: #94A3B8;
   display: block;
-  margin-bottom: 12rpx;
+  margin-bottom: 8rpx;
 }
 .earning-value {
-  font-size: 44rpx;
+  font-size: 36rpx;
   font-weight: bold;
 }
 .earning-value.orange { color: #F97316; }
-.earning-value.blue { color: #3A7CC4; }
+.earning-value.blue { color: #0EA5E9; }
+.earning-value.green { color: #10B981; }
 .earning-divider {
   width: 1rpx;
-  height: 80rpx;
-  background: #eee;
+  height: 64rpx;
+  background: #FED7AA;
 }
+
+/* ===== 功能入口 ===== */
 .menu-grid {
   display: flex;
   flex-wrap: wrap;
   background: #fff;
   border-radius: 20rpx;
-  padding: 20rpx 0;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
+  padding: 24rpx 0;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
 }
 .menu-item {
   width: 50%;
   display: flex;
   flex-direction: column;
   align-items: center;
-  padding: 30rpx 0;
+  padding: 24rpx 0;
   box-sizing: border-box;
 }
-.menu-icon {
-  font-size: 48rpx;
+.menu-icon-wrap {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 40rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
   margin-bottom: 12rpx;
 }
+.icon-commission { background: #FEF3C7; }
+.icon-withdraw { background: #DBEAFE; }
+.icon-invite { background: #D1FAE5; }
+.icon-team { background: #FCE7F3; }
+.menu-icon-text {
+  font-size: 36rpx;
+}
 .menu-text {
-  font-size: 28rpx;
-  color: #333;
+  font-size: 26rpx;
+  color: #1E293B;
+  font-weight: 500;
 }
 </style>

+ 112 - 28
cfc-frontend/pages/promotion/invite.vue

@@ -1,5 +1,26 @@
 <template>
   <view class="container">
+    <!-- 邀请统计 -->
+    <view class="stats-card">
+      <view class="stats-item">
+        <text class="stats-num" v-if="summary">{{ summary.totalInvited || 0 }}</text>
+        <text class="stats-num" v-else>0</text>
+        <text class="stats-label">已邀请</text>
+      </view>
+      <view class="stats-divider"></view>
+      <view class="stats-item">
+        <text class="stats-num" v-if="summary">{{ summary.activeCount || 0 }}</text>
+        <text class="stats-num" v-else>0</text>
+        <text class="stats-label">活跃</text>
+      </view>
+      <view class="stats-divider"></view>
+      <view class="stats-item">
+        <text class="stats-num" v-if="summary">{{ '¥' + (summary.totalEarnings || '0.00') }}</text>
+        <text class="stats-num" v-else>¥0.00</text>
+        <text class="stats-label">总收益</text>
+      </view>
+    </view>
+
     <!-- 邀请码展示 -->
     <view class="code-card">
       <text class="code-title">我的邀请码</text>
@@ -26,7 +47,9 @@
         <text>还没有邀请好友,快去分享吧</text>
       </view>
       <view class="invite-item" v-for="item in list" :key="item.id">
-        <text class="invite-avatar">&#x1F464;</text>
+        <view class="invite-avatar-wrap">
+          <text class="invite-avatar">{{ item.avatar ? '' : '👤' }}</text>
+        </view>
         <view class="invite-info">
           <text class="invite-name">{{ item.nickname || '好友' }}</text>
           <text class="invite-time">{{ formatTime(item.createdAt) }}</text>
@@ -40,7 +63,7 @@
 </template>
 
 <script>
-import { getReferralCode, bindReferral, getReferralList } from '../../utils/api.js'
+import { getReferralCode, bindReferral, getReferralList, getReferralSummary } from '../../utils/api.js'
 
 export default {
   data() {
@@ -51,11 +74,13 @@ export default {
       total: 0,
       page: 1,
       loading: false,
-      hasMore: true
+      hasMore: true,
+      summary: null
     }
   },
   onLoad() {
     this.loadCode()
+    this.loadSummary()
     this.loadList()
   },
   onReachBottom() {
@@ -75,13 +100,23 @@ export default {
         console.error('获取邀请码失败', e)
       }
     },
+    async loadSummary() {
+      try {
+        const res = await getReferralSummary()
+        if (res.data) {
+          this.summary = res.data
+        }
+      } catch (e) {
+        console.error('获取邀请统计失败', e)
+      }
+    },
     async loadList() {
       this.loading = true
       try {
         const res = await getReferralList(this.page)
         if (res.data) {
           if (res.data.records) {
-            this.list = this.page === 1 ? res.data.records : [...this.list, ...res.data.records]
+            this.list = this.page === 1 ? res.data.records : this.list.concat(res.data.records)
           }
           this.total = res.data.total || 0
           if (res.data.records) {
@@ -99,7 +134,7 @@ export default {
       if (!this.referralCode) return
       uni.setClipboardData({
         data: this.referralCode,
-        success: () => {
+        success: function() {
           uni.showToast({ title: '已复制邀请码', icon: 'success' })
         }
       })
@@ -126,8 +161,8 @@ export default {
     },
     formatTime(time) {
       if (!time) return ''
-      const t = new Date(time)
-      return t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0')
+      var t = new Date(time)
+      return t.getFullYear() + '-' + String(t.getMonth() + 1).padStart(2, '0') + '-' + String(t.getDate()).padStart(2, '0')
     }
   }
 }
@@ -137,24 +172,57 @@ export default {
 .container {
   padding: 30rpx;
   min-height: 100vh;
-  background: #f5f7fa;
+  background: #FFF7ED;
+}
+
+/* ===== 邀请统计 ===== */
+.stats-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 32rpx 24rpx;
+  display: flex;
+  align-items: center;
+  margin-bottom: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
+}
+.stats-item {
+  flex: 1;
+  text-align: center;
 }
+.stats-num {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #F97316;
+  display: block;
+}
+.stats-label {
+  font-size: 22rpx;
+  color: #94A3B8;
+  margin-top: 4rpx;
+  display: block;
+}
+.stats-divider {
+  width: 1rpx;
+  height: 48rpx;
+  background: #FED7AA;
+}
+
 .code-card {
-  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  background: linear-gradient(135deg, #F97316, #EA580C);
   border-radius: 20rpx;
   padding: 40rpx;
   text-align: center;
   color: #fff;
-  margin-bottom: 30rpx;
+  margin-bottom: 24rpx;
 }
 .code-title {
-  font-size: 26rpx;
-  opacity: 0.9;
+  font-size: 24rpx;
+  opacity: 0.85;
 }
 .code-value {
   font-size: 48rpx;
   font-weight: bold;
-  letter-spacing: 6rpx;
+  letter-spacing: 8rpx;
   margin: 20rpx 0 30rpx;
   display: block;
 }
@@ -173,16 +241,19 @@ export default {
   border: 1rpx solid rgba(255,255,255,0.3);
 }
 .action-btn::after { border: none; }
+.action-btn:active { opacity: 0.8; }
+
 .bind-card {
   background: #fff;
   border-radius: 20rpx;
   padding: 30rpx;
-  margin-bottom: 30rpx;
+  margin-bottom: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
 }
 .bind-title {
   font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
+  font-weight: 600;
+  color: #1E293B;
   display: block;
   margin-bottom: 16rpx;
 }
@@ -192,30 +263,34 @@ export default {
 }
 .bind-input {
   flex: 1;
-  border: 1rpx solid #ddd;
-  border-radius: 10rpx;
+  border: 1rpx solid #CBD5E1;
+  border-radius: 12rpx;
   padding: 20rpx;
   font-size: 28rpx;
 }
 .bind-btn {
-  background: #3A7CC4;
+  background: #F97316;
   color: #fff;
   font-size: 26rpx;
-  border-radius: 10rpx;
+  border-radius: 12rpx;
   height: 64rpx;
   line-height: 64rpx;
   padding: 0 30rpx;
+  font-weight: 500;
 }
 .bind-btn::after { border: none; }
+.bind-btn:active { opacity: 0.9; }
+
 .list-section {
   background: #fff;
   border-radius: 20rpx;
   padding: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
 }
 .section-title {
   font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
+  font-weight: 600;
+  color: #1E293B;
   display: block;
   margin-bottom: 20rpx;
 }
@@ -223,28 +298,37 @@ export default {
   display: flex;
   align-items: center;
   padding: 20rpx 0;
-  border-bottom: 1rpx solid #f0f0f0;
+  border-bottom: 1rpx solid #FED7AA;
 }
 .invite-item:last-child { border-bottom: none; }
-.invite-avatar {
-  font-size: 48rpx;
+.invite-avatar-wrap {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 50%;
+  background: #FEF3C7;
+  display: flex;
+  align-items: center;
+  justify-content: center;
   margin-right: 20rpx;
 }
+.invite-avatar {
+  font-size: 32rpx;
+}
 .invite-info { flex: 1; }
 .invite-name {
   font-size: 28rpx;
-  color: #333;
+  color: #1E293B;
   display: block;
 }
 .invite-time {
   font-size: 22rpx;
-  color: #999;
+  color: #94A3B8;
   margin-top: 4rpx;
 }
 .empty-state, .loading-state {
   text-align: center;
   padding: 60rpx;
-  color: #999;
+  color: #94A3B8;
   font-size: 28rpx;
 }
 </style>

+ 107 - 43
cfc-frontend/pages/promotion/withdraw.vue

@@ -3,7 +3,25 @@
     <!-- 可提现余额 -->
     <view class="balance-card">
       <text class="balance-label">可提现金额</text>
-      <text class="balance-value">¥{{ availableAmount || '0.00' }}</text>
+      <text class="balance-value" v-if="availableAmount">{{ '¥' + availableAmount }}</text>
+      <text class="balance-value" v-else>¥0.00</text>
+      <text class="balance-sub">累计佣金 ¥{{ totalCommission || '0.00' }}</text>
+    </view>
+
+    <!-- 快捷金额 -->
+    <view class="quick-amounts">
+      <view class="quick-item" :class="amount === '100' ? 'quick-active' : ''" @click="amount = '100'">
+        <text>¥100</text>
+      </view>
+      <view class="quick-item" :class="amount === '200' ? 'quick-active' : ''" @click="amount = '200'">
+        <text>¥200</text>
+      </view>
+      <view class="quick-item" :class="amount === '500' ? 'quick-active' : ''" @click="amount = '500'">
+        <text>¥500</text>
+      </view>
+      <view class="quick-item quick-all" @click="setAllAmount">
+        <text>全部提现</text>
+      </view>
     </view>
 
     <!-- 提现表单 -->
@@ -13,7 +31,6 @@
         <view class="input-row">
           <input v-model="amount" type="digit" placeholder="输入提现金额" class="form-input" />
           <text class="unit">元</text>
-          <text class="btn-all" @click="amount = availableAmount">全部提现</text>
         </view>
       </view>
       <view class="form-item">
@@ -45,6 +62,7 @@ export default {
   data() {
     return {
       availableAmount: '0.00',
+      totalCommission: '0.00',
       amount: '',
       accountInfo: '',
       history: [],
@@ -61,6 +79,7 @@ export default {
         const res = await getCommissionSummary()
         if (res.data) {
           this.availableAmount = res.data.availableAmount || '0.00'
+          this.totalCommission = res.data.totalCommission || '0.00'
         }
       } catch (e) {
         console.error('获取佣金总览失败', e)
@@ -76,8 +95,11 @@ export default {
         console.error('获取提现记录失败', e)
       }
     },
+    setAllAmount() {
+      this.amount = String(parseFloat(this.availableAmount))
+    },
     submitWithdraw() {
-      const amountNum = parseFloat(this.amount)
+      var amountNum = parseFloat(this.amount)
       if (!this.amount || isNaN(amountNum) || amountNum < 1) {
         uni.showToast({ title: '提现金额不能少于1元', icon: 'none' })
         return
@@ -93,24 +115,27 @@ export default {
       uni.showModal({
         title: '确认提现',
         content: '确认提现 ¥' + amountNum.toFixed(2) + ' 到 ' + this.accountInfo + ' ?',
-        success: async (res) => {
+        success: function(res) {
           if (res.confirm) {
-            try {
-              await applyWithdrawal(amountNum, this.accountInfo)
-              uni.showToast({ title: '提现申请已提交', icon: 'success' })
-              this.amount = ''
-              this.accountInfo = ''
-              this.loadBalance()
-              this.loadHistory()
-            } catch (e) {
-              uni.showToast({ title: e.message || '提现失败', icon: 'none' })
-            }
+            this.doWithdraw(amountNum)
           }
-        }
+        }.bind(this)
       })
     },
+    async doWithdraw(amountNum) {
+      try {
+        await applyWithdrawal(amountNum, this.accountInfo)
+        uni.showToast({ title: '提现申请已提交', icon: 'success' })
+        this.amount = ''
+        this.accountInfo = ''
+        this.loadBalance()
+        this.loadHistory()
+      } catch (e) {
+        uni.showToast({ title: e.message || '提现失败', icon: 'none' })
+      }
+    },
     statusText(status) {
-      const map = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
+      var map = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
       return map[status] || status
     },
     statusClass(status) {
@@ -120,8 +145,8 @@ export default {
     },
     formatTime(time) {
       if (!time) return ''
-      const t = new Date(time)
-      return t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0')
+      var t = new Date(time)
+      return t.getFullYear() + '-' + String(t.getMonth() + 1).padStart(2, '0') + '-' + String(t.getDate()).padStart(2, '0')
     }
   }
 }
@@ -131,45 +156,86 @@ export default {
 .container {
   padding: 30rpx;
   min-height: 100vh;
-  background: #f5f7fa;
+  background: #FFF7ED;
 }
 .balance-card {
-  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  background: linear-gradient(135deg, #F97316, #EA580C);
   border-radius: 20rpx;
   padding: 40rpx;
   text-align: center;
   color: #fff;
-  margin-bottom: 30rpx;
+  margin-bottom: 24rpx;
 }
 .balance-label {
   font-size: 26rpx;
   opacity: 0.9;
 }
 .balance-value {
-  font-size: 56rpx;
+  font-size: 60rpx;
   font-weight: bold;
   margin-top: 12rpx;
 }
+.balance-sub {
+  font-size: 22rpx;
+  opacity: 0.7;
+  margin-top: 8rpx;
+  display: block;
+}
+
+/* ===== 快捷金额 ===== */
+.quick-amounts {
+  display: flex;
+  gap: 16rpx;
+  margin-bottom: 24rpx;
+}
+.quick-item {
+  flex: 1;
+  height: 72rpx;
+  line-height: 72rpx;
+  text-align: center;
+  background: #fff;
+  border: 1rpx solid #FED7AA;
+  border-radius: 12rpx;
+  font-size: 26rpx;
+  color: #F97316;
+  font-weight: 500;
+}
+.quick-item:active {
+  opacity: 0.8;
+}
+.quick-active {
+  background: #F97316;
+  border-color: #F97316;
+  color: #fff;
+}
+.quick-all {
+  color: #64748B;
+  border-color: #CBD5E1;
+  font-size: 22rpx;
+}
+
 .form-card {
   background: #fff;
   border-radius: 20rpx;
   padding: 40rpx;
   margin-bottom: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
 }
 .form-item {
   margin-bottom: 30rpx;
 }
 .form-label {
   font-size: 28rpx;
-  color: #333;
+  color: #1E293B;
   display: block;
   margin-bottom: 16rpx;
+  font-weight: 500;
 }
 .input-row {
   display: flex;
   align-items: center;
-  border: 1rpx solid #ddd;
-  border-radius: 10rpx;
+  border: 1rpx solid #CBD5E1;
+  border-radius: 12rpx;
   padding: 20rpx;
 }
 .form-input {
@@ -178,39 +244,36 @@ export default {
 }
 .unit {
   font-size: 28rpx;
-  color: #999;
+  color: #94A3B8;
   margin: 0 16rpx;
 }
-.btn-all {
-  font-size: 24rpx;
-  color: #3A7CC4;
-  padding: 6rpx 12rpx;
-  border: 1rpx solid #3A7CC4;
-  border-radius: 6rpx;
-}
 .form-tip {
-  font-size: 24rpx;
-  color: #999;
-  margin-bottom: 30rpx;
+  font-size: 22rpx;
+  color: #94A3B8;
+  margin-bottom: 24rpx;
 }
 .submit-btn {
-  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  background: linear-gradient(135deg, #F97316, #EA580C);
   color: #fff;
   font-size: 30rpx;
   border-radius: 40rpx;
   height: 80rpx;
   line-height: 80rpx;
+  font-weight: 600;
+  border: none;
 }
 .submit-btn::after { border: none; }
+.submit-btn:active { opacity: 0.9; }
 .history-section {
   background: #fff;
   border-radius: 20rpx;
   padding: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
 }
 .section-title {
   font-size: 28rpx;
   font-weight: bold;
-  color: #333;
+  color: #1E293B;
   display: block;
   margin-bottom: 20rpx;
 }
@@ -219,7 +282,7 @@ export default {
   justify-content: space-between;
   align-items: center;
   padding: 20rpx 0;
-  border-bottom: 1rpx solid #f0f0f0;
+  border-bottom: 1rpx solid #FED7AA;
 }
 .history-item:last-child { border-bottom: none; }
 .history-left {
@@ -229,16 +292,17 @@ export default {
   font-size: 28rpx;
   font-weight: bold;
   display: block;
+  color: #1E293B;
 }
 .history-time {
   font-size: 22rpx;
-  color: #999;
+  color: #94A3B8;
   margin-top: 4rpx;
 }
 .history-status {
   font-size: 24rpx;
 }
-.text-success { color: #52c41a; }
-.text-warning { color: #faad14; }
-.text-danger { color: #ff4d4f; }
+.text-success { color: #10B981; }
+.text-warning { color: #D97706; }
+.text-danger { color: #EF4444; }
 </style>

+ 1 - 4
cfc-frontend/pages/shop/index.vue

@@ -88,16 +88,13 @@
       <view class="bottom-spacer"></view>
     </scroll-view>
 
-    <bottom-nav v-if="!isLoggedIn" current="action" />
+
   </view>
 </template>
 
 <script>
 import { goodsCategory, productList } from '@/utils/api.js'
-import BottomNav from '../../components/bottom-nav.vue'
-
 export default {
-  components: { BottomNav },
   data() {
     return {
       categories: [],

+ 153 - 0
cfc-frontend/pages/teacher/index.vue

@@ -100,6 +100,18 @@
         </view>
       </view>
 
+      <!-- ===== 今日日程 ===== -->
+      <view class="schedule-section" v-if="teacherInfo && todayAppointments.length > 0">
+        <view class="section-title">📅 今日日程</view>
+        <view class="schedule-card" v-for="apt in todayAppointments" :key="apt.id">
+          <view class="schedule-time">{{ apt.appointmentTime || apt.time }}</view>
+          <view class="schedule-content">
+            <text class="schedule-title">{{ apt.title }}</text>
+            <text class="schedule-desc">{{ apt.description }}</text>
+          </view>
+        </view>
+      </view>
+
       <!-- 数据趋势 -->
       <view class="trend-section" v-if="teacherInfo">
         <view class="section-title">本周数据趋势</view>
@@ -157,6 +169,44 @@
         </view>
       </view>
 
+      <!-- ===== 绑定家庭 ===== -->
+      <view class="family-section" v-if="teacherInfo">
+        <view class="section-title">👨‍👩‍👧‍👦 绑定家庭</view>
+        <scroll-view class="family-scroll" scroll-x enhanced :show-scrollbar="false" v-if="boundFamilies.length > 0">
+          <view class="family-card" v-for="family in boundFamilies" :key="family.id" @click="goToFamilyDetail(family)">
+            <text class="family-name">{{ family.familyName || family.name }}</text>
+            <text class="family-child-count">{{ family.childCount || 0 }}个孩子</text>
+            <text class="family-latest-task" v-if="family.latestTask">最近: {{ family.latestTask }}</text>
+          </view>
+        </scroll-view>
+        <view class="family-empty" v-else>
+          <text class="family-empty-text">暂无绑定家庭,邀请家长或等待家长邀请</text>
+        </view>
+      </view>
+
+      <!-- ===== 快捷操作 ===== -->
+      <view class="quick-actions-section" v-if="teacherInfo">
+        <view class="section-title">⚡ 快捷操作</view>
+        <view class="quick-actions-grid">
+          <view class="quick-action-item" @click="goToAssignTask">
+            <view class="quick-action-icon qa-icon--assign"><text class="qa-emoji">📝</text></view>
+            <text class="qa-label">下发任务</text>
+          </view>
+          <view class="quick-action-item" @click="goToInputAssessment">
+            <view class="quick-action-icon qa-icon--assessment"><text class="qa-emoji">📊</text></view>
+            <text class="qa-label">录入测评</text>
+          </view>
+          <view class="quick-action-item" @click="goToContactParent">
+            <view class="quick-action-icon qa-icon--contact"><text class="qa-emoji">💬</text></view>
+            <text class="qa-label">联系家长</text>
+          </view>
+          <view class="quick-action-item" @click="goToIncome">
+            <view class="quick-action-icon qa-icon--income"><text class="qa-emoji">💰</text></view>
+            <text class="qa-label">查看收入</text>
+          </view>
+        </view>
+      </view>
+
       <!-- 快捷工具 -->
       <view class="tools-section" v-if="teacherInfo">
         <view class="section-title">快捷工具</view>
@@ -203,6 +253,8 @@ export default {
       weekDays: [],
       todayIndex: 6,
       familyBars: [30, 50, 45, 65, 80, 70, 55],
+      todayAppointments: [],
+      boundFamilies: [],
       _teacherMounted: false
     }
   },
@@ -298,6 +350,21 @@ export default {
     switchToParent() {
       uni.switchTab({ url: '/pages/index/parent-index' })
     },
+    goToFamilyDetail(family) {
+      uni.navigateTo({ url: '/pages/guide/family-detail?familyId=' + family.id })
+    },
+    goToAssignTask() {
+      uni.navigateTo({ url: '/pages/guide/assign-task' })
+    },
+    goToInputAssessment() {
+      uni.navigateTo({ url: '/pages/assessment/input-result' })
+    },
+    goToContactParent() {
+      uni.navigateTo({ url: '/pages/guide/messages' })
+    },
+    goToIncome() {
+      uni.navigateTo({ url: '/pages/guide/income' })
+    },
     goToFamilies() { uni.navigateTo({ url: '/pages/teacher/families' }) },
     goToPackages() { uni.navigateTo({ url: '/pages/guide/packages/index' }) },
     goToTeam() { uni.navigateTo({ url: '/pages/teacher/team' }) },
@@ -517,6 +584,7 @@ export default {
   font-size: 32rpx;
   font-weight: 700;
   color: #1E293B;
+  margin-bottom: 16rpx;
 }
 
 .section-subtitle {
@@ -630,6 +698,91 @@ export default {
   color: #F59E0B;
 }
 
+/* ========================================
+   今日日程
+   ======================================== */
+.schedule-section { margin-bottom: 32rpx; }
+.schedule-card {
+  display: flex;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  margin-bottom: 12rpx;
+  border: 4rpx solid #BFDBFE;
+  box-shadow: var(--clay-shadow-sm);
+}
+.schedule-time {
+  font-size: 26rpx;
+  font-weight: 700;
+  color: #3B82F6;
+  min-width: 120rpx;
+  flex-shrink: 0;
+}
+.schedule-content { flex: 1; min-width: 0; margin-left: 16rpx; }
+.schedule-title { font-size: 28rpx; font-weight: 600; color: #1E293B; display: block; }
+.schedule-desc { font-size: 24rpx; color: #64748B; margin-top: 4rpx; display: block; }
+
+/* ========================================
+   绑定家庭
+   ======================================== */
+.family-section { margin-bottom: 32rpx; }
+.family-scroll { display: flex; flex-direction: row; white-space: nowrap; overflow: hidden; padding: 8rpx 0; }
+.family-card {
+  display: inline-flex; flex-direction: column;
+  width: 240rpx; min-height: 160rpx;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  margin-right: 16rpx;
+  border: 4rpx solid #BFDBFE;
+  box-shadow: var(--clay-shadow-sm);
+  flex-shrink: 0;
+}
+.family-card:active { transform: scale(0.96); }
+.family-name { font-size: 28rpx; font-weight: 700; color: #1E293B; display: block; }
+.family-child-count { font-size: 22rpx; color: #64748B; margin-top: 6rpx; display: block; }
+.family-latest-task { font-size: 22rpx; color: #3B82F6; margin-top: 8rpx; display: block; }
+.family-empty {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 32rpx;
+  text-align: center;
+  border: 4rpx solid #BFDBFE;
+  box-shadow: var(--clay-shadow-sm);
+}
+.family-empty-text { font-size: 24rpx; color: #94A3B8; }
+
+/* ========================================
+   快捷操作 2x2
+   ======================================== */
+.quick-actions-section { margin-bottom: 32rpx; }
+.quick-actions-grid { display: flex; flex-wrap: wrap; gap: 16rpx; }
+.quick-action-item {
+  width: calc(50% - 8rpx);
+  display: flex; flex-direction: column; align-items: center;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 28rpx;
+  border: 4rpx solid #BFDBFE;
+  box-shadow: var(--clay-shadow-sm);
+  transition: var(--clay-transition);
+}
+.quick-action-item:active { transform: scale(0.94); box-shadow: var(--clay-shadow-sm); }
+.quick-action-icon {
+  width: 72rpx; height: 72rpx;
+  border-radius: 20rpx;
+  display: flex; align-items: center; justify-content: center;
+  margin-bottom: 12rpx;
+  border: 3rpx solid transparent;
+  box-shadow: inset -2rpx -2rpx 5rpx rgba(255,255,255,0.4), 3rpx 3rpx 8rpx rgba(0,0,0,0.04);
+}
+.qa-emoji { font-size: 34rpx; }
+.qa-icon--assign { background: rgba(59,130,246,0.2); border-color: #BFDBFE; }
+.qa-icon--assessment { background: rgba(16,185,129,0.2); border-color: #A7F3D0; }
+.qa-icon--contact { background: rgba(245,158,11,0.2); border-color: #FDE68A; }
+.qa-icon--income { background: rgba(139,92,246,0.2); border-color: #DDD6FE; }
+.qa-label { font-size: 24rpx; font-weight: 600; color: #475569; }
+
 /* ========================================
    数据趋势 - claymorphism
    ======================================== */

+ 366 - 0
cfc-frontend/pages/wealth/checkin.vue

@@ -0,0 +1,366 @@
+<template>
+  <scroll-view class="container" scroll-y>
+    <!-- 能量提示 -->
+    <view class="energy-tip">
+      <text class="energy-tip-text">⚡ 每次打卡可获得 5 能量值</text>
+    </view>
+
+    <!-- 快速打卡 -->
+    <view class="quick-checkin">
+      <view class="section-title">
+        <text class="section-title-text">今日打卡</text>
+      </view>
+      <view class="checkin-types">
+        <view
+          v-for="(item, index) in checkinTypes"
+          :key="index"
+          class="checkin-btn"
+          :class="{ disabled: todayTimer }"
+          @click="doCheckin(item.type)">
+          <text class="checkin-icon">{{ item.icon }}</text>
+          <text class="checkin-label">{{ item.label }}</text>
+        </view>
+      </view>
+      <view v-if="todayCount > 0" class="today-count">
+        <text>今日已打卡 {{ todayCount }} 次</text>
+      </view>
+    </view>
+
+    <!-- 统计卡片 -->
+    <view class="stats-card">
+      <view class="stat-item">
+        <text class="stat-value">{{ stats.totalCheckins || 0 }}</text>
+        <text class="stat-label">总打卡</text>
+      </view>
+      <view class="stat-divider"></view>
+      <view class="stat-item">
+        <text class="stat-value">{{ stats.totalEnergy || 0 }}</text>
+        <text class="stat-label">总能量</text>
+      </view>
+      <view class="stat-divider"></view>
+      <view class="stat-item">
+        <text class="stat-value">{{ stats.dailyPlanCount || 0 }}</text>
+        <text class="stat-label">计划</text>
+      </view>
+      <view class="stat-divider"></view>
+      <view class="stat-item">
+        <text class="stat-value">{{ stats.savingCount || 0 }}</text>
+        <text class="stat-label">储蓄</text>
+      </view>
+    </view>
+
+    <!-- 打卡记录 -->
+    <view class="record-section">
+      <view class="section-title">
+        <text class="section-title-text">打卡记录</text>
+      </view>
+      <view v-if="checkins.length === 0" class="empty-state">
+        <text>暂无打卡记录,开始打卡吧!</text>
+      </view>
+      <view
+        v-for="(item, index) in checkins"
+        :key="index"
+        class="record-item"
+        @longpress="confirmDelete(item)">
+        <view class="record-left">
+          <text class="record-icon">{{ getTypeIcon(item.checkinType) }}</text>
+          <view class="record-info">
+            <text class="record-type">{{ getTypeLabel(item.checkinType) }}</text>
+            <text class="record-date">{{ formatDate(item.createdAt) }}</text>
+          </view>
+        </view>
+        <view class="record-right">
+          <text class="record-energy">+{{ item.earnedEnergy || 5 }}</text>
+        </view>
+      </view>
+    </view>
+  </scroll-view>
+</template>
+
+<script>
+import { getCheckinList, getCheckinStats, createCheckin, deleteCheckin } from '../../utils/api'
+
+export default {
+  data() {
+    return {
+      checkinTypes: [
+        { type: 'daily_plan', label: '记账规划', icon: '📋' },
+        { type: 'saving', label: '储蓄记录', icon: '💰' },
+        { type: 'expense', label: '消费记录', icon: '🛒' },
+        { type: 'review', label: '财务回顾', icon: '📊' }
+      ],
+      checkins: [],
+      stats: {},
+      todayTimer: null,
+      todayCount: 0
+    }
+  },
+
+  onShow() {
+    this.loadData()
+  },
+
+  methods: {
+    async loadData() {
+      try {
+        const res = await getCheckinList({})
+        if (res && res.code === 200) {
+          this.checkins = res.data || []
+        }
+        const statsRes = await getCheckinStats({})
+        if (statsRes && statsRes.code === 200) {
+          this.stats = statsRes.data || {}
+        }
+        this.calcTodayCount()
+      } catch (e) {
+        console.error('加载打卡数据失败', e)
+      }
+    },
+
+    calcTodayCount() {
+      const today = new Date()
+      const todayStr = today.toISOString().split('T')[0]
+      this.todayCount = this.checkins.filter(function(c) {
+        if (!c.createdAt) return false
+        return c.createdAt.indexOf(todayStr) === 0
+      }).length
+    },
+
+    async doCheckin(type) {
+      if (this.todayTimer) {
+        uni.showToast({ title: '操作太频繁,请稍后', icon: 'none' })
+        return
+      }
+      try {
+        const res = await createCheckin({
+          checkinType: type,
+          checkinDate: new Date().toISOString().split('T')[0],
+          earnedEnergy: 5
+        })
+        if (res && res.code === 200) {
+          uni.showToast({ title: '打卡成功!+5能量', icon: 'success' })
+          this.loadData()
+        } else {
+          uni.showToast({ title: res && res.message || '打卡失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '网络错误', icon: 'none' })
+      }
+    },
+
+    confirmDelete(item) {
+      var self = this
+      uni.showModal({
+        title: '提示',
+        content: '确定删除此打卡记录?',
+        success: function(res) {
+          if (res.confirm) {
+            self.deleteRecord(item)
+          }
+        }
+      })
+    },
+
+    async deleteRecord(item) {
+      try {
+        const res = await deleteCheckin(item.id)
+        if (res && res.code === 200) {
+          uni.showToast({ title: '已删除', icon: 'success' })
+          this.loadData()
+        }
+      } catch (e) {
+        console.error('删除失败', e)
+      }
+    },
+
+    getTypeIcon(type) {
+      const map = { daily_plan: '📋', saving: '💰', expense: '🛒', review: '📊' }
+      return map[type] || '📌'
+    },
+
+    getTypeLabel(type) {
+      const map = { daily_plan: '记账规划', saving: '储蓄记录', expense: '消费记录', review: '财务回顾' }
+      return map[type] || type
+    },
+
+    formatDate(dateStr) {
+      if (!dateStr) return ''
+      return dateStr.substring(0, 16).replace('T', ' ')
+    }
+  }
+}
+</script>
+
+<style>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding: 20rpx 30rpx 120rpx;
+}
+
+.energy-tip {
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  border-radius: 16rpx;
+  padding: 20rpx 30rpx;
+  margin-bottom: 24rpx;
+}
+
+.energy-tip-text {
+  color: #fff;
+  font-size: 26rpx;
+  font-weight: 500;
+}
+
+.section-title {
+  margin-bottom: 20rpx;
+}
+
+.section-title-text {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #333;
+}
+
+.quick-checkin {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  margin-bottom: 24rpx;
+}
+
+.checkin-types {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.checkin-btn {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  width: 150rpx;
+  height: 120rpx;
+  background: #FFF7ED;
+  border-radius: 16rpx;
+  border: 2rpx solid #FED7AA;
+}
+
+.checkin-btn.disabled {
+  opacity: 0.5;
+}
+
+.checkin-icon {
+  font-size: 40rpx;
+  margin-bottom: 8rpx;
+}
+
+.checkin-label {
+  font-size: 24rpx;
+  color: #666;
+}
+
+.today-count {
+  text-align: center;
+  margin-top: 16rpx;
+  font-size: 24rpx;
+  color: #999;
+}
+
+.stats-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  align-items: center;
+  margin-bottom: 24rpx;
+}
+
+.stat-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.stat-value {
+  font-size: 36rpx;
+  font-weight: 700;
+  color: #F97316;
+}
+
+.stat-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 4rpx;
+}
+
+.stat-divider {
+  width: 2rpx;
+  height: 60rpx;
+  background: #eee;
+}
+
+.record-section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+}
+
+.empty-state {
+  text-align: center;
+  padding: 48rpx 0;
+  color: #999;
+  font-size: 26rpx;
+}
+
+.record-item {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 2rpx solid #f5f5f5;
+}
+
+.record-item:last-child {
+  border-bottom: none;
+}
+
+.record-left {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.record-icon {
+  font-size: 36rpx;
+  margin-right: 16rpx;
+}
+
+.record-info {
+  display: flex;
+  flex-direction: column;
+}
+
+.record-type {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.record-date {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 4rpx;
+}
+
+.record-right {}
+
+.record-energy {
+  font-size: 28rpx;
+  color: #F97316;
+  font-weight: 600;
+}
+</style>

+ 363 - 0
cfc-frontend/pages/wealth/index.vue

@@ -0,0 +1,363 @@
+<template>
+  <view class="container">
+    <PageBanner theme="wealth"
+      tagline="财商培养 · 富足人生"
+      quote="君子爱财,取之有道" />
+
+    <view class="section" v-if="sectionVisible('today_tasks') && isLoggedIn">
+      <view class="section-header">
+        <text class="section-title">📋 我的任务</text>
+        <text class="section-more" @click="goTasks">全部 ›</text>
+      </view>
+      <view class="task-wrap" v-if="todayTasks.length > 0">
+        <view class="task-item" v-for="task in todayTasks" :key="task.id">
+          <text class="task-name">{{ task.title }}</text>
+          <text class="task-points">+{{ task.points || 0 }}分</text>
+        </view>
+      </view>
+      <view class="task-empty" v-else>
+        <text class="task-empty-text">今日暂无任务</text>
+      </view>
+    </view>
+
+    <view class="func-section" v-if="sectionVisible('func_entries')">
+      <view class="func-grid">
+        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
+          <view class="func-icon-wrap">
+            <text class="func-icon">{{ item.icon }}</text>
+          </view>
+          <text class="func-label">{{ item.label }}</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="section" v-if="sectionVisible('hot_activities')">
+      <view class="section-header">
+        <text class="section-title">🔥 热门活动</text>
+        <text class="section-more" @click="goMoreActivities">更多 ›</text>
+      </view>
+      <scroll-view scroll-x class="activity-scroll" show-scrollbar="false">
+        <view class="activity-card" v-for="act in hotActivities" :key="act.id" @click="goActivityDetail(act.id)">
+          <image class="activity-img" :src="act.image || '/static/default-activity.png'" mode="aspectFill" />
+          <view class="activity-info">
+            <text class="activity-name">{{ act.name }}</text>
+            <text class="activity-meta">{{ act.date }} · {{ act.location }}</text>
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+
+    <view class="section" v-if="sectionVisible('recommended_products')">
+      <view class="section-header">
+        <text class="section-title">🛍️ 推荐商品</text>
+        <text class="section-more" @click="goMoreProducts">更多 ›</text>
+      </view>
+      <view class="product-list">
+        <view class="product-card" v-for="prod in recommendedProducts" :key="prod.id" @click="goProductDetail(prod.id)">
+          <image class="product-img" :src="prod.image || '/static/default-product.png'" mode="aspectFill" />
+          <text class="product-name line-clamp-2">{{ prod.name }}</text>
+          <text class="product-price">¥{{ prod.price }}</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import PageBanner from '../../components/PageBanner.vue'
+import { getVisibleSections, getEnergyOverview, getTodayTasks } from '../../utils/api.js'
+
+export default {
+  components: { PageBanner },
+  data() {
+    return {
+      isLoggedIn: false,
+      role: null,
+      currentChildId: null,
+      totalEnergy: null,
+      visibleSections: [],
+      funcList: [
+        { icon: '\u{1F4B0}', label: '财商打卡', needLogin: true, page: '/pages/wealth/checkin' },
+        { icon: '\u{1F4B5}', label: '零花钱管理', needLogin: true, page: '' },
+        { icon: '\u{1F4C8}', label: '推广中心', needLogin: true, page: '/pages/promotion/index' },
+        { icon: '\u{1F3E6}', label: '商城', needLogin: false, page: '/pages/shop/index' },
+        { icon: '\u{1F4CB}', label: '保单管理', needLogin: true, page: '/pages/wealth/insurance-list' }
+      ],
+      hotActivities: [
+        { id: 1, name: '小小理财师体验营', date: '周六', location: '浠艾福中心', image: '' },
+        { id: 2, name: '跳蚤市场亲子活动', date: '周日', location: '朝阳公园', image: '' },
+        { id: 3, name: '财商启蒙绘本共读', date: '下周六', location: '线上', image: '' }
+      ],
+      recommendedProducts: [
+        { id: 1, name: '儿童理财启蒙套装', price: 199, image: '' },
+        { id: 2, name: '亲子财商桌游', price: 159, image: '' },
+        { id: 3, name: '零花钱记账本', price: 49, image: '' },
+        { id: 4, name: '少儿财商课程', price: 399, image: '' }
+      ],
+      todayTasks: []
+    }
+  },
+  onShow() {
+    var token = uni.getStorageSync('token')
+    this.isLoggedIn = !!token
+    if (this.isLoggedIn) {
+      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      this.currentChildId = uni.getStorageSync('currentChildId') || null
+    }
+    this.loadSectionConfig()
+    if (this.isLoggedIn) {
+      this.loadEnergyData()
+      this.loadTodayTasks()
+    }
+  },
+  methods: {
+    async loadSectionConfig() {
+      if (!uni.getStorageSync('token')) {
+        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
+        return
+      }
+      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      try {
+        var res = await getVisibleSections({ pageKey: 'wealth', role: role })
+        if (res.code === 200 && res.data) {
+          this.visibleSections = res.data.map(function(s) { return s.sectionKey })
+        }
+      } catch (e) {
+        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
+      }
+    },
+    async loadEnergyData() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getEnergyOverview(childId)
+        if (res && res.data) {
+          this.totalEnergy = res.data.totalEnergy || 0
+        }
+      } catch (e) { console.log('获取能量概览失败', e) }
+    },
+    async loadTodayTasks() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getTodayTasks(childId)
+        if (res && res.data) {
+          this.todayTasks = res.data.slice(0, 5)
+        }
+      } catch (e) { console.log('获取今日任务失败', e) }
+    },
+    sectionVisible(key) {
+      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
+    },
+    onFuncClick(item) {
+      if (item.needLogin && !this.isLoggedIn) {
+        uni.showModal({
+          title: '提示',
+          content: '请先登录后使用此功能',
+          success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
+        })
+        return
+      }
+      if (item.page) {
+        var nativeTabs = ['/pages/tasks/tasks', '/pages/index/index', '/pages/body/index', '/pages/mind/index', '/pages/action/index', '/pages/wisdom/index', '/pages/profile/profile']
+        if (nativeTabs.indexOf(item.page) !== -1) {
+          uni.switchTab({ url: item.page })
+        } else {
+          uni.navigateTo({ url: item.page })
+        }
+      }
+    },
+    goMoreActivities() {},
+    goActivityDetail(id) {},
+    goMoreProducts() {},
+    goProductDetail(id) {
+      var token = uni.getStorageSync('token')
+      if (token) {
+        uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/wealth/index') })
+      }
+    },
+    goTasks() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
+    goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+.func-section {
+  margin: -40rpx 20rpx 0;
+  position: relative;
+  z-index: 2;
+}
+.func-grid {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 30rpx 16rpx;
+  display: flex;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.func-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.func-item:active {
+  opacity: 0.7;
+}
+.func-icon-wrap {
+  width: 88rpx;
+  height: 88rpx;
+  border-radius: 50%;
+  background: #E3F2FD;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+.func-icon {
+  font-size: 40rpx;
+}
+.func-label {
+  font-size: 24rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.section {
+  margin: 20rpx 20rpx;
+}
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.section-more {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.activity-scroll {
+  white-space: nowrap;
+}
+.activity-card {
+  display: inline-block;
+  width: 280rpx;
+  margin-right: 20rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.activity-img {
+  width: 280rpx;
+  height: 180rpx;
+  background: #f0f0f0;
+}
+.activity-info {
+  padding: 16rpx;
+}
+.activity-name {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+.activity-meta {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.product-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+.product-card {
+  width: calc(50% - 10rpx);
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.product-img {
+  width: 100%;
+  height: 240rpx;
+  background: #f0f0f0;
+}
+.product-name {
+  font-size: 26rpx;
+  color: #333;
+  padding: 12rpx 16rpx 0;
+}
+.product-price {
+  font-size: 28rpx;
+  color: #F97316;
+  font-weight: bold;
+  padding: 8rpx 16rpx 16rpx;
+  display: block;
+}
+.line-clamp-2 {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.task-wrap {
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.task-item {
+  display: flex;
+  justify-content: space-between;
+  padding: 28rpx 24rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.task-item:last-child {
+  border-bottom: none;
+}
+.task-name {
+  font-size: 26rpx;
+  color: #333;
+}
+.task-points {
+  font-size: 24rpx;
+  color: #F97316;
+}
+.task-empty {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 60rpx;
+  text-align: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.task-empty-text {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.bottom-spacer {
+  height: 20rpx;
+}
+</style>

+ 312 - 0
cfc-frontend/pages/wealth/insurance-add.vue

@@ -0,0 +1,312 @@
+<template>
+  <scroll-view class="container" scroll-y>
+    <view class="form-card">
+      <view class="form-group">
+        <text class="form-label">保单名称 <text class="required">*</text></text>
+        <input class="form-input" v-model="form.policyName" placeholder="请输入保单名称" />
+      </view>
+
+      <view class="form-group">
+        <text class="form-label">保险公司</text>
+        <input class="form-input" v-model="form.insuranceCompany" placeholder="请输入保险公司名称" />
+      </view>
+
+      <view class="form-group">
+        <text class="form-label">保单类型</text>
+        <picker class="form-picker" :value="policyTypeIndex" :range="policyTypes" @change="onTypeChange">
+          <view class="picker-value">{{ policyTypes[policyTypeIndex] || '请选择保单类型' }}</view>
+        </picker>
+      </view>
+
+      <view class="form-group">
+        <text class="form-label">被保人 <text class="required">*</text></text>
+        <input class="form-input" v-model="form.insuredPerson" placeholder="请输入被保人姓名" />
+      </view>
+
+      <view class="form-row">
+        <view class="form-group half">
+          <text class="form-label">保费</text>
+          <input class="form-input" v-model="form.premium" placeholder="0.00" type="digit" />
+        </view>
+        <view class="form-group half">
+          <text class="form-label">保额</text>
+          <input class="form-input" v-model="form.sumInsured" placeholder="0.00" type="digit" />
+        </view>
+      </view>
+
+      <view class="form-row">
+        <view class="form-group half">
+          <text class="form-label">生效日期</text>
+          <picker class="form-picker" mode="date" :value="form.startDate" @change="onStartDateChange">
+            <view class="picker-value">{{ form.startDate || '请选择' }}</view>
+          </picker>
+        </view>
+        <view class="form-group half">
+          <text class="form-label">到期日期</text>
+          <picker class="form-picker" mode="date" :value="form.endDate" @change="onEndDateChange">
+            <view class="picker-value">{{ form.endDate || '请选择' }}</view>
+          </picker>
+        </view>
+      </view>
+
+      <view class="form-group">
+        <text class="form-label">续费类型</text>
+        <picker class="form-picker" :value="renewalTypeIndex" :range="renewalTypes" @change="onRenewalChange">
+          <view class="picker-value">{{ renewalTypes[renewalTypeIndex] || '请选择续费类型' }}</view>
+        </picker>
+      </view>
+
+      <view class="form-group">
+        <text class="form-label">保单号</text>
+        <input class="form-input" v-model="form.policyNo" placeholder="请输入保单号" />
+      </view>
+
+      <view class="form-group">
+        <text class="form-label">备注</text>
+        <textarea class="form-textarea" v-model="form.remark" placeholder="备注信息" />
+      </view>
+    </view>
+
+    <view class="submit-btn-wrap">
+      <button class="submit-btn" :loading="submitting" @click="submitForm">
+        {{ isEdit ? '更新保单' : '提交保存' }}
+      </button>
+    </view>
+  </scroll-view>
+</template>
+
+<script>
+import { getInsuranceDetail, createInsurance, updateInsurance } from '../../utils/api'
+
+export default {
+  data() {
+    return {
+      isEdit: false,
+      submitting: false,
+      policyTypes: ['医疗险', '重疾险', '意外险', '教育金', '养老险'],
+      policyTypeValues: ['medical', 'life', 'accident', 'education', 'endowment'],
+      policyTypeIndex: -1,
+      renewalTypes: ['年缴', '月缴', '趸交'],
+      renewalTypeValues: ['annual', 'monthly', 'single'],
+      renewalTypeIndex: -1,
+      form: {
+        policyName: '',
+        insuranceCompany: '',
+        policyType: '',
+        insuredPerson: '',
+        premium: '',
+        sumInsured: '',
+        startDate: '',
+        endDate: '',
+        renewalType: 'annual',
+        policyNo: '',
+        remark: ''
+      }
+    }
+  },
+
+  onLoad(options) {
+    if (options && options.id) {
+      this.isEdit = true
+      this.loadDetail(options.id)
+    }
+  },
+
+  methods: {
+    async loadDetail(id) {
+      try {
+        const res = await getInsuranceDetail(id)
+        if (res && res.code === 200 && res.data) {
+          const data = res.data
+          this.form.policyName = data.policyName || ''
+          this.form.insuranceCompany = data.insuranceCompany || ''
+          this.form.policyType = data.policyType || ''
+          this.form.insuredPerson = data.insuredPerson || ''
+          this.form.premium = data.premium ? String(data.premium) : ''
+          this.form.sumInsured = data.sumInsured ? String(data.sumInsured) : ''
+          this.form.startDate = data.startDate ? data.startDate.substring(0, 10) : ''
+          this.form.endDate = data.endDate ? data.endDate.substring(0, 10) : ''
+          this.form.renewalType = data.renewalType || 'annual'
+          this.form.policyNo = data.policyNo || ''
+          this.form.remark = data.remark || ''
+
+          this.policyTypeIndex = this.policyTypeValues.indexOf(data.policyType)
+          this.renewalTypeIndex = this.renewalTypeValues.indexOf(data.renewalType)
+
+          uni.setNavigationBarTitle({ title: '保单详情' })
+        }
+      } catch (e) {
+        console.error('加载保单详情失败', e)
+      }
+    },
+
+    onTypeChange(e) {
+      this.policyTypeIndex = parseInt(e.detail.value)
+      this.form.policyType = this.policyTypeValues[this.policyTypeIndex]
+    },
+
+    onRenewalChange(e) {
+      this.renewalTypeIndex = parseInt(e.detail.value)
+      this.form.renewalType = this.renewalTypeValues[this.renewalTypeIndex]
+    },
+
+    onStartDateChange(e) {
+      this.form.startDate = e.detail.value
+    },
+
+    onEndDateChange(e) {
+      this.form.endDate = e.detail.value
+    },
+
+    async submitForm() {
+      if (!this.form.policyName) {
+        uni.showToast({ title: '请输入保单名称', icon: 'none' })
+        return
+      }
+      if (!this.form.insuredPerson) {
+        uni.showToast({ title: '请输入被保人姓名', icon: 'none' })
+        return
+      }
+
+      this.submitting = true
+      try {
+        var payload = {
+          policyName: this.form.policyName,
+          insuranceCompany: this.form.insuranceCompany,
+          policyType: this.form.policyType,
+          insuredPerson: this.form.insuredPerson,
+          premium: this.form.premium ? parseFloat(this.form.premium) : null,
+          sumInsured: this.form.sumInsured ? parseFloat(this.form.sumInsured) : null,
+          startDate: this.form.startDate || null,
+          endDate: this.form.endDate || null,
+          renewalType: this.form.renewalType,
+          policyNo: this.form.policyNo,
+          remark: this.form.remark
+        }
+
+        var res
+        if (this.isEdit) {
+          payload.id = this.$mp.query && this.$mp.query.id
+          res = await updateInsurance(payload)
+        } else {
+          res = await createInsurance(payload)
+        }
+
+        if (res && res.code === 200) {
+          uni.showToast({ title: this.isEdit ? '更新成功' : '创建成功', icon: 'success' })
+          setTimeout(function() {
+            uni.navigateBack()
+          }, 1500)
+        } else {
+          uni.showToast({ title: res && res.message || '提交失败', icon: 'none' })
+        }
+      } catch (e) {
+        uni.showToast({ title: '网络错误', icon: 'none' })
+      } finally {
+        this.submitting = false
+      }
+    }
+  }
+}
+</script>
+
+<style>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding: 20rpx 30rpx 120rpx;
+}
+
+.form-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 24rpx;
+}
+
+.form-group {
+  margin-bottom: 28rpx;
+}
+
+.form-label {
+  display: block;
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 500;
+  margin-bottom: 12rpx;
+}
+
+.required {
+  color: #DC2626;
+}
+
+.form-input {
+  width: 100%;
+  height: 80rpx;
+  line-height: 80rpx;
+  background: #F5F7FA;
+  border-radius: 12rpx;
+  padding: 0 20rpx;
+  font-size: 28rpx;
+  color: #333;
+  box-sizing: border-box;
+}
+
+.form-picker {
+  width: 100%;
+  height: 80rpx;
+  line-height: 80rpx;
+  background: #F5F7FA;
+  border-radius: 12rpx;
+  padding: 0 20rpx;
+  box-sizing: border-box;
+}
+
+.picker-value {
+  font-size: 28rpx;
+  color: #333;
+  line-height: 80rpx;
+}
+
+.form-textarea {
+  width: 100%;
+  height: 160rpx;
+  background: #F5F7FA;
+  border-radius: 12rpx;
+  padding: 20rpx;
+  font-size: 28rpx;
+  color: #333;
+  box-sizing: border-box;
+}
+
+.form-row {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+}
+
+.form-group.half {
+  width: 48%;
+}
+
+.submit-btn-wrap {
+  padding: 20rpx 0 40rpx;
+}
+
+.submit-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+  background: #F97316;
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: 600;
+  border-radius: 44rpx;
+  border: none;
+}
+
+.submit-btn::after {
+  border: none;
+}
+</style>

+ 296 - 0
cfc-frontend/pages/wealth/insurance-list.vue

@@ -0,0 +1,296 @@
+<template>
+  <scroll-view class="container" scroll-y>
+    <!-- 状态过滤 -->
+    <view class="filter-tabs">
+      <view
+        v-for="(tab, index) in filterTabs"
+        :key="index"
+        class="filter-tab"
+        :class="{ active: currentFilter === tab.value }"
+        @click="currentFilter = tab.value; loadPolicies()">
+        <text>{{ tab.label }}</text>
+      </view>
+    </view>
+
+    <!-- 保单列表 -->
+    <view v-if="policies.length === 0" class="empty-state">
+      <text class="empty-icon">📋</text>
+      <text class="empty-text">暂无保单</text>
+      <text class="empty-hint">点击下方按钮添加保单</text>
+    </view>
+
+    <view
+      v-for="(item, index) in policies"
+      :key="index"
+      class="policy-card"
+      @click="viewDetail(item)">
+      <view class="policy-header">
+        <text class="policy-name">{{ item.policyName }}</text>
+        <text class="policy-status" :class="item.status === 'active' ? 'status-active' : item.status === 'expired' ? 'status-expired' : 'status-cancelled'">{{ getStatusLabel(item.status) }}</text>
+      </view>
+      <view class="policy-body">
+        <view class="policy-row">
+          <text class="policy-label">保险公司</text>
+          <text class="policy-value">{{ item.insuranceCompany || '未知' }}</text>
+        </view>
+        <view class="policy-row">
+          <text class="policy-label">被保人</text>
+          <text class="policy-value">{{ item.insuredPerson }}</text>
+        </view>
+        <view class="policy-row">
+          <text class="policy-label">保费</text>
+          <text class="policy-value orange">{{ '¥' + (item.premium || '0.00') }}</text>
+        </view>
+        <view class="policy-row">
+          <text class="policy-label">到期日</text>
+          <text class="policy-value">{{ item.endDate ? item.endDate.substring(0, 10) : '长期' }}</text>
+        </view>
+      </view>
+      <view class="policy-footer" @click.stop="deletePolicy(item)">
+        <text class="delete-btn">删除</text>
+      </view>
+    </view>
+
+    <!-- 新增按钮 -->
+    <view class="add-btn-wrap">
+      <button class="add-btn" @click="addPolicy">+ 新增保单</button>
+    </view>
+  </scroll-view>
+</template>
+
+<script>
+import { getInsuranceList, deleteInsurance } from '../../utils/api'
+
+export default {
+  data() {
+    return {
+      filterTabs: [
+        { label: '全部', value: '' },
+        { label: '有效', value: 'active' },
+        { label: '已过期', value: 'expired' },
+        { label: '已取消', value: 'cancelled' }
+      ],
+      currentFilter: '',
+      policies: []
+    }
+  },
+
+  onShow() {
+    this.loadPolicies()
+  },
+
+  methods: {
+    async loadPolicies() {
+      try {
+        const res = await getInsuranceList({ status: this.currentFilter || undefined })
+        if (res && res.code === 200) {
+          this.policies = res.data || []
+        }
+      } catch (e) {
+        console.error('加载保单失败', e)
+      }
+    },
+
+    viewDetail(item) {
+      uni.navigateTo({
+        url: '/pages/wealth/insurance-add?id=' + item.id
+      })
+    },
+
+    addPolicy() {
+      uni.navigateTo({
+        url: '/pages/wealth/insurance-add'
+      })
+    },
+
+    deletePolicy(item) {
+      var self = this
+      uni.showModal({
+        title: '提示',
+        content: '确定删除该保单?',
+        success: async function(res) {
+          if (res.confirm) {
+            try {
+              const result = await deleteInsurance(item.id)
+              if (result && result.code === 200) {
+                uni.showToast({ title: '已删除', icon: 'success' })
+                self.loadPolicies()
+              }
+            } catch (e) {
+              console.error('删除失败', e)
+            }
+          }
+        }
+      })
+    },
+
+    getStatusClass(status) {
+      return status === 'active' ? 'status-active' : status === 'expired' ? 'status-expired' : 'status-cancelled'
+    },
+
+    getStatusLabel(status) {
+      return status === 'active' ? '有效' : status === 'expired' ? '已过期' : '已取消'
+    }
+  }
+}
+</script>
+
+<style>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding: 20rpx 30rpx 120rpx;
+}
+
+.filter-tabs {
+  display: flex;
+  flex-direction: row;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 12rpx;
+  margin-bottom: 24rpx;
+}
+
+.filter-tab {
+  flex: 1;
+  text-align: center;
+  padding: 12rpx 0;
+  font-size: 26rpx;
+  color: #666;
+  border-radius: 12rpx;
+}
+
+.filter-tab.active {
+  background: #F97316;
+  color: #fff;
+  font-weight: 600;
+}
+
+.empty-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 80rpx 0;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+  margin-bottom: 20rpx;
+}
+
+.empty-text {
+  font-size: 30rpx;
+  color: #999;
+}
+
+.empty-hint {
+  font-size: 24rpx;
+  color: #ccc;
+  margin-top: 8rpx;
+}
+
+.policy-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+}
+
+.policy-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+  padding-bottom: 16rpx;
+  border-bottom: 2rpx solid #f5f5f5;
+}
+
+.policy-name {
+  font-size: 30rpx;
+  font-weight: 600;
+  color: #333;
+}
+
+.policy-status {
+  font-size: 24rpx;
+  padding: 4rpx 16rpx;
+  border-radius: 8rpx;
+}
+
+.status-active {
+  background: #DCFCE7;
+  color: #16A34A;
+}
+
+.status-expired {
+  background: #FEF2F2;
+  color: #DC2626;
+}
+
+.status-cancelled {
+  background: #F5F5F5;
+  color: #999;
+}
+
+.policy-body {
+  margin-bottom: 12rpx;
+}
+
+.policy-row {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  padding: 8rpx 0;
+}
+
+.policy-label {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.policy-value {
+  font-size: 26rpx;
+  color: #333;
+}
+
+.policy-value.orange {
+  color: #F97316;
+  font-weight: 600;
+}
+
+.policy-footer {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-end;
+  padding-top: 12rpx;
+  border-top: 2rpx solid #f5f5f5;
+}
+
+.delete-btn {
+  font-size: 24rpx;
+  color: #DC2626;
+  padding: 8rpx 20rpx;
+}
+
+.add-btn-wrap {
+  padding: 20rpx 0 40rpx;
+}
+
+.add-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  text-align: center;
+  background: #F97316;
+  color: #fff;
+  font-size: 30rpx;
+  font-weight: 600;
+  border-radius: 44rpx;
+  border: none;
+}
+
+.add-btn::after {
+  border: none;
+}
+</style>

+ 363 - 0
cfc-frontend/pages/wisdom/index.vue

@@ -0,0 +1,363 @@
+<template>
+  <view class="container">
+    <PageBanner theme="wisdom"
+      tagline="学以启智 · 慧及未来"
+      quote="知者不惑,仁者不忧" />
+
+    <view class="section" v-if="sectionVisible('today_tasks') && isLoggedIn">
+      <view class="section-header">
+        <text class="section-title">📋 我的任务</text>
+        <text class="section-more" @click="goTasks">全部 ›</text>
+      </view>
+      <view class="task-wrap" v-if="todayTasks.length > 0">
+        <view class="task-item" v-for="task in todayTasks" :key="task.id">
+          <text class="task-name">{{ task.title }}</text>
+          <text class="task-points">+{{ task.points || 0 }}分</text>
+        </view>
+      </view>
+      <view class="task-empty" v-else>
+        <text class="task-empty-text">今日暂无任务</text>
+      </view>
+    </view>
+
+    <view class="func-section" v-if="sectionVisible('func_entries')">
+      <view class="func-grid">
+        <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
+          <view class="func-icon-wrap">
+            <text class="func-icon">{{ item.icon }}</text>
+          </view>
+          <text class="func-label">{{ item.label }}</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="section" v-if="sectionVisible('hot_activities')">
+      <view class="section-header">
+        <text class="section-title">🔥 热门活动</text>
+        <text class="section-more" @click="goMoreActivities">更多 ›</text>
+      </view>
+      <scroll-view scroll-x class="activity-scroll" show-scrollbar="false">
+        <view class="activity-card" v-for="act in hotActivities" :key="act.id" @click="goActivityDetail(act.id)">
+          <image class="activity-img" :src="act.image || '/static/default-activity.png'" mode="aspectFill" />
+          <view class="activity-info">
+            <text class="activity-name">{{ act.name }}</text>
+            <text class="activity-meta">{{ act.date }} · {{ act.location }}</text>
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+
+    <view class="section" v-if="sectionVisible('recommended_products')">
+      <view class="section-header">
+        <text class="section-title">🛍️ 推荐商品</text>
+        <text class="section-more" @click="goMoreProducts">更多 ›</text>
+      </view>
+      <view class="product-list">
+        <view class="product-card" v-for="prod in recommendedProducts" :key="prod.id" @click="goProductDetail(prod.id)">
+          <image class="product-img" :src="prod.image || '/static/default-product.png'" mode="aspectFill" />
+          <text class="product-name line-clamp-2">{{ prod.name }}</text>
+          <text class="product-price">¥{{ prod.price }}</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import PageBanner from '../../components/PageBanner.vue'
+import { getVisibleSections, getEnergyOverview, getTodayTasks } from '../../utils/api.js'
+
+export default {
+  components: { PageBanner },
+  data() {
+    return {
+      isLoggedIn: false,
+      role: null,
+      currentChildId: null,
+      totalEnergy: null,
+      visibleSections: [],
+      funcList: [
+        { icon: '\u{1F4DA}', label: '学习任务', needLogin: true, page: '/pages/tasks/tasks' },
+        { icon: '\u{1F3AE}', label: '小游戏', needLogin: false, page: '/pages/games/list' },
+        { icon: '\u{1F9E9}', label: '测评中心', needLogin: false, page: '/pages/assessment/apply' },
+        { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind/articles' },
+        { icon: '\u{1F4A1}', label: '知识挑战', needLogin: false, page: '' }
+      ],
+      hotActivities: [
+        { id: 1, name: '少儿编程启蒙营', date: '周六', location: '线上', image: '' },
+        { id: 2, name: '经典阅读分享会', date: '周日', location: '浠艾福中心', image: '' },
+        { id: 3, name: '科学实验探索课', date: '下周六', location: '浠艾福中心', image: '' }
+      ],
+      recommendedProducts: [
+        { id: 1, name: '儿童百科全书套装', price: 199, image: '' },
+        { id: 2, name: '益智棋类礼盒', price: 159, image: '' },
+        { id: 3, name: '科学实验套装', price: 299, image: '' },
+        { id: 4, name: '中外名著精选', price: 129, image: '' }
+      ],
+      todayTasks: []
+    }
+  },
+  onShow() {
+    var token = uni.getStorageSync('token')
+    this.isLoggedIn = !!token
+    if (this.isLoggedIn) {
+      this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      this.currentChildId = uni.getStorageSync('currentChildId') || null
+    }
+    this.loadSectionConfig()
+    if (this.isLoggedIn) {
+      this.loadEnergyData()
+      this.loadTodayTasks()
+    }
+  },
+  methods: {
+    async loadSectionConfig() {
+      if (!uni.getStorageSync('token')) {
+        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
+        return
+      }
+      var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      try {
+        var res = await getVisibleSections({ pageKey: 'wisdom', role: role })
+        if (res.code === 200 && res.data) {
+          this.visibleSections = res.data.map(function(s) { return s.sectionKey })
+        }
+      } catch (e) {
+        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
+      }
+    },
+    async loadEnergyData() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getEnergyOverview(childId)
+        if (res && res.data) {
+          this.totalEnergy = res.data.totalEnergy || 0
+        }
+      } catch (e) { console.log('获取能量概览失败', e) }
+    },
+    async loadTodayTasks() {
+      var childId = this.currentChildId
+      if (!childId) return
+      try {
+        var res = await getTodayTasks(childId)
+        if (res && res.data) {
+          this.todayTasks = res.data.slice(0, 5)
+        }
+      } catch (e) { console.log('获取今日任务失败', e) }
+    },
+    sectionVisible(key) {
+      return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
+    },
+    onFuncClick(item) {
+      if (item.needLogin && !this.isLoggedIn) {
+        uni.showModal({
+          title: '提示',
+          content: '请先登录后使用此功能',
+          success: function(res) { if (res.confirm) uni.navigateTo({ url: '/pages/login/login' }) }
+        })
+        return
+      }
+      if (item.page) {
+        var nativeTabs = ['/pages/tasks/tasks', '/pages/index/index', '/pages/body/index', '/pages/mind/index', '/pages/action/index', '/pages/wisdom/index', '/pages/profile/profile']
+        if (nativeTabs.indexOf(item.page) !== -1) {
+          uni.switchTab({ url: item.page })
+        } else {
+          uni.navigateTo({ url: item.page })
+        }
+      }
+    },
+    goMoreActivities() {},
+    goActivityDetail(id) {},
+    goMoreProducts() {},
+    goProductDetail(id) {
+      var token = uni.getStorageSync('token')
+      if (token) {
+        uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/wisdom/index') })
+      }
+    },
+    goTasks() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
+    goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+.func-section {
+  margin: -40rpx 20rpx 0;
+  position: relative;
+  z-index: 2;
+}
+.func-grid {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 30rpx 16rpx;
+  display: flex;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+.func-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.func-item:active {
+  opacity: 0.7;
+}
+.func-icon-wrap {
+  width: 88rpx;
+  height: 88rpx;
+  border-radius: 50%;
+  background: #FFF8E1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+.func-icon {
+  font-size: 40rpx;
+}
+.func-label {
+  font-size: 24rpx;
+  color: #333;
+  font-weight: 500;
+}
+
+.section {
+  margin: 20rpx 20rpx;
+}
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.section-more {
+  font-size: 24rpx;
+  color: #999;
+}
+
+.activity-scroll {
+  white-space: nowrap;
+}
+.activity-card {
+  display: inline-block;
+  width: 280rpx;
+  margin-right: 20rpx;
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.activity-img {
+  width: 280rpx;
+  height: 180rpx;
+  background: #f0f0f0;
+}
+.activity-info {
+  padding: 16rpx;
+}
+.activity-name {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+.activity-meta {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.product-list {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+.product-card {
+  width: calc(50% - 10rpx);
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.product-img {
+  width: 100%;
+  height: 240rpx;
+  background: #f0f0f0;
+}
+.product-name {
+  font-size: 26rpx;
+  color: #333;
+  padding: 12rpx 16rpx 0;
+}
+.product-price {
+  font-size: 28rpx;
+  color: #F97316;
+  font-weight: bold;
+  padding: 8rpx 16rpx 16rpx;
+  display: block;
+}
+.line-clamp-2 {
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.task-wrap {
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.task-item {
+  display: flex;
+  justify-content: space-between;
+  padding: 28rpx 24rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.task-item:last-child {
+  border-bottom: none;
+}
+.task-name {
+  font-size: 26rpx;
+  color: #333;
+}
+.task-points {
+  font-size: 24rpx;
+  color: #F97316;
+}
+.task-empty {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 60rpx;
+  text-align: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.task-empty-text {
+  font-size: 26rpx;
+  color: #999;
+}
+
+.bottom-spacer {
+  height: 20rpx;
+}
+</style>

+ 1 - 1
cfc-frontend/project.private.config.json

@@ -1,5 +1,5 @@
 {
-	"name": "知行益家",
+	"name": "浠艾福",
 	"version": "1.0.0",
 	"id": "",
 	"description": "家庭教育任务管理小程序",

+ 10 - 0
cfc-frontend/store/index.js

@@ -83,9 +83,19 @@ const store = new Vuex.Store({
       state.currentChildId = ''
       state.children = []
       state.isSwitchedChild = false
+      state.isSwitchedTeacher = false
+      // 清除全部登录态存储
       uni.removeStorageSync('token')
       uni.removeStorageSync('currentRole')
       uni.removeStorageSync('isSwitchedChild')
+      uni.removeStorageSync('isSwitchedTeacher')
+      uni.removeStorageSync('userId')
+      uni.removeStorageSync('role')
+      uni.removeStorageSync('familyId')
+      uni.removeStorageSync('openid')
+      uni.removeStorageSync('userInfo')
+      uni.removeStorageSync('nickname')
+      uni.removeStorageSync('currentChildId')
     }
   },
   actions: {

+ 115 - 2
cfc-frontend/utils/api.js

@@ -504,6 +504,18 @@ export const validateInviteCode = (code) => {
   return request('/api/guide/invite-code/validate', 'GET', null, { code })
 }
 
+export const checkTeamInviteStatus = (inviteCode) => {
+  return request('/api/guide/team/check-status', 'POST', { inviteCode })
+}
+
+export const joinTeamByInvite = (inviteCode) => {
+  return request('/api/guide/team/join-by-invite', 'POST', { inviteCode })
+}
+
+export const leaveCurrentTeam = () => {
+  return request('/api/guide/team/leave', 'POST')
+}
+
 // 获取下级成长规划师列表
 export const getChildGuides = () => {
   return request('/api/guide/child-guides', 'GET')
@@ -973,8 +985,12 @@ export const getCommissionSummary = () => {
 }
 
 // 佣金明细
-export const getCommissionList = (page = 1, size = 20) => {
-  return request('/api/commission/list', 'POST', { page, size })
+export const getCommissionList = (page = 1, size = 20, status) => {
+  var params = { page: page, size: size }
+  if (status) {
+    params.status = status
+  }
+  return request('/api/commission/list', 'POST', params)
 }
 
 // 申请提现
@@ -1009,3 +1025,100 @@ export const getEnergyOverview = (childId) => {
 export const getEnergyLogs = (childId, dimensionCode, page = 1, size = 10) => {
   return request('/api/energy/logs', 'POST', { childId, dimensionCode, page, size })
 }
+
+// ===== 成就徽章 =====
+export const getUserBadges = (userId) => {
+  return request('/api/user/badges', 'POST', { userId })
+}
+
+// ===== 成长报告 =====
+export const getGrowthReport = (childId) => {
+  return request('/api/growth/report', 'POST', { childId })
+}
+
+// ===== 用户行动统计(我的页面) =====
+export const getUserActionStats = () => {
+  return request('/api/user/action-stats', 'POST')
+}
+
+// ===== 热门活动(行远 Tab) =====
+export const getHotActivities = (data) => {
+  return request('/api/activities/hot', 'POST', data)
+}
+
+// ===== 推荐商品(行远 Tab) =====
+export const getRecommendedProducts = (data) => {
+  return request('/api/products/recommend', 'POST', data)
+}
+
+// ===================== 会员升级与续费 =====================
+
+// 升级会员
+export const upgradeMember = (levelCode) => {
+  return request('/api/membership/upgrade', 'POST', { levelCode })
+}
+
+// 获取当前会员等级详情
+export const getMemberLevel = (levelCode) => {
+  return request('/api/membership/level', 'POST', { levelCode })
+}
+
+// 获取升级记录
+export const getUpgradeRecords = (page = 1, size = 10) => {
+  return request('/api/membership/upgrade-records', 'POST', { page, size })
+}
+
+// ===================== 佣金团队统计 =====================
+
+// 推广团队列表
+export const getCommissionTeam = (page = 1, size = 10) => {
+  return request('/api/commission/team', 'POST', { page, size })
+}
+
+// 佣金统计数据
+export const getCommissionStats = () => {
+  return request('/api/commission/stats', 'POST')
+}
+
+// 佣金余额
+export const getCommissionBalance = () => {
+  return request('/api/commission/balance', 'POST')
+}
+
+// ===== 财富维度 · 财商打卡 =====
+export const getCheckinList = (data) => {
+  return request('/api/wealth/checkin/list', 'POST', data)
+}
+
+export const getCheckinStats = (data) => {
+  return request('/api/wealth/checkin/stats', 'POST', data)
+}
+
+export const createCheckin = (data) => {
+  return request('/api/wealth/checkin/create', 'POST', data)
+}
+
+export const deleteCheckin = (id) => {
+  return request('/api/wealth/checkin/delete', 'POST', { id })
+}
+
+// ===== 财富维度 · 保单管理 =====
+export const getInsuranceList = (data) => {
+  return request('/api/wealth/insurance/list', 'POST', data)
+}
+
+export const getInsuranceDetail = (id) => {
+  return request('/api/wealth/insurance/detail', 'POST', { id })
+}
+
+export const createInsurance = (data) => {
+  return request('/api/wealth/insurance/create', 'POST', data)
+}
+
+export const updateInsurance = (data) => {
+  return request('/api/wealth/insurance/update', 'POST', data)
+}
+
+export const deleteInsurance = (id) => {
+  return request('/api/wealth/insurance/delete', 'POST', { id })
+}

+ 2 - 2
cfc-web/public/index.html

@@ -5,11 +5,11 @@
     <meta http-equiv="X-UA-Compatible" content="IE=edge">
     <meta name="viewport" content="width=device-width,initial-scale=1.0">
     <link rel="icon" href="<%= BASE_URL %>logo.png">
-    <title>知行益家管理后台</title>
+    <title>浠艾福管理后台</title>
   </head>
   <body>
     <noscript>
-      <strong>We're sorry but 知行益家管理后台 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
+      <strong>We're sorry but 浠艾福管理后台 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
     </noscript>
     <div id="app"></div>
   </body>

+ 40 - 0
cfc-web/src/api/admin.js

@@ -506,3 +506,43 @@ export function updateProductProfitRate(productId, profitRate) {
     data: { productId, profitRate }
   })
 }
+
+// ========== 教育体系管理 API ==========
+
+export function getEducationSystems() {
+  return request({
+    url: '/api/admin/education-systems/list',
+    method: 'post'
+  })
+}
+
+export function getAllEducationSystems() {
+  return request({
+    url: '/api/admin/education-systems/all',
+    method: 'post'
+  })
+}
+
+export function createEducationSystem(data) {
+  return request({
+    url: '/api/admin/education-systems/create',
+    method: 'post',
+    data
+  })
+}
+
+export function updateEducationSystem(data) {
+  return request({
+    url: '/api/admin/education-systems/update',
+    method: 'post',
+    data
+  })
+}
+
+export function deleteEducationSystem(id) {
+  return request({
+    url: '/api/admin/education-systems/delete',
+    method: 'post',
+    data: { id }
+  })
+}

+ 8 - 1
cfc-web/src/router/index.js

@@ -234,6 +234,12 @@ const routes = [
         name: 'WithdrawAudit',
         component: () => import('@/views/admin/WithdrawAudit.vue'),
         meta: { title: '提现审核' }
+      },
+      {
+        path: 'education-systems',
+        name: 'EducationSystems',
+        component: () => import('@/views/admin/EducationSystems.vue'),
+        meta: { title: '教育体系管理' }
       }
     ]
   }
@@ -324,7 +330,8 @@ router.beforeEach(async (to, from, next) => {
     'ServiceTypes', 'ServiceContents', 'PackageTemplates',
     'AssessmentAdmin', 'VendorReview',
     'ProductManage', 'OrderManage', 'SysConfig', 'EnergySandbox',
-    'ArticleCategory', 'ArticleManage', 'ArticleEdit'
+    'ArticleCategory', 'ArticleManage', 'ArticleEdit',
+    'EducationSystems'
   ]
   if (adminRoutes.includes(to.name) && role === 'teacher') {
     console.warn('Access denied: Admin role required for', to.name)

+ 1 - 1
cfc-web/src/views/Dashboard.vue

@@ -135,7 +135,7 @@
           <div class="system-info">
             <div class="info-item">
               <span class="info-label">系统名称</span>
-              <span class="info-value">知行益家管理后台</span>
+              <span class="info-value">浠艾福管理后台</span>
             </div>
             <div class="info-item">
               <span class="info-label">版本</span>

+ 37 - 6
cfc-web/src/views/Layout.vue

@@ -1,7 +1,7 @@
 <template>
   <el-container class="layout-container">
     <el-aside width="200px">
-      <div class="logo">知行益家</div>
+      <div class="logo">浠艾福</div>
       <el-menu
         :default-active="activeMenu"
         class="sidebar-menu"
@@ -10,8 +10,8 @@
         active-text-color="#6366F1"
         @select="handleMenuSelect"
       >
-        <!-- 规划师菜单 -->
-        <template v-if="isTeacher">
+        <!-- 规划师菜单 (vendorType=planner or role=teacher) -->
+        <template v-if="isPlanner">
           <el-menu-item index="/teacher-dashboard">
             <i class="el-icon-s-data"></i>
             <span slot="title">工作台</span>
@@ -68,6 +68,18 @@
           </el-menu-item>
         </template>
 
+        <!-- 服务商通用菜单 (vendorType=activity_provider/product_supplier, but not planner) -->
+        <template v-else-if="isVendor">
+          <el-menu-item index="/teacher-dashboard">
+            <i class="el-icon-s-data"></i>
+            <span slot="title">工作台</span>
+          </el-menu-item>
+          <el-menu-item index="/teacher-orders">
+            <i class="el-icon-s-order"></i>
+            <span slot="title">订单佣金</span>
+          </el-menu-item>
+        </template>
+
         <!-- 管理员菜单 -->
         <template v-else>
           <el-menu-item index="/dashboard">
@@ -169,6 +181,10 @@
             <i class="el-icon-data-line"></i>
             <span>五维能量</span>
           </el-menu-item>
+          <el-menu-item index="/education-systems">
+            <i class="el-icon-s-management"></i>
+            <span>教育体系</span>
+          </el-menu-item>
         </el-submenu>
         </template>
       </el-menu>
@@ -176,7 +192,7 @@
     <el-container>
       <el-header>
         <div class="header-right">
-          <el-button v-if="isTeacher" type="text" class="invite-code-btn" @click="showInviteCode" icon="el-icon-share">
+          <el-button v-if="isPlanner" type="text" class="invite-code-btn" @click="showInviteCode" icon="el-icon-share">
             邀请码
           </el-button>
           <el-dropdown trigger="click" @command="handleProfileCommand">
@@ -322,12 +338,27 @@ export default {
     currentRole() {
       return localStorage.getItem('role') || 'admin'
     },
+    vendorType() {
+      return localStorage.getItem('vendorType') || ''
+    },
+    isAdmin() {
+      return this.currentRole === 'admin'
+    },
     isTeacher() {
       return this.currentRole === 'teacher'
     },
+    isVendor() {
+      return !!this.vendorType
+    },
+    isPlanner() {
+      return this.vendorType === 'planner' || this.currentRole === 'teacher'
+    },
     displayRoleName() {
-      if (this.currentRole === 'teacher') {
-        return '指导师'
+      if (this.isPlanner) {
+        return '规划师'
+      }
+      if (this.isVendor) {
+        return '服务商'
       }
       return '管理员'
     },

+ 1 - 1
cfc-web/src/views/Login.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="login-container">
     <el-card class="login-card">
-      <h2 class="title">知行益家管理后台</h2>
+      <h2 class="title">浠艾福管理后台</h2>
       <el-form :model="loginForm" :rules="rules" ref="loginForm">
         <el-form-item prop="username">
           <el-input 

+ 131 - 0
cfc-web/src/views/admin/EducationSystems.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="education-systems">
+    <el-card>
+      <div slot="header">
+        <span>教育体系管理</span>
+        <el-button style="float: right" type="primary" size="small" @click="handleCreate">+ 添加体系</el-button>
+      </div>
+      <el-table :data="systems" stripe>
+        <el-table-column prop="id" label="ID" width="80"></el-table-column>
+        <el-table-column prop="name" label="编码" width="120"></el-table-column>
+        <el-table-column prop="label" label="显示名称"></el-table-column>
+        <el-table-column prop="description" label="描述"></el-table-column>
+        <el-table-column prop="sortOrder" label="排序" width="80"></el-table-column>
+        <el-table-column prop="status" label="状态" width="100">
+          <template slot-scope="scope">
+            <el-tag :type="scope.row.status === 'ACTIVE' ? 'success' : 'danger'">
+              {{ scope.row.status === 'ACTIVE' ? '启用' : '停用' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" width="150">
+          <template slot-scope="scope">
+            <el-button size="mini" @click="handleEdit(scope.row)">编辑</el-button>
+            <el-button size="mini" :type="scope.row.status === 'ACTIVE' ? 'warning' : 'success'" @click="toggleStatus(scope.row)">
+              {{ scope.row.status === 'ACTIVE' ? '停用' : '启用' }}
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </el-card>
+
+    <el-dialog :visible.sync="dialogVisible" :title="isEdit ? '编辑教育体系' : '添加教育体系'" width="500px">
+      <el-form :model="form" label-width="80px">
+        <el-form-item label="编码">
+          <el-input v-model="form.name" placeholder="如: bachelor"></el-input>
+        </el-form-item>
+        <el-form-item label="显示名称">
+          <el-input v-model="form.label" placeholder="如: 本科"></el-input>
+        </el-form-item>
+        <el-form-item label="描述">
+          <el-input v-model="form.description" type="textarea"></el-input>
+        </el-form-item>
+        <el-form-item label="排序">
+          <el-input-number v-model="form.sortOrder" :min="0"></el-input-number>
+        </el-form-item>
+      </el-form>
+      <div slot="footer">
+        <el-button @click="dialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="handleSubmit">保存</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { getAllEducationSystems, createEducationSystem, updateEducationSystem } from '@/api/admin'
+
+export default {
+  name: 'EducationSystems',
+  data() {
+    return {
+      systems: [],
+      dialogVisible: false,
+      isEdit: false,
+      form: {
+        id: null,
+        name: '',
+        label: '',
+        description: '',
+        sortOrder: 0,
+        status: 'ACTIVE'
+      }
+    }
+  },
+  created() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      try {
+        const res = await getAllEducationSystems()
+        if (res.data) {
+          this.systems = res.data
+        }
+      } catch (e) {
+        console.error(e)
+      }
+    },
+    handleCreate() {
+      this.isEdit = false
+      this.form = { id: null, name: '', label: '', description: '', sortOrder: 0, status: 'ACTIVE' }
+      this.dialogVisible = true
+    },
+    handleEdit(row) {
+      this.isEdit = true
+      this.form = { ...row }
+      this.dialogVisible = true
+    },
+    async toggleStatus(row) {
+      row.status = row.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE'
+      try {
+        await updateEducationSystem(row)
+        this.$message.success('状态已更新')
+      } catch (e) {
+        row.status = row.status === 'ACTIVE' ? 'DISABLED' : 'ACTIVE'
+        this.$message.error('更新失败')
+      }
+    },
+    async handleSubmit() {
+      try {
+        if (this.isEdit) {
+          await updateEducationSystem(this.form)
+        } else {
+          await createEducationSystem(this.form)
+        }
+        this.$message.success('保存成功')
+        this.dialogVisible = false
+        this.loadData()
+      } catch (e) {
+        this.$message.error('保存失败')
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.education-systems {
+  padding: 20px;
+}
+</style>

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно