Преглед изворни кода

feat(terminology): G2 契约/目录/DB 列名中性化(L2 前端契约与页面目录 + L3 后端内部命名)

L2 前端契约层(cfc-frontend/utils/api.js:新增中性别名,旧名一律保留为 @deprecated 转发别名)
- getChildren→getFamilyMembers、getParentDashboard→getFamilyDashboard
- createChild→createFamilyMember、updateChild→updateMemberProfile
- switchToChild→switchToMemberView、switchBackToParent→switchBackToSelf
- getTodayParentTasks→getTodayOwnerTasks、completeParentTask→completeOwnerTask
- create/get/exchange/approveParentWishlist→*FamilyOwnerWishlist 系列
- getChildOverview→getMemberOverview、getChildCompletionStats→getMemberCompletionStats
- getChildBadges→getMemberBadges、getChildBadgeStats→getMemberBadgeStats
- getChildRecords→getMemberGrowthRecords、getCognitiveChildScores→getCognitiveMemberScores
- getChildNutritionProfile→getMemberNutritionProfile、getDanReportByChild→getDanReportByMember
- generateParentInvite→generateGuideInvite、acceptParentInvite→acceptGuideInvite
- getEmiFamilyReports→getEmiFamilyMemberReports、getMyAssessmentPlans→getMyMemberAssessmentPlans
- 旧接口 URL 一律不变(/api/user/switch-to-child、/api/stats/child/overview 等),契约不断链

L2 页面目录(pages/parent + pages/child → pages/family;parent-index→family-index;child-index→member-index)
- pages/family 并入 7 页(appointments、member-detail、tasks、rewards、plans/index、plans/detail、wishes/index)
- pages.json 同步:pages/family 子包补齐,新旧路径同时注册
- 旧路径全部保留跳转壳(uni.redirectTo,失败回退 reLaunch,查询参数透传):
  pages/parent/{appointments,child-detail,plans/index,plans/detail,wishes/index}、
  pages/child/{tasks,rewards}、pages/home-pages/{parent-index,child-index}
- 跳转壳统一走 cfc-frontend/utils/redirect.js
- 全仓导航引用同步到新路径(assessment/results、growth-main、guide/bind/invite、profile* 等)

L2 cfc-web 契约同步
- api/admin.js:getMemberList/getMemberDetail/updateMember/deleteMember/adjustMemberPoints 5 个中性别名,旧名保留转发
- api/guideTask.js:fetchMemberTasks 别名,旧名 fetchChildTasks 保留转发
- router/index.js:新增 /family-members 别名路由(复用同一组件与 perm),旧 /children 路由保留

L3 后端内部命名(URL 契约不变)
- isChild→isMinorMember:ActivityOrderService 报名费用三档选择(快照 type 值 "child"/"parent" 保持不变)、
  InsurancePlanningService.calcEducationCoverage
- ParentController→FamilyOwnerController(@RequestMapping("/api/parent") 不变)、
  ParentAssessmentController→FamilyAssessmentController、ParentDashboardController→FamilyDashboardController
- FamilyOwnerController 内 createParentWishlist/getParentWishlist/exchangeParentWishlist 同步改名

L3 DB 列迁移(迁移421,幂等,可重复执行)
- activity_registrations 新增 member_id 列 + idx_member_id 索引,并从 child_id 回填
- 应用层双写:ActivityRegistrationService 两处 insert、ActivityOrderService.writeRegistrations 同时写 memberId 与 childId
- child_id 本版本不 DROP(旧列保留一个版本,下线前需主理人确认);schema.sql 的 CREATE TABLE 与列注释同步,
  迁移118 的 MODIFY COLUMN 注释同步中性化

兼容性
- ActivityRegistration 实体同时映射 memberId 与 childId,读写兼容;读取仍以 child_id 为准(唯一键/索引未变)
- cfc-web 活动报名审核页过滤条件新增 memberId,原 childId 条件保留
- tests/e2e/family-member-management.spec.js 断言同步 member-index

验证
- cfc-backend:mvn clean compile 通过(EXIT=0,旧 ParentController.class 已不存在)
- cfc-frontend:全部 JS node --check 0 失败;395 个 .vue 的 <script> 块 node --check 0 失败;
  394 个模板 vue-template-compiler 编译 0 error;pages.json 解析通过且全部子包声明页面文件均存在
- cfc-web:vue-cli-service build 通过(EXIT=0,DONE Build complete)
- 小程序整包 build 本环境无法执行:uni-app v2 工具链依赖 util.isRegExp(Node ≥22 已移除),
  本机 Node v24.14.0,属既有工具链不兼容,与本次改动无关
Sisyphus Agent пре 2 недеља
родитељ
комит
b9b24bc8e3
41 измењених фајлова са 6636 додато и 6274 уклоњено
  1. 38 1
      cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java
  2. 4 4
      cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyOwnerController.java
  3. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/parent/FamilyAssessmentController.java
  4. 1 1
      cfc-backend/src/main/java/com/etotem/cfc/controller/parent/FamilyDashboardController.java
  5. 3 0
      cfc-backend/src/main/java/com/etotem/cfc/entity/ActivityRegistration.java
  6. 5 4
      cfc-backend/src/main/java/com/etotem/cfc/service/ActivityOrderService.java
  7. 4 2
      cfc-backend/src/main/java/com/etotem/cfc/service/ActivityRegistrationService.java
  8. 3 3
      cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePlanningService.java
  9. 4 2
      cfc-backend/src/main/resources/schema.sql
  10. 58 3
      cfc-frontend/pages.json
  11. 1 1
      cfc-frontend/pages/assessment/results.vue
  12. 14 161
      cfc-frontend/pages/child/rewards.vue
  13. 5 774
      cfc-frontend/pages/child/tasks.vue
  14. 252 0
      cfc-frontend/pages/family/appointments.vue
  15. 513 0
      cfc-frontend/pages/family/member-detail.vue
  16. 120 0
      cfc-frontend/pages/family/plans/detail.vue
  17. 103 0
      cfc-frontend/pages/family/plans/index.vue
  18. 161 0
      cfc-frontend/pages/family/rewards.vue
  19. 783 0
      cfc-frontend/pages/family/tasks.vue
  20. 558 0
      cfc-frontend/pages/family/wishes/index.vue
  21. 1 1
      cfc-frontend/pages/growth-main/index.vue
  22. 1 1
      cfc-frontend/pages/guide/bind/invite.vue
  23. 5 1477
      cfc-frontend/pages/home-pages/child-index.vue
  24. 2294 0
      cfc-frontend/pages/home-pages/family-index.vue
  25. 1486 0
      cfc-frontend/pages/home-pages/member-index.vue
  26. 5 2285
      cfc-frontend/pages/home-pages/parent-index.vue
  27. 5 243
      cfc-frontend/pages/parent/appointments.vue
  28. 5 504
      cfc-frontend/pages/parent/child-detail.vue
  29. 5 111
      cfc-frontend/pages/parent/plans/detail.vue
  30. 5 94
      cfc-frontend/pages/parent/plans/index.vue
  31. 14 558
      cfc-frontend/pages/parent/wishes/index.vue
  32. 1 1
      cfc-frontend/pages/profile-extra/gates.vue
  33. 1 1
      cfc-frontend/pages/profile-main/profile.vue
  34. 1 1
      cfc-frontend/pages/profile/components/ProfileMenu.vue
  35. 91 31
      cfc-frontend/utils/api.js
  36. 48 0
      cfc-frontend/utils/redirect.js
  37. 22 6
      cfc-web/src/api/admin.js
  38. 6 2
      cfc-web/src/api/guideTask.js
  39. 7 0
      cfc-web/src/router/index.js
  40. 1 0
      cfc-web/src/views/admin/ActivityRegistrationReview.vue
  41. 1 1
      tests/e2e/family-member-management.spec.js

+ 38 - 1
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -7884,7 +7884,7 @@ sqls.add("INSERT IGNORE INTO health_norm_reference (dimension, gender, age_min,
             log.warn("添加activity_registrations.registrant_phone字段失败: {}", e.getMessage());
         }
         try {
-            jdbcTemplate.execute("ALTER TABLE activity_registrations MODIFY COLUMN child_id BIGINT COMMENT '孩子ID(手动输入报名人为空)'");
+            jdbcTemplate.execute("ALTER TABLE activity_registrations MODIFY COLUMN child_id BIGINT COMMENT '报名人家庭成员ID(旧列,与 member_id 双写过渡,保留一个版本后下线)'");
             log.info("已完成activity_registrations.child_id改为可空");
         } catch (Exception e) {
             log.warn("修改activity_registrations.child_id失败: {}", e.getMessage());
@@ -11058,6 +11058,9 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         createDietFamilySelectedIngredientsTable();
         createSsoTicketTable();
 
+        // 迁移421: 活动报名表 child_id → member_id 中性列(新增 + 回填 + 双写过渡,child_id 本版本不删除)
+        addActivityRegistrationMemberIdColumn();
+
         // 迁移327: 创建 report_feedback 表(报告反馈+AI解答)
         try {
             jdbcTemplate.execute("CREATE TABLE IF NOT EXISTS report_feedback (" +
@@ -12866,6 +12869,40 @@ log.info("迁移298: 已为无 openid 的 child 账号补齐 family_members 记
         }
     }
 
+    // 迁移421: activity_registrations 新增 member_id 列(G2 L3 列名中性化)
+    // 策略:新增 member_id + 从 child_id 回填 + 应用层双写;child_id 保留一个版本后由主理人确认下线。
+    // 只做 ADD/UPDATE,不做 DROP COLUMN,可重复执行(幂等)。
+    private void addActivityRegistrationMemberIdColumn() {
+        if (!columnExists("activity_registrations", "member_id")) {
+            jdbcTemplate.execute("ALTER TABLE activity_registrations ADD COLUMN member_id BIGINT NULL COMMENT '报名人家庭成员ID(中性语义,family_members.id)'");
+            log.info("迁移421: 已添加 activity_registrations.member_id 列");
+        }
+        try {
+            int backfilled = jdbcTemplate.update(
+                    "UPDATE activity_registrations SET member_id = child_id " +
+                    "WHERE member_id IS NULL AND child_id IS NOT NULL");
+            try {
+                jdbcTemplate.execute("ALTER TABLE activity_registrations ADD INDEX idx_member_id (member_id)");
+            } catch (Exception idx) {
+                log.warn("迁移421: member_id 索引已存在或创建失败(忽略): {}", idx.getMessage());
+            }
+            if (backfilled > 0) {
+                log.info("迁移421: 已从 child_id 回填 member_id {} 行", backfilled);
+            }
+        } catch (Exception e) {
+            log.warn("迁移421: member_id 回填失败(读取仍走 child_id,不影响主链路): {}", e.getMessage());
+        }
+        Integer mismatched = jdbcTemplate.queryForObject(
+                "SELECT COUNT(*) FROM activity_registrations " +
+                "WHERE member_id IS NOT NULL AND child_id IS NOT NULL AND member_id <> child_id",
+                Integer.class);
+        if (mismatched != null && mismatched > 0) {
+            log.warn("迁移421: member_id 与 child_id 不一致 {} 行,请人工核查", mismatched);
+        } else {
+            log.info("迁移421: member_id/child_id 一致性校验通过");
+        }
+    }
+
     // 迁移386: 历史 teacher_status='pending' 占位假数据清理,并把默认值改为 NULL
     private void cleanPlannerPlaceholderData() {
         try {

+ 4 - 4
cfc-backend/src/main/java/com/etotem/cfc/controller/family/ParentController.java → cfc-backend/src/main/java/com/etotem/cfc/controller/family/FamilyOwnerController.java

@@ -19,7 +19,7 @@ import java.util.Map;
 
 @RestController
 @RequestMapping("/api/parent")
-public class ParentController {
+public class FamilyOwnerController {
 
     @Resource
     private RewardService rewardService;
@@ -38,7 +38,7 @@ public class ParentController {
      * 家长心愿单 - 创建心愿(直接进入可用列表)
      */
     @PostMapping("/wishlist")
-    public Result<Long> createParentWishlist(@RequestAttribute("userId") Long userId,
+    public Result<Long> createFamilyOwnerWishlist(@RequestAttribute("userId") Long userId,
                                               @RequestBody CreateRewardDTO dto) {
         // 家长直接创建奖励,状态为available
         Long rewardId = rewardService.createReward(userId, dto);
@@ -49,7 +49,7 @@ public class ParentController {
      * 家长心愿单 - 获取列表(所有家庭成员的心愿)
      */
     @PostMapping("/wishlist/list")
-    public Result<List<RewardDTO>> getParentWishlist(@RequestAttribute("userId") Long userId) {
+    public Result<List<RewardDTO>> getFamilyOwnerWishlist(@RequestAttribute("userId") Long userId) {
         User user = userService.getUserInfo(userId);
         if (user == null || user.getFamilyId() == null) {
             return Result.noFamily("请先创建或加入家庭");
@@ -95,7 +95,7 @@ public class ParentController {
      * 家长心愿单 - 兑换家长的心愿
      */
     @PostMapping("/wishlist/{id}/exchange")
-    public Result<Map<String, Object>> exchangeParentWishlist(@PathVariable Long id,
+    public Result<Map<String, Object>> exchangeFamilyOwnerWishlist(@PathVariable Long id,
                                                                @RequestAttribute("userId") Long userId) {
         User user = userService.getUserInfo(userId);
         if (user == null) {

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/parent/ParentAssessmentController.java → cfc-backend/src/main/java/com/etotem/cfc/controller/parent/FamilyAssessmentController.java

@@ -22,7 +22,7 @@ import java.util.Map;
 @Tag(name = "家长测评", description = "家长查看DAN测评任务与历史")
 @RestController
 @RequestMapping("/api/parent/assessment")
-public class ParentAssessmentController {
+public class FamilyAssessmentController {
 
     @Resource
     private AssessmentService assessmentService;

+ 1 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/parent/ParentDashboardController.java → cfc-backend/src/main/java/com/etotem/cfc/controller/parent/FamilyDashboardController.java

@@ -24,7 +24,7 @@ import java.util.Map;
 @Tag(name = "家长首页", description = "家长首页数据接口")
 @RestController
 @RequestMapping("/api/parent")
-public class ParentDashboardController {
+public class FamilyDashboardController {
 
     @Resource
     private UserService userService;

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/ActivityRegistration.java

@@ -13,6 +13,9 @@ public class ActivityRegistration implements Serializable {
     @TableId(type = IdType.AUTO)
     private Long id;
     private Long activityId;
+    /** 报名人家庭成员ID(中性语义,见 member_id 列) */
+    private Long memberId;
+    /** 报名人家庭成员ID(旧列 child_id,与 member_id 双写过渡;child_id 保留一个版本后下线) */
     private Long childId;
     private Long userId;
     /** 报名人姓名(手动输入时必填,从家庭成员选择时自动填入) */

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

@@ -78,8 +78,8 @@ public class ActivityOrderService {
             if (memberId != null && isRegistered(activityId, memberId)) {
                 return Result.error("已报名: " + name);
             }
-            boolean isChild = memberId != null;
-            Integer fee = isChild
+            boolean isMinorMember = memberId != null;
+            Integer fee = isMinorMember
                     ? (isMember ? activity.getChildMemberFee() : activity.getChildFee())
                     : (isMember ? activity.getParentMemberFee() : activity.getParentFee());
             if (fee == null) fee = 0;
@@ -88,7 +88,7 @@ public class ActivityOrderService {
             item.put("name", name.trim());
             item.put("phone", phone != null ? phone.trim() : null);
             item.put("memberId", memberId);
-            item.put("type", isChild ? "child" : "parent");
+            item.put("type", isMinorMember ? "child" : "parent");
             item.put("fee", fee);
             snapshot.add(item);
         }
@@ -188,7 +188,8 @@ public class ActivityOrderService {
             ActivityRegistration ar = new ActivityRegistration();
             ar.setActivityId(activityId);
             ar.setUserId(userId);
-            ar.setChildId(memberId);
+            ar.setMemberId(memberId);
+            ar.setChildId(memberId); // child_id 与 member_id 双写过渡(迁移421)
             ar.setRegistrantName((String) item.get("name"));
             ar.setRegistrantPhone((String) item.get("phone"));
             ar.setStatus(status);

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

@@ -68,7 +68,8 @@ public class ActivityRegistrationService {
                 }
             }
             ActivityRegistration ar = new ActivityRegistration();
-            ar.setActivityId(activityId); ar.setUserId(userId); ar.setChildId(memberId);
+            ar.setActivityId(activityId); ar.setUserId(userId);
+            ar.setMemberId(memberId); ar.setChildId(memberId); // child_id 与 member_id 双写过渡(迁移421)
             ar.setRegistrantName(name.trim()); ar.setRegistrantPhone(phone != null ? phone.trim() : null);
             ar.setStatus(needReview ? "pending" : "approved"); ar.setRegisteredAt(LocalDateTime.now());
             ar.setCreatedAt(LocalDateTime.now()); ar.setUpdatedAt(LocalDateTime.now());
@@ -104,7 +105,8 @@ public class ActivityRegistrationService {
             }
         }
         ActivityRegistration reg = new ActivityRegistration();
-        reg.setActivityId(activityId); reg.setUserId(userId); reg.setChildId(memberId);
+        reg.setActivityId(activityId); reg.setUserId(userId);
+        reg.setMemberId(memberId); reg.setChildId(memberId); // child_id 与 member_id 双写过渡(迁移421)
         reg.setStatus(needReview ? "pending" : "approved"); reg.setRegisteredAt(LocalDateTime.now());
         reg.setCreatedAt(LocalDateTime.now()); reg.setUpdatedAt(LocalDateTime.now());
         if (!needReview) {

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

@@ -221,10 +221,10 @@ public class InsurancePlanningService {
      * - comprehensive: 有教育金险 + wisdomScore >= 60
      * - adequate: 有教育金险
      * - none: 无教育金险
-     * 只对 child 类型有意义
+     * 只对 child 类型成员有意义
      */
-    private String calcEducationCoverage(List<InsurancePolicy> policies, Integer wisdomScore, boolean isChild) {
-        if (!isChild) return "not_applicable";
+    private String calcEducationCoverage(List<InsurancePolicy> policies, Integer wisdomScore, boolean isMinorMember) {
+        if (!isMinorMember) return "not_applicable";
         boolean hasEdu = policies.stream()
                 .anyMatch(p -> "education".equals(p.getPolicyType()));
         if (!hasEdu) return "none";

+ 4 - 2
cfc-backend/src/main/resources/schema.sql

@@ -2087,8 +2087,9 @@ CREATE TABLE IF NOT EXISTS blood_type_config (
 CREATE TABLE IF NOT EXISTS activity_registrations (
     id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '主键ID',
     activity_id BIGINT NOT NULL COMMENT '活动ID',
-    child_id BIGINT COMMENT '孩子ID(手动输入报名人为空)',
-    user_id BIGINT NOT NULL COMMENT '报名人用户ID(家长)',
+    member_id BIGINT COMMENT '报名人家庭成员ID(中性语义,family_members.id,见迁移421)',
+    child_id BIGINT COMMENT '报名人家庭成员ID(旧列,与 member_id 双写过渡,保留一个版本后下线)',
+    user_id BIGINT NOT NULL COMMENT '报名人用户ID(发起报名的家庭成员)',
     registrant_name VARCHAR(100) COMMENT '报名人姓名',
     registrant_phone VARCHAR(20) COMMENT '报名人联系方式',
     status VARCHAR(20) DEFAULT 'pending' COMMENT '状态: pending/approved/rejected/cancelled',
@@ -2103,6 +2104,7 @@ CREATE TABLE IF NOT EXISTS activity_registrations (
     UNIQUE KEY uk_activity_child (activity_id, child_id),
     INDEX idx_activity_id (activity_id),
     INDEX idx_child_id (child_id),
+    INDEX idx_member_id (member_id),
     INDEX idx_user_id (user_id),
     INDEX idx_status (status)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动报名表';

+ 58 - 3
cfc-frontend/pages.json

@@ -252,7 +252,7 @@
         {
           "path": "child-detail",
           "style": {
-            "navigationBarTitleText": "孩子详情"
+            "navigationBarTitleText": "成员详情"
           }
         }
       ]
@@ -263,13 +263,13 @@
         {
           "path": "tasks",
           "style": {
-            "navigationBarTitleText": "孩子任务"
+            "navigationBarTitleText": "成员任务"
           }
         },
         {
           "path": "rewards",
           "style": {
-            "navigationBarTitleText": "孩子心愿单"
+            "navigationBarTitleText": "成员心愿单"
           }
         }
       ]
@@ -1492,6 +1492,48 @@
           "style": {
             "navigationBarTitleText": "MBTI 人格百科"
           }
+        },
+        {
+          "path": "appointments",
+          "style": {
+            "navigationBarTitleText": "我的预约"
+          }
+        },
+        {
+          "path": "member-detail",
+          "style": {
+            "navigationBarTitleText": "成员详情"
+          }
+        },
+        {
+          "path": "tasks",
+          "style": {
+            "navigationBarTitleText": "成员任务"
+          }
+        },
+        {
+          "path": "rewards",
+          "style": {
+            "navigationBarTitleText": "成员心愿单"
+          }
+        },
+        {
+          "path": "plans/index",
+          "style": {
+            "navigationBarTitleText": "我的计划"
+          }
+        },
+        {
+          "path": "plans/detail",
+          "style": {
+            "navigationBarTitleText": "计划详情"
+          }
+        },
+        {
+          "path": "wishes/index",
+          "style": {
+            "navigationBarTitleText": "心愿管理"
+          }
         }
       ]
     },
@@ -1781,6 +1823,19 @@
     {
       "root": "pages/home-pages",
       "pages": [
+        {
+          "path": "family-index",
+          "style": {
+            "navigationBarTitleText": "首页",
+            "enablePullDownRefresh": true
+          }
+        },
+        {
+          "path": "member-index",
+          "style": {
+            "navigationBarTitleText": "首页"
+          }
+        },
         {
           "path": "parent-index",
           "style": {

+ 1 - 1
cfc-frontend/pages/assessment/results.vue

@@ -225,7 +225,7 @@ export default {
       return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
     },
     goToAppointments() {
-      uni.navigateTo({ url: '/pages/parent/appointments' })
+      uni.navigateTo({ url: '/pages/family/appointments' })
     }
   }
 }

+ 14 - 161
cfc-frontend/pages/child/rewards.vue

@@ -1,161 +1,14 @@
-<template>
-  <view class="container" :style="{ '--font-scale': fontScale }">
-    <!-- 退出切换按钮 -->
-    <view class="exit-switch" v-if="isSwitchedChild" @click="exitSwitch">
-      <text>🔄 退出切换</text>
-    </view>
-    
-    <view class="reward-list" v-if="rewards.length > 0">
-      <view v-for="reward in rewards" :key="reward.id" class="reward-item">
-        <view class="reward-info">
-          <view class="reward-title">{{ reward.title }}</view>
-          <view class="reward-progress">
-            <progress :percent="reward.pointsRequired > 0 ? (currentPoints / reward.pointsRequired) * 100 : 0" stroke-width="6" :activeColor="primaryColor" :backgroundColor="trackColor" />
-            <text class="progress-text">{{ currentPoints }}/{{ reward.pointsRequired }}积分</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <view class="empty" v-else>
-      <text>暂无心愿单,快去添加吧</text>
-    </view>
-
-    <button class="btn-primary add-btn" @click="showAddModal = true">+ 添加心愿</button>
-
-    <view class="modal-mask" v-if="showAddModal" @click="showAddModal = false">
-      <view class="modal" @click.stop>
-        <view class="modal-title">添加心愿</view>
-        <view class="form-item">
-          <view class="form-label">心愿名称 <text class="required">*</text></view>
-          <input v-model="newReward.title" placeholder="请输入心愿名称" />
-        </view>
-        <view class="form-tip">
-          <text>孩子创建心愿后,需要家长填写所需积分才能生效</text>
-        </view>
-        <view class="modal-btns">
-          <button @click="showAddModal = false">取消</button>
-          <button class="btn-primary" @click="createChildReward">确定</button>
-        </view>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { BrandColors } from '@/utils/brand-colors.js'
-import { getWishlist, createReward as createRewardApi, getPointsBalance, getChildren, switchBackToParent, verifyPassword } from '../../utils/api.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
-
-export default {
-  mixins: [fontScaleMixin],
-  data() {
-    return {
-      trackColor: BrandColors.hairline2,
-      primaryColor: BrandColors.primary,
-      rewards: [],
-      currentPoints: 0,
-      currentChildId: '',
-      showAddModal: false,
-      newReward: { title: '' },
-      isSwitchedChild: false // 家长切换到孩子状态的标记
-    }
-  },
-  onShow() {
-    this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
-    this.loadRewards()
-  },
-  methods: {
-    async loadRewards() {
-      try {
-        const childrenRes = await getChildren()
-        const children = childrenRes.data || []
-        const storedChildId = uni.getStorageSync('currentChildId')
-        const currentChild = children.find(c => c.id == storedChildId) || children[0]
-        if (!currentChild) {
-          this.rewards = []
-          this.currentPoints = 0
-          return
-        }
-        this.currentChildId = currentChild.id
-        const balanceRes = await getPointsBalance(currentChild.id)
-        this.currentPoints = balanceRes.data.balance
-        const res = await getWishlist(currentChild.id)
-        this.rewards = res.data || []
-      } catch (e) {
-        console.error('加载心愿单失败', e)
-      }
-    },
-    async createChildReward() {
-      if (!this.newReward.title) {
-        uni.showToast({ title: '请填写心愿名称', icon: 'none' })
-        return
-      }
-      try {
-        await createRewardApi({ title: this.newReward.title, memberId: this.currentChildId })
-        uni.showToast({ title: '已提交,等待家长填写积分', icon: 'success' })
-        this.showAddModal = false
-        this.newReward.title = ''
-        this.loadRewards()
-      } catch (e) {
-        console.error('添加心愿失败', e)
-      }
-    },
-    exitSwitch() {
-      if (!this.isSwitchedChild) return
-      
-      uni.showModal({
-        title: '退出切换',
-        content: '请输入家长密码以确认退出',
-        editable: true,
-        success: async (res) => {
-          if (res.confirm && res.content) {
-            try {
-              const verifyResult = await verifyPassword(res.content)
-              if (verifyResult.code === 200) {
-                await this.switchBackToParentMode()
-              } else {
-                uni.showToast({ title: '密码错误', icon: 'none' })
-              }
-            } catch (e) {
-              uni.showToast({ title: '密码验证失败', icon: 'none' })
-            }
-          }
-        }
-      })
-    },
-    async switchBackToParentMode() {
-      try {
-        const result = await switchBackToParent()
-        if (result.data) {
-          this.$store.commit('switchBackToParent')
-          uni.showToast({ title: '已切换回家长模式', icon: 'success' })
-          uni.reLaunch({ url: '/pages/home-pages/parent-index' })
-        }
-      } catch (e) { 
-        uni.showToast({ title: '切换失败', icon: 'none' }) 
-      }
-    }
-  }
-}
-</script>
-
-<style scoped lang="scss">
-.container { padding: 30rpx; padding-bottom: 120rpx; }
-.exit-switch { background: $facade-fill-2; padding: 20rpx; border-radius: 10rpx; margin-bottom: 20rpx; text-align: center; color: $facade-text-body; font-size: 28rpx; }
-.reward-item { background: $app-surface; border-radius: 16rpx; padding: 30rpx; margin-bottom: 20rpx; }
-.reward-title { font-size: calc(30rpx * var(--font-scale, 1)); color: $facade-text-strong; margin-bottom: 15rpx; }
-.reward-progress { width: 100%; }
-.progress-text { font-size: calc(22rpx * var(--font-scale, 1)); color: $facade-text-muted; display: block; margin-top: 8rpx; }
-.add-btn { position: fixed; bottom: 30rpx; left: 30rpx; right: 30rpx; }
-.empty { text-align: center; color: $facade-text-muted; padding: 100rpx; }
-.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; }
-.modal { background: $app-surface; border-radius: 20rpx; padding: 40rpx; width: 80%; }
-.modal-title { font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; text-align: center; }
-.form-item { margin-bottom: 20rpx; }
-.form-label { font-size: calc(28rpx * var(--font-scale, 1)); color: $facade-text-strong; margin-bottom: 10rpx; }
-.required { color: $brand-primary; }
-.form-tip { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-muted; padding: 20rpx; background: $facade-fill-2; border-radius: 10rpx; margin-bottom: 20rpx; }
-.modal-btns { display: flex; gap: 20rpx; margin-top: 30rpx; }
-.modal-btns button { flex: 1; }
-</style>
+<template>
+  <view class="redirect-shell"></view>
+</template>
+
+<script>
+// 旧路径跳转壳:/pages/child/rewards → /pages/family/rewards
+import { redirectToNewPath } from '../../utils/redirect.js'
+
+export default {
+  onLoad: function () {
+    redirectToNewPath('/pages/family/rewards')
+  }
+}
+</script>

+ 5 - 774
cfc-frontend/pages/child/tasks.vue

@@ -1,783 +1,14 @@
 <template>
-  <view class="page-bg" :style="{ '--font-scale': fontScale }">
-    <!-- 退出切换(家长切换到孩子状态时显示) -->
-    <view class="exit-switch" v-if="isSwitchedChild" @click="exitSwitch">
-      <text class="exit-switch__icon">🔄</text>
-      <text class="exit-switch__text">退出切换</text>
-    </view>
-
-    <!-- 顶部标签栏 -->
-    <view class="tab-bar clay-card">
-      <view
-        v-for="tab in tabs"
-        :key="tab.key"
-        class="tab-item"
-        :class="{ 'tab-item--active': activeTab === tab.key }"
-        hover-class="tab-item--hover"
-        @click="switchTab(tab.key)"
-      >
-        <text class="tab-item__label">{{ tab.label }}</text>
-        <text class="tab-item__count" v-if="tab.count > 0">{{ tab.count }}</text>
-      </view>
-    </view>
-
-    <!-- 加载状态 -->
-    <BaseLoading :loading="loading" text="小助理正在准备任务..." />
-
-    <!-- 任务列表 -->
-    <view class="task-list" v-if="!loading && filteredTasks.length > 0">
-      <view
-        v-for="(task, index) in filteredTasks"
-        :key="task.id"
-        class="animate-slide-up"
-        :class="'animate-stagger-' + ((index % 20) + 1)"
-      >
-        <view
-          class="task-card clay-card"
-          :class="{
-            'task-card--completed': task.status === 'completed',
-            'task-card--overdue': getIsOverdue(task),
-            'task-card--completing': completingTaskId === task.id
-          }"
-        >
-          <!-- 完成时庆祝遮罩 -->
-          <view class="task-card__celebrate" v-if="completingTaskId === task.id">
-            <text class="task-card__celebrate-icon">🎉</text>
-          </view>
-
-          <view class="task-card__inner">
-            <!-- 左侧图标 -->
-            <view class="task-icon">
-              <text class="task-icon__emoji">{{ getCategoryIcon(task.category) }}</text>
-            </view>
-
-            <!-- 中间内容 -->
-            <view class="task-content">
-              <view class="task-title">{{ task.title }}</view>
-              <view class="task-meta">
-                <BaseBadge variant="warning" size="xs">
-                  <text class="points-text">+{{ task.points }} CF</text>
-                </BaseBadge>
-                <text
-                  class="task-time"
-                  v-if="task.status === 'pending' && !getIsOverdue(task)"
-                >
-                  ⏱ {{ getTimeLeft(task) }}
-                </text>
-                <text
-                  class="task-time task-time--overdue"
-                  v-if="getIsOverdue(task)"
-                >
-                  ⏰ 已超时{{ getOverdueDays(task) }}
-                </text>
-              </view>
-            </view>
-
-            <!-- 右侧操作 -->
-            <view class="task-action">
-              <!-- 待完成任务 -->
-              <template v-if="task.status === 'pending' && !getIsOverdue(task)">
-                <PlayfulButton
-                  variant="clay"
-                  size="sm"
-                  :disabled="completingTaskId === task.id"
-                  @click="handleTaskClick(task)"
-                >
-                  <text>{{ getActionLabel(task) }}</text>
-                </PlayfulButton>
-              </template>
-
-              <!-- 已超时且未完成的任务,显示已超时状态 -->
-              <view
-                class="status-badge status-badge--overdue"
-                v-if="getIsOverdue(task) && task.status !== 'completed'"
-              >
-                <text class="status-badge__text">⏰ 已超时</text>
-              </view>
-
-              <!-- 已完成任务 -->
-              <view
-                class="status-badge status-badge--completed"
-                v-if="task.status === 'completed'"
-              >
-                <text class="status-badge__text">✅ 已完成</text>
-              </view>
-            </view>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 空状态 -->
-    <view class="empty-wrapper" v-if="!loading && filteredTasks.length === 0">
-      <BaseEmpty
-        :icon="emptyIcon"
-        :title="emptyTitle"
-        :description="emptyDescription"
-      >
-      </BaseEmpty>
-    </view>
-  </view>
+  <view class="redirect-shell"></view>
 </template>
 
 <script>
-import PlayfulButton from '@/components/PlayfulButton.vue'
-import BaseBadge from '@/components/BaseBadge.vue'
-import BaseEmpty from '@/components/BaseEmpty.vue'
-import BaseLoading from '@/components/BaseLoading.vue'
-import { getTodayTasks, completeTask as completeTaskApi, getChildren, switchBackToParent, verifyPassword } from '../../utils/api.js'
-import { parseDate } from '../../utils/format.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
+// 旧路径跳转壳:/pages/child/tasks → /pages/family/tasks
+import { redirectToNewPath } from '../../utils/redirect.js'
 
 export default {
-  mixins: [fontScaleMixin],
-  components: { PlayfulButton, BaseBadge, BaseEmpty, BaseLoading },
-  data() {
-    return {
-      tasks: [],
-      currentChildId: '',
-      isSwitchedChild: false,
-      loading: false,
-      activeTab: 'pending',
-      completingTaskId: null
-    }
-  },
-  computed: {
-    tabs() {
-      var pending = 0
-      var completed = 0
-      var overdue = 0
-      var task, i
-      for (i = 0; i < this.tasks.length; i++) {
-        task = this.tasks[i]
-        if (task.status === 'completed') {
-          completed++
-        } else if (this.checkIsOverdue(task)) {
-          overdue++
-        } else {
-          pending++
-        }
-      }
-      return [
-        { key: 'pending', label: '📋 待完成', count: pending },
-        { key: 'completed', label: '✅ 已完成', count: completed },
-        { key: 'overdue', label: '⏰ 已超时', count: overdue }
-      ]
-    },
-    filteredTasks() {
-      var result = []
-      var task, i
-      for (i = 0; i < this.tasks.length; i++) {
-        task = this.tasks[i]
-        if (this.activeTab === 'pending') {
-          if (task.status === 'pending' && !this.checkIsOverdue(task)) {
-            result.push(task)
-          }
-        } else if (this.activeTab === 'completed') {
-          if (task.status === 'completed') {
-            result.push(task)
-          }
-        } else if (this.activeTab === 'overdue') {
-          if (this.checkIsOverdue(task)) {
-            result.push(task)
-          }
-        }
-      }
-      return result
-    },
-    emptyIcon() {
-      var icons = {
-        pending: '📝',
-        completed: '🎉',
-        overdue: '😴'
-      }
-      return icons[this.activeTab] || '📋'
-    },
-    emptyTitle() {
-      var titles = {
-        pending: '暂无待完成任务',
-        completed: '还没有已完成的任务',
-        overdue: '太棒了,没有超时任务!'
-      }
-      return titles[this.activeTab] || ''
-    },
-    emptyDescription() {
-      var descs = {
-        pending: '让家长布置一些任务吧~\n完成后可以获得积分奖励哦',
-        completed: '完成的任务都会出现在这里,\n继续保持好习惯!',
-        overdue: '继续保持良好的完成习惯,\n按时完成任务吧!'
-      }
-      return descs[this.activeTab] || ''
-    }
-  },
-  onShow() {
-    this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
-    this.loadData()
-  },
-  methods: {
-    async loadData() {
-      var childrenRes, children, storedChildId, currentChild, i, res
-      this.loading = true
-      try {
-        childrenRes = await getChildren()
-        children = childrenRes.data || []
-        storedChildId = uni.getStorageSync('currentChildId')
-        currentChild = null
-        for (i = 0; i < children.length; i++) {
-          if (String(children[i].id) === String(storedChildId)) {
-            currentChild = children[i]
-            break
-          }
-        }
-        if (!currentChild) {
-          currentChild = children[0]
-        }
-        if (!currentChild) {
-          this.tasks = []
-          this.currentChildId = ''
-          return
-        }
-        this.currentChildId = currentChild.id
-        res = await getTodayTasks(currentChild.id)
-        this.tasks = res.data || []
-      } catch (e) {
-        console.error('加载成员任务失败', e)
-      } finally {
-        this.loading = false
-      }
-    },
-    async completeChildTask(taskId) {
-      var res, points
-      this.completingTaskId = taskId
-      try {
-        res = await completeTaskApi(taskId, this.currentChildId, '')
-        if (res.data && res.data.needReview) {
-          uni.showToast({ title: '已提交,待审核', icon: 'none' })
-        } else {
-          points = res.data.pointsEarned
-          uni.showToast({ title: '获得 ' + points + ' CF', icon: 'success' })
-        }
-        setTimeout(() => {
-          this.completingTaskId = null
-          this.loadData()
-        }, 600)
-      } catch (e) {
-        this.completingTaskId = null
-        console.error('完成任务失败', e)
-      }
-    },
-    handleTaskClick(task) {
-      var gamePages, gamePage
-      if (this.completingTaskId === task.id) return
-      // 小游戏类保留原逻辑:按 minigameCode 跳转
-      if (task.category === '小游戏类' && task.minigameCode) {
-        gamePages = {
-          'schulte': '/pages/games/schulte',
-          '1a2b': '/pages/games/1a2b',
-          'sudoku': '/pages/games/sudoku'
-        }
-        gamePage = gamePages[task.minigameCode]
-        if (gamePage) {
-          uni.navigateTo({
-            url: gamePage + '?taskId=' + task.id + '&memberId=' + this.currentChildId
-          })
-        }
-        return
-      }
-      // 打卡/联动类任务(饮食/运动/睡眠/阅读/购买/问卷/活动等):跳转对应功能页,
-      // 提交后由后端回写完成;其余普通任务才直接完成
-      if (this.navigateByAction(task)) return
-      this.completeChildTask(task.id)
-    },
-    /** 按 action_type 跳转联动页面(与家长端 pages/tasks/tasks.vue 的 navigateByAction 同款) */
-    navigateByAction(task) {
-      var at = task.actionType
-      var taskId = task.id
-      var cfg = {}
-      try { cfg = typeof task.actionConfig === 'string' ? JSON.parse(task.actionConfig || '{}') : (task.actionConfig || {}) } catch (e) {}
-      if (at === 'minigame') {
-        var gameId = cfg.gameId || ''
-        uni.navigateTo({ url: '/pages/games/index?taskId=' + taskId + (gameId ? '&gameId=' + gameId : '') })
-        return true
-      }
-      if (at === 'focus') {
-        uni.navigateTo({ url: '/pages/games/focus-timer?taskId=' + taskId })
-        return true
-      }
-      if (at === 'survey') {
-        uni.navigateTo({ url: '/pages/survey/index?taskId=' + taskId })
-        return true
-      }
-      if (at === 'article') {
-        var articleId = cfg.articleId || task.sourceId
-        uni.navigateTo({ url: '/pages/article-center/article-detail?taskId=' + taskId + (articleId ? '&id=' + articleId : '') })
-        return true
-      }
-      if (at === 'activity') {
-        uni.navigateTo({ url: '/pages/activity/list?taskId=' + taskId })
-        return true
-      }
-      if (at === 'cart' || at === 'order') {
-        var isExternal = cfg.isExternal === 1 || cfg.isExternal === '1'
-        if (isExternal) {
-          var tTitle = encodeURIComponent(task.title || '外部平台商品')
-          uni.navigateTo({ url: '/pages/tasks/task-upload-purchase?taskId=' + taskId + '&title=' + tTitle })
-        } else {
-          uni.navigateTo({ url: '/pages/shop/cart?taskId=' + taskId })
-        }
-        return true
-      }
-      if (at === 'diet') {
-        uni.navigateTo({ url: '/pages/growth/diet-checkin?taskId=' + taskId })
-        return true
-      }
-      if (at === 'sleep') {
-        uni.navigateTo({ url: '/pages/growth/sleep-checkin?taskId=' + taskId })
-        return true
-      }
-      if (at === 'exercise') {
-        uni.navigateTo({ url: '/pages/growth/exercise-checkin?taskId=' + taskId })
-        return true
-      }
-      return false
-    },
-    /** 按钮文案:按 action_type 分派(与家长端 getActionLabel 同款) */
-    getActionLabel(task) {
-      if (task.category === '小游戏类' || task.actionType === 'minigame') return '🎮 开始游戏'
-      var at = task.actionType
-      if (at === 'focus') return '⏱️ 开始专注'
-      if (at === 'survey') return '📝 去填写'
-      if (at === 'article') return '📖 去阅读'
-      if (at === 'activity') return '🎯 去报名'
-      if (at === 'cart' || at === 'order') return '🛒 去购物'
-      if (at === 'diet' || at === 'sleep' || at === 'exercise') return '✅ 去打卡'
-      return '✅ 完成了!'
-    },
-    getCategoryIcon(category) {
-      var icons = {
-        '学习类': '📚',
-        '生活类': '🏠',
-        '运动类': '⚽',
-        '小游戏类': '🎮',
-        '其他': '⭐'
-      }
-      return icons[category] || '📝'
-    },
-    getTimeLeft(task) {
-      var deadline, now, diff, hours, minutes, days
-      if (!task.deadline) return '无截止时间'
-      deadline = parseDate(task.deadline)
-      if (!deadline) return ''
-      now = new Date()
-      diff = deadline - now
-      if (diff < 0) return ''
-      hours = Math.floor(diff / (1000 * 60 * 60))
-      minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
-      if (hours > 24) {
-        days = Math.floor(hours / 24)
-        return '还剩 ' + days + ' 天'
-      }
-      if (hours > 0) return '还剩 ' + hours + ' 小时'
-      if (minutes > 0) return '还剩 ' + minutes + ' 分钟'
-      return '即将截止'
-    },
-    checkIsOverdue(task) {
-      var deadline, now
-      if (task.status === 'completed') return false
-      if (!task.deadline) return false
-      deadline = parseDate(task.deadline)
-      if (!deadline) return false
-      now = new Date()
-      return deadline < now
-    },
-    getOverdueDays(task) {
-      var deadline, now, diff, days, hours
-      if (!task.deadline) return ''
-      deadline = parseDate(task.deadline)
-      if (!deadline) return ''
-      now = new Date()
-      diff = now - deadline
-      days = Math.floor(diff / (1000 * 60 * 60 * 24))
-      if (days > 0) return ' ' + days + '天'
-      hours = Math.floor(diff / (1000 * 60 * 60))
-      if (hours > 0) return ' ' + hours + '小时'
-      return ''
-    },
-    switchTab(tabKey) {
-      this.activeTab = tabKey
-    },
-    exitSwitch() {
-      var self = this
-      if (!self.isSwitchedChild) return
-      uni.showModal({
-        title: '退出切换',
-        content: '请输入家长密码以确认退出',
-        editable: true,
-        success: function(res) {
-          if (res.confirm && res.content) {
-            verifyPassword(res.content).then(function(verifyResult) {
-              if (verifyResult.code === 200) {
-                self.switchBackToParentMode()
-              } else {
-                uni.showToast({ title: '密码错误', icon: 'none' })
-              }
-            }).catch(function() {
-              uni.showToast({ title: '密码验证失败', icon: 'none' })
-            })
-          }
-        }
-      })
-    },
-    async switchBackToParentMode() {
-      var result
-      try {
-        result = await switchBackToParent()
-        if (result.data) {
-          this.$store.commit('switchBackToParent')
-          uni.showToast({ title: '已切换回家长模式', icon: 'success' })
-          uni.reLaunch({ url: '/pages/home-pages/parent-index' })
-        }
-      } catch (_) {
-        uni.showToast({ title: '切换失败', icon: 'none' })
-      }
-    }
+  onLoad: function () {
+    redirectToNewPath('/pages/family/tasks')
   }
 }
 </script>
-
-<style scoped lang="scss">
-/* =============================================
-   页面基础
-   ============================================= */
-.page-bg {
-  min-height: 100vh;
-  background: var(--bg, $uni-bg-color);
-  padding-bottom: 40rpx;
-}
-
-/* =============================================
-   退出切换横幅
-   ============================================= */
-.exit-switch {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  gap: 12rpx;
-  padding: 24rpx;
-  margin: 24rpx 32rpx 0;
-  background: var(--surface, $app-surface);
-  border: 2rpx solid var(--border, $uni-border-color);
-  border-radius: var(--radius-lg, 32rpx);
-  box-shadow: var(--shadow-sm);
-}
-
-.exit-switch:active {
-  opacity: 0.7;
-  transform: scale(0.98);
-}
-
-.exit-switch__icon {
-  font-size: 32rpx;
-}
-
-.exit-switch__text {
-  font-size: 28rpx;
-  color: var(--text-secondary, $uni-text-color-secondary);
-  font-weight: var(--font-weight-medium, 500);
-}
-
-/* =============================================
-   顶部标签栏
-   ============================================= */
-.tab-bar {
-  display: flex;
-  flex-direction: row;
-  margin: 24rpx 32rpx 0;
-  padding: 8rpx;
-  border-radius: var(--radius-xl, 40rpx);
-  background: var(--surface, $app-surface);
-  position: relative;
-}
-
-.tab-item {
-  flex: 1;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: center;
-  gap: 8rpx;
-  padding: 20rpx 12rpx;
-  border-radius: var(--radius-lg, 32rpx);
-  transition: background-color var(--transition-base, 0.25s ease), box-shadow var(--transition-base, 0.25s ease);
-  position: relative;
-}
-
-.tab-item--hover {
-  background: rgba(249,115,22,0.06);
-}
-
-.tab-item--active {
-  background: linear-gradient(145deg, var(--color-primary-light, $brand-primary-light), var(--color-primary, $brand-primary));
-  box-shadow: inset -2rpx -2rpx 8rpx rgba(255, 255, 255, 0.3),
-    4rpx 4rpx 12rpx rgba(249,115,22,0.2);
-}
-
-.tab-item__label {
-  font-size: 26rpx;
-  font-weight: var(--font-weight-medium, 500);
-  color: var(--text-secondary, $uni-text-color-secondary);
-  transition: color var(--transition-fast, 0.15s ease);
-}
-
-.tab-item--active .tab-item__label {
-  color: var(--text-color-inverse, $app-surface);
-  font-weight: var(--font-weight-bold, 600);
-}
-
-.tab-item__count {
-  display: inline-flex;
-  align-items: center;
-  justify-content: center;
-  min-width: 36rpx;
-  height: 36rpx;
-  padding: 0 8rpx;
-  border-radius: 999rpx;
-  background: rgba(249,115,22,0.12);
-  color: var(--color-primary, $brand-primary);
-  font-size: 20rpx;
-  font-weight: var(--font-weight-bold, 600);
-  line-height: 1;
-}
-
-.tab-item--active .tab-item__count {
-  background: rgba(255, 255, 255, 0.25);
-  color: $app-surface;
-}
-
-/* =============================================
-   任务列表
-   ============================================= */
-.task-list {
-  padding: 24rpx 32rpx;
-}
-
-/* =============================================
-   任务卡片(Claymorphism 风格)
-   ============================================= */
-.task-card {
-  position: relative;
-  overflow: hidden;
-  margin-bottom: 24rpx;
-  transition: transform var(--transition-fast, 0.15s ease),
-    opacity var(--transition-base, 0.25s ease);
-}
-
-.task-card:active {
-  transform: scale(0.98);
-}
-
-/* --- 已完成状态 --- */
-.task-card--completed {
-  opacity: 0.7;
-  border-color: var(--color-success, $status-success-2);
-  background: linear-gradient(145deg, $status-success-tint-2, $status-success-tint);
-  box-shadow: inset -2rpx -2rpx 6rpx rgba(34, 197, 94, 0.05),
-    3rpx 3rpx 10rpx rgba(34, 197, 94, 0.08);
-}
-
-/* --- 超时状态 --- */
-.task-card--overdue {
-  border-color: var(--color-error, $status-danger);
-  background: linear-gradient(145deg, $status-danger-tint-2, $status-danger-soft);
-  box-shadow: inset -2rpx -2rpx 6rpx rgba(239, 68, 68, 0.05),
-    3rpx 3rpx 10rpx rgba(239, 68, 68, 0.08);
-}
-
-/* --- 完成中动画 --- */
-.task-card--completing {
-  animation: cardComplete 0.6s ease forwards;
-  pointer-events: none;
-}
-
-.task-card__celebrate {
-  position: absolute;
-  inset: 0;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: rgba(255, 255, 255, 0.6);
-  border-radius: var(--radius-md, 20rpx);
-  z-index: 10;
-}
-
-.task-card__celebrate-icon {
-  font-size: 80rpx;
-  animation: celebrateBounce 0.6s ease both;
-}
-
-/* --- 内部布局 --- */
-.task-card__inner {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  gap: 20rpx;
-}
-
-/* =============================================
-   任务图标
-   ============================================= */
-.task-icon {
-  width: 88rpx;
-  height: 88rpx;
-  border-radius: var(--radius-md, 20rpx);
-  background: linear-gradient(145deg, $uni-bg-color, $uni-border-color-light);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  border: 2rpx solid var(--border-light, $uni-border-color-light);
-}
-
-.task-card--completed .task-icon {
-  background: linear-gradient(145deg, $status-success-tint-2, $status-success-soft-2);
-  border-color: $tint-green-line;
-}
-
-.task-card--overdue .task-icon {
-  background: linear-gradient(145deg, $status-danger-tint-2, $danger-line);
-  border-color: $danger-line;
-}
-
-.task-icon__emoji {
-  font-size: 40rpx;
-  line-height: 1;
-}
-
-/* =============================================
-   任务内容区
-   ============================================= */
-.task-content {
-  flex: 1;
-  min-width: 0;
-}
-
-.task-title {
-  font-size: calc(32rpx * var(--font-scale, 1));
-  font-weight: var(--font-weight-bold, 600);
-  color: var(--text, $uni-text-color);
-  line-height: 1.4;
-  margin-bottom: 10rpx;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-
-.task-card--completed .task-title {
-  text-decoration: line-through;
-  color: var(--text-secondary, $uni-text-color-secondary);
-}
-
-.task-meta {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  gap: 16rpx;
-  flex-wrap: wrap;
-}
-
-.points-text {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  line-height: 1.4;
-}
-
-.task-time {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: var(--text-secondary, $uni-text-color-secondary);
-  line-height: 1.4;
-}
-
-.task-time--overdue {
-  color: var(--color-error, $status-danger);
-  font-weight: var(--font-weight-medium, 500);
-}
-
-/* =============================================
-   右侧操作区
-   ============================================= */
-.task-action {
-  flex-shrink: 0;
-  margin-left: 8rpx;
-}
-
-/* =============================================
-   状态徽章
-   ============================================= */
-.status-badge {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 16rpx 24rpx;
-  border-radius: 999rpx;
-  font-weight: var(--font-weight-bold, 600);
-}
-
-.status-badge--completed {
-  background: rgba(34, 197, 94, 0.12);
-  color: var(--color-success, $status-success-2);
-  border: 2rpx solid rgba(34, 197, 94, 0.2);
-}
-
-.status-badge--overdue {
-  background: rgba(239, 68, 68, 0.12);
-  color: var(--color-error, $status-danger);
-  border: 2rpx solid rgba(239, 68, 68, 0.2);
-}
-
-.status-badge__text {
-  font-size: 24rpx;
-  line-height: 1;
-}
-
-/* =============================================
-   空状态
-   ============================================= */
-.empty-wrapper {
-  padding-top: 60rpx;
-}
-
-/* =============================================
-   关键帧动画
-   ============================================= */
-@keyframes cardComplete {
-  0% {
-    transform: scale(1);
-    opacity: 1;
-  }
-  30% {
-    transform: scale(1.05);
-    opacity: 0.9;
-  }
-  100% {
-    transform: scale(0.95);
-    opacity: 0.6;
-  }
-}
-
-@keyframes celebrateBounce {
-  0% {
-    transform: scale(0) rotate(-180deg);
-    opacity: 0;
-  }
-  50% {
-    transform: scale(1.3) rotate(10deg);
-    opacity: 1;
-  }
-  70% {
-    transform: scale(0.9) rotate(-5deg);
-  }
-  100% {
-    transform: scale(1) rotate(0deg);
-    opacity: 1;
-  }
-}
-</style>

+ 252 - 0
cfc-frontend/pages/family/appointments.vue

@@ -0,0 +1,252 @@
+<template>
+  <view class="container" :style="{ '--font-scale': fontScale }">
+    <view class="header">
+      <text class="title">我的预约</text>
+      <text class="subtitle">查看测评预约状态</text>
+    </view>
+
+    <!-- 预约列表 -->
+    <view class="appointment-list" v-if="appointments.length > 0">
+      <view class="appointment-card" v-for="item in appointments" :key="item.id">
+        <view class="card-header">
+          <text class="appointment-id">#{{ item.id }}</text>
+          <view class="status-badge" :class="'status-' + item.status">
+            <text>{{ item.statusText || getStatusText(item.status) }}</text>
+          </view>
+        </view>
+
+        <view class="card-body">
+          <view class="info-row">
+            <text class="label">预约日期</text>
+            <text class="value">{{ item.appointmentDate || '待确认' }}</text>
+          </view>
+          <view class="info-row">
+            <text class="label">预约时间</text>
+            <text class="value">{{ item.appointmentTime || '待确认' }}</text>
+          </view>
+          <view class="info-row" v-if="item.notes">
+            <text class="label">备注</text>
+            <text class="value">{{ item.notes }}</text>
+          </view>
+          <view class="info-row" v-if="item.guideName">
+            <text class="label">规划师</text>
+            <text class="value guide-name">{{ item.guideName }}</text>
+          </view>
+          <view class="info-row" v-if="item.status === 1 && !item.guideName">
+            <text class="label">规划师</text>
+            <text class="value">待分配</text>
+          </view>
+          <view class="info-row">
+            <text class="label">创建时间</text>
+            <text class="value">{{ formatTime(item.createdAt) }}</text>
+          </view>
+        </view>
+
+        <view class="card-footer" v-if="item.status === 0">
+          <button class="btn-cancel" size="mini" @click="handleCancel(item.id)">取消预约</button>
+        </view>
+        <view class="card-footer" v-if="item.status === 2">
+          <button class="btn-view-report" size="mini" @click="viewReport(item.id)">查看报告</button>
+        </view>
+      </view>
+    </view>
+
+    <view class="empty-state" v-else>
+      <text class="empty-icon">📋</text>
+      <text class="empty-text">暂无预约记录</text>
+      <text class="empty-hint">申请测评后此处会显示预约状态</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getMyAppointments, cancelAppointment } from '../../utils/api.js'
+import { parseDate } from '../../utils/format.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  data() {
+    return {
+      appointments: []
+    }
+  },
+  onShow() {
+    this.loadAppointments()
+  },
+  methods: {
+    async loadAppointments() {
+      try {
+        const res = await getMyAppointments()
+        if (res.code === 200) {
+          this.appointments = res.data || []
+        }
+      } catch (e) {
+        console.error('加载预约列表失败', e)
+      }
+    },
+
+    getStatusText(status) {
+      const map = { 0: '待确认', 1: '已确认', 2: '已完成', 3: '已取消', 4: '未到' }
+      return map[status] || '未知'
+    },
+
+    formatTime(ts) {
+      if (!ts) return ''
+      const d = parseDate(ts)
+      if (!d) return ''
+      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
+    },
+
+    handleCancel(id) {
+      uni.showModal({
+        title: '取消预约',
+        content: '确定取消此预约?',
+        success: async (res) => {
+          if (res.confirm) {
+            try {
+              const result = await cancelAppointment(id)
+              if (result.code === 200) {
+                uni.showToast({ title: '已取消', icon: 'success' })
+                this.loadAppointments()
+              }
+            } catch (e) {
+              uni.showToast({ title: '操作失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    },
+
+    viewReport(appointmentId) {
+      uni.navigateTo({ url: '/pages/assessment/results' })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.container {
+  padding: 30rpx;
+  background: $facade-fill-page;
+  min-height: 100vh;
+}
+
+.header {
+  text-align: center;
+  padding: 40rpx 0 30rpx;
+}
+
+.title {
+  font-size: calc(48rpx * var(--font-scale, 1));
+  font-weight: bold;
+  color: $facade-text-strong;
+  display: block;
+  margin-bottom: 16rpx;
+}
+
+.subtitle {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $facade-text-body;
+}
+
+.appointment-list {
+  display: flex;
+  flex-direction: column;
+  gap: 24rpx;
+}
+
+.appointment-card {
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 30rpx;
+}
+
+.card-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+  padding-bottom: 16rpx;
+  border-bottom: 1rpx solid $facade-hairline-2;
+}
+
+.appointment-id {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+}
+
+.status-badge {
+  padding: 6rpx 16rpx;
+  border-radius: 8rpx;
+  font-size: 22rpx;
+}
+
+.status-0 { background: $fortune-1-tint; color: $mat-orange-base; }
+.status-1 { background: $mat-blue-tint; color: $mat-blue-base; }
+.status-2 { background: $mat-green-tint; color: $mat-green; }
+.status-3 { background: $facade-fill-2; color: #9E9E9E; }
+.status-4 { background: $fortune-0-tint; color: $mat-red; }
+
+.card-body {
+  margin-bottom: 20rpx;
+}
+
+.info-row {
+  display: flex;
+  justify-content: space-between;
+  padding: 10rpx 0;
+  font-size: calc(26rpx * var(--font-scale, 1));
+}
+
+.label { color: $facade-text-muted; }
+.value { color: $facade-text-strong; }
+.guide-name { color: $brand-primary; font-weight: bold; }
+
+.card-footer {
+  padding-top: 16rpx;
+  border-top: 1rpx dashed $facade-hairline-2;
+  display: flex;
+  justify-content: flex-end;
+}
+
+.btn-cancel {
+  background: $facade-fill-2;
+  color: $facade-text-muted;
+  font-size: 26rpx;
+  border-radius: 10rpx;
+  width: 200rpx;
+}
+
+.btn-view-report {
+  background: $accent-indigo;
+  color: $app-surface;
+  font-size: 26rpx;
+  border-radius: 10rpx;
+  width: 200rpx;
+}
+
+.empty-state {
+  text-align: center;
+  padding: 120rpx 0;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.empty-text {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+  display: block;
+  margin-bottom: 10rpx;
+}
+
+.empty-hint {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-ghost;
+  display: block;
+}
+</style>

+ 513 - 0
cfc-frontend/pages/family/member-detail.vue

@@ -0,0 +1,513 @@
+<template>
+  <view class="container" :style="{ '--font-scale': fontScale }">
+    <!-- 加载状态 -->
+    <view class="loading-container" v-if="loading">
+      <text class="loading-text">加载中...</text>
+    </view>
+
+    <template v-if="!loading">
+      <!-- 孩子信息卡片 -->
+      <view class="child-info-card">
+        <view class="child-info-avatar" :class="'child-avatar-color-' + (memberId % 6)">
+          <text class="child-avatar-text">{{ childName ? childName[0] : '?' }}</text>
+        </view>
+        <view class="child-info-body">
+          <text class="child-info-name">{{ childName }}</text>
+          <view class="child-info-stats">
+            <text class="stat-item stat-points">⭐ {{ childInfo.totalPoints || 0 }} 分</text>
+            <text class="stat-item stat-streak" v-if="childInfo.streakDays > 0">🔥 连续 {{ childInfo.streakDays }} 天</text>
+          </view>
+          <view class="child-info-progress" v-if="todayTasks.length > 0">
+            <text class="progress-label">
+              今日任务 {{ completedCount }}/{{ todayTasks.length }} 完成
+            </text>
+            <view class="progress-bar-bg">
+              <view class="progress-bar-fill" :style="{ width: taskProgressPercent + '%' }"></view>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 操作区 -->
+      <view class="action-section">
+        <view class="section-title">快捷操作</view>
+        <view class="action-grid">
+          <view class="action-btn" @click="goToCreateTask">
+            <view class="action-btn-icon create-icon">📝</view>
+            <text class="action-btn-label">创建任务</text>
+          </view>
+          <view class="action-btn" @click="goToWishReview">
+            <view class="action-btn-icon wish-icon">💝</view>
+            <text class="action-btn-label">心愿审核</text>
+            <text class="action-btn-badge" v-if="pendingWishCount > 0">{{ pendingWishCount }}</text>
+          </view>
+          <view class="action-btn" @click="goToGrowth">
+            <view class="action-btn-icon growth-icon">📊</view>
+            <text class="action-btn-label">成长记录</text>
+          </view>
+          <view class="action-btn" @click="goToCompletionStats">
+            <view class="action-btn-icon stats-icon">📈</view>
+            <text class="action-btn-label">完成分析</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 今日任务 -->
+      <view class="tasks-section">
+        <view class="section-title">今日任务</view>
+        <view class="tasks-card" v-if="todayTasks.length > 0">
+          <view
+            class="task-item"
+            v-for="task in todayTasks"
+            :key="task.id"
+          >
+            <text class="task-status-icon">{{ task.status === 'completed' ? '✅' : '⏳' }}</text>
+            <view class="task-info">
+              <text class="task-title">{{ task.title }}</text>
+              <text class="task-points" v-if="task.points">+{{ task.points }}CF</text>
+            </view>
+          </view>
+        </view>
+        <view class="empty-card" v-else>
+          <text class="empty-icon">🎉</text>
+          <text class="empty-text">今日暂无任务</text>
+        </view>
+      </view>
+
+      <!-- 待处理 -->
+      <view class="pending-section">
+        <view class="section-title">待处理</view>
+        <view class="pending-card" v-if="pendingReviewTasks.length > 0 || pendingWishes.length > 0">
+          <view
+            class="pending-item"
+            v-for="task in pendingReviewTasks"
+            :key="task.id"
+          >
+            <text class="pending-icon">📋</text>
+            <text class="pending-desc">{{ task.title }}</text>
+            <text class="pending-tag tag-review">待审核</text>
+          </view>
+          <view
+            class="pending-item"
+            v-for="wish in pendingWishes"
+            :key="wish.id"
+          >
+            <text class="pending-icon">💝</text>
+            <text class="pending-desc">{{ wish.title }}</text>
+            <text class="pending-tag tag-wish">{{ wish.status === 'draft' ? '待定价' : '待审批' }}</text>
+          </view>
+        </view>
+        <view class="empty-card" v-else>
+          <text class="empty-icon">✅</text>
+          <text class="empty-text">全部已处理</text>
+        </view>
+      </view>
+    </template>
+  </view>
+</template>
+
+<script>
+import { getChildren, getTodayTasks, getPendingReviewTasks, getPendingWishes } from '../../utils/api.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  data() {
+    return {
+      loading: true,
+      memberId: '',
+      childName: '',
+      childInfo: {},
+      todayTasks: [],
+      pendingReviewTasks: [],
+      pendingWishes: []
+    }
+  },
+  computed: {
+    completedCount() {
+      return this.todayTasks.filter(t => t.status === 'completed').length
+    },
+    taskProgressPercent() {
+      if (this.todayTasks.length === 0) return 0
+      return Math.round((this.completedCount / this.todayTasks.length) * 100)
+    },
+    pendingWishCount() {
+      return this.pendingWishes.length
+    }
+  },
+  onLoad(options) {
+    this.memberId = options.memberId || ''
+    this.childName = options.childName ? decodeURIComponent(options.childName) : '成员'
+    this.loadChildData()
+  },
+  methods: {
+    async loadChildData() {
+      this.loading = true
+      try {
+        // 获取孩子信息
+        const childrenRes = await getChildren()
+        const children = childrenRes.data || []
+        const matched = children.find(c => String(c.id) === String(this.memberId))
+        if (matched) {
+          this.childInfo = matched
+if (!this.childName || this.childName === '成员') {
+    this.childName = matched.nickname || matched.name || '成员'
+          }
+        }
+
+        // 并行加载今日任务和待处理
+        const [tasksRes, reviewRes, wishRes] = await Promise.all([
+          getTodayTasks(this.memberId),
+          getPendingReviewTasks(),
+          getPendingWishes()
+        ])
+
+        this.todayTasks = tasksRes.data || []
+
+        // 过滤属于该孩子的待审核任务
+        const allReviewTasks = reviewRes.data || []
+        this.pendingReviewTasks = allReviewTasks.filter(t => {
+          return String(t.memberId || t.assigneeId) === String(this.memberId)
+        }).slice(0, 5)
+
+        // 过滤属于该孩子的待处理心愿
+        const allWishes = wishRes.data || []
+        this.pendingWishes = allWishes.filter(w => String(w.memberId) === String(this.memberId)).slice(0, 5)
+      } catch (e) {
+        console.error('加载成员详情失败', e)
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      } finally {
+        this.loading = false
+      }
+    },
+    goToCreateTask() {
+      uni.navigateTo({
+        url: '/pages/tasks/create-task?memberId=' + this.memberId
+      })
+    },
+    goToWishReview() {
+      uni.navigateTo({
+        url: '/pages/family/wishes/index?memberId=' + this.memberId
+      })
+    },
+    goToGrowth() {
+      uni.switchTab({ url: '/pages/growth-main/index' })
+    },
+    goToCompletionStats() {
+      uni.navigateTo({
+        url: '/pages/stats/completion-rate?memberId=' + this.memberId
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+// ===== 孩子详情页专属色阶(flat 图表青绿 + 米白卡片;单页专用,约定见 uni.scss §14)=====
+$flat-bg:         $flat-bg; // 页面米白底
+$flat-card:       $flat-card; // 暖白卡片底
+$flat-line:       $flat-line; // 暖分隔线
+$flat-ink:        $flat-ink; // 主文字(深灰)
+$flat-muted:      $flat-muted; // 次要文字(灰)
+$flat-muted-2:     $flat-muted-2; // 辅助文字(中灰)
+$c-cd-orange:     #FF7A2E; // 橙渐变终档
+$flat-teal:       $flat-teal; // 青绿主色
+$flat-teal-deep:  $flat-teal-deep; // 青绿深档
+$flat-green:      $flat-green; // 绿浅档
+$flat-green-deep: $flat-green-deep; // 绿深档
+$c-cd-pink:       #F093FB; // 粉紫渐变起
+$c-cd-red:        #F5576C; // 红粉渐变终
+$c-cd-danger-bg:  $status-danger-tint-5; // 警示浅红底
+
+.container {
+  padding: 30rpx;
+  background: $flat-bg;
+  min-height: 100vh;
+}
+
+.loading-container {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 200rpx 0;
+}
+
+.loading-text {
+  font-size: 28rpx;
+  color: $flat-muted;
+}
+
+/* 孩子信息卡片 */
+.child-info-card {
+  display: flex;
+  align-items: flex-start;
+  background: $app-surface;
+  border-radius: 24rpx;
+  padding: 24rpx;
+  margin-bottom: 32rpx;
+  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
+}
+
+.child-info-avatar {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  margin-right: 16rpx;
+}
+
+.child-avatar-text {
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: $app-surface;
+}
+
+.child-avatar-color-0 { background: linear-gradient(135deg, $dim-body, $c-cd-orange); }
+.child-avatar-color-1 { background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep); }
+.child-avatar-color-2 { background: linear-gradient(135deg, $flat-teal, $flat-teal-deep); }
+.child-avatar-color-3 { background: linear-gradient(135deg, $brand-primary, $brand-primary-light); }
+.child-avatar-color-4 { background: linear-gradient(135deg, $flat-green, $flat-green-deep); }
+.child-avatar-color-5 { background: linear-gradient(135deg, $c-cd-pink, $c-cd-red); }
+
+.child-info-body {
+  flex: 1;
+  min-width: 0;
+}
+
+.child-info-name {
+  font-size: calc(36rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $flat-ink;
+  display: block;
+}
+
+.child-info-stats {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12rpx;
+  margin-top: 12rpx;
+}
+
+.stat-item {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  font-weight: 500;
+}
+
+.stat-points { color: $dim-body; }
+.stat-dan { color: $accent-indigo; }
+.stat-streak { color: $brand-primary; }
+
+.child-info-progress {
+  margin-top: 16rpx;
+}
+
+.progress-label {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: $flat-muted-2;
+  display: block;
+  margin-bottom: 8rpx;
+}
+
+.progress-bar-bg {
+  height: 10rpx;
+  background: $flat-card;
+  border-radius: 9999rpx;
+  overflow: hidden;
+}
+
+.progress-bar-fill {
+  height: 100%;
+  background: linear-gradient(90deg, $flat-teal, $flat-teal-deep);
+  border-radius: 9999rpx;
+  transition: width 0.3s ease;
+}
+
+/* 区块标题 */
+.section-title {
+  font-size: calc(30rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: $flat-ink;
+  margin-bottom: 20rpx;
+}
+
+/* 操作区 */
+.action-section {
+  margin-bottom: 40rpx;
+}
+
+.action-grid {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.action-btn {
+  width: 50%;
+  box-sizing: border-box;
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 24rpx 16rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
+  position: relative;
+  border: 8rpx solid $flat-bg;
+}
+
+.action-btn-icon {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 36rpx;
+  margin-bottom: 12rpx;
+}
+
+.create-icon { background: rgba(255, 140, 66, 0.15); }
+.wish-icon { background: rgba(255, 107, 107, 0.15); }
+.growth-icon { background: rgba(78, 205, 196, 0.15); }
+.stats-icon { background: rgba(255, 217, 61, 0.15); }
+
+.action-btn-label {
+  font-size: 24rpx;
+  color: $flat-muted-2;
+  font-weight: 500;
+}
+
+.action-btn-badge {
+  position: absolute;
+  top: 12rpx;
+  right: 12rpx;
+  background: $brand-primary;
+  color: $app-surface;
+  font-size: 20rpx;
+  min-width: 32rpx;
+  height: 32rpx;
+  line-height: 32rpx;
+  text-align: center;
+  border-radius: 16rpx;
+  padding: 0 8rpx;
+}
+
+/* 今日任务 */
+.tasks-section {
+  margin-bottom: 40rpx;
+}
+
+.tasks-card {
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 8rpx 24rpx;
+  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
+}
+
+.task-item {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid $flat-line;
+}
+
+.task-item:last-child {
+  border-bottom: none;
+}
+
+.task-status-icon {
+  font-size: 32rpx;
+  margin-right: 16rpx;
+}
+
+.task-info {
+  flex: 1;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.task-title {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: $flat-ink;
+}
+
+.task-points {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $dim-body;
+  font-weight: 600;
+}
+
+.empty-card {
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 48rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
+}
+
+.empty-icon {
+  font-size: 48rpx;
+  margin-bottom: 12rpx;
+}
+
+.empty-text {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: $flat-muted;
+}
+
+/* 待处理 */
+.pending-section {
+  margin-bottom: 48rpx;
+}
+
+.pending-card {
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 8rpx 24rpx;
+  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
+}
+
+.pending-item {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid $flat-line;
+}
+
+.pending-item:last-child {
+  border-bottom: none;
+}
+
+.pending-icon {
+  font-size: 32rpx;
+  margin-right: 16rpx;
+}
+
+.pending-desc {
+  flex: 1;
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: $flat-ink;
+}
+
+.pending-tag {
+  font-size: 22rpx;
+  padding: 4rpx 14rpx;
+  border-radius: 20rpx;
+  font-weight: 500;
+}
+
+.tag-review {
+  background: $flat-card;
+  color: $dim-body;
+}
+
+.tag-wish {
+  background: $c-cd-danger-bg;
+  color: $brand-primary;
+}
+</style>

+ 120 - 0
cfc-frontend/pages/family/plans/detail.vue

@@ -0,0 +1,120 @@
+<template>
+  <view class="container" :style="{ '--font-scale': fontScale }">
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">
+      <text>加载中...</text>
+    </view>
+
+    <!-- 计划详情 -->
+    <template v-else-if="plan">
+      <view class="header">
+        <text class="title">{{ plan.title }}</text>
+        <view class="status-badge" :class="'status-' + (plan.status || 0)">
+          <text>{{ statusText(plan.status) }}</text>
+        </view>
+      </view>
+
+      <view class="info-card" v-if="plan.description">
+        <text class="info-label">计划说明</text>
+        <text class="info-value">{{ plan.description }}</text>
+      </view>
+
+      <view class="info-card">
+        <text class="info-label">成员</text>
+        <text class="info-value">{{ plan.childName || '--' }}</text>
+      </view>
+
+      <view class="info-card" v-if="plan.guideName">
+        <text class="info-label">规划师</text>
+        <text class="info-value">{{ plan.guideName }}</text>
+      </view>
+
+      <view class="info-card">
+        <text class="info-label">创建时间</text>
+        <text class="info-value">{{ plan.createTime || '--' }}</text>
+      </view>
+
+      <!-- 计划条目 -->
+      <view class="items-section" v-if="plan.items && plan.items.length > 0">
+        <text class="section-title">计划内容</text>
+        <view class="item-card" v-for="item in plan.items" :key="item.id">
+          <view class="item-top">
+            <text class="item-title">{{ item.title }}</text>
+            <text class="item-status" v-if="item.completed">✅ 已完成</text>
+          </view>
+          <text class="item-desc" v-if="item.description">{{ item.description }}</text>
+        </view>
+      </view>
+    </template>
+
+    <!-- 空状态 -->
+    <view class="empty" v-else>
+      <text class="empty-icon">📋</text>
+      <text class="empty-text">计划不存在</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getGuidePlanDetail } from '../../../utils/api.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  data() {
+    return {
+      loading: false,
+      plan: null
+    }
+  },
+  onLoad: function(options) {
+    if (options.planId) {
+      this.planId = options.planId
+      this.loadDetail()
+    }
+  },
+  methods: {
+    loadDetail: function() {
+      if (!this.planId) return
+      this.loading = true
+      var self = this
+      getGuidePlanDetail({ planId: this.planId }).then(function(res) {
+        self.loading = false
+        if (res.code === 200 && res.data) {
+          self.plan = res.data
+        }
+      }).catch(function() {
+        self.loading = false
+      })
+    },
+    statusText: function(status) {
+      var map = { 0: '待审核', 1: '进行中', 2: '已完成', 3: '已取消' }
+      return map[status] || '未知'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.container { min-height: 100vh; background: $facade-fill-page; padding-bottom: 30rpx; }
+.loading, .empty { display: flex; flex-direction: column; align-items: center; padding: 100rpx 0; }
+.empty-icon { font-size: 80rpx; }
+.empty-text { font-size: calc(30rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 20rpx; }
+.header { background: $app-surface; padding: 40rpx 30rpx; display: flex; justify-content: space-between; align-items: center; }
+.title { font-size: calc(36rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; flex: 1; }
+.status-badge { font-size: 24rpx; padding: 6rpx 20rpx; border-radius: 20rpx; }
+.status-0 { background: $fortune-1-tint; color: $brand-primary; }
+.status-1 { background: $mat-green-tint; color: $mat-green; }
+.status-2 { background: $mat-blue-tint; color: $mat-blue-base; }
+.status-3 { background: $facade-fill-2; color: $facade-text-muted; }
+.info-card { background: $app-surface; margin: 20rpx 30rpx; padding: 24rpx 30rpx; border-radius: 16rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.info-label { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-muted; display: block; margin-bottom: 8rpx; }
+.info-value { font-size: calc(28rpx * var(--font-scale, 1)); color: $facade-text-strong; display: block; }
+.items-section { margin: 20rpx 30rpx; }
+.section-title { font-size: calc(30rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; display: block; margin-bottom: 16rpx; }
+.item-card { background: $app-surface; border-radius: 12rpx; padding: 24rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); }
+.item-top { display: flex; justify-content: space-between; align-items: center; }
+.item-title { font-size: calc(28rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; }
+.item-status { font-size: calc(24rpx * var(--font-scale, 1)); color: $mat-green; }
+.item-desc { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 10rpx; display: block; line-height: 1.5; }
+</style>

+ 103 - 0
cfc-frontend/pages/family/plans/index.vue

@@ -0,0 +1,103 @@
+<template>
+  <view class="container" :style="{ '--font-scale': fontScale }">
+    <view class="header">
+      <text class="title">我的计划</text>
+      <text class="subtitle">查看和管理家庭成长计划</text>
+    </view>
+
+    <!-- 加载中 -->
+    <view class="loading" v-if="loading">
+      <text>加载中...</text>
+    </view>
+
+    <!-- 空状态 -->
+    <view class="empty" v-else-if="plans.length === 0">
+      <text class="empty-icon">📋</text>
+      <text class="empty-text">暂无计划</text>
+      <text class="empty-hint">规划师可为您创建专属成长计划</text>
+    </view>
+
+    <!-- 计划列表 -->
+    <view class="plan-list" v-else>
+      <view class="plan-card" v-for="plan in plans" :key="plan.id" @click="goDetail(plan.id)">
+        <view class="card-top">
+          <text class="plan-title">{{ plan.title }}</text>
+          <view class="plan-status" :class="'status-' + (plan.status || 0)">
+            <text>{{ statusText(plan.status) }}</text>
+          </view>
+        </view>
+        <text class="plan-desc" v-if="plan.description">{{ plan.description }}</text>
+        <view class="card-meta">
+          <text class="meta-item">成员:{{ plan.childName || '--' }}</text>
+          <text class="meta-item">创建时间:{{ plan.createTime || '--' }}</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { listFamilyPlans } from '../../../utils/api.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  data() {
+    return {
+      loading: false,
+      plans: []
+    }
+  },
+  computed: {},
+  onShow() {
+    this.loadPlans()
+  },
+  methods: {
+    loadPlans: function() {
+      this.loading = true
+      var self = this
+      listFamilyPlans({}).then(function(res) {
+        self.loading = false
+        if (res.code === 200 && Array.isArray(res.data)) {
+          self.plans = res.data
+        } else if (res.code === 200 && res.data && res.data.records) {
+          self.plans = res.data.records
+        }
+      }).catch(function() {
+        self.loading = false
+        self.plans = []
+      })
+    },
+    goDetail: function(id) {
+      uni.navigateTo({ url: './detail?planId=' + id })
+    },
+    statusText: function(status) {
+      var map = { 0: '待审核', 1: '进行中', 2: '已完成', 3: '已取消' }
+      return map[status] || '未知'
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.container { min-height: 100vh; background: $facade-fill-page; padding-bottom: 30rpx; }
+.header { padding: 40rpx 30rpx 20rpx; }
+.title { font-size: calc(36rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; display: block; }
+.subtitle { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-muted; margin-top: 8rpx; display: block; }
+.loading, .empty { display: flex; flex-direction: column; align-items: center; padding: 100rpx 0; }
+.empty-icon { font-size: 80rpx; }
+.empty-text { font-size: calc(30rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 20rpx; }
+.empty-hint { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-ghost-light; margin-top: 10rpx; }
+.plan-list { padding: 0 30rpx; }
+.plan-card { background: $app-surface; border-radius: 16rpx; padding: 30rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.card-top { display: flex; justify-content: space-between; align-items: center; }
+.plan-title { font-size: calc(30rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; flex: 1; }
+.plan-status { font-size: calc(22rpx * var(--font-scale, 1)); padding: 4rpx 16rpx; border-radius: 20rpx; }
+.status-0 { background: $fortune-1-tint; color: $brand-primary; }
+.status-1 { background: $mat-green-tint; color: $mat-green; }
+.status-2 { background: $mat-blue-tint; color: $mat-blue-base; }
+.status-3 { background: $facade-fill-2; color: $facade-text-muted; }
+.plan-desc { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 12rpx; display: block; line-height: 1.5; }
+.card-meta { display: flex; flex-direction: column; margin-top: 16rpx; gap: 6rpx; }
+.meta-item { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-ghost-light; }
+</style>

+ 161 - 0
cfc-frontend/pages/family/rewards.vue

@@ -0,0 +1,161 @@
+<template>
+  <view class="container" :style="{ '--font-scale': fontScale }">
+    <!-- 退出切换按钮 -->
+    <view class="exit-switch" v-if="isSwitchedChild" @click="exitSwitch">
+      <text>🔄 退出切换</text>
+    </view>
+    
+    <view class="reward-list" v-if="rewards.length > 0">
+      <view v-for="reward in rewards" :key="reward.id" class="reward-item">
+        <view class="reward-info">
+          <view class="reward-title">{{ reward.title }}</view>
+          <view class="reward-progress">
+            <progress :percent="reward.pointsRequired > 0 ? (currentPoints / reward.pointsRequired) * 100 : 0" stroke-width="6" :activeColor="primaryColor" :backgroundColor="trackColor" />
+            <text class="progress-text">{{ currentPoints }}/{{ reward.pointsRequired }}积分</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <view class="empty" v-else>
+      <text>暂无心愿单,快去添加吧</text>
+    </view>
+
+    <button class="btn-primary add-btn" @click="showAddModal = true">+ 添加心愿</button>
+
+    <view class="modal-mask" v-if="showAddModal" @click="showAddModal = false">
+      <view class="modal" @click.stop>
+        <view class="modal-title">添加心愿</view>
+        <view class="form-item">
+          <view class="form-label">心愿名称 <text class="required">*</text></view>
+          <input v-model="newReward.title" placeholder="请输入心愿名称" />
+        </view>
+        <view class="form-tip">
+          <text>孩子创建心愿后,需要家长填写所需积分才能生效</text>
+        </view>
+        <view class="modal-btns">
+          <button @click="showAddModal = false">取消</button>
+          <button class="btn-primary" @click="createChildReward">确定</button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { BrandColors } from '@/utils/brand-colors.js'
+import { getWishlist, createReward as createRewardApi, getPointsBalance, getChildren, switchBackToParent, verifyPassword } from '../../utils/api.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  data() {
+    return {
+      trackColor: BrandColors.hairline2,
+      primaryColor: BrandColors.primary,
+      rewards: [],
+      currentPoints: 0,
+      currentChildId: '',
+      showAddModal: false,
+      newReward: { title: '' },
+      isSwitchedChild: false // 家长切换到孩子状态的标记
+    }
+  },
+  onShow() {
+    this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
+    this.loadRewards()
+  },
+  methods: {
+    async loadRewards() {
+      try {
+        const childrenRes = await getChildren()
+        const children = childrenRes.data || []
+        const storedChildId = uni.getStorageSync('currentChildId')
+        const currentChild = children.find(c => c.id == storedChildId) || children[0]
+        if (!currentChild) {
+          this.rewards = []
+          this.currentPoints = 0
+          return
+        }
+        this.currentChildId = currentChild.id
+        const balanceRes = await getPointsBalance(currentChild.id)
+        this.currentPoints = balanceRes.data.balance
+        const res = await getWishlist(currentChild.id)
+        this.rewards = res.data || []
+      } catch (e) {
+        console.error('加载心愿单失败', e)
+      }
+    },
+    async createChildReward() {
+      if (!this.newReward.title) {
+        uni.showToast({ title: '请填写心愿名称', icon: 'none' })
+        return
+      }
+      try {
+        await createRewardApi({ title: this.newReward.title, memberId: this.currentChildId })
+        uni.showToast({ title: '已提交,等待家长填写积分', icon: 'success' })
+        this.showAddModal = false
+        this.newReward.title = ''
+        this.loadRewards()
+      } catch (e) {
+        console.error('添加心愿失败', e)
+      }
+    },
+    exitSwitch() {
+      if (!this.isSwitchedChild) return
+      
+      uni.showModal({
+        title: '退出切换',
+        content: '请输入家长密码以确认退出',
+        editable: true,
+        success: async (res) => {
+          if (res.confirm && res.content) {
+            try {
+              const verifyResult = await verifyPassword(res.content)
+              if (verifyResult.code === 200) {
+                await this.switchBackToParentMode()
+              } else {
+                uni.showToast({ title: '密码错误', icon: 'none' })
+              }
+            } catch (e) {
+              uni.showToast({ title: '密码验证失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    },
+    async switchBackToParentMode() {
+      try {
+        const result = await switchBackToParent()
+        if (result.data) {
+          this.$store.commit('switchBackToParent')
+          uni.showToast({ title: '已切换回家长模式', icon: 'success' })
+          uni.reLaunch({ url: '/pages/home-pages/family-index' })
+        }
+      } catch (e) { 
+        uni.showToast({ title: '切换失败', icon: 'none' }) 
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.container { padding: 30rpx; padding-bottom: 120rpx; }
+.exit-switch { background: $facade-fill-2; padding: 20rpx; border-radius: 10rpx; margin-bottom: 20rpx; text-align: center; color: $facade-text-body; font-size: 28rpx; }
+.reward-item { background: $app-surface; border-radius: 16rpx; padding: 30rpx; margin-bottom: 20rpx; }
+.reward-title { font-size: calc(30rpx * var(--font-scale, 1)); color: $facade-text-strong; margin-bottom: 15rpx; }
+.reward-progress { width: 100%; }
+.progress-text { font-size: calc(22rpx * var(--font-scale, 1)); color: $facade-text-muted; display: block; margin-top: 8rpx; }
+.add-btn { position: fixed; bottom: 30rpx; left: 30rpx; right: 30rpx; }
+.empty { text-align: center; color: $facade-text-muted; padding: 100rpx; }
+.modal-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0, 0, 0, 0.5); display: flex; align-items: center; justify-content: center; }
+.modal { background: $app-surface; border-radius: 20rpx; padding: 40rpx; width: 80%; }
+.modal-title { font-size: 32rpx; font-weight: bold; margin-bottom: 30rpx; text-align: center; }
+.form-item { margin-bottom: 20rpx; }
+.form-label { font-size: calc(28rpx * var(--font-scale, 1)); color: $facade-text-strong; margin-bottom: 10rpx; }
+.required { color: $brand-primary; }
+.form-tip { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-muted; padding: 20rpx; background: $facade-fill-2; border-radius: 10rpx; margin-bottom: 20rpx; }
+.modal-btns { display: flex; gap: 20rpx; margin-top: 30rpx; }
+.modal-btns button { flex: 1; }
+</style>

+ 783 - 0
cfc-frontend/pages/family/tasks.vue

@@ -0,0 +1,783 @@
+<template>
+  <view class="page-bg" :style="{ '--font-scale': fontScale }">
+    <!-- 退出切换(家长切换到孩子状态时显示) -->
+    <view class="exit-switch" v-if="isSwitchedChild" @click="exitSwitch">
+      <text class="exit-switch__icon">🔄</text>
+      <text class="exit-switch__text">退出切换</text>
+    </view>
+
+    <!-- 顶部标签栏 -->
+    <view class="tab-bar clay-card">
+      <view
+        v-for="tab in tabs"
+        :key="tab.key"
+        class="tab-item"
+        :class="{ 'tab-item--active': activeTab === tab.key }"
+        hover-class="tab-item--hover"
+        @click="switchTab(tab.key)"
+      >
+        <text class="tab-item__label">{{ tab.label }}</text>
+        <text class="tab-item__count" v-if="tab.count > 0">{{ tab.count }}</text>
+      </view>
+    </view>
+
+    <!-- 加载状态 -->
+    <BaseLoading :loading="loading" text="小助理正在准备任务..." />
+
+    <!-- 任务列表 -->
+    <view class="task-list" v-if="!loading && filteredTasks.length > 0">
+      <view
+        v-for="(task, index) in filteredTasks"
+        :key="task.id"
+        class="animate-slide-up"
+        :class="'animate-stagger-' + ((index % 20) + 1)"
+      >
+        <view
+          class="task-card clay-card"
+          :class="{
+            'task-card--completed': task.status === 'completed',
+            'task-card--overdue': getIsOverdue(task),
+            'task-card--completing': completingTaskId === task.id
+          }"
+        >
+          <!-- 完成时庆祝遮罩 -->
+          <view class="task-card__celebrate" v-if="completingTaskId === task.id">
+            <text class="task-card__celebrate-icon">🎉</text>
+          </view>
+
+          <view class="task-card__inner">
+            <!-- 左侧图标 -->
+            <view class="task-icon">
+              <text class="task-icon__emoji">{{ getCategoryIcon(task.category) }}</text>
+            </view>
+
+            <!-- 中间内容 -->
+            <view class="task-content">
+              <view class="task-title">{{ task.title }}</view>
+              <view class="task-meta">
+                <BaseBadge variant="warning" size="xs">
+                  <text class="points-text">+{{ task.points }} CF</text>
+                </BaseBadge>
+                <text
+                  class="task-time"
+                  v-if="task.status === 'pending' && !getIsOverdue(task)"
+                >
+                  ⏱ {{ getTimeLeft(task) }}
+                </text>
+                <text
+                  class="task-time task-time--overdue"
+                  v-if="getIsOverdue(task)"
+                >
+                  ⏰ 已超时{{ getOverdueDays(task) }}
+                </text>
+              </view>
+            </view>
+
+            <!-- 右侧操作 -->
+            <view class="task-action">
+              <!-- 待完成任务 -->
+              <template v-if="task.status === 'pending' && !getIsOverdue(task)">
+                <PlayfulButton
+                  variant="clay"
+                  size="sm"
+                  :disabled="completingTaskId === task.id"
+                  @click="handleTaskClick(task)"
+                >
+                  <text>{{ getActionLabel(task) }}</text>
+                </PlayfulButton>
+              </template>
+
+              <!-- 已超时且未完成的任务,显示已超时状态 -->
+              <view
+                class="status-badge status-badge--overdue"
+                v-if="getIsOverdue(task) && task.status !== 'completed'"
+              >
+                <text class="status-badge__text">⏰ 已超时</text>
+              </view>
+
+              <!-- 已完成任务 -->
+              <view
+                class="status-badge status-badge--completed"
+                v-if="task.status === 'completed'"
+              >
+                <text class="status-badge__text">✅ 已完成</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 空状态 -->
+    <view class="empty-wrapper" v-if="!loading && filteredTasks.length === 0">
+      <BaseEmpty
+        :icon="emptyIcon"
+        :title="emptyTitle"
+        :description="emptyDescription"
+      >
+      </BaseEmpty>
+    </view>
+  </view>
+</template>
+
+<script>
+import PlayfulButton from '@/components/PlayfulButton.vue'
+import BaseBadge from '@/components/BaseBadge.vue'
+import BaseEmpty from '@/components/BaseEmpty.vue'
+import BaseLoading from '@/components/BaseLoading.vue'
+import { getTodayTasks, completeTask as completeTaskApi, getChildren, switchBackToParent, verifyPassword } from '../../utils/api.js'
+import { parseDate } from '../../utils/format.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  components: { PlayfulButton, BaseBadge, BaseEmpty, BaseLoading },
+  data() {
+    return {
+      tasks: [],
+      currentChildId: '',
+      isSwitchedChild: false,
+      loading: false,
+      activeTab: 'pending',
+      completingTaskId: null
+    }
+  },
+  computed: {
+    tabs() {
+      var pending = 0
+      var completed = 0
+      var overdue = 0
+      var task, i
+      for (i = 0; i < this.tasks.length; i++) {
+        task = this.tasks[i]
+        if (task.status === 'completed') {
+          completed++
+        } else if (this.checkIsOverdue(task)) {
+          overdue++
+        } else {
+          pending++
+        }
+      }
+      return [
+        { key: 'pending', label: '📋 待完成', count: pending },
+        { key: 'completed', label: '✅ 已完成', count: completed },
+        { key: 'overdue', label: '⏰ 已超时', count: overdue }
+      ]
+    },
+    filteredTasks() {
+      var result = []
+      var task, i
+      for (i = 0; i < this.tasks.length; i++) {
+        task = this.tasks[i]
+        if (this.activeTab === 'pending') {
+          if (task.status === 'pending' && !this.checkIsOverdue(task)) {
+            result.push(task)
+          }
+        } else if (this.activeTab === 'completed') {
+          if (task.status === 'completed') {
+            result.push(task)
+          }
+        } else if (this.activeTab === 'overdue') {
+          if (this.checkIsOverdue(task)) {
+            result.push(task)
+          }
+        }
+      }
+      return result
+    },
+    emptyIcon() {
+      var icons = {
+        pending: '📝',
+        completed: '🎉',
+        overdue: '😴'
+      }
+      return icons[this.activeTab] || '📋'
+    },
+    emptyTitle() {
+      var titles = {
+        pending: '暂无待完成任务',
+        completed: '还没有已完成的任务',
+        overdue: '太棒了,没有超时任务!'
+      }
+      return titles[this.activeTab] || ''
+    },
+    emptyDescription() {
+      var descs = {
+        pending: '让家长布置一些任务吧~\n完成后可以获得积分奖励哦',
+        completed: '完成的任务都会出现在这里,\n继续保持好习惯!',
+        overdue: '继续保持良好的完成习惯,\n按时完成任务吧!'
+      }
+      return descs[this.activeTab] || ''
+    }
+  },
+  onShow() {
+    this.isSwitchedChild = uni.getStorageSync('isSwitchedChild') || false
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      var childrenRes, children, storedChildId, currentChild, i, res
+      this.loading = true
+      try {
+        childrenRes = await getChildren()
+        children = childrenRes.data || []
+        storedChildId = uni.getStorageSync('currentChildId')
+        currentChild = null
+        for (i = 0; i < children.length; i++) {
+          if (String(children[i].id) === String(storedChildId)) {
+            currentChild = children[i]
+            break
+          }
+        }
+        if (!currentChild) {
+          currentChild = children[0]
+        }
+        if (!currentChild) {
+          this.tasks = []
+          this.currentChildId = ''
+          return
+        }
+        this.currentChildId = currentChild.id
+        res = await getTodayTasks(currentChild.id)
+        this.tasks = res.data || []
+      } catch (e) {
+        console.error('加载成员任务失败', e)
+      } finally {
+        this.loading = false
+      }
+    },
+    async completeChildTask(taskId) {
+      var res, points
+      this.completingTaskId = taskId
+      try {
+        res = await completeTaskApi(taskId, this.currentChildId, '')
+        if (res.data && res.data.needReview) {
+          uni.showToast({ title: '已提交,待审核', icon: 'none' })
+        } else {
+          points = res.data.pointsEarned
+          uni.showToast({ title: '获得 ' + points + ' CF', icon: 'success' })
+        }
+        setTimeout(() => {
+          this.completingTaskId = null
+          this.loadData()
+        }, 600)
+      } catch (e) {
+        this.completingTaskId = null
+        console.error('完成任务失败', e)
+      }
+    },
+    handleTaskClick(task) {
+      var gamePages, gamePage
+      if (this.completingTaskId === task.id) return
+      // 小游戏类保留原逻辑:按 minigameCode 跳转
+      if (task.category === '小游戏类' && task.minigameCode) {
+        gamePages = {
+          'schulte': '/pages/games/schulte',
+          '1a2b': '/pages/games/1a2b',
+          'sudoku': '/pages/games/sudoku'
+        }
+        gamePage = gamePages[task.minigameCode]
+        if (gamePage) {
+          uni.navigateTo({
+            url: gamePage + '?taskId=' + task.id + '&memberId=' + this.currentChildId
+          })
+        }
+        return
+      }
+      // 打卡/联动类任务(饮食/运动/睡眠/阅读/购买/问卷/活动等):跳转对应功能页,
+      // 提交后由后端回写完成;其余普通任务才直接完成
+      if (this.navigateByAction(task)) return
+      this.completeChildTask(task.id)
+    },
+    /** 按 action_type 跳转联动页面(与家长端 pages/tasks/tasks.vue 的 navigateByAction 同款) */
+    navigateByAction(task) {
+      var at = task.actionType
+      var taskId = task.id
+      var cfg = {}
+      try { cfg = typeof task.actionConfig === 'string' ? JSON.parse(task.actionConfig || '{}') : (task.actionConfig || {}) } catch (e) {}
+      if (at === 'minigame') {
+        var gameId = cfg.gameId || ''
+        uni.navigateTo({ url: '/pages/games/index?taskId=' + taskId + (gameId ? '&gameId=' + gameId : '') })
+        return true
+      }
+      if (at === 'focus') {
+        uni.navigateTo({ url: '/pages/games/focus-timer?taskId=' + taskId })
+        return true
+      }
+      if (at === 'survey') {
+        uni.navigateTo({ url: '/pages/survey/index?taskId=' + taskId })
+        return true
+      }
+      if (at === 'article') {
+        var articleId = cfg.articleId || task.sourceId
+        uni.navigateTo({ url: '/pages/article-center/article-detail?taskId=' + taskId + (articleId ? '&id=' + articleId : '') })
+        return true
+      }
+      if (at === 'activity') {
+        uni.navigateTo({ url: '/pages/activity/list?taskId=' + taskId })
+        return true
+      }
+      if (at === 'cart' || at === 'order') {
+        var isExternal = cfg.isExternal === 1 || cfg.isExternal === '1'
+        if (isExternal) {
+          var tTitle = encodeURIComponent(task.title || '外部平台商品')
+          uni.navigateTo({ url: '/pages/tasks/task-upload-purchase?taskId=' + taskId + '&title=' + tTitle })
+        } else {
+          uni.navigateTo({ url: '/pages/shop/cart?taskId=' + taskId })
+        }
+        return true
+      }
+      if (at === 'diet') {
+        uni.navigateTo({ url: '/pages/growth/diet-checkin?taskId=' + taskId })
+        return true
+      }
+      if (at === 'sleep') {
+        uni.navigateTo({ url: '/pages/growth/sleep-checkin?taskId=' + taskId })
+        return true
+      }
+      if (at === 'exercise') {
+        uni.navigateTo({ url: '/pages/growth/exercise-checkin?taskId=' + taskId })
+        return true
+      }
+      return false
+    },
+    /** 按钮文案:按 action_type 分派(与家长端 getActionLabel 同款) */
+    getActionLabel(task) {
+      if (task.category === '小游戏类' || task.actionType === 'minigame') return '🎮 开始游戏'
+      var at = task.actionType
+      if (at === 'focus') return '⏱️ 开始专注'
+      if (at === 'survey') return '📝 去填写'
+      if (at === 'article') return '📖 去阅读'
+      if (at === 'activity') return '🎯 去报名'
+      if (at === 'cart' || at === 'order') return '🛒 去购物'
+      if (at === 'diet' || at === 'sleep' || at === 'exercise') return '✅ 去打卡'
+      return '✅ 完成了!'
+    },
+    getCategoryIcon(category) {
+      var icons = {
+        '学习类': '📚',
+        '生活类': '🏠',
+        '运动类': '⚽',
+        '小游戏类': '🎮',
+        '其他': '⭐'
+      }
+      return icons[category] || '📝'
+    },
+    getTimeLeft(task) {
+      var deadline, now, diff, hours, minutes, days
+      if (!task.deadline) return '无截止时间'
+      deadline = parseDate(task.deadline)
+      if (!deadline) return ''
+      now = new Date()
+      diff = deadline - now
+      if (diff < 0) return ''
+      hours = Math.floor(diff / (1000 * 60 * 60))
+      minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
+      if (hours > 24) {
+        days = Math.floor(hours / 24)
+        return '还剩 ' + days + ' 天'
+      }
+      if (hours > 0) return '还剩 ' + hours + ' 小时'
+      if (minutes > 0) return '还剩 ' + minutes + ' 分钟'
+      return '即将截止'
+    },
+    checkIsOverdue(task) {
+      var deadline, now
+      if (task.status === 'completed') return false
+      if (!task.deadline) return false
+      deadline = parseDate(task.deadline)
+      if (!deadline) return false
+      now = new Date()
+      return deadline < now
+    },
+    getOverdueDays(task) {
+      var deadline, now, diff, days, hours
+      if (!task.deadline) return ''
+      deadline = parseDate(task.deadline)
+      if (!deadline) return ''
+      now = new Date()
+      diff = now - deadline
+      days = Math.floor(diff / (1000 * 60 * 60 * 24))
+      if (days > 0) return ' ' + days + '天'
+      hours = Math.floor(diff / (1000 * 60 * 60))
+      if (hours > 0) return ' ' + hours + '小时'
+      return ''
+    },
+    switchTab(tabKey) {
+      this.activeTab = tabKey
+    },
+    exitSwitch() {
+      var self = this
+      if (!self.isSwitchedChild) return
+      uni.showModal({
+        title: '退出切换',
+        content: '请输入家长密码以确认退出',
+        editable: true,
+        success: function(res) {
+          if (res.confirm && res.content) {
+            verifyPassword(res.content).then(function(verifyResult) {
+              if (verifyResult.code === 200) {
+                self.switchBackToParentMode()
+              } else {
+                uni.showToast({ title: '密码错误', icon: 'none' })
+              }
+            }).catch(function() {
+              uni.showToast({ title: '密码验证失败', icon: 'none' })
+            })
+          }
+        }
+      })
+    },
+    async switchBackToParentMode() {
+      var result
+      try {
+        result = await switchBackToParent()
+        if (result.data) {
+          this.$store.commit('switchBackToParent')
+          uni.showToast({ title: '已切换回家长模式', icon: 'success' })
+          uni.reLaunch({ url: '/pages/home-pages/family-index' })
+        }
+      } catch (_) {
+        uni.showToast({ title: '切换失败', icon: 'none' })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+/* =============================================
+   页面基础
+   ============================================= */
+.page-bg {
+  min-height: 100vh;
+  background: var(--bg, $uni-bg-color);
+  padding-bottom: 40rpx;
+}
+
+/* =============================================
+   退出切换横幅
+   ============================================= */
+.exit-switch {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  gap: 12rpx;
+  padding: 24rpx;
+  margin: 24rpx 32rpx 0;
+  background: var(--surface, $app-surface);
+  border: 2rpx solid var(--border, $uni-border-color);
+  border-radius: var(--radius-lg, 32rpx);
+  box-shadow: var(--shadow-sm);
+}
+
+.exit-switch:active {
+  opacity: 0.7;
+  transform: scale(0.98);
+}
+
+.exit-switch__icon {
+  font-size: 32rpx;
+}
+
+.exit-switch__text {
+  font-size: 28rpx;
+  color: var(--text-secondary, $uni-text-color-secondary);
+  font-weight: var(--font-weight-medium, 500);
+}
+
+/* =============================================
+   顶部标签栏
+   ============================================= */
+.tab-bar {
+  display: flex;
+  flex-direction: row;
+  margin: 24rpx 32rpx 0;
+  padding: 8rpx;
+  border-radius: var(--radius-xl, 40rpx);
+  background: var(--surface, $app-surface);
+  position: relative;
+}
+
+.tab-item {
+  flex: 1;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  gap: 8rpx;
+  padding: 20rpx 12rpx;
+  border-radius: var(--radius-lg, 32rpx);
+  transition: background-color var(--transition-base, 0.25s ease), box-shadow var(--transition-base, 0.25s ease);
+  position: relative;
+}
+
+.tab-item--hover {
+  background: rgba(249,115,22,0.06);
+}
+
+.tab-item--active {
+  background: linear-gradient(145deg, var(--color-primary-light, $brand-primary-light), var(--color-primary, $brand-primary));
+  box-shadow: inset -2rpx -2rpx 8rpx rgba(255, 255, 255, 0.3),
+    4rpx 4rpx 12rpx rgba(249,115,22,0.2);
+}
+
+.tab-item__label {
+  font-size: 26rpx;
+  font-weight: var(--font-weight-medium, 500);
+  color: var(--text-secondary, $uni-text-color-secondary);
+  transition: color var(--transition-fast, 0.15s ease);
+}
+
+.tab-item--active .tab-item__label {
+  color: var(--text-color-inverse, $app-surface);
+  font-weight: var(--font-weight-bold, 600);
+}
+
+.tab-item__count {
+  display: inline-flex;
+  align-items: center;
+  justify-content: center;
+  min-width: 36rpx;
+  height: 36rpx;
+  padding: 0 8rpx;
+  border-radius: 999rpx;
+  background: rgba(249,115,22,0.12);
+  color: var(--color-primary, $brand-primary);
+  font-size: 20rpx;
+  font-weight: var(--font-weight-bold, 600);
+  line-height: 1;
+}
+
+.tab-item--active .tab-item__count {
+  background: rgba(255, 255, 255, 0.25);
+  color: $app-surface;
+}
+
+/* =============================================
+   任务列表
+   ============================================= */
+.task-list {
+  padding: 24rpx 32rpx;
+}
+
+/* =============================================
+   任务卡片(Claymorphism 风格)
+   ============================================= */
+.task-card {
+  position: relative;
+  overflow: hidden;
+  margin-bottom: 24rpx;
+  transition: transform var(--transition-fast, 0.15s ease),
+    opacity var(--transition-base, 0.25s ease);
+}
+
+.task-card:active {
+  transform: scale(0.98);
+}
+
+/* --- 已完成状态 --- */
+.task-card--completed {
+  opacity: 0.7;
+  border-color: var(--color-success, $status-success-2);
+  background: linear-gradient(145deg, $status-success-tint-2, $status-success-tint);
+  box-shadow: inset -2rpx -2rpx 6rpx rgba(34, 197, 94, 0.05),
+    3rpx 3rpx 10rpx rgba(34, 197, 94, 0.08);
+}
+
+/* --- 超时状态 --- */
+.task-card--overdue {
+  border-color: var(--color-error, $status-danger);
+  background: linear-gradient(145deg, $status-danger-tint-2, $status-danger-soft);
+  box-shadow: inset -2rpx -2rpx 6rpx rgba(239, 68, 68, 0.05),
+    3rpx 3rpx 10rpx rgba(239, 68, 68, 0.08);
+}
+
+/* --- 完成中动画 --- */
+.task-card--completing {
+  animation: cardComplete 0.6s ease forwards;
+  pointer-events: none;
+}
+
+.task-card__celebrate {
+  position: absolute;
+  inset: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: rgba(255, 255, 255, 0.6);
+  border-radius: var(--radius-md, 20rpx);
+  z-index: 10;
+}
+
+.task-card__celebrate-icon {
+  font-size: 80rpx;
+  animation: celebrateBounce 0.6s ease both;
+}
+
+/* --- 内部布局 --- */
+.task-card__inner {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 20rpx;
+}
+
+/* =============================================
+   任务图标
+   ============================================= */
+.task-icon {
+  width: 88rpx;
+  height: 88rpx;
+  border-radius: var(--radius-md, 20rpx);
+  background: linear-gradient(145deg, $uni-bg-color, $uni-border-color-light);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+  border: 2rpx solid var(--border-light, $uni-border-color-light);
+}
+
+.task-card--completed .task-icon {
+  background: linear-gradient(145deg, $status-success-tint-2, $status-success-soft-2);
+  border-color: $tint-green-line;
+}
+
+.task-card--overdue .task-icon {
+  background: linear-gradient(145deg, $status-danger-tint-2, $danger-line);
+  border-color: $danger-line;
+}
+
+.task-icon__emoji {
+  font-size: 40rpx;
+  line-height: 1;
+}
+
+/* =============================================
+   任务内容区
+   ============================================= */
+.task-content {
+  flex: 1;
+  min-width: 0;
+}
+
+.task-title {
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: var(--font-weight-bold, 600);
+  color: var(--text, $uni-text-color);
+  line-height: 1.4;
+  margin-bottom: 10rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.task-card--completed .task-title {
+  text-decoration: line-through;
+  color: var(--text-secondary, $uni-text-color-secondary);
+}
+
+.task-meta {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 16rpx;
+  flex-wrap: wrap;
+}
+
+.points-text {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  line-height: 1.4;
+}
+
+.task-time {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: var(--text-secondary, $uni-text-color-secondary);
+  line-height: 1.4;
+}
+
+.task-time--overdue {
+  color: var(--color-error, $status-danger);
+  font-weight: var(--font-weight-medium, 500);
+}
+
+/* =============================================
+   右侧操作区
+   ============================================= */
+.task-action {
+  flex-shrink: 0;
+  margin-left: 8rpx;
+}
+
+/* =============================================
+   状态徽章
+   ============================================= */
+.status-badge {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 16rpx 24rpx;
+  border-radius: 999rpx;
+  font-weight: var(--font-weight-bold, 600);
+}
+
+.status-badge--completed {
+  background: rgba(34, 197, 94, 0.12);
+  color: var(--color-success, $status-success-2);
+  border: 2rpx solid rgba(34, 197, 94, 0.2);
+}
+
+.status-badge--overdue {
+  background: rgba(239, 68, 68, 0.12);
+  color: var(--color-error, $status-danger);
+  border: 2rpx solid rgba(239, 68, 68, 0.2);
+}
+
+.status-badge__text {
+  font-size: 24rpx;
+  line-height: 1;
+}
+
+/* =============================================
+   空状态
+   ============================================= */
+.empty-wrapper {
+  padding-top: 60rpx;
+}
+
+/* =============================================
+   关键帧动画
+   ============================================= */
+@keyframes cardComplete {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  30% {
+    transform: scale(1.05);
+    opacity: 0.9;
+  }
+  100% {
+    transform: scale(0.95);
+    opacity: 0.6;
+  }
+}
+
+@keyframes celebrateBounce {
+  0% {
+    transform: scale(0) rotate(-180deg);
+    opacity: 0;
+  }
+  50% {
+    transform: scale(1.3) rotate(10deg);
+    opacity: 1;
+  }
+  70% {
+    transform: scale(0.9) rotate(-5deg);
+  }
+  100% {
+    transform: scale(1) rotate(0deg);
+    opacity: 1;
+  }
+}
+</style>

+ 558 - 0
cfc-frontend/pages/family/wishes/index.vue

@@ -0,0 +1,558 @@
+<template>
+  <view class="container" :style="{ '--font-scale': fontScale }">
+    <!-- 标签页切换 -->
+    <view class="tabs">
+      <view 
+        class="tab-item" 
+        :class="{ active: activeTab === 'pending' }"
+        @click="activeTab = 'pending'"
+      >
+        待处理
+        <text class="badge" v-if="pendingCount > 0">{{ pendingCount }}</text>
+      </view>
+      <view 
+        class="tab-item" 
+        :class="{ active: activeTab === 'all' }"
+        @click="activeTab = 'all'"
+      >
+        全部心愿
+      </view>
+    </view>
+
+    <!-- 待处理列表 -->
+    <view class="wish-list" v-if="activeTab === 'pending'">
+      <!-- 待定价 -->
+      <view class="section" v-if="draftWishes.length > 0">
+        <view class="section-title">📝 待定价</view>
+        <view 
+          v-for="wish in draftWishes" 
+          :key="wish.id" 
+          class="wish-card"
+        >
+          <view class="wish-info">
+            <text class="wish-child">{{ wish.childName }}</text>
+            <text class="wish-title">{{ wish.title }}</text>
+            <text class="wish-category" v-if="wish.category">{{ wish.category }}</text>
+          </view>
+          <view class="wish-actions">
+            <button class="btn-price" @click="showPriceDialog(wish)">定价</button>
+            <button class="btn-reject" @click="rejectWish(wish)">拒绝</button>
+          </view>
+        </view>
+      </view>
+
+      <!-- 待审批兑换 -->
+      <view class="section" v-if="pendingExchangeWishes.length > 0">
+        <view class="section-title">⏳ 待审批兑换</view>
+        <view 
+          v-for="wish in pendingExchangeWishes" 
+          :key="wish.id" 
+          class="wish-card"
+        >
+          <view class="wish-info">
+            <text class="wish-child">{{ wish.childName }}</text>
+            <text class="wish-title">{{ wish.title }}</text>
+            <text class="wish-points">{{ wish.pointsRequired }}积分</text>
+          </view>
+          <view class="wish-actions">
+            <button class="btn-approve" @click="approveExchange(wish, true)">批准</button>
+            <button class="btn-reject" @click="approveExchange(wish, false)">拒绝</button>
+          </view>
+        </view>
+      </view>
+
+      <!-- 待兑现 -->
+      <view class="section" v-if="approvedWishes.length > 0">
+        <view class="section-title">📦 待兑现</view>
+        <view 
+          v-for="wish in approvedWishes" 
+          :key="wish.id" 
+          class="wish-card"
+        >
+          <view class="wish-info">
+            <text class="wish-child">{{ wish.childName }}</text>
+            <text class="wish-title">{{ wish.title }}</text>
+            <text class="wish-points">已扣{{ wish.pointsRequired }}积分</text>
+          </view>
+          <view class="wish-actions">
+            <button class="btn-fulfill" @click="markFulfilled(wish)">已购买</button>
+          </view>
+        </view>
+      </view>
+
+      <!-- 空状态 -->
+      <view class="empty" v-if="pendingWishes.length === 0">
+        <text class="empty-icon">✅</text>
+        <text class="empty-text">暂无待处理心愿</text>
+      </view>
+    </view>
+
+    <!-- 全部心愿列表 -->
+    <view class="wish-list" v-if="activeTab === 'all'">
+      <view 
+        v-for="wish in allWishes" 
+        :key="wish.id" 
+        class="wish-card"
+        :class="wish.status"
+      >
+        <view class="wish-header">
+          <view class="wish-info-left">
+            <text class="wish-child">{{ wish.childName }}</text>
+            <text class="wish-title">{{ wish.title }}</text>
+          </view>
+          <text class="wish-status" :class="wish.status">{{ wish.statusText }}</text>
+        </view>
+        <view class="wish-meta">
+          <text class="wish-points" v-if="wish.pointsRequired">{{ wish.pointsRequired }}积分</text>
+          <text class="wish-time">{{ wish.createdAt }}</text>
+        </view>
+      </view>
+
+      <!-- 空状态 -->
+      <view class="empty" v-if="allWishes.length === 0">
+        <text class="empty-icon">🎁</text>
+        <text class="empty-text">暂无心愿</text>
+      </view>
+    </view>
+
+    <!-- 定价弹窗 -->
+    <view class="modal-mask" v-if="priceDialogVisible" @click="priceDialogVisible = false">
+      <view class="modal" @click.stop>
+        <view class="modal-title">设置积分</view>
+        <view class="modal-content">
+          <text class="modal-wish-title">{{ currentWish && currentWish.title }}</text>
+          <view class="input-row">
+            <text class="inputLabel">所需积分:</text>
+            <input 
+              type="number" 
+              v-model="priceValue" 
+              placeholder="请输入积分"
+              class="price-input"
+            />
+          </view>
+        </view>
+        <view class="modal-btns">
+          <button class="btn-cancel" @click="priceDialogVisible = false">取消</button>
+          <button class="btn-confirm" @click="submitPrice">确定</button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { 
+  getPendingWishes, 
+  getWishes, 
+  setWishPrice, 
+  rejectWish,
+  approveWishExchange,
+  markWishFulfilled
+} from '../../../utils/api.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  data() {
+    return {
+      activeTab: 'pending',
+      memberId: '',
+      childName: '',
+      pendingWishes: [],
+      allWishes: [],
+      priceDialogVisible: false,
+      currentWish: null,
+      priceValue: ''
+    }
+  },
+  computed: {
+    pendingCount() {
+      return this.pendingWishes.length
+    },
+    draftWishes() {
+      return this.pendingWishes.filter(w => w.status === 'draft')
+    },
+    pendingExchangeWishes() {
+      return this.pendingWishes.filter(w => w.status === 'pending_exchange')
+    },
+    approvedWishes() {
+      return this.pendingWishes.filter(w => w.status === 'approved')
+    }
+  },
+  onShow() {
+    this.loadData()
+  },
+  onLoad(options) {
+    if (options.memberId) {
+      this.memberId = options.memberId
+      this.childName = options.childName || '该成员'
+    }
+  },
+  methods: {
+    async loadData() {
+      try {
+        // 获取待处理列表
+        const pendingRes = await getPendingWishes()
+        let wishes = pendingRes.data || []
+
+        // 如果指定了 memberId,按成员过滤
+        if (this.memberId) {
+          wishes = wishes.filter(w => String(w.memberId) === String(this.memberId))
+        }
+        this.pendingWishes = wishes
+        
+        // 获取全部心愿
+        const allRes = await getWishes({})
+        let allWishes = allRes.data || []
+        if (this.memberId) {
+          allWishes = allWishes.filter(w => String(w.memberId) === String(this.memberId))
+        }
+        this.allWishes = allWishes
+      } catch (e) {
+        console.error('加载数据失败', e)
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      }
+    },
+    
+    showPriceDialog(wish) {
+      this.currentWish = wish
+      this.priceValue = ''
+      this.priceDialogVisible = true
+    },
+    
+    async submitPrice() {
+      const points = parseInt(this.priceValue)
+      if (!points || points <= 0) {
+        uni.showToast({ title: '请输入有效积分', icon: 'none' })
+        return
+      }
+      
+      try {
+        await setWishPrice(this.currentWish.id, points)
+        uni.showToast({ title: '定价成功', icon: 'success' })
+        this.priceDialogVisible = false
+        this.loadData()
+      } catch (e) {
+        uni.showToast({ title: e.message || '定价失败', icon: 'none' })
+      }
+    },
+    
+    async rejectWish(wish) {
+      uni.showModal({
+        title: '拒绝心愿',
+        content: `确定要拒绝"${wish.title}"吗?`,
+        success: async (res) => {
+          if (res.confirm) {
+            try {
+              await rejectWish(wish.id, '不合适')
+              uni.showToast({ title: '已拒绝', icon: 'success' })
+              this.loadData()
+            } catch (e) {
+              uni.showToast({ title: '操作失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    },
+    
+    async approveExchange(wish, approved) {
+      const action = approved ? '批准' : '拒绝'
+      uni.showModal({
+        title: `${action}兑换`,
+        content: `确定要${action}"${wish.title}"的兑换申请吗?`,
+        success: async (res) => {
+          if (res.confirm) {
+            try {
+              await approveWishExchange(wish.id, approved)
+              uni.showToast({ title: `已${action}`, icon: 'success' })
+              this.loadData()
+            } catch (e) {
+              uni.showToast({ title: e.message || '操作失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    },
+    
+    async markFulfilled(wish) {
+      uni.showModal({
+        title: '标记已购买',
+        content: `确定已购买"${wish.title}"吗?`,
+        success: async (res) => {
+          if (res.confirm) {
+            try {
+              await markWishFulfilled(wish.id)
+              uni.showToast({ title: '已标记', icon: 'success' })
+              this.loadData()
+            } catch (e) {
+              uni.showToast({ title: '操作失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+.container {
+  padding: 30rpx;
+  min-height: 100vh;
+  background: $facade-fill-2;
+}
+
+/* 标签页 */
+.tabs {
+  display: flex;
+  background: $app-surface;
+  border-radius: 50rpx;
+  padding: 8rpx;
+  margin-bottom: 30rpx;
+}
+
+.tab-item {
+  flex: 1;
+  text-align: center;
+  padding: 16rpx;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+  color: $facade-text-body;
+  position: relative;
+}
+
+.tab-item.active {
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
+}
+
+.badge {
+  position: absolute;
+  top: 4rpx;
+  right: 20rpx;
+  background: $brand-primary;
+  color: $app-surface;
+  font-size: 20rpx;
+  padding: 2rpx 10rpx;
+  border-radius: 20rpx;
+  min-width: 30rpx;
+  text-align: center;
+}
+
+.tab-item.active .badge {
+  background: $app-surface;
+  color: $brand-primary;
+}
+
+/* 分组 */
+.section {
+  margin-bottom: 30rpx;
+}
+
+.section-title {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: $facade-text-body;
+  margin-bottom: 15rpx;
+  padding-left: 10rpx;
+}
+
+/* 心愿卡片 */
+.wish-card {
+  background: $app-surface;
+  border-radius: 15rpx;
+  padding: 25rpx;
+  margin-bottom: 15rpx;
+}
+
+.wish-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+}
+
+.wish-info {
+  flex: 1;
+}
+
+.wish-info-left {
+  flex: 1;
+}
+
+.wish-child {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $brand-primary;
+  background: $status-danger-tint;
+  padding: 4rpx 12rpx;
+  border-radius: 10rpx;
+  margin-right: 10rpx;
+}
+
+.wish-title {
+  font-size: calc(30rpx * var(--font-scale, 1));
+  color: $facade-text-strong;
+  font-weight: bold;
+  margin-left: 10rpx;
+}
+
+.wish-category {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-body;
+  background: $facade-fill-2;
+  padding: 4rpx 12rpx;
+  border-radius: 10rpx;
+  margin-left: 10rpx;
+}
+
+.wish-meta {
+  display: flex;
+  justify-content: space-between;
+  margin-top: 15rpx;
+}
+
+.wish-points {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $brand-primary;
+  font-weight: bold;
+}
+
+.wish-time {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+}
+
+.wish-status {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  padding: 6rpx 16rpx;
+  border-radius: 20rpx;
+  background: $facade-fill;
+  color: $facade-text-body;
+}
+
+.wish-status.active { background: $status-success-soft; color: $status-success-deep; }
+.wish-status.pending_exchange { background: $status-warning-soft; color: $status-warning-deep; }
+.wish-status.approved { background: $status-info-soft; color: $status-info-deep; }
+.wish-status.exchanging { background: $status-info-soft; color: $status-info-deep; }
+.wish-status.fulfilled { background: $violet-tint-100; color: $facade-violet-deep; }
+
+.wish-actions {
+  display: flex;
+  gap: 15rpx;
+  margin-top: 20rpx;
+}
+
+.btn-price, .btn-approve, .btn-fulfill {
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
+  font-size: 26rpx;
+  padding: 12rpx 24rpx;
+  border-radius: 30rpx;
+  margin: 0;
+}
+
+.btn-reject {
+  background: $app-surface;
+  color: $facade-text-muted;
+  border: 1rpx solid $facade-border-soft;
+  font-size: 26rpx;
+  padding: 12rpx 24rpx;
+  border-radius: 30rpx;
+  margin: 0;
+}
+
+/* 空状态 */
+.empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 80rpx;
+}
+
+.empty-icon {
+  font-size: 80rpx;
+  margin-bottom: 20rpx;
+}
+
+.empty-text {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+}
+
+/* 弹窗 */
+.modal-mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0,0,0,0.5);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.modal {
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  width: 80%;
+}
+
+.modal-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  text-align: center;
+  margin-bottom: 30rpx;
+}
+
+.modal-content {
+  margin-bottom: 30rpx;
+}
+
+.modal-wish-title {
+  font-size: 28rpx;
+  color: $facade-text-strong;
+  display: block;
+  text-align: center;
+  margin-bottom: 30rpx;
+}
+
+.input-row {
+  display: flex;
+  align-items: center;
+}
+
+.inputLabel {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: $facade-text-strong;
+}
+
+.price-input {
+  flex: 1;
+  background: $facade-fill-2;
+  border-radius: 15rpx;
+  padding: 20rpx;
+  font-size: 28rpx;
+}
+
+.modal-btns {
+  display: flex;
+  gap: 20rpx;
+}
+
+.btn-cancel, .btn-confirm {
+  flex: 1;
+  padding: 20rpx;
+  border-radius: 40rpx;
+  font-size: 28rpx;
+}
+
+.btn-cancel {
+  background: $facade-fill-2;
+  color: $facade-text-body;
+}
+
+.btn-confirm {
+  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
+  color: $app-surface;
+}
+</style>

+ 1 - 1
cfc-frontend/pages/growth-main/index.vue

@@ -349,7 +349,7 @@ export default {
     },
     goActivityDetail: function(act) { uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id }) },
     goMoreActivities: function() { uni.navigateTo({ url: '/pages/activity/index' }) },
-    goToTasks: function() { uni.navigateTo({ url: '/pages/child/tasks' }) },
+    goToTasks: function() { uni.navigateTo({ url: '/pages/family/tasks' }) },
     handleGuestFeatureTap: function() { uni.showToast({ title: '登录后即可体验该功能', icon: 'none' }) },
     goToCaution: function() {
       var mid = uni.getStorageSync('currentChildId') || uni.getStorageSync('currentMemberId') || ''

+ 1 - 1
cfc-frontend/pages/guide/bind/invite.vue

@@ -215,7 +215,7 @@ export default {
       return `${d.getMonth() + 1}-${d.getDate()}`
     },
     goHome() {
-      uni.navigateTo({ url: '/pages/home-pages/parent-index' })
+      uni.navigateTo({ url: '/pages/home-pages/family-index' })
     }
   }
 }

+ 5 - 1477
cfc-frontend/pages/home-pages/child-index.vue

@@ -1,1486 +1,14 @@
 <template>
-  <view class="container" :style="{ '--font-scale': fontScale }">
-    <!-- 加载状态 -->
-    <BaseLoading :loading="loading" text="正在加载..." />
-
-    <!-- 撒花庆祝 -->
-    <ConfettiCelebration :show="showConfetti" />
-
-    <!-- 首次登录引导浮层 -->
-    <OnboardingGuide
-      role="child"
-      position="child"
-      :show="showOnboarding"
-      @close="showOnboarding = false" />
-    <!-- 配置驱动弹窗(guide_popup 表,管理端可配置) -->
-    <ConfigurableGuide
-      :popup="guidePopup"
-      :show="showGuidePopup"
-      @done="handleGuidePopupDone" />
-
-    <!-- 空状态 - 无孩子 -->
-    <BaseEmpty
-      v-if="!loading && !hasChildren"
-      icon="🌈"
-      title="欢迎来到知心益家!"
-      description="让爸爸妈妈帮你添加,开始快乐成长之旅"
-    >
-      <template #action>
-        <PlayfulButton variant="clay" size="lg" @click="goToAddChild">
-          ➕ 添加我
-        </PlayfulButton>
-      </template>
-    </BaseEmpty>
-
-<!-- ====== 主内容(有孩子时) ====== -->
-<view class="main-content" v-if="!loading && hasChildren">
-<!-- ===== A) 顶部 Banner ===== -->
-<!-- 通关引导进度条 -->
-<GateGuideBar :unlockStatus="unlockStatus" />
-
-<!-- 五维能量沙盘 -->
-<GateLock v-if="sandboxLocked && sandboxLockGate" :gate="sandboxLockGate" />
-<WuxingSandbox
-  v-else-if="sandboxData"
-  mode="live"
-  :scores="familySandboxScores"
-  :overallScore="familySandboxOverall"
-  :members="familySandboxMembers"
-  :personalData="sandboxPersonalData"
-  :dimensions="energyDimensions"
-  :totalEnergy="totalEnergy"
-  :totalHealthIndex="totalHealthIndex"
-  :familyUnlocked="familyUnlocked"
-  :currentView="currentSandboxView"
-  :memberCount="familyMembersVisible.length"
-  starHeight="400rpx"
-  showDimensionBar
-  @view-change="onSandboxViewChange"
-  @point-click="onDomainClick" />
-<!-- 家庭能量横条 -->
-<FamilyEnergyBar
-  v-if="hasChildren"
-  dimensionCode="child"
-  :sandboxData="sandboxData" />
-
-<!-- 家庭成员选择条 -->
-<MemberSelector
-  variant="strip"
-  v-if="hasChildren && familyMembersVisible.length > 0"
-  :members="familyMembersVisible"
-  :selectedMemberId="selectedMemberId"
-  @select="onFamilyMemberSelect"
-  @removed="loadFamilyMembersVisible" />
-
-<!-- 连续打卡徽章 -->
-<view
-class="streak-badge-wrap animate-slide-up animate-stagger-3"
-v-if="streakDays > 0"
-@click="showStreakDetail"
-hover-class="streak-pressed"
->
-<text class="streak-fire">🔥</text>
-<view class="streak-info">
-<text class="streak-days">{{ streakDays }}</text>
-<text class="streak-unit">天坚持</text>
-</view>
-</view>
-
-<!-- ============================================================
-B) 积分卡片 — Claymorphism 暖橙黏土风格
-============================================================ -->
-      <view
-        class="points-card animate-bounce-in"
-        @click="goToRewards"
-        hover-class="points-card-pressed"
-      >
-        <!-- 装饰圆点 -->
-        <view class="points-deco points-deco--1"></view>
-        <view class="points-deco points-deco--2"></view>
-
-        <view class="points-top">
-          <view class="points-left">
-            <text class="points-label">🌟 我的星星</text>
-            <text class="points-number">{{ totalPoints }}</text>
-          </view>
-          <view class="points-level-tag">
-            <text class="level-emoji">{{ levelEmoji }}</text>
-            <text class="level-name">{{ levelName }}</text>
-          </view>
-        </view>
-
-        <view class="points-progress">
-          <text class="progress-hint" v-if="pointsToNext > 0">
-            还差 <text class="progress-hint-num">{{ pointsToNext }}</text> 颗星星升到 {{ nextLevelName }}
-          </text>
-          <text class="progress-hint" v-else>
-            🎉 已经是最高等级啦!
-          </text>
-          <view class="progress-track">
-            <view
-              class="progress-fill"
-              :style="'width: ' + levelProgress + '%'"
-            ></view>
-          </view>
-        </view>
-      </view>
-
-      <!-- ============================================================
-      C) 今日任务 — 可水平滑动的黏土卡片
-      ============================================================ -->
-      <view class="tasks-section animate-slide-up animate-stagger-3">
-        <view class="section-header">
-          <text class="section-title">📋 今日任务</text>
-          <text class="section-link" @click="goToTasks">查看全部 →</text>
-        </view>
-
-        <scroll-view
-          class="tasks-scroll"
-          scroll-x
-          enhanced
-          :show-scrollbar="false"
-          v-if="todayTasks.length > 0"
-        >
-          <view
-            class="task-clay-card"
-            v-for="(task, tIdx) in todayTasks"
-            :key="task.id"
-            @click="handleTaskClick(task)"
-            hover-class="task-card-pressed"
-            :style="'animation-delay: ' + (tIdx * 80) + 'ms'"
-          >
-            <view class="task-card-icon-wrap" :class="'task-cat--' + getTaskCategoryClass(task)">
-              <text class="task-card-icon">{{ getTaskIcon(task) }}</text>
-            </view>
-            <text class="task-card-name">{{ task.title }}</text>
-            <text class="task-card-points" v-if="task.points">+{{ task.points }}⭐</text>
-            <view class="task-card-badge">
-              <BaseBadge :variant="getBadgeVariant(task)" size="xs">
-                {{ getStatusText(task) }}
-              </BaseBadge>
-            </view>
-          </view>
-        </scroll-view>
-
-        <!-- 今日任务空状态 -->
-        <view class="tasks-empty" v-else @click="goToTasks">
-          <view class="tasks-empty-icon">🎯</view>
-          <text class="tasks-empty-title">今天没有任务哦</text>
-          <text class="tasks-empty-desc">让爸爸妈妈给你布置任务吧~</text>
-        </view>
-      </view>
-
-      <!-- ============================================================
-      D) 快捷操作 — 2×2 黏土按钮网格
-      ============================================================ -->
-      <view class="quick-section animate-slide-up animate-stagger-4">
-        <view class="section-header">
-          <text class="section-title">🚀 快捷入口</text>
-        </view>
-        <view class="quick-grid">
-          <view
-            class="quick-clay-btn"
-            @click="goToTasks"
-            hover-class="quick-btn-pressed"
-          >
-            <view class="quick-btn-icon quick-icon--tasks">
-              <text class="quick-emoji">📋</text>
-            </view>
-            <text class="quick-btn-label">我的任务</text>
-          </view>
-
-          <view
-            class="quick-clay-btn"
-            @click="goToRewards"
-            hover-class="quick-btn-pressed"
-          >
-            <view class="quick-btn-icon quick-icon--rewards">
-              <text class="quick-emoji">⭐</text>
-            </view>
-            <text class="quick-btn-label">心愿商城</text>
-          </view>
-
-          <view
-            class="quick-clay-btn"
-            @click="goToGrowth"
-            hover-class="quick-btn-pressed"
-          >
-            <view class="quick-btn-icon quick-icon--growth">
-              <text class="quick-emoji">🏆</text>
-            </view>
-            <text class="quick-btn-label">成长记录</text>
-          </view>
-
-          <view
-            class="quick-clay-btn"
-            @click="goToGames"
-            hover-class="quick-btn-pressed"
-          >
-            <view class="quick-btn-icon quick-icon--games">
-              <text class="quick-emoji">🎮</text>
-            </view>
-            <text class="quick-btn-label">小游戏</text>
-          </view>
-
-          <view
-            class="quick-clay-btn"
-            @click="goToSelfQuiz"
-            hover-class="quick-btn-pressed"
-          >
-            <view class="quick-btn-icon quick-icon--quiz">
-              <text class="quick-emoji">✏️</text>
-            </view>
-            <text class="quick-btn-label">自己出题</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- ============================================================
-      E) 最近成就 — 水平滚动徽章
-      ============================================================ -->
-      <view
-        class="achievement-section animate-slide-up animate-stagger-5"
-        v-if="earnedBadges.length > 0"
-      >
-        <view class="section-header">
-          <text class="section-title">🏅 最近成就</text>
-        </view>
-        <scroll-view
-          class="achievement-scroll"
-          scroll-x
-          enhanced
-          :show-scrollbar="false"
-        >
-          <view
-            class="achievement-item"
-            v-for="badge in earnedBadges"
-            :key="badge.id"
-            hover-class="achievement-pressed"
-          >
-            <view class="achievement-icon-wrap">
-              <text class="achievement-icon">{{ badge.icon }}</text>
-            </view>
-            <text class="achievement-name">{{ badge.name }}</text>
-          </view>
-
-          <!-- 锁定占位徽章 -->
-          <view
-            class="achievement-item achievement-item--locked"
-            v-for="lockedBadge in lockedBadges"
-            :key="lockedKey(lockedBadge)"
-          >
-            <view class="achievement-icon-wrap achievement-icon-wrap--locked">
-              <text class="achievement-icon achievement-icon--locked">🔒</text>
-            </view>
-            <text class="achievement-name achievement-name--locked">{{ lockedBadge.name }}</text>
-          </view>
-        </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>
-
-      <!-- ===== 推荐商品+活动(混合信息流) ===== -->
-      <RecommendedFeed
-        v-if="hasChildren"
-        :products="dimensionProducts"
-        :activities="dimensionActivities"
-        @itemClick="onFeedItemClick"
-        @more="goMoreDiscover" />
-
-      <!-- 底部安全留白 -->
-      <view class="bottom-spacer"></view>
-    </view>
-  </view>
+  <view class="redirect-shell"></view>
 </template>
 
 <script>
-import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getFamilyMemberList, getGuideConfig, getGuidePopupList, consumeGuidePopup } from '../../utils/api.js'
-import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
-import WuxingSandbox from '../../components/wuxing-sandbox.vue'
-import RecommendedFeed from '../../components/RecommendedFeed.vue'
-import PlayfulCard from '../../components/PlayfulCard.vue'
-import PlayfulButton from '../../components/PlayfulButton.vue'
-import BaseBadge from '../../components/BaseBadge.vue'
-import BaseEmpty from '../../components/BaseEmpty.vue'
-import BaseLoading from '../../components/BaseLoading.vue'
-import GateLock from '../../components/gate-lock.vue'
-import GateGuideBar from '@/pages/home-pages/components/gate-guide-bar.vue'
-import OnboardingGuide from '../../components/OnboardingGuide.vue'
-import ConfigurableGuide from '../../components/ConfigurableGuide.vue'
-import ConfettiCelebration from '@/pages/home-pages/components/ConfettiCelebration.vue'
-import MemberSelector from '@/components/MemberSelector.vue'
-
-/**
- * 等级配置表
- * 星星宝宝(0) → 月亮小朋友(200) → 太阳小天使(500) → 宇宙小英雄(1000)
- */
-var LEVELS = [
-  { min: 0,    max: 200,  name: '星星宝宝',   nextName: '月亮小朋友', emoji: '⭐' },
-  { min: 200,  max: 500,  name: '月亮小朋友', nextName: '太阳小天使', emoji: '🌙' },
-  { min: 500,  max: 1000, name: '太阳小天使', nextName: '宇宙小英雄', emoji: '☀️' },
-  { min: 1000, max: Infinity, name: '宇宙小英雄', nextName: '👑 MAX', emoji: '🦸' }
-]
-
-/**
- * 任务分类 → 图标映射
- */
-var TASK_ICONS = {
-  '学习类': '📚',
-  '阅读类': '📖',
-  '生活类': '🏠',
-  '运动类': '⚽',
-  '家务类': '🧹',
-  '艺术类': '🎨',
-  '其他': '⭐'
-}
-import fontScaleMixin from '@/mixins/font-scale.js'
+// 旧路径跳转壳:/pages/home-pages/child-index → /pages/home-pages/member-index
+import { redirectToNewPath } from '../../utils/redirect.js'
 
 export default {
-  mixins: [fontScaleMixin],
-  components: {
-    FamilyEnergyBar,
-    WuxingSandbox,
-    RecommendedFeed,
-    PlayfulCard,
-    PlayfulButton,
-    BaseBadge,
-    BaseEmpty,
-    BaseLoading,
-    OnboardingGuide,
-    ConfigurableGuide,
-    ConfettiCelebration,
-    MemberSelector,
-    GateLock,
-    GateGuideBar
-  },
-  data() {
-    return {
-      loading: true,
-      showOnboarding: false,
-      hasChildren: false,
-      guidePopup: null,
-      showGuidePopup: false,
-      _guidePopupAttempted: false,
-      nickname: '',
-      memberId: '',
-      totalPoints: 0,
-      streakDays: 0,
-      completedCount: 0,
-      totalTaskCount: 0,
-      todayTasks: [],
-      badges: [
-        { id: 1, icon: '🌟', name: '早起鸟', unlocked: false },
-        { id: 2, icon: '📚', name: '阅读小达人', unlocked: false },
-        { id: 3, icon: '🏃', name: '运动健将', unlocked: false },
-        { id: 4, icon: '🎨', name: '小艺术家', unlocked: false },
-        { id: 5, icon: '🧹', name: '家务小能手', unlocked: false }
-      ],
-      _mounted: false,
-      energyDimensions: [],
-      totalEnergy: 0,
-      totalHealthIndex: 0,
-      energyLogs: [],
-      sandboxData: null,
-      sandboxPersonalData: null,
-      sandboxFamilyData: null,
-      sandboxLocked: false,
-      sandboxLockGate: null,
-      currentSandboxView: 'personal',
-      familyUnlocked: false,
-      unlockStatus: null,
-      dimensionActivities: [],
-      dimensionProducts: [],
-      familyMembersVisible: [],
-      selectedMemberId: null
-    }
-  },
-  computed: {
-    // ---------- 能量沙盘 ----------
-    sandboxScores() {
-      if (!this.sandboxData) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
-      return {
-        body: this.sandboxData.bodyScore || 0,
-        mind: this.sandboxData.mindScore || 0,
-        wisdom: this.sandboxData.wisdomScore || 0,
-        action: this.sandboxData.actionScore || 0,
-        wealth: this.sandboxData.wealthScore || 0
-      }
-    },
-    familySandboxScores() {
-      var d = this.sandboxFamilyData || {}
-      return {
-        body: d.bodyScore || 0,
-        mind: d.mindScore || 0,
-        wisdom: d.wisdomScore || 0,
-        action: d.actionScore || 0,
-        wealth: d.wealthScore || 0
-      }
-    },
-    familySandboxOverall() {
-      return this.sandboxFamilyData ? (this.sandboxFamilyData.overallScore || 0) : 0
-    },
-    familySandboxMembers() {
-      return this.sandboxFamilyData ? (this.sandboxFamilyData.members || []) : []
-    },
-    // ---------- 等级相关 ----------
-    currentLevelIndex() {
-      const pts = this.totalPoints
-      if (pts >= 1000) return 3
-      if (pts >= 500) return 2
-      if (pts >= 200) return 1
-      return 0
-    },
-    levelName() {
-      return LEVELS[this.currentLevelIndex].name
-    },
-    nextLevelName() {
-      return LEVELS[this.currentLevelIndex].nextName
-    },
-    levelEmoji() {
-      return LEVELS[this.currentLevelIndex].emoji
-    },
-    currentLevelMin() {
-      return LEVELS[this.currentLevelIndex].min
-    },
-    nextLevelMax() {
-      return LEVELS[this.currentLevelIndex].max
-    },
-    levelProgress() {
-      if (this.totalPoints >= 1000) return 100
-      const range = this.nextLevelMax - this.currentLevelMin
-      if (range <= 0) return 100
-      const progress = ((this.totalPoints - this.currentLevelMin) / range) * 100
-      return Math.min(100, Math.floor(progress))
-    },
-    pointsToNext() {
-      if (this.totalPoints >= 1000) return 0
-      return this.nextLevelMax - this.totalPoints
-    },
-
-    // ---------- 任务相关 ----------
-    taskProgress() {
-      if (this.totalTaskCount === 0) return 0
-      return Math.floor((this.completedCount / this.totalTaskCount) * 100)
-    },
-
-    // ---------- 庆祝动画 ----------
-    showConfetti() {
-      return this.taskProgress >= 100 && this.totalTaskCount > 0
-    },
-
-    // ---------- 头像表情 ----------
-    avatarEmoji() {
-      const level = this.currentLevelIndex
-      if (level >= 3) return '🦸'
-      if (level >= 2) return '😇'
-      if (level >= 1) return '🌙'
-      return '🐣'
-    },
-
-    // ---------- 问候语 ----------
-    timeGreeting() {
-      const hour = new Date().getHours()
-      if (hour < 6) return '🌛 夜深了,还没睡呀'
-      if (hour < 9) return '🌅 早安!新的一天开始啦'
-      if (hour < 12) return '☀️ 上午好!今天也要加油哦'
-      if (hour < 14) return '🌤️ 中午好!吃饱了才有力气'
-      if (hour < 18) return '🌻 下午好!继续保持好状态'
-      if (hour < 21) return '🌆 晚上好!今天过得开心吗'
-      return '🌛 该休息啦,明天继续加油'
-    },
-
-    // ---------- 成就徽章 ----------
-    earnedBadges() {
-      return this.badges.filter(function(b) { return b.unlocked })
-    },
-    lockedBadges() {
-      return this.badges.filter(function(b) { return !b.unlocked })
-    }
-  },
-  mounted() {
-    // 首次登录引导:异步拉取配置并按版本号判断是否展示
-    this._checkOnboardingShow()
-    this.loadData()
-    this.loadGuidePopup()
-  },
-  onShow() {
-    if (this._mounted) {
-      this.loadData()
-    }
-    this._mounted = true
-  },
-  methods: {
-    onFamilyMemberSelect(member) {
-      this.selectedMemberId = member.id
-    },
-    loadFamilyMembersVisible() {
-      var self = this
-      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
-        self.familyMembersVisible = res.data || []
-      }).catch(function(e) {
-        console.log('获取可见家庭成员失败', e)
-        self.familyMembersVisible = []
-      })
-    },
-    // ======================== 数据加载 ========================
-    async loadData() {
-      this.loading = true
-      try {
-        const userInfo = uni.getStorageSync('userInfo')
-        const role = uni.getStorageSync('currentRole') || 'child'
-        if (role !== 'child') {
-          uni.switchTab({ url: '/pages/index-home/index' })
-          return
-        }
-        this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : '小朋友'
-
-        const childrenRes = await getFamilyMemberList({ visibleOnly: true })
-        var allMembers = childrenRes.data || []
-        if (!Array.isArray(allMembers)) allMembers = []
-        // 同步设置 familyMembersVisible,避免 loadFamilyMembersVisible() 再调一次
-        this.familyMembersVisible = allMembers
-        const children = allMembers.filter(function(m) { return m.effectiveRole === 'child' || m.source !== 'parent' })
-        if (children.length === 0) {
-          this.hasChildren = false
-          this.loading = false
-          return
-        }
-        this.hasChildren = true
-        const child = children[0] || {}
-        this.memberId = child.id || ''
-        this.totalPoints = child.totalPoints || 0
-        this.streakDays = child.streakDays || 0
-
-        const tasksRes = await getTodayTasks()
-        const rawTasks = tasksRes.data || []
-        this.todayTasks = rawTasks
-        this.completedCount = rawTasks.filter(function(t) { return t.status === 'completed' }).length
-        this.totalTaskCount = rawTasks.length
-
-        // 解锁成就
-        if (this.streakDays >= 7) {
-          this.unlockBadge(1)
-        }
-        if (this.totalPoints >= 1000) {
-          this.unlockBadge(2)
-        }
-
-        // 按类别统计已完成任务次数,解锁对应徽章
-        var categoryCompletedCount = { '运动类': 0, '艺术类': 0, '家务类': 0 }
-        for (var i = 0; i < rawTasks.length; i++) {
-          var task = rawTasks[i]
-          if (task.status === 'completed') {
-            var cat = task.category || '其他'
-            if (cat === '运动类') {
-              categoryCompletedCount['运动类']++
-            } else if (cat === '艺术类') {
-              categoryCompletedCount['艺术类']++
-            } else if (cat === '家务类') {
-              categoryCompletedCount['家务类']++
-            }
-          }
-        }
-        if (categoryCompletedCount['运动类'] >= 5) {
-          this.unlockBadge(3)
-        }
-        if (categoryCompletedCount['艺术类'] >= 5) {
-          this.unlockBadge(4)
-        }
-        if (categoryCompletedCount['家务类'] >= 5) {
-          this.unlockBadge(5)
-        }
-
-        // 加载五维能量概览(memberId 由后端从 JWT 派生)
-        try {
-          var overviewRes = await getEnergyOverview()
-          if (overviewRes && overviewRes.data) {
-            this.energyDimensions = overviewRes.data.dimensions || []
-            this.totalEnergy = overviewRes.data.totalEnergy || 0
-            this.totalHealthIndex = overviewRes.data.totalHealthIndex || 0
-          }
-        } catch (e) {
-          console.log('获取能量概览失败', e)
-        }
-
-        // 加载最近能量变动
-        try {
-          var logsRes = await getEnergyLogs('', 1, 5)
-          if (logsRes && logsRes.data) {
-            this.energyLogs = logsRes.data.records || logsRes.data || []
-          }
-        } catch (e) {
-          console.log('获取能量日志失败', e)
-          this.energyLogs = []
-        }
-
-        // 加载通关解锁状态
-        try {
-          var statusRes = await getUnlockStatus()
-          if (statusRes && statusRes.data) {
-            this.unlockStatus = statusRes.data
-          }
-        } catch (e) {
-          console.log('获取通关解锁状态失败', e)
-        }
-
-        // 加载沙盘数据(含通关门禁,一次返回个人+全家)
-        try {
-          var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId') || null
-          var sandboxRes = await getEnergySandbox(this.currentSandboxView, memberId)
-          if (sandboxRes && sandboxRes.data) {
-            var ed = sandboxRes.data
-            if (ed.locked) {
-              this.sandboxLocked = true
-              this.sandboxLockGate = ed.gate || null
-              this.sandboxData = null
-              this.sandboxPersonalData = null
-              this.sandboxFamilyData = null
-            } else {
-              this.sandboxLocked = false
-              this.sandboxLockGate = null
-              this.sandboxPersonalData = ed.personalData || null
-              this.sandboxFamilyData = ed.familyData || null
-              this.sandboxData = this.currentSandboxView === 'family' ? (ed.familyData || null) : (ed.personalData || null)
-              this.familyUnlocked = ed.familyUnlocked || false
-            }
-          }
-        } catch (e) {
-          console.log('获取沙盘数据失败', e)
-        }
-
-        // 家庭成员已在上方 loadData 开头通过 getFamilyMemberList 一次获取,不再重复调用
-        // this.loadFamilyMembersVisible()
-
-        // 加载维度活动
-        try {
-          var actRes = await getActivityList({ dimensionCode: 'child', page: 1, size: 3 })
-          if (actRes && actRes.data) {
-            var actList = Array.isArray(actRes.data) ? actRes.data : (actRes.data.records || [])
-            this.dimensionActivities = actList.slice(0, 3)
-          }
-        } catch (e) {
-          console.log('获取活动数据失败', e)
-        }
-
-        // 加载维度商品
-        try {
-          var prodRes = await getProductsByDomain('child', 1, 4)
-          if (prodRes && prodRes.data) {
-            var prodList = Array.isArray(prodRes.data) ? prodRes.data : (prodRes.data.records || [])
-            this.dimensionProducts = prodList.slice(0, 4)
-          }
-        } catch (e) {
-          console.log('获取商品数据失败', e)
-        }
-      } catch (e) {
-        console.error('加载数据失败', e)
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      } finally {
-        this.loading = false
-        this.$emit('ready')
-      }
-    },
-    unlockBadge(id) {
-      var badge = this.badges.find(function(b) { return b.id === id })
-      if (badge) badge.unlocked = true
-    },
-
-    // ======================== 导航方法 ========================
-    onDomainClick(evt) {
-      var code = evt && evt.code
-      if (!code || !this.memberId) return
-      uni.navigateTo({
-        url: '/pages/energy/detail?memberId=' + this.memberId + '&code=' + code
-      })
-    },
-    /** 切换沙盘个人/全家视角(本地切换,不重新请求) */
-    onSandboxViewChange: function(view) {
-      if (view === this.currentSandboxView) return
-      this.currentSandboxView = view
-      this.sandboxData = view === 'family' ? (this.sandboxFamilyData || null) : (this.sandboxPersonalData || null)
-    },
-    goToRewards() {
-      this.$emit('navigate', { page: 'rewards' })
-      uni.navigateTo({ url: '/pages/rewards/rewards' })
-    },
-    goToTasks() {
-      this.$emit('navigate', { page: 'tasks' })
-      uni.navigateTo({ url: '/pages/tasks/tasks' })
-    },
-    goToGrowth() {
-      this.$emit('navigate', { page: 'growth' })
-      uni.switchTab({ url: '/pages/growth-main/index' })
-    },
-    goToGames() {
-      this.$emit('navigate', { page: 'games' })
-      uni.navigateTo({ url: '/pages/games/games' })
-    },
-    goToSelfQuiz() {
-      this.$emit('navigate', { page: 'self-quiz' })
-      uni.navigateTo({ url: '/pages/wisdom-detail/self-quiz' })
-    },
-    goToAddChild() {
-      this.$emit('navigate', { page: 'add-child' })
-      uni.navigateTo({ url: '/pages/profile-extra/family-members' })
-    },
-    showStreakDetail() {
-      this.$emit('navigate', { page: 'streak-detail' })
-      uni.showToast({ title: '连续打卡 ' + this.streakDays + ' 天啦!太棒了!', icon: 'none' })
-    },
-    goToEnergyDetail() {
-      if (this.memberId) {
-        uni.navigateTo({ url: '/pages/energy/detail?memberId=' + this.memberId + '&code=all' })
-      }
-    },
-    showAchievements() {
-      uni.navigateTo({ url: '/pages/achievement/index' })
-    },
-    switchMode() {
-      uni.showModal({
-        title: '切换模式',
-        content: '确定要切换回家长模式吗?',
-        success: function(res) {
-          if (res.confirm) {
-            uni.switchTab({ url: '/pages/index-home/index' })
-          }
-        }
-      })
-    },
-    _checkOnboardingShow() {
-      var self = this
-      getGuideConfig().then(function(res) {
-        var cfg = (res && res.data) ? res.data : null
-        var cfgVersion = cfg ? (cfg.version || '1.0') : '1.0'
-        // 检查启用
-        if (cfg && cfg.enabled === false) return
-        // 检查位置
-        if (cfg && cfg.positions && cfg.positions.indexOf('child') === -1) return
-        // 比较版本
-        var stored = uni.getStorageSync('_onboardingDone')
-        var seenVersion = '0.0'
-        if (stored) {
-          try {
-            var obj = typeof stored === 'string' ? JSON.parse(stored) : stored
-            if (obj && obj.version) seenVersion = obj.version
-          } catch (e) {}
-        }
-        if (self._compareVersion(seenVersion, cfgVersion) >= 0) return
-        self.showOnboarding = true
-      }).catch(function() {
-        // 接口失败按旧逻辑:无配置则 version='1.0',已看过则不显示
-        var stored = uni.getStorageSync('_onboardingDone')
-        if (stored) {
-          try {
-            var obj = typeof stored === 'string' ? JSON.parse(stored) : stored
-            if (obj && obj.version && self._compareVersion(obj.version, '1.0') >= 0) return
-          } catch (e) {}
-        }
-        self.showOnboarding = true
-      })
-    },
-    loadGuidePopup() {
-      var self = this
-      if (self._guidePopupAttempted) return
-      self._guidePopupAttempted = true
-      getGuidePopupList().then(function(res) {
-        var arr = (res && res.data) || []
-        if (arr && arr.length > 0) {
-          self.guidePopup = arr[0]
-          self.showGuidePopup = true
-        }
-      }).catch(function() {})
-    },
-    handleGuidePopupDone(payload) {
-      var popup = payload && payload.popup
-      var buttonClicked = payload && payload.buttonClicked
-      if (popup && popup.id) {
-        consumeGuidePopup(popup.id, buttonClicked)
-      }
-      this.showGuidePopup = false
-      if (buttonClicked && payload && payload.btnAction) {
-        uni.navigateTo({ url: payload.btnAction })
-      }
-    },
-    _compareVersion(v1, v2) {
-      var parts1 = String(v1).split('.').map(function(s) { return parseInt(s || '0', 10) })
-      var parts2 = String(v2).split('.').map(function(s) { return parseInt(s || '0', 10) })
-      for (var i = 0; i < Math.max(parts1.length, parts2.length); i++) {
-        if ((parts1[i] || 0) !== (parts2[i] || 0)) return (parts1[i] || 0) - (parts2[i] || 0)
-      }
-      return 0
-    },
-    goToDomain(domain) {
-      uni.showToast({ title: '即将上线', icon: 'none' })
-    },
-    goToEnergyDetail(code) {
-      // 跳转到能量详情页(T10 创建)
-      var memberId = this.memberId
-      if (!memberId || !code) return
-      uni.navigateTo({
-        url: '/pages/energy/detail?memberId=' + memberId + '&code=' + code
-      })
-    },
-
-    // ======================== 推荐信息流 ========================
-    onFeedItemClick: function(item) {
-      if (item.type === 'product') {
-        uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + item.id })
-      } else if (item.type === 'activity') {
-        uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + item.id })
-      }
-    },
-    goMoreDiscover: function() {
-      uni.navigateTo({ url: '/pages/discover/index' })
-    },
-
-    // ======================== 任务交互 ========================
-    handleTaskClick(task) {
-      if (!task || !task.id) return
-      this.$emit('navigate', { page: 'task-detail', taskId: task.id })
-
-      // 如果任务尚未完成,弹出确认完成
-      if (task.status !== 'completed' && task.status !== 'overdue') {
-        uni.showModal({
-          title: '完成任务',
-          content: '确定完成「' + task.title + '」吗?',
-          success: function(res) {
-            if (res.confirm) {
-              task.status = 'completed'
-              // 重新计算完成数
-              this.completedCount = this.todayTasks.filter(function(t) { return t.status === 'completed' }).length
-              if (task.points) {
-                uni.showToast({ title: '🎉 获得 ' + task.points + ' 颗星星!', icon: 'success' })
-              }
-            }
-          }.bind(this)
-        })
-      }
-    },
-
-    // ======================== 任务工具方法 ========================
-    getTaskIcon(task) {
-      if (!task) return '📝'
-      var cat = task.category || '其他'
-      return TASK_ICONS[cat] || '📝'
-    },
-    getTaskCategoryClass(task) {
-      if (!task) return 'default'
-      var cat = task.category || '其他'
-      var map = {
-        '学习类': 'study',
-        '阅读类': 'reading',
-        '生活类': 'life',
-        '运动类': 'sports',
-        '家务类': 'chores',
-        '艺术类': 'art'
-      }
-      return map[cat] || 'default'
-    },
-    getTaskStatus(task) {
-      if (!task || !task.status) return 'pending'
-      return task.status
-    },
-    getStatusText(task) {
-      var status = this.getTaskStatus(task)
-      if (status === 'completed') return '✅ 已完成'
-      if (status === 'overdue') return '⏰ 已超时'
-      return '⏳ 待完成'
-    },
-    getBadgeVariant(task) {
-      var status = this.getTaskStatus(task)
-      if (status === 'completed') return 'success'
-      if (status === 'overdue') return 'error'
-      return 'warning'
-    },
-    lockedKey(item) {
-      return 'locked-' + item.id
-    }
+  onLoad: function () {
+    redirectToNewPath('/pages/home-pages/member-index')
   }
 }
 </script>
-
-<style scoped lang="scss">
-// ===== 孩子首页专属色阶(天空蓝渐变族 + 徽章金;单页专用,约定见 uni.scss §14)=====
-$c-ci-sky:    #E0ECF8; // 顶部渐变终(浅天蓝)
-$c-ci-navy:   #1E4A7A; // 深海军蓝文字
-$c-ci-gold:   #FFB800; // 徽章金渐变终
-$c-ci-blue-1: #DCE8F5; // 空态卡渐变起
-$c-ci-blue-2: #C8DDF0; // 空态卡渐变终
-
-/* ============================================================
-   知心益家 — 孩子端首页 Flat Rounded 设计系统
-   色彩体系:品牌橙($brand-primary)+浅橙($brand-primary-light) | 双重阴影 | 4rpx边框
-   参考:uni.scss --color-primary, --bg, --shadow-clay, --radius-md
-   ============================================================ */
-
-/* ----------------------------------------------------------
-   容器 & 背景
-   ---------------------------------------------------------- */
-.container {
-  min-height: 100vh;
-  background: var(--bg, $uni-bg-color);
-  padding: 0 32rpx 48rpx;
-}
-
-.main-content {
-  padding-top: 16rpx;
-}
-
-.bottom-spacer {
-  height: 48rpx;
-}
-
-/* 连续打卡徽章 — 独立行 */
-.streak-badge-wrap {
-display: flex;
-align-items: center;
-gap: 10rpx;
-justify-content: flex-end;
-margin-bottom: 24rpx;
-padding-top: 4rpx;
-}
-
-.streak-badge {
-  display: flex;
-  align-items: center;
-  gap: 10rpx;
-  background: linear-gradient(145deg, $uni-bg-color, $c-ci-sky);
-  padding: 12rpx 24rpx 12rpx 18rpx;
-  border-radius: 40rpx;
-  border: 4rpx solid $brand-primary-light;
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
-              4rpx 4rpx 14rpx rgba(251,146,60,0.30);
-  flex-shrink: 0;
-  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-.streak-badge:active,
-.streak-pressed {
-  transform: scale(0.94);
-}
-
-.streak-fire {
-  font-size: calc(32rpx * var(--font-scale, 1));
-}
-
-.streak-info {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  line-height: 1.2;
-}
-
-.streak-days {
-  font-size: calc(32rpx * var(--font-scale, 1));
-  font-weight: 800;
-  color: $c-ci-navy;
-  line-height: 1.1;
-}
-
-.streak-unit {
-  font-size: calc(18rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: $status-info-deep;
-}
-
-/* ----------------------------------------------------------
-   积分卡片 — Flat Rounded 橙色渐变
-   ---------------------------------------------------------- */
-.points-card {
-  position: relative;
-  background: linear-gradient(145deg, $brand-primary-light, $brand-primary);
-  border-radius: 32rpx;
-  padding: 32rpx;
-  margin-bottom: 28rpx;
-  border: 4rpx solid $brand-primary-dark;
-  box-shadow: inset -4rpx -4rpx 14rpx rgba(255,255,255,0.20),
-              8rpx 8rpx 28rpx rgba(249,115,22,0.25);
-  overflow: hidden;
-  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-.points-card:active,
-.points-card-pressed {
-  transform: scale(0.97);
-  box-shadow: inset 2rpx 2rpx 10rpx rgba(0,0,0,0.08),
-              4rpx 4rpx 16rpx rgba(249,115,22,0.18);
-}
-
-/* 装饰圆 */
-.points-deco {
-  position: absolute;
-  border-radius: 50%;
-  background: rgba(255,255,255,0.08);
-  pointer-events: none;
-}
-
-.points-deco--1 {
-  top: -60rpx;
-  right: -40rpx;
-  width: 220rpx;
-  height: 220rpx;
-}
-
-.points-deco--2 {
-  bottom: -30rpx;
-  left: -30rpx;
-  width: 120rpx;
-  height: 120rpx;
-}
-
-.points-top {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-start;
-  margin-bottom: 20rpx;
-  position: relative;
-  z-index: 1;
-}
-
-.points-left {
-  display: flex;
-  flex-direction: column;
-}
-
-.points-label {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: rgba(255,255,255,0.85);
-  font-weight: 500;
-  margin-bottom: 4rpx;
-}
-
-.points-number {
-  font-size: calc(64rpx * var(--font-scale, 1));
-  font-weight: 800;
-  color: $app-surface;
-  line-height: 1.1;
-  text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.10);
-}
-
-.points-level-tag {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  background: rgba(255,255,255,0.15);
-  padding: 12rpx 20rpx;
-  border-radius: 24rpx;
-  border: 2rpx solid rgba(255,255,255,0.20);
-  min-width: 120rpx;
-}
-
-.level-emoji {
-  font-size: calc(36rpx * var(--font-scale, 1));
-  line-height: 1;
-}
-
-.level-name {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $accent-gold;
-  margin-top: 4rpx;
-}
-
-/* 进度条 */
-.points-progress {
-  position: relative;
-  z-index: 1;
-}
-
-.progress-hint {
-  display: block;
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: rgba(255,255,255,0.75);
-  margin-bottom: 12rpx;
-}
-
-.progress-hint-num {
-  font-weight: 700;
-  color: $accent-gold;
-  font-size: calc(24rpx * var(--font-scale, 1));
-}
-
-.progress-track {
-  height: 14rpx;
-  background: rgba(255,255,255,0.20);
-  border-radius: 7rpx;
-  overflow: hidden;
-  border: 2rpx solid rgba(255,255,255,0.10);
-}
-
-.progress-fill {
-  height: 100%;
-  background: linear-gradient(90deg, $accent-gold, $c-ci-gold);
-  border-radius: 6rpx;
-  transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
-}
-
-/* ----------------------------------------------------------
-   任务区域 — 水平滚动黏土卡片
-   ---------------------------------------------------------- */
-.tasks-section {
-  margin-bottom: 28rpx;
-}
-
-.section-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-
-.section-title {
-  font-size: calc(32rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: var(--text, $uni-text-color);
-}
-
-.section-link {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: var(--color-primary, $brand-primary);
-  padding: 8rpx 4rpx;
-}
-
-.section-link:active {
-  opacity: 0.6;
-}
-
-/* 任务水平滚动容器 */
-.tasks-scroll {
-  display: flex;
-  flex-direction: row;
-  white-space: nowrap;
-  overflow-x: auto;
-  padding: 8rpx 0 16rpx;
-  -webkit-overflow-scrolling: touch;
-}
-
-/* 单张任务黏土卡片 */
-.task-clay-card {
-  display: inline-flex;
-  flex-direction: column;
-  align-items: center;
-  width: 200rpx;
-  min-height: 260rpx;
-  background: linear-gradient(145deg, $app-surface, var(--bg-grey, $uni-bg-color-grey));
-  border-radius: 28rpx;
-  padding: 24rpx 16rpx 20rpx;
-  margin-right: 20rpx;
-  border: 4rpx solid var(--border, $uni-border-color);
-  box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
-              6rpx 6rpx 20rpx rgba(249,115,22,0.08);
-  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
-  animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
-  flex-shrink: 0;
-}
-
-.task-clay-card:active,
-.task-card-pressed {
-  transform: scale(0.94);
-}
-
-/* 任务分类图标容器 */
-.task-card-icon-wrap {
-  width: 88rpx;
-  height: 88rpx;
-  border-radius: 24rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 12rpx;
-  border: 3rpx solid var(--border-light, $uni-border-color-light);
-  box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
-              3rpx 3rpx 8rpx rgba(249,115,22,0.06);
-}
-
-.task-card-icon {
-  font-size: 42rpx;
-  line-height: 1;
-}
-
-/* 分类图标底色 */
-.task-cat--study   .task-card-icon-wrap { background: rgba(14,165,233,0.12); border-color: $tint-sky-line; }
-.task-cat--reading .task-card-icon-wrap { background: rgba(34,197,94,0.12);  border-color: $tint-green-line; }
-.task-cat--life    .task-card-icon-wrap { background: rgba(2,132,199,0.12); border-color: $uni-border-color; }
-.task-cat--sports  .task-card-icon-wrap { background: rgba(59,130,246,0.12); border-color: $tint-blue-line; }
-.task-cat--chores  .task-card-icon-wrap { background: rgba(245,158,11,0.12); border-color: $status-warning-light; }
-.task-cat--art     .task-card-icon-wrap { background: rgba(236,72,153,0.12); border-color: $tint-pink-line; }
-.task-cat--default .task-card-icon-wrap { background: rgba(148,163,184,0.12); border-color: $uni-border-color-light; }
-
-.task-card-name {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: var(--text, $uni-text-color);
-  text-align: center;
-  line-height: 1.3;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-  max-width: 100%;
-  margin-bottom: 6rpx;
-}
-
-.task-card-points {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: var(--color-primary, $brand-primary);
-  margin-bottom: 10rpx;
-}
-
-.task-card-badge {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  min-height: 36rpx;
-}
-
-/* 任务空状态 */
-.tasks-empty {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  padding: 48rpx 32rpx;
-  background: var(--surface, $app-surface);
-  border-radius: 28rpx;
-  border: 4rpx solid var(--border, $uni-border-color);
-  box-shadow: var(--shadow-clay);
-  transition: transform 0.2s ease;
-}
-
-.tasks-empty:active {
-  transform: scale(0.98);
-}
-
-.tasks-empty-icon {
-  font-size: 64rpx;
-  margin-bottom: 16rpx;
-}
-
-.tasks-empty-title {
-  font-size: calc(30rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: var(--text, $uni-text-color);
-  margin-bottom: 8rpx;
-}
-
-.tasks-empty-desc {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: var(--text-secondary, $uni-text-color-secondary);
-}
-
-/* ----------------------------------------------------------
-   快捷操作 — 2×2 黏土按钮网格
-   ---------------------------------------------------------- */
-.quick-section {
-  margin-bottom: 28rpx;
-}
-
-.quick-grid {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20rpx;
-}
-.quick-grid .quick-clay-btn {
-  width: calc(50% - 10rpx);
-}
-
-.quick-clay-btn {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  padding: 28rpx 16rpx;
-  background: linear-gradient(145deg, $app-surface, var(--bg-grey, $uni-bg-color-grey));
-  border-radius: 28rpx;
-  border: 4rpx solid var(--border, $uni-border-color);
-  box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
-              6rpx 6rpx 20rpx rgba(249,115,22,0.08);
-  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
-  min-height: 180rpx;
-}
-
-.quick-clay-btn:active,
-.quick-btn-pressed {
-  transform: scale(0.94);
-  box-shadow: inset 2rpx 2rpx 8rpx rgba(0,0,0,0.06),
-              3rpx 3rpx 10rpx rgba(249,115,22,0.06);
-}
-
-.quick-btn-icon {
-  width: 96rpx;
-  height: 96rpx;
-  border-radius: 28rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 14rpx;
-  border: 3rpx solid var(--border-light, $uni-border-color-light);
-  box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
-              3rpx 3rpx 8rpx rgba(249,115,22,0.06);
-  transition: transform 0.2s ease;
-}
-
-.quick-emoji {
-  font-size: calc(44rpx * var(--font-scale, 1));
-  line-height: 1;
-}
-
-/* 各按钮图标色彩 */
-.quick-icon--tasks   { background: rgba(14,165,233,0.15); border-color: $tint-sky-line; }
-.quick-icon--rewards { background: rgba(245,158,11,0.15); border-color: $status-warning-light; }
-.quick-icon--growth  { background: rgba(2,132,199,0.15); border-color: $uni-border-color; }
-.quick-icon--games   { background: rgba(236,72,153,0.12); border-color: $tint-pink-line; }
-.quick-icon--quiz    { background: rgba(99,102,241,0.15); border-color: $tint-indigo-line; }
-
-.quick-btn-label {
-  font-size: 28rpx;
-  font-weight: 700;
-  color: var(--text, $uni-text-color);
-}
-
-/* ----------------------------------------------------------
-   成就区域 — 水平滚动徽章
-   ---------------------------------------------------------- */
-.achievement-section {
-  margin-bottom: 28rpx;
-}
-
-.achievement-scroll {
-  display: flex;
-  flex-direction: row;
-  white-space: nowrap;
-  overflow-x: auto;
-  padding: 8rpx 0 8rpx;
-  -webkit-overflow-scrolling: touch;
-}
-
-.achievement-item {
-  display: inline-flex;
-  flex-direction: column;
-  align-items: center;
-  width: 160rpx;
-  margin-right: 20rpx;
-  transition: transform 0.2s ease;
-  flex-shrink: 0;
-}
-
-.achievement-item:active,
-.achievement-pressed {
-  transform: scale(0.92);
-}
-
-.achievement-icon-wrap {
-  width: 100rpx;
-  height: 100rpx;
-  border-radius: 50%;
-  background: linear-gradient(145deg, $c-ci-blue-1, $c-ci-blue-2);
-  border: 4rpx solid var(--border, $uni-border-color);
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
-              4rpx 4rpx 14rpx rgba(249,115,22,0.08);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 10rpx;
-}
-
-.achievement-icon {
-  font-size: 44rpx;
-  line-height: 1;
-}
-
-.achievement-name {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: var(--text, $uni-text-color);
-  text-align: center;
-}
-
-/* 锁定徽章 */
-.achievement-item--locked {
-  opacity: 0.50;
-}
-
-.achievement-icon-wrap--locked {
-  background: var(--bg-grey, $uni-bg-color-grey);
-  border-color: var(--border-light, $uni-border-color-light);
-  box-shadow: none;
-}
-
-.achievement-icon--locked {
-  filter: grayscale(1);
-}
-
-.achievement-name--locked {
-  color: var(--muted, $app-text-subtle);
-}
-
-/* ----------------------------------------------------------
-   加载 & 通用
-   ---------------------------------------------------------- */
-.loading-state {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 200rpx 0;
-}
-
-/* ----------------------------------------------------------
-   能量变动日志
-   ---------------------------------------------------------- */
-.energy-log-section { margin-bottom: 28rpx; }
-.energy-log-list { background: $app-surface; 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 $facade-fill;
-}
-.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: calc(26rpx * var(--font-scale, 1)); color: $facade-text-strong; display: block; }
-.log-time { font-size: calc(20rpx * var(--font-scale, 1)); color: $facade-text-muted; margin-top: 4rpx; display: block; }
-.log-amount { font-size: calc(28rpx * var(--font-scale, 1)); font-weight: 700; margin-left: 12rpx; }
-.log-amount--up { color: $status-success; }
-.log-amount--down { color: $status-danger; }
-
-/* 连续打卡徽章的浮动动画 — 使用 uni.scss 全局 float keyframe */
-.streak-badge {
-  animation: float 3s ease-in-out infinite;
-}
-
-/* 等级标签的金色光晕 — 使用 uni.scss 全局 pulseGlow keyframe */
-.points-level-tag {
-  animation: pulseGlow 2.5s ease-in-out infinite;
-}
-</style>

+ 2294 - 0
cfc-frontend/pages/home-pages/family-index.vue

@@ -0,0 +1,2294 @@
+<template>
+<view class="container" :style="{ '--font-scale': fontScale }">
+<!-- 加载状态 -->
+<BaseLoading :loading="loading" text="正在加载..." />
+
+<template v-if="!loading">
+<!-- ===== a) 顶部 Banner ===== -->
+<!-- ===== 新手任务进度条(onboarding 置顶引导) ===== -->
+<OnboardingProgressBar ref="onboardingProgress" />
+
+<!-- ===== 家庭成员选择条 ===== -->
+<MemberSelector
+  variant="strip"
+  v-if="isLoggedIn"
+  :members="familyMembersVisible"
+  :selectedMemberId="selectedMemberId"
+  :isParent="isFamilyAdmin"
+  :pendingCounts="memberPendingCounts"
+  @select="onFamilyMemberSelect"
+  @switch="onFamilyMemberSwitch"
+  @removed="loadFamilyMembersVisible" />
+
+<!-- ===== 无家庭成员空状态引导卡 ===== -->
+<view class="empty-family-card" v-if="isLoggedIn && familyMembersVisible.length === 0">
+  <view class="empty-family-icon">🏠</view>
+  <text class="empty-family-title">创建我的家庭</text>
+  <text class="empty-family-desc">添加家庭成员,开启五维能量之旅</text>
+  <button class="empty-family-btn" @click="goAddMember">立即添加</button>
+</view>
+
+<!-- ===== 微行动卡片(P0:有效启动) ===== -->
+<MicroActionCard
+  v-if="selectedMemberId && selectedChild && selectedChild.id"
+  :memberId="selectedChild.id"
+  @completed="onMicroActionCompleted" />
+
+<!-- ===== 健康预警横幅(P1-2) ===== -->
+<HealthAlertBanner
+  v-if="selectedMemberId && selectedChild && selectedChild.id"
+  :alerts="healthAlerts"
+  @dismiss="onAlertDismiss" />
+
+      <!-- ===== 家庭健康排行榜(P2-2) ===== -->
+        <FamilyHealthRanking
+          v-if="rankingMembers && rankingMembers.length > 0"
+          :list="rankingMembers"
+          :totalScore="rankingTotalScore" />
+
+      <!-- ===== 家庭挑战区域 ===== -->
+      <view class="challenge-section">
+        <view class="challenge-section-header" @click="goToChallengeManage">
+          <text class="challenge-section-title">家庭挑战</text>
+          <text class="challenge-section-action">管理 →</text>
+        </view>
+        <FamilyChallengeCard
+          v-for="ch in activeChallenges"
+          :key="ch.id"
+          :challenge="ch"
+          @cardClick="goToChallengeManage(ch.id)" />
+      </view>
+
+    <!-- ===== 健康圈入口(P3:社区扩散) ===== -->
+    <view class="circle-entry-section">
+      <!-- 有健康圈数据:显示排行榜预览 -->
+      <PlayfulCard v-if="circleName && circleRankings.length > 0" variant="flat" shadow="sm" :clickable="true" @click="goToHealthCircle" class="circle-card-wide">
+        <view class="circle-card-content">
+          <view class="circle-header">
+            <view class="circle-icon-wrap">
+              <text class="circle-icon">🏅</text>
+            </view>
+            <view class="circle-header-body">
+              <text class="circle-card-title">健康圈</text>
+              <text class="circle-card-subtitle">{{ circleName }}</text>
+            </view>
+          </view>
+          <view class="circle-ranking-preview">
+            <view class="circle-rank-row" v-for="(item, idx) in circleRankings.slice(0, 3)" :key="idx">
+              <text class="circle-rank-badge">{{ rankIcon(idx) }}</text>
+              <text class="circle-rank-family">家庭 {{ idx + 1 }}</text>
+              <text class="circle-rank-score">{{ item.totalScore || 0 }} 分</text>
+            </view>
+          </view>
+          <view class="circle-action">
+            <text class="circle-action-text">查看详情 →</text>
+          </view>
+        </view>
+      </PlayfulCard>
+      <!-- 无健康圈数据:邀请加入 -->
+      <PlayfulCard v-else variant="flat" shadow="sm" :clickable="true" @click="goToHealthCircle" class="circle-card-wide circle-card-invite">
+        <view class="circle-invite-content">
+          <view class="circle-invite-icon">🏅</view>
+          <text class="circle-invite-title">健康圈</text>
+          <text class="circle-invite-desc">加入健康圈,和更多家庭一起健康打卡</text>
+          <text class="circle-invite-cta">去看看 →</text>
+        </view>
+      </PlayfulCard>
+    </view>
+
+    <!-- ===== 五维家庭自检入口(P0-1/P0-2) ===== -->
+    <view class="selfcheck-entry-section">
+      <PlayfulCard variant="flat" shadow="sm" :clickable="true" @click="goToSelfCheck" class="selfcheck-card-wide">
+        <view class="selfcheck-card-content">
+          <view class="selfcheck-icon-wrap">
+            <view class="selfcheck-icon-circle selfcheck-icon-body">
+              <text class="selfcheck-icon-char">身</text>
+            </view>
+            <view class="selfcheck-icon-circle selfcheck-icon-wisdom">
+              <text class="selfcheck-icon-char">智</text>
+            </view>
+            <view class="selfcheck-icon-circle selfcheck-icon-wealth">
+              <text class="selfcheck-icon-char">富</text>
+            </view>
+            <view class="selfcheck-icon-circle selfcheck-icon-action">
+              <text class="selfcheck-icon-char">行</text>
+            </view>
+            <view class="selfcheck-icon-circle selfcheck-icon-mind">
+              <text class="selfcheck-icon-char">心</text>
+            </view>
+          </view>
+          <view class="selfcheck-body">
+            <text class="selfcheck-title">健康自检问卷</text>
+            <text class="selfcheck-subtitle" v-if="selfCheckScore != null">
+              已完成健康自检 · 点击再次填写
+            </text>
+            <text class="selfcheck-subtitle" v-else>
+              4步问卷 · 完成自检解锁能量沙盘
+            </text>
+          </view>
+          <text class="selfcheck-arrow">→</text>
+        </view>
+      </PlayfulCard>
+    </view>
+
+    <!-- ===== 家庭成员关系图谱 ===== -->
+<FamilyRelationGraph
+  v-if="familyMembersVisible.length > 0"
+  dimensionCode="home"
+  :selfId="selfMemberId"
+  :members="familyMembersVisible"
+  :energyMap="energyMapForGraph"
+  :intimacyMap="intimacyMapForGraph"
+  @memberTap="goMemberDetail" />
+
+      <!-- 通关引导进度条 -->
+      <GateGuideBar :unlockStatus="unlockStatus" />
+
+      <!-- ===== 新增:首页广告栏 ===== -->
+      <AdBanner
+        :activities="recommendedActivities"
+        :products="recommendedProducts"
+        :autoPlayInterval="3000"
+        :swiperDuration="500" />
+      <!-- ===== 能量沙盘 ===== -->
+      <GateLock v-if="sandboxLocked && sandboxLockGate" :gate="sandboxLockGate" />
+      <WuxingSandbox
+        v-else-if="sandboxData"
+        mode="live"
+        :scores="familySandboxScores"
+        :overallScore="familySandboxOverall"
+        :members="familySandboxMembers"
+        :personalData="sandboxPersonalData"
+        :dimensions="energyDimensions"
+        :totalEnergy="totalEnergy"
+        :totalHealthIndex="totalHealthIndex"
+        :familyUnlocked="familyUnlocked"
+        :currentView="currentSandboxView"
+        :memberCount="familyMembersVisible.length"
+        starHeight="400rpx"
+        showDimensionBar
+        @view-change="onSandboxViewChange"
+        @point-click="onDomainClick" />
+
+      <!-- 能量数据加载失败兜底 -->
+      <view class="error-state" v-if="errorEnergy && !sandboxData && !sandboxLocked" @click="loadData">
+        <text class="error-state-text">家庭能量数据加载失败,点击重试</text>
+      </view>
+
+      <!-- ===== c) 成长数据 ===== -->
+      <view class="stats-section animate-fade-in animate-stagger-5">
+        <view class="section-title">成长数据</view>
+        <view class="stats-row">
+          <!-- 任务完成率:自己的 + 全家平均 -->
+          <PlayfulCard variant="flat" shadow="sm" :clickable="true" @click="goToCompletionStats" class="stat-card-wide" padding="0">
+            <view class="stat-card-content">
+              <view class="stat-header">
+                <view class="stat-icon-wrap stat-icon-orange">
+                  <text class="stat-icon-symbol">📋</text>
+                </view>
+                <view class="stat-body">
+                  <text class="stat-label-header">任务完成</text>
+                </view>
+              </view>
+              <view class="stat-compare">
+                <view class="stat-compare-row">
+                  <text class="compare-label">自己的</text>
+                  <text class="compare-value accent-color">{{ myCompletionRate }}%</text>
+                </view>
+                <view class="progress-bar-sm">
+                  <view class="progress-fill-sm" :style="{ width: myCompletionRate + '%' }"></view>
+                </view>
+                <view class="stat-compare-row">
+                  <text class="compare-label">全家平均</text>
+                  <text class="compare-value">{{ familyAvgCompletionRate }}%</text>
+                </view>
+                <view class="progress-bar-sm">
+                  <view class="progress-fill-sm muted" :style="{ width: familyAvgCompletionRate + '%' }"></view>
+                </view>
+              </view>
+              <text class="stat-more">查看分析 ›</text>
+            </view>
+          </PlayfulCard>
+
+          <!-- 成长积分:自己的 + 全家共 -->
+          <PlayfulCard variant="flat" shadow="sm" :clickable="true" @click="goToRewards" class="stat-card-wide" padding="0">
+            <view class="stat-card-content">
+              <view class="stat-header">
+                <view class="stat-icon-wrap stat-icon-gold">
+                  <text class="stat-icon-symbol">⭐</text>
+                </view>
+                <view class="stat-body">
+                  <text class="stat-label-header">成长积分</text>
+                </view>
+              </view>
+              <view class="stat-compare">
+                <view class="stat-compare-row">
+                  <text class="compare-label">自己的</text>
+                  <text class="compare-value gold-color">{{ myPoints }}</text>
+                </view>
+                <view class="stat-compare-row">
+                  <text class="compare-label">全家共</text>
+                  <text class="compare-value">{{ familyTotalPoints }}</text>
+                </view>
+              </view>
+              <text class="stat-encourage" v-if="myPoints > 0">太棒了!</text>
+              <text class="stat-encourage muted" v-else>开始赚星星吧</text>
+            </view>
+          </PlayfulCard>
+        </view>
+      </view>
+
+      <!-- ===== d) 今日重点 ===== -->
+      <view class="today-section animate-fade-in animate-stagger-7">
+        <view class="section-title">📋 今日任务</view>
+        <PlayfulCard variant="flat" class="today-card">
+          <template slot="header">
+            <view class="today-header">
+              <text class="today-label">今日待完成</text>
+              <text class="today-count">{{ pendingTasks.length }}项任务</text>
+            </view>
+          </template>
+          <view class="task-list" v-if="pendingTasks.length > 0">
+            <view class="task-item" v-for="task in pendingTasks.slice(0, 3)" :key="task.id">
+              <view class="task-dot" :class="'dot-color-' + (task.id % 5)"></view>
+              <text class="task-title">{{ task.title }}</text>
+            </view>
+          </view>
+          <BaseEmpty v-else icon="🎉" title="今日任务已完成" description="太棒了!" :compact="true" />
+          <view slot="actions" class="task-view-all" @click="goToTaskList">
+            <text>查看全部</text>
+            <text class="arrow">→</text>
+          </view>
+        </PlayfulCard>
+      </view>
+
+      <!-- ===== e) 成长里程碑 ===== -->
+      <view class="milestone-section animate-fade-in animate-stagger-8">
+        <view class="section-title">成长里程碑</view>
+        <PlayfulCard variant="flat" class="milestone-card">
+          <view class="milestone-item" v-if="selectedChild && selectedChild.streakDays >= 7">
+            <view class="milestone-icon-wrap milestone-star">
+              <text class="milestone-icon">🌟</text>
+            </view>
+            <text class="milestone-text">连续7天打卡</text>
+          </view>
+          <view class="milestone-item" v-if="selectedChild && selectedChild.streakDays >= 30">
+            <view class="milestone-icon-wrap milestone-trophy">
+              <text class="milestone-icon">🏆</text>
+            </view>
+            <text class="milestone-text">坚持一个月啦!</text>
+          </view>
+          <view class="milestone-item" v-if="(selectedChild && selectedChild.totalPoints) >= 1000">
+            <view class="milestone-icon-wrap milestone-sparkle">
+              <text class="milestone-icon">💫</text>
+            </view>
+            <text class="milestone-text">积分突破1000</text>
+          </view>
+          <BaseEmpty v-if="!hasMilestones" icon="🌱" title="开始养成好习惯" description="见证成长" :compact="true" />
+        </PlayfulCard>
+      </view>
+
+      <!-- ===== f) 待处理事项 ===== -->
+      <view class="pending-section animate-fade-in animate-stagger-9">
+        <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>
+
+      <!-- ===== 重要关系(外部通讯录) ===== -->
+      <view class="contact-section" v-if="!loading && familyMembersVisible.length > 0">
+        <view class="contact-header">
+          <text class="contact-section-title">重要关系</text>
+          <view class="contact-header-right">
+            <text class="contact-more" @click="loadContacts">刷新</text>
+            <text class="contact-import-btn" @click="onShowImport">+ 导入</text>
+          </view>
+        </view>
+        <scroll-view class="contact-scroll" scroll-x enable-flex>
+          <view class="contact-scroll-inner">
+            <view class="contact-card-wrap" v-for="item in contactList" :key="item.id" @click="onContactClick(item)">
+              <ContactCard :contact="item" />
+            </view>
+            <view class="contact-card-wrap contact-add-card" @click="onShowImport">
+              <view class="add-card-inner">
+                <text class="add-card-icon">+</text>
+                <text class="add-card-label">导入通讯录</text>
+              </view>
+            </view>
+          </view>
+        </scroll-view>
+        <view class="contact-empty" v-if="contactList.length === 0">
+          <text class="contact-empty-text">暂无重要联系人,点击导入添加</text>
+        </view>
+      </view>
+
+      <!-- 导入联系人弹窗 -->
+      <ContactImport
+        :visible="showImportModal"
+        @close="onCloseImport"
+        @success="onImportSuccess" />
+      
+      <!-- 邀请码二维码弹窗 -->
+      <view class="qr-code-modal" v-if="showQrCodeModal" @touchmove.stop.prevent @click="onCloseQrCodeModal">
+        <view class="qr-code-modal-content" @click.stop>
+          <view class="qr-code-modal-header">
+            <text class="qr-code-modal-title">邀请二维码</text>
+            <text class="qr-code-modal-close" @click="onCloseQrCodeModal">×</text>
+          </view>
+          <view class="qr-code-modal-body">
+            <view class="qr-code-image-container">
+              <image :src="(qrCodeData && qrCodeData.qrCodeBase64) || ''" class="qr-code-image" mode="aspectFit" />
+            </view>
+            <view class="qr-code-referral-code">
+              <text class="qr-code-label">邀请码:</text>
+              <text class="qr-code-code">{{ (qrCodeData && qrCodeData.referralCode) || '' }}</text>
+              <text class="qr-code-copy" @click="onCopyReferralCode">复制</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- ===== 推荐商品+活动(混合信息流) ===== -->
+      <RecommendedFeed
+        :products="recommendedProducts"
+        :activities="recommendedActivities"
+        :loading="loadingProducts || loadingActivities"
+        @itemClick="onFeedItemClick"
+        @more="goMoreDiscover" />
+      <view class="error-state" v-if="errorProducts || errorActivities">
+        <text class="error-state-text">推荐加载失败</text>
+      </view>
+
+      <!-- ===== 推荐阅读(书册风格) ===== -->
+      <view class="article-section animate-fade-in animate-stagger-11">
+        <view class="article-header">
+          <view class="section-title">📖 推荐阅读</view>
+          <text class="article-more" @click="goArticles">更多 ›</text>
+        </view>
+        <ArticleBookshelf
+          :articles="indexArticles"
+          :loading="articlesLoading"
+          :isLoggedIn="isLoggedIn"
+          @articleClick="goArticleDetail"
+        />
+      </view>
+
+      <!-- ===== h) 待审核任务 ===== -->
+      <view class="review-section animate-fade-in animate-stagger-11" v-if="pendingReviewTasks.length > 0">
+        <view class="section-title">待审核任务</view>
+        <PlayfulCard
+          variant="flat"
+          shadow="sm"
+          class="review-card"
+          v-for="task in pendingReviewTasks"
+          :key="task.id"
+          padding="0"
+        >
+          <view class="review-card-content">
+            <view class="review-info">
+              <text class="task-name">{{ task.title }}</text>
+              <text class="task-child" v-if="task.childName">提供者: {{ task.childName }}</text>
+            </view>
+            <view class="review-actions">
+              <PlayfulButton variant="success" size="xs" @click="approveTask(task.id)">通过</PlayfulButton>
+              <PlayfulButton variant="error" size="xs" @click="rejectTask(task.id)">拒绝</PlayfulButton>
+            </view>
+          </view>
+        </PlayfulCard>
+      </view>
+
+      <!-- ===== g) 快捷操作 ===== -->
+      <view class="quick-section animate-fade-in animate-stagger-10">
+        <view class="section-title">快捷操作</view>
+        <PlayfulCard variant="flat" class="quick-card">
+          <view class="quick-actions">
+            <view class="action-item" @click="addTask">
+              <view class="action-icon act-add">
+                <text class="action-icon-text">➕</text>
+              </view>
+              <text class="action-label">添加任务</text>
+            </view>
+            <view class="action-item" @click="goToReview">
+              <view class="action-icon act-review">
+                <text class="action-icon-text">📋</text>
+              </view>
+              <text class="action-label">审核任务</text>
+            </view>
+            <view class="action-item" @click="manageChildren">
+              <view class="action-icon act-child">
+                <text class="action-icon-text">👨‍👩‍👧</text>
+              </view>
+              <text class="action-label">成员管理</text>
+            </view>
+            <view class="action-item" @click="goToRewards">
+              <view class="action-icon act-reward">
+                <text class="action-icon-text">🎁</text>
+              </view>
+              <text class="action-label">奖励中心</text>
+            </view>
+            <!-- Added: QR Code button -->
+            <view class="action-item" @click="onShowQrCodeModal">
+              <view class="action-icon act-invite">
+                <text class="action-icon-text">📱</text>
+              </view>
+              <text class="action-label">邀请码</text>
+            </view>
+          </view>
+        </PlayfulCard>
+      </view>
+    </template>
+    <!-- ===== L1 即时反馈:Toast + 庆祝 ===== -->
+    <FeedbackToast
+      :visible="toastVisible"
+      :text="toastText"
+      :icon="toastIcon"
+      :animating="toastAnimating" />
+    <MilestoneCelebration
+      :visible="celebrationVisible"
+      :title="celebrationTitle"
+      :subtitle="celebrationSubtitle"
+      :particles="celebrationParticles"
+      @dismiss="celebrationVisible = false" />
+    <!-- ===== L2 深度反馈:每日健康小语 ===== -->
+    <DailySummary
+      v-if="selectedMemberId && selectedChild && selectedChild.id"
+      :memberId="selectedChild.id"
+      @dismiss="onDailySummaryDismiss" />
+  </view>
+</template>
+
+<script>
+import { getFamilyMemberList, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getEnergyOverview, getActivityList, getProductsByDomain, getContactList, getFeaturedArticles, getActiveAlerts, dismissAlert, getFamilyRanking, getChallengeList, getCircleList, getCircleRanking, getInviteQrCode, switchToFamilyMember, getSelfCheckStatus, getTodayTasks } from '../../utils/api.js'
+import store from '../../store/index.js'
+import { BrandColors } from '../../utils/brand-colors.js'
+import PlayfulCard from '../../components/PlayfulCard.vue'
+import PlayfulButton from '../../components/PlayfulButton.vue'
+import BaseBadge from '../../components/BaseBadge.vue'
+import BaseEmpty from '../../components/BaseEmpty.vue'
+import BaseLoading from '../../components/BaseLoading.vue'
+import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
+import ContactCard from '@/pages/home-pages/components/ContactCard.vue'
+import FamilyHealthRanking from '@/pages/home-pages/components/FamilyHealthRanking.vue'
+import FamilyChallengeCard from '../../components/FamilyChallengeCard.vue'
+import ContactImport from '@/pages/home-pages/components/ContactImport.vue'
+import RecommendedFeed from '../../components/RecommendedFeed.vue'
+import MemberSelector from '@/components/MemberSelector.vue'
+import MicroActionCard from '@/pages/home-pages/components/MicroActionCard.vue'
+import FeedbackToast from '@/pages/home-pages/components/FeedbackToast.vue'
+import MilestoneCelebration from '@/pages/home-pages/components/MilestoneCelebration.vue'
+import DailySummary from '@/pages/home-pages/components/DailySummary.vue'
+import HealthAlertBanner from '@/pages/home-pages/components/HealthAlertBanner.vue'
+import CircleRanking from '@/pages/home-pages/components/CircleRanking.vue'
+import AdBanner from '@/pages/home-pages/components/AdBanner.vue'
+import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
+import OnboardingProgressBar from '../../components/OnboardingProgressBar.vue'
+import GateLock from '../../components/gate-lock.vue'
+import GateGuideBar from '@/pages/home-pages/components/gate-guide-bar.vue'
+import feedbackEngine from '../../utils/feedback-engine.js'
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  components: { RecommendedFeed, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading, FamilyRelationGraph, WuxingSandbox, ContactCard, ContactImport, MemberSelector, MicroActionCard, FeedbackToast, MilestoneCelebration, DailySummary, HealthAlertBanner, FamilyHealthRanking, FamilyChallengeCard, CircleRanking, ArticleBookshelf, OnboardingProgressBar, GateLock, GateGuideBar, AdBanner },
+  data() {
+    return {
+      nickname: '',
+      familyMembers: [],
+      children: [],
+      selectedChild: null,
+      pendingReviewTasks: [],
+      pendingReviewWishes: [],
+      pendingWishesCount: 0,
+      pendingTasks: [],
+      memberPendingCounts: {},
+      hotActivityCount: 0,
+      hasNewReport: false,
+      myCompletionRate: 0,
+      familyAvgCompletionRate: 0,
+      myPoints: 0,
+      familyTotalPoints: 0,
+      // membershipText removed (no real function)
+      loading: true,
+      currentChildId: '',
+      _initialLoadDone: false,
+      _familyId: null,
+      familyMembersVisible: [],
+      selfMemberId: null,
+      energyData: null,
+      energyDimensions: [],
+      totalEnergy: 0,
+      totalHealthIndex: 0,
+      recommendedActivities: [],
+      recommendedProducts: [],
+      sandboxData: null,
+      sandboxPersonalData: null,
+      sandboxFamilyData: null,
+      errorEnergy: false,
+      // 通关解锁状态
+      sandboxLocked: false,
+      sandboxLockGate: null,
+      currentSandboxView: 'personal',
+      familyUnlocked: false,
+      familyLockGate: null,
+      unlockStatus: null,
+      errorTasks: false,
+      errorActivities: false,
+      errorProducts: false,
+      loadingActivities: false,
+      loadingProducts: false,
+      contactList: [],
+      showImportModal: false,
+      // QR Code modal
+      showQrCodeModal: false,
+      qrCodeData: null,
+      // 推荐阅读
+      indexArticles: [],
+      articlesLoading: false,
+      isLoggedIn: false,
+      gradientColors: [
+        'linear-gradient(135deg, ' + BrandColors.success + ', ' + BrandColors.successLight3 + ')',
+        'linear-gradient(135deg, ' + BrandColors.infoSky + ', ' + BrandColors.infoLight + ')',
+        'linear-gradient(135deg, ' + BrandColors.violet + ', ' + BrandColors.violetLighter + ')',
+        'linear-gradient(135deg, ' + BrandColors.primary + ', ' + BrandColors.primaryLight + ')',
+        'linear-gradient(135deg, ' + BrandColors.wisdom + ', ' + BrandColors.wisdomLight + ')'
+      ],
+      selectedMemberId: null,
+      // L1 即时反馈状态
+      toastVisible: false,
+      toastText: '',
+      toastIcon: '✅',
+      toastAnimating: false,
+      celebrationVisible: false,
+      celebrationTitle: '',
+      celebrationSubtitle: '',
+      celebrationParticles: [],
+      // P1-2 健康预警
+      healthAlerts: [],
+      // P2-2 家庭互动
+      rankingMembers: [],
+      rankingTotalScore: 0,
+      activeChallenges: [],
+      // P3 健康圈(社区扩散)
+      circleRankings: [],
+      circleName: '',
+      circleLoading: false,
+      selfCheckScore: null
+    }
+  },
+  computed: {
+    isFamilyAdmin() {
+      if (!this.familyMembersVisible || this.familyMembersVisible.length === 0) return false
+      // isAdmin 语义为"成员本人是否为家庭创建者";自己(isSelf)是创建者即管理员
+      var me = this.familyMembersVisible.filter(function(m) { return m.isSelf })[0]
+      return !!(me && me.isAdmin === true)
+    },
+    topPendingTasks() {
+      return this.pendingTasks.slice(0, 3)
+    },
+    energyScores() {
+      var d = this.energyData
+      if (!d) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
+      return { body: d.bodyScore, mind: d.mindScore, wisdom: d.wisdomScore, action: d.actionScore, wealth: d.wealthScore }
+    },
+    energyOverall() {
+      return this.energyData ? this.energyData.overallScore : 0
+    },
+    energyMembers() {
+      return this.energyData ? this.energyData.members : []
+    },
+    sandboxScores() {
+      if (!this.sandboxData) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
+      return {
+        body: this.sandboxData.bodyScore || 0,
+        mind: this.sandboxData.mindScore || 0,
+        wisdom: this.sandboxData.wisdomScore || 0,
+        action: this.sandboxData.actionScore || 0,
+        wealth: this.sandboxData.wealthScore || 0
+      }
+    },
+    familySandboxScores() {
+      var d = this.sandboxFamilyData || {}
+      return {
+        body: d.bodyScore || 0,
+        mind: d.mindScore || 0,
+        wisdom: d.wisdomScore || 0,
+        action: d.actionScore || 0,
+        wealth: d.wealthScore || 0
+      }
+    },
+    familySandboxOverall() {
+      return this.sandboxFamilyData ? (this.sandboxFamilyData.overallScore || 0) : 0
+    },
+    familySandboxMembers() {
+      return this.sandboxFamilyData ? (this.sandboxFamilyData.members || []) : []
+    },
+    greetingText() {
+      const hour = new Date().getHours()
+      if (hour < 6) return '夜深了'
+      if (hour < 9) return '早上好'
+      if (hour < 12) return '上午好'
+      if (hour < 14) return '中午好'
+      if (hour < 18) return '下午好'
+      if (hour < 22) return '晚上好'
+      return '夜深了'
+    },
+    hasMilestones() {
+      const c = this.selectedChild
+      if (!c) return false
+      return (c.streakDays >= 7) || (c.streakDays >= 30) || (c.totalPoints >= 1000)
+    },
+    energyMapForGraph() {
+      var map = {}
+      if (this.sandboxData && this.sandboxData.members) {
+        for (var i = 0; i < this.sandboxData.members.length; i++) {
+          var m = this.sandboxData.members[i]
+          map[m.memberId || m.id] = {
+            bodyScore: m.bodyScore || 0,
+            mindScore: m.mindScore || 0,
+            actionScore: m.actionScore || 0
+          }
+        }
+      }
+      return map
+    },
+    intimacyMapForGraph() {
+      return {}
+    }
+  },
+  // 使用 mounted 而非 onLoad,因为本组件作为条件渲染子组件(v-if),
+  // page 生命周期钩子(onLoad/onShow)可能在组件挂载前已触发完毕
+  mounted() {
+    if (uni.getStorageSync('token')) {
+      this.isLoggedIn = true
+      this.loadData()
+    }
+  },
+  onShow() {
+    // 首次 onShow 紧接 mounted 之后,跳过避免双重加载
+    if (!this._initialLoadDone) {
+      this._initialLoadDone = true
+      return
+    }
+    const currentRole = uni.getStorageSync('currentRole') || 'parent'
+    if (currentRole === 'child') {
+      uni.reLaunch({ url: '/pages/home-pages/member-index' })
+      return
+    }
+    if (uni.getStorageSync('token')) {
+      this.isLoggedIn = true
+      this.loadData()
+    }
+    if (this.$refs.onboardingProgress) {
+      this.$refs.onboardingProgress.reload()
+    }
+  },
+  onPullDownRefresh() {
+    this.loadData().finally(() => {
+      uni.stopPullDownRefresh()
+    })
+  },
+  methods: {
+    rankIcon: function(idx) {
+      var medals = ['🥇', '🥈', '🥉']
+      return medals[idx] || '#' + (idx + 1)
+    },
+    async loadData() {
+      this.loading = true
+      try {
+        var userInfo = uni.getStorageSync('userInfo')
+        this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : ''
+        this.selfMemberId = uni.getStorageSync('userId') || null
+
+        // 优先使用仪表盘接口(包含孩子列表 + 统计数据)
+        let dashboardLoaded = false
+        var _membersVisibleSet = false
+        try {
+          const dashRes = await getParentDashboard()
+          const dashData = dashRes.data || {}
+          const dashChildren = dashData.children || []
+          // 过滤:仅保留孩子角色成员,排除家长/本人
+          var currentUserId = uni.getStorageSync('userId')
+          var filteredChildren = dashChildren.filter(function(c) {
+            return c && c.id && c.id !== currentUserId && (c.role === 'child' || c.memberType === 'child')
+          })
+          if (filteredChildren.length > 0) {
+            this.children = filteredChildren
+            // 不自动选孩子:默认显示家长自己,等待用户从 FamilyMemberStrip 选择
+            this.familyAvgCompletionRate = dashData.avgCompletionRate || 0
+            // 家长自身数据(积分 + 任务完成率)
+            this.myPoints = dashData.parentPoints || 0
+            this.myCompletionRate = dashData.parentCompletionRate || 0
+            // 从仪表盘获取 familyId
+            if (dashData.familyId) {
+              this._familyId = dashData.familyId
+            }
+            // 从仪表盘同步设置 familyMembersVisible(含家长本人),避免后续 loadFamilyMembersVisible() 再调 getFamilyMemberList
+            var dashMembers = dashData.members || dashData.familyMembers || filteredChildren
+            this.familyMembersVisible = dashMembers
+            _membersVisibleSet = true
+            dashboardLoaded = true
+          }
+        } catch (e) {
+          console.log('获取仪表盘数据失败,降级到单独接口', e)
+        }
+
+        // 降级:使用原有单独接口(仅调用一次 getFamilyMemberList,合并 children 与 familyMembers 派生)
+        if (!dashboardLoaded) {
+          try {
+            const currentUserId = uni.getStorageSync('userId')
+            const membersRes = await getFamilyMemberList({ visibleOnly: true, includeFamily: true })
+            const data = membersRes.data || {}
+            var members = data.members || data || []
+            if (!Array.isArray(members)) members = []
+            const parents = members.filter(function(p) { return p.source === 'parent' && p.id && p.id !== currentUserId }).map(function(p) { return { ...p, role: 'parent' } })
+            const children = members.filter(function(c) { return c.source !== 'parent' && c.id }).map(function(c) { return { ...c, role: 'child' } })
+            this.familyMembers = [].concat(parents, children)
+            // 从同一响应派生 children,避免再调 getChildren()(= getFamilyMemberList({}))
+            this.children = children
+            // 同步设置 familyMembersVisible,避免 loadFamilyMembersVisible() 重复调用 getFamilyMemberList
+            this.familyMembersVisible = members
+            _membersVisibleSet = true
+            // 不自动选孩子:默认显示家长自己,等待用户从 FamilyMemberStrip 选择
+            // 保存 familyId 用于后续请求
+            if (data.familyId) {
+              this._familyId = data.familyId
+            }
+          } catch (e) {
+            console.log('获取家庭成员失败', e)
+          }
+        }
+
+        const reviewRes = await getPendingReviewTasks()
+        const allReviewTasks = reviewRes.data || []
+        this.pendingReviewTasks = allReviewTasks.slice(0, 3)
+
+        try {
+          const wishRes = await getPendingWishes()
+          this.pendingReviewWishes = wishRes.data || []
+        } catch (e) {
+          console.log('获取待处理心愿失败', e)
+          this.pendingReviewWishes = []
+        }
+
+        const reviewMap = {}
+        const wishMap = {}
+        for (let i = 0; i < allReviewTasks.length; i++) {
+          const task = allReviewTasks[i]
+          const cid = task.memberId || task.assigneeId
+          if (cid) {
+            reviewMap[cid] = (reviewMap[cid] || 0) + 1
+          }
+        }
+        for (let j = 0; j < (this.pendingReviewWishes || []).length; j++) {
+          const wish = this.pendingReviewWishes[j]
+          const cid = wish.memberId
+          if (cid) {
+            wishMap[cid] = (wishMap[cid] || 0) + 1
+          }
+        }
+        this.children = this.children.map(function(c) {
+          return {
+            ...c,
+            pendingReviewCount: reviewMap[c.id] || 0,
+            pendingWishCount: wishMap[c.id] || 0
+          }
+        })
+
+        // 计算全家总积分(家长 + 所有孩子)
+        var totalPts = this.myPoints
+        for (var pi = 0; pi < this.children.length; pi++) {
+          totalPts += (this.children[pi].totalPoints || 0)
+        }
+        this.familyTotalPoints = totalPts
+
+        // 如果仪表盘未加载到数据,降级:从孩子数据取近似值
+        if (!dashboardLoaded) {
+          // 降级:家长积分取自己的(从缓存或API),完成率取第一个孩子的
+          const targetChild = this.selectedChild || (this.children.length > 0 ? this.children[0] : null)
+          if (targetChild) {
+            try {
+              const statsRes = await getChildCompletionStats({ memberId: targetChild.id })
+              if (statsRes && statsRes.data) {
+                this.myCompletionRate = statsRes.data.completionRate || 0
+              }
+            } catch (e) {
+              console.log('获取个人完成率失败', e)
+            }
+            userInfo = uni.getStorageSync('userInfo')
+            this.myPoints = (userInfo && userInfo.totalPoints) || 0
+            this.familyAvgCompletionRate = this.myCompletionRate
+          }
+        }
+
+        // 待完成任务 - 优先使用后端返回的真实任务列表
+        this.pendingTasks = []
+        if (this.children.length > 0) {
+          const targetChild = this.selectedChild || this.children[0]
+          try {
+            const r2 = await getChildCompletionStats({ memberId: targetChild.id })
+            if (r2 && r2.data && r2.data.pendingTaskList && r2.data.pendingTaskList.length > 0) {
+              this.pendingTasks = r2.data.pendingTaskList.slice(0, 3)
+            } else if (r2 && r2.data && r2.data.pendingTasks > 0) {
+              // 降级:只有数量没有列表时使用占位
+              for (let k = 0; k < Math.min(r2.data.pendingTasks, 3); k++) {
+                this.pendingTasks.push({ id: 'pending-' + k, title: '待完成任务' })
+              }
+            }
+            } catch (e) {
+            this.pendingTasks = []
+          }
+        }
+
+        // 加载通关解锁状态
+        try {
+          var statusRes = await getUnlockStatus()
+          if (statusRes && statusRes.data) {
+            this.unlockStatus = statusRes.data
+          }
+        } catch (e) {
+          console.log('获取通关解锁状态失败', e)
+        }
+
+        // 加载能量沙盘(含通关门禁)
+        try {
+          var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId') || null
+          var energyRes = await getEnergySandbox(this.currentSandboxView, memberId)
+          if (energyRes && energyRes.data) {
+            var ed = energyRes.data
+            if (ed.locked) {
+              this.sandboxLocked = true
+              this.sandboxLockGate = ed.gate || null
+              this.sandboxData = null
+              this.sandboxPersonalData = null
+              this.sandboxFamilyData = null
+              this.energyData = null
+            } else {
+              this.sandboxLocked = false
+              this.sandboxLockGate = null
+              this.sandboxPersonalData = ed.personalData || null
+              this.sandboxFamilyData = ed.familyData || null
+              this.sandboxData = this.currentSandboxView === 'family' ? (ed.familyData || null) : (ed.personalData || null)
+              this.energyData = ed.personalData || null
+              this.familyUnlocked = ed.familyUnlocked || false
+              this.familyLockGate = ed.familyGate || null
+            }
+          }
+        } catch (e) {
+          console.log('获取能量沙盘数据失败', e)
+          this.energyData = null
+          this.sandboxData = null
+          this.errorEnergy = true
+        }
+
+        // 加载全家今日任务(用于成员待完成数角标)
+        getTodayTasks(null, { familyWide: true }).then(function(res) {
+          if (res.code === 200 && Array.isArray(res.data)) {
+            this.memberPendingCounts = this._countTasksByMember(res.data)
+          }
+        }).catch(function() {
+          this.memberPendingCounts = {}
+        }.bind(this))
+
+        // 加载推荐活动
+        this.loadRecommendedActivities()
+        // 加载推荐商品
+        this.loadRecommendedProducts()
+
+        // 加载家庭成员可见列表(降级路径已在上面设置,此处跳过避免重复请求)
+        if (!_membersVisibleSet) {
+          this.loadFamilyMembersVisible()
+        }
+        this.loadSelfCheckStatus()
+        // 加载健康预警(P1-2)
+        this.loadAlerts()
+        // 加载家庭健康互动(P2-2)
+        this.loadFamilyInteraction()
+        // 加载健康圈(P3:社区扩散)
+        this.loadHealthCircle()
+        // 加载外部联系人
+        this.loadContacts()
+        // 加载推荐阅读
+        this.loadFeaturedArticles()
+
+        // 加载五维能量概览(账本模式)
+        var overviewChildId = this.currentChildId
+        if (overviewChildId) {
+          try {
+            var overviewRes = await getEnergyOverview(overviewChildId)
+            if (overviewRes && overviewRes.data) {
+              this.energyDimensions = overviewRes.data.dimensions || []
+              this.totalEnergy = overviewRes.data.totalEnergy || 0
+              this.totalHealthIndex = overviewRes.data.totalHealthIndex || 0
+            }
+          } catch (e) {
+            console.log('获取能量概览失败', e)
+          }
+        }
+      } catch (e) {
+        console.error('加载数据失败', e)
+      } finally {
+        this.loading = false
+        this.$emit('ready')
+      }
+    },
+    async approveTask(taskId) {
+      try {
+        await approveTask(taskId)
+        uni.showToast({ title: '已通过', icon: 'success' })
+        this.loadData()
+      } catch (e) {
+        uni.showToast({ title: '操作失败', icon: 'none' })
+      }
+    },
+    async rejectTask(taskId) {
+      try {
+        await rejectTask(taskId)
+        uni.showToast({ title: '已拒绝', icon: 'success' })
+        this.loadData()
+      } catch (e) {
+        uni.showToast({ title: '操作失败', icon: 'none' })
+      }
+    },
+    // 加载可见家庭成员
+    async loadFamilyMembersVisible() {
+      try {
+        var res = await getFamilyMemberList({ visibleOnly: true })
+        this.familyMembersVisible = res.data || []
+      } catch (e) {
+        console.log('获取可见家庭成员失败', e)
+        this.familyMembersVisible = []
+      }
+    },
+    // 成员选择
+    goToCompletionStats() {
+      if (this.children.length === 1) {
+        uni.navigateTo({ url: '/pages/stats/completion-rate?memberId=' + this.children[0].id })
+      } else {
+        uni.navigateTo({ url: '/pages/stats/completion-rate?familyView=1' })
+      }
+    },
+    onDomainClick(evt) {
+      this.goToDomain(evt.code)
+    },
+    /** 切换沙盘个人/全家视角(本地切换,不重新请求) */
+    onSandboxViewChange: function(view) {
+      if (view === this.currentSandboxView) return
+      this.currentSandboxView = view
+      this.sandboxData = view === 'family' ? (this.sandboxFamilyData || null) : (this.sandboxPersonalData || null)
+    },
+    /** 单独加载沙盘数据(首次进入时使用,一次返回个人+全家) */
+    loadSandboxData: async function() {
+      try {
+        var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId') || null
+        var energyRes = await getEnergySandbox(this.currentSandboxView, memberId)
+        if (energyRes && energyRes.data) {
+          var ed = energyRes.data
+          if (ed.locked) {
+            this.sandboxLocked = true
+            this.sandboxLockGate = ed.gate || null
+            this.sandboxData = null
+            this.sandboxPersonalData = null
+            this.sandboxFamilyData = null
+          } else {
+            this.sandboxLocked = false
+            this.sandboxLockGate = null
+            this.sandboxPersonalData = ed.personalData || null
+            this.sandboxFamilyData = ed.familyData || null
+            this.sandboxData = this.currentSandboxView === 'family' ? (ed.familyData || null) : (ed.personalData || null)
+            this.familyUnlocked = ed.familyUnlocked || false
+            this.familyLockGate = ed.familyGate || null
+          }
+        }
+      } catch (e) {
+        console.log('加载沙盘数据失败', e)
+      }
+    },
+    goToDomain(code) {
+      // 跳转到能量详情页
+      var memberId = this.currentChildId
+      if (!memberId || !code) return
+      uni.navigateTo({
+        url: '/pages/energy/detail?memberId=' + memberId + '&code=' + code
+      })
+    },
+    onFamilyMemberSelect(member) {
+      this.selectedMemberId = member.id
+      // 选中的是孩子时,设置 selectedChild 以便子组件加载孩子数据
+      var matched = this.children.find(function(c) { return c.id === member.id })
+      if (matched) {
+        this.selectedChild = matched
+        this.currentChildId = matched.id
+      } else {
+        this.selectedChild = null
+        this.currentChildId = ''
+      }
+    },
+    async onFamilyMemberSwitch(member) {
+      if (!member || !member.id) return
+      var self = this
+      uni.showModal({
+        title: '切换确认',
+        content: '切换到"' + (member.nickname || '该成员') + '"后,退出时需要输入您的密码。是否继续?',
+        success: async function(res) {
+          if (!res.confirm) return
+          try {
+            var result = await switchToFamilyMember(member.id, member.source || 'family_member')
+            if (result.code === 200 && result.data) {
+              store.commit('switchToMember', {
+                memberId: result.data.memberId,
+                effectiveRole: result.data.effectiveRole
+              })
+              uni.showToast({ title: '已切换到' + (member.nickname || '该成员'), icon: 'success' })
+              setTimeout(function() {
+                uni.reLaunch({ url: '/pages/index-home/index' })
+              }, 1000)
+            } else {
+              uni.showToast({ title: result.message || '切换失败', icon: 'none' })
+            }
+          } catch (e) {
+            uni.showToast({ title: '切换失败', icon: 'none' })
+          }
+        }
+      })
+    },
+    onMicroActionCompleted() {
+      this.loadData()
+      feedbackEngine.trigger('micro_action')
+    },
+    goAddMember() {
+      uni.navigateTo({ url: '/pages/family/add-member' })
+    },
+    onDailySummaryDismiss() {
+      // DailySummary 已关闭,无额外操作
+    },
+    loadAlerts: function() {
+      var memberId = this.selectedChild && this.selectedChild.id
+      if (!memberId) return
+      var self = this
+      getActiveAlerts({ memberId: memberId }).then(function(res) {
+        if (res.code === 200) {
+          self.healthAlerts = res.data || []
+        }
+      }).catch(function() {})
+    },
+    handleDismissAlert: function(alertId) {
+      var self = this
+      dismissAlert({ id: alertId }).then(function(res) {
+        if (res.code === 200) {
+          self.healthAlerts = self.healthAlerts.filter(function(a) { return a.id !== alertId })
+        }
+      }).catch(function() {})
+    },
+    loadFamilyInteraction: function() {
+      var familyId = this._familyId
+      if (!familyId) return
+      var self = this
+      getFamilyRanking({}).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.rankingMembers = res.data.members || []
+          self.rankingTotalScore = res.data.totalScore || 0
+        }
+      }).catch(function() {})
+      getChallengeList({}).then(function(res) {
+        if (res.code === 200 && Array.isArray(res.data)) {
+          self.activeChallenges = res.data
+        }
+      }).catch(function() {})
+    },
+    loadHealthCircle: function() {
+      var familyId = this._familyId
+      if (!familyId) return
+      var self = this
+      self.circleLoading = true
+      getCircleList({}).then(function(res) {
+        if (res.code === 200 && res.data && res.data.length > 0) {
+          var circleId = res.data[0].id
+          getCircleRanking({ circleId: circleId }).then(function(r) {
+            if (r.code === 200 && r.data) {
+              self.circleName = r.data.circleName || ''
+              self.circleRankings = r.data.rankings || []
+            }
+          }).catch(function() {
+            self.circleRankings = []
+            self.circleName = ''
+          }).finally(function() {
+            self.circleLoading = false
+          })
+        } else {
+          self.circleRankings = []
+          self.circleName = ''
+          self.circleLoading = false
+        }
+      }).catch(function() {
+        self.circleRankings = []
+        self.circleName = ''
+        self.circleLoading = false
+      })
+    },
+    goToHealthCircle: function() {
+      uni.navigateTo({ url: '/pages/share/index' })
+    },
+    goToSelfCheck: function() {
+      uni.navigateTo({ url: '/pages/health/health-plan-summary' })
+    },
+    loadSelfCheckStatus: function() {
+      var self = this
+      getSelfCheckStatus().then(function(res) {
+        if (res.code === 200 && res.data && res.data.lastResult) {
+          self.selfCheckScore = res.data.lastResult.totalScore
+        }
+      }).catch(function() {})
+    },
+    goMemberDetail(member) {
+      if (!member || !member.memberId) return
+      uni.navigateTo({
+        url: '/pages/member-detail/member-detail?memberId=' + member.memberId + '&memberType=' + (member.memberType || 'child') + '&entrySource=relation-graph'
+      })
+    },
+    addTask() { uni.navigateTo({ url: '/pages/tasks/create-task' }) },
+    goToChallengeManage: function(id) {
+      uni.navigateTo({ url: '/pages/health/challenge-manage?id=' + id })
+    },
+    goToReview() { uni.navigateTo({ url: '/pages/tasks/review' }) },
+    goToWishApprove() { uni.navigateTo({ url: '/pages/wishes/approve' }) },
+    goToAssessmentReport() { uni.navigateTo({ url: '/pages/assessment/report' }) },
+    goToActivities() { uni.navigateTo({ url: '/pages/activity/index' }) },
+    goToRewards() { uni.navigateTo({ url: '/pages/rewards/rewards' }) },
+    onFeedItemClick(item) {
+      if (item.type === 'product') {
+        uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + item.id })
+      } else if (item.type === 'activity') {
+        uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + item.id })
+      }
+    },
+    goMoreDiscover() {
+      uni.navigateTo({ url: '/pages/discover/index' })
+    },
+    loadRecommendedActivities() {
+      var self = this
+      getActivityList({ page: 1, size: 6 }).then(function(res) {
+        if (res && res.data) {
+          self.recommendedActivities = res.data.records || []
+        }
+      }).catch(function() {
+        self.errorActivities = true
+      })
+    },
+    loadRecommendedProducts() {
+      var self = this
+      getProductsByDomain('all', 1, 6).then(function(res) {
+        if (res && res.data) {
+          self.recommendedProducts = res.data.records || []
+        }
+      }).catch(function() {
+        self.errorProducts = true
+      })
+    },
+    goToTaskList() { uni.navigateTo({ url: '/pages/tasks/tasks' }) },
+    manageChildren() { uni.navigateTo({ url: '/pages/profile-extra/family-members' }) },
+
+    loadContacts: function() {
+      var self = this
+      getContactList({}).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.contactList = Array.isArray(res.data) ? res.data : []
+        }
+      }).catch(function(e) {
+        console.log('获取联系人失败', e)
+      })
+    },
+    onContactClick: function(item) {
+      uni.navigateTo({ url: '/pages/action-detail/contact-detail?id=' + item.id })
+    },
+    onShowImport: function() {
+      this.showImportModal = true
+    },
+    onCloseImport: function() {
+      this.showImportModal = false
+    },
+    onImportSuccess: function() {
+      this.showImportModal = false
+      this.loadContacts()
+    },
+    // QR Code modal methods
+    onShowQrCodeModal: async function() {
+      this.showQrCodeModal = true
+      this.qrCodeData = null
+      try {
+        const res = await this.getInviteQrCode()
+        if (res.code === 200) {
+          this.qrCodeData = res.data
+        }
+      } catch (e) {
+        console.error('获取二维码失败', e)
+        uni.showToast({ title: '获取二维码失败', icon: 'none' })
+      }
+    },
+    onCloseQrCodeModal: function() {
+      this.showQrCodeModal = false
+      this.qrCodeData = null
+    },
+    onCopyReferralCode: function() {
+      if (this.qrCodeData && this.qrCodeData.referralCode) {
+        uni.setClipboardData({
+          data: this.qrCodeData.referralCode,
+          success: () => {
+            uni.showToast({ title: '复制成功', icon: 'success' })
+          }
+        })
+      }
+    },
+    // 兼容旧引用
+    getInviteQrCode: function() {
+      return getInviteQrCode()
+    },
+    // ===== 推荐阅读 =====
+    loadFeaturedArticles: function() {
+      var self = this
+      this.articlesLoading = true
+      getFeaturedArticles({ size: 5 }).then(function(res) {
+        self.articlesLoading = false
+        if (res && res.code === 200) {
+          var rawList = res.data && (Array.isArray(res.data) ? res.data : (res.data.data || res.data.records || []))
+          var list = Array.isArray(rawList) ? rawList : []
+          self.indexArticles = list.map(function(item, index) {
+            return {
+              id: item.id,
+              category: item.categoryName || item.category || '热门文章',
+              categoryColor: self.gradientColors[index % self.gradientColors.length],
+              title: item.title || '',
+              summary: item.summary || '',
+              views: item.viewCount ? String(item.viewCount) : '0',
+              date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
+              coverImage: item.coverImage
+            }
+          })
+        }
+      }).catch(function() {
+        self.articlesLoading = false
+      })
+    },
+    goArticles: function() {
+      uni.navigateTo({ url: '/pages/article-center/index' })
+    },
+    goArticleDetail: function(articleOrId) {
+      var id = typeof articleOrId === 'object' ? articleOrId.id : articleOrId
+      uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
+    },
+    // 按成员统计待完成数(pending + in_progress)
+    _countTasksByMember: function(tasks) {
+      var counts = {}
+      for (var i = 0; i < tasks.length; i++) {
+        var task = tasks[i]
+        if (!task || !task.familyMemberId) continue
+        var memberId = task.familyMemberId
+        if (!counts[memberId]) {
+          counts[memberId] = 0
+        }
+        if (task.status === 'pending' || task.status === 'in_progress') {
+          counts[memberId] += 1
+        }
+      }
+      return counts
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+/* ========================================
+   Flat Card Design System — 家长端暖橙扁平
+   ======================================== */
+
+.container {
+  padding: 32rpx;
+  background: var(--bg, $uni-bg-color);
+  min-height: 100vh;
+}
+
+/* ========================================
+   区块标题
+   ======================================== */
+.section-title {
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: var(--text, $uni-text-color);
+  margin-bottom: var(--space-md);
+}
+
+/* ========================================
+   c) 成长数据
+   ======================================== */
+.stats-section {
+  margin-bottom: 48rpx;
+}
+
+.stats-row {
+  display: flex;
+  gap: 16rpx;
+}
+
+.stat-card-wide {
+  flex: 1;
+  min-width: 0;
+  box-sizing: border-box;
+}
+
+.stat-card-content {
+  padding: 24rpx;
+}
+
+.stat-header {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+
+.stat-icon-wrap {
+  width: 56rpx;
+  height: 56rpx;
+  border-radius: var(--radius-sm, 12rpx);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 14rpx;
+  flex-shrink: 0;
+}
+
+.stat-icon-symbol {
+  font-size: 28rpx;
+}
+
+.stat-icon-orange { background: rgba(249,115,22,0.12); }
+.stat-icon-gold { background: rgba(245, 158, 11, 0.15); }
+
+.stat-body {
+  flex: 1;
+  min-width: 0;
+}
+
+.stat-label-header {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: var(--text, $uni-text-color);
+}
+
+/* 对比行布局 */
+.stat-compare {
+  margin-bottom: 12rpx;
+}
+
+.stat-compare-row {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 4rpx;
+}
+
+.compare-label {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: var(--text-secondary, $uni-text-color-secondary);
+}
+
+.compare-value {
+  font-size: calc(30rpx * var(--font-scale, 1));
+  font-weight: 700;
+}
+
+/* 进度条 - 紧凑版 */
+.progress-bar-sm {
+  height: 6rpx;
+  background: var(--border-light, $uni-border-color-light);
+  border-radius: 999rpx;
+  overflow: hidden;
+  margin-bottom: 8rpx;
+}
+
+.progress-fill-sm {
+  height: 100%;
+  background: linear-gradient(90deg, var(--color-primary, $brand-primary), $dim-wealth);
+  border-radius: 999rpx;
+  transition: width 0.3s ease;
+}
+
+.progress-fill-sm.muted {
+  background: linear-gradient(90deg, $app-text-subtle, $uni-border-color);
+}
+
+.accent-color { color: var(--color-primary, $brand-primary); }
+.gold-color { color: $dim-wealth; }
+
+.stat-more {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: var(--color-accent, $uni-info);
+  font-weight: 500;
+  display: block;
+}
+
+.stat-encourage {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: $status-success;
+  font-weight: 500;
+  margin-top: 8rpx;
+  display: block;
+}
+
+.stat-encourage.muted {
+  color: var(--muted, $app-text-subtle);
+}
+
+/* ========================================
+   d) 今日重点
+   ======================================== */
+.today-section {
+  margin-bottom: 48rpx;
+}
+
+.today-card .today-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.today-label {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: var(--text, $uni-text-color);
+}
+
+.today-count {
+  font-size: 24rpx;
+  color: var(--color-primary, $brand-primary);
+  font-weight: 600;
+}
+
+.task-list {
+  margin-bottom: 8rpx;
+}
+
+.task-item {
+  display: flex;
+  align-items: center;
+  padding: 18rpx 0;
+  border-bottom: 1rpx solid var(--border-light, $uni-border-color-light);
+}
+
+.task-item:last-child {
+  border-bottom: none;
+}
+
+.task-dot {
+  width: 14rpx;
+  height: 14rpx;
+  border-radius: 50%;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+
+.dot-color-0 { background: var(--color-primary, $brand-primary); }
+.dot-color-1 { background: $status-success; }
+.dot-color-2 { background: var(--color-accent, $uni-info); }
+.dot-color-3 { background: $dim-wealth; }
+.dot-color-4 { background: var(--color-error, $status-danger); }
+
+.task-title {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: var(--text, $uni-text-color);
+}
+
+.task-view-all {
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  color: var(--text-secondary, $uni-text-color-secondary);
+  font-size: calc(24rpx * var(--font-scale, 1));
+  padding-top: 16rpx;
+  border-top: 1rpx solid var(--border-light, $uni-border-color-light);
+}
+
+.task-view-all .arrow {
+  margin-left: 8rpx;
+}
+
+/* ========================================
+   e) 成长里程碑
+   ======================================== */
+.milestone-section {
+  margin-bottom: 48rpx;
+}
+
+.milestone-card {
+  padding: 24rpx !important;
+}
+
+.milestone-item {
+  display: flex;
+  align-items: center;
+  padding: 16rpx 0;
+  border-bottom: 1rpx solid var(--border-light, $uni-border-color-light);
+}
+
+.milestone-item:last-child {
+  border-bottom: none;
+}
+
+.milestone-icon-wrap {
+  width: 56rpx;
+  height: 56rpx;
+  border-radius: var(--radius-sm, 12rpx);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+
+.milestone-icon {
+  font-size: 28rpx;
+}
+
+.milestone-star { background: rgba(245, 158, 11, 0.15); }
+.milestone-trophy { background: rgba(14,165,233,0.12); }
+.milestone-sparkle { background: rgba(16, 185, 129, 0.12); }
+
+.milestone-text {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: var(--text, $uni-text-color);
+}
+
+/* ========================================
+   f) 待处理事项
+   ======================================== */
+.pending-section { margin-bottom: 48rpx; }
+.pending-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16rpx;
+}
+.pending-card {
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  display: flex;
+  align-items: center;
+  width: calc(50% - 8rpx);
+  box-sizing: border-box;
+}
+.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(14,165,233,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: calc(32rpx * var(--font-scale, 1)); }
+.pending-info { flex: 1; min-width: 0; }
+.pending-count { font-size: 36rpx; font-weight: 800; display: block; }
+.pending-label { font-size: calc(22rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 4rpx; display: block; }
+.pink-color { color: $accent-pink; }
+.orange-color { color: $brand-primary; }
+
+/* ========================================
+   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(14,165,233,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: calc(28rpx * var(--font-scale, 1)); font-weight: 600; color: $facade-text-strong; display: block; }
+.advice-desc { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-muted; margin-top: 4rpx; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+.advice-arrow { font-size: 36rpx; color: $facade-text-ghost; margin-left: 12rpx; }
+
+/* ========================================
+   h) 待审核任务
+   ======================================== */
+.review-section {
+  margin-bottom: 48rpx;
+}
+
+.review-card {
+  margin-bottom: 16rpx;
+}
+
+.review-card:last-child {
+  margin-bottom: 0;
+}
+
+.review-card-content {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 24rpx;
+}
+
+.review-info {
+  flex: 1;
+  min-width: 0;
+}
+
+.review-info .task-name {
+  font-size: calc(28rpx * var(--font-scale, 1));
+  color: var(--text, $uni-text-color);
+  display: block;
+  margin-bottom: 4rpx;
+}
+
+.review-info .task-child {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: var(--muted, $app-text-subtle);
+}
+
+.review-actions {
+  display: flex;
+  gap: 12rpx;
+  flex-shrink: 0;
+  margin-left: 16rpx;
+}
+
+/* ========================================
+   g) 快捷操作
+   ======================================== */
+.quick-section {
+  margin-bottom: 48rpx;
+}
+
+.quick-card {
+  padding: 16rpx 12rpx !important;
+}
+
+.quick-actions {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.action-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 20%;
+  margin-bottom: 12rpx;
+  transition: transform 0.15s ease;
+}
+
+.action-item:active {
+  transform: scale(0.92);
+}
+
+.action-icon {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: var(--radius-sm, 12rpx);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+
+.action-icon-text {
+  font-size: 32rpx;
+}
+
+.act-add { background: rgba(249,115,22,0.12); }
+.act-review { background: rgba(16, 185, 129, 0.12); }
+.act-child { background: rgba(14, 165, 233, 0.12); }
+.act-reward { background: rgba(245, 158, 11, 0.15); }
+.act-invite { background: rgba(139, 92, 246, 0.12); }
+
+.action-label {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: var(--text-secondary, $uni-text-color-secondary);
+  font-weight: 500;
+  text-align: center;
+}
+
+/* ===== Error State ===== */
+.error-state {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  padding: 32rpx 24rpx;
+  margin-bottom: 24rpx;
+  background: $app-surface;
+  border-radius: 16rpx;
+  border: 1rpx dashed $uni-border-color-light;
+}
+.error-state-text {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $app-text-subtle;
+  text-align: center;
+}
+
+/* ===== 重要关系(通讯录) ===== */
+.contact-section {
+  margin: 20rpx 20rpx 0;
+}
+.contact-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.contact-section-title {
+  font-size: calc(30rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: $facade-text-strong;
+}
+.contact-header-right {
+  display: flex;
+  gap: 16rpx;
+  align-items: center;
+}
+.contact-more {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+}
+.contact-import-btn {
+  font-size: 24rpx;
+  color: $brand-primary;
+  font-weight: 500;
+}
+.contact-scroll {
+  white-space: nowrap;
+  overflow: hidden;
+}
+.contact-scroll-inner {
+  display: flex;
+  flex-direction: row;
+  gap: 16rpx;
+  padding: 8rpx 0 16rpx;
+}
+.contact-card-wrap {
+  flex-shrink: 0;
+  width: 220rpx;
+}
+.contact-card-wrap:active {
+  opacity: 0.8;
+}
+.contact-add-card {
+  width: 160rpx;
+}
+.add-card-inner {
+  width: 160rpx;
+  height: 280rpx;
+  background: $app-surface;
+  border-radius: 16rpx;
+  border: 2rpx dashed $facade-border-soft;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  gap: 12rpx;
+}
+.add-card-icon {
+  font-size: 48rpx;
+  color: $facade-text-ghost;
+}
+.add-card-label {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+}
+.contact-empty {
+  padding: 40rpx 0;
+  text-align: center;
+}
+.contact-empty-text {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $facade-text-ghost;
+}
+
+/* ===== 推荐阅读 ===== */
+.article-section {
+  margin-bottom: 48rpx;
+}
+.article-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: var(--space-md);
+}
+.article-header .section-title {
+  margin-bottom: 0;
+}
+.article-more {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: var(--color-primary, $brand-primary);
+}
+.article-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+.article-card {
+  background: $app-surface;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.article-card:active {
+  opacity: 0.8;
+}
+.article-category {
+  display: inline-block;
+  padding: 4rpx 14rpx;
+  border-radius: 8rpx;
+  margin-bottom: 10rpx;
+}
+.article-category-text {
+  font-size: calc(20rpx * var(--font-scale, 1));
+  color: $app-surface;
+}
+.article-title {
+  display: block;
+  font-size: calc(28rpx * var(--font-scale, 1));
+  font-weight: bold;
+  color: $facade-text-strong;
+  margin-bottom: 8rpx;
+  line-height: 1.4;
+}
+.article-summary {
+  display: block;
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-faint;
+  line-height: 1.5;
+  margin-bottom: 12rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+.article-meta {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+}
+.meta-item {
+  display: flex;
+  align-items: center;
+  gap: 4rpx;
+}
+.meta-icon {
+  font-size: 24rpx;
+}
+.meta-text {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+}
+.article-date {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: $facade-text-ghost-light;
+}
+
+/* ===== 家庭挑战区域 ===== */
+.challenge-section {
+  margin-bottom: 28rpx;
+}
+.challenge-section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin: 0 20rpx 12rpx;
+}
+.challenge-section-title {
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $uni-text-color;
+}
+.challenge-section-action {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $status-success;
+  font-weight: 600;
+}
+
+/* ===== 健康圈入口卡片(P3:社区扩散) ===== */
+.circle-entry-section {
+  margin: 0 20rpx 28rpx 20rpx;
+}
+.circle-card-wide {
+  width: 100%;
+}
+.circle-card-content {
+  padding: 24rpx;
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+.circle-header {
+  display: flex;
+  align-items: center;
+  gap: 12rpx;
+}
+.circle-icon-wrap {
+  width: 56rpx;
+  height: 56rpx;
+  border-radius: 14rpx;
+  background: linear-gradient(135deg, $status-success, $status-success-light-3);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.circle-icon {
+  font-size: 28rpx;
+}
+.circle-header-body {
+  flex: 1;
+}
+.circle-card-title {
+  display: block;
+  font-size: calc(28rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $uni-text-color;
+  line-height: 1.4;
+}
+.circle-card-subtitle {
+  display: block;
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: $facade-text-slate;
+  margin-top: 2rpx;
+}
+.circle-ranking-preview {
+  background: $status-success-tint-2;
+  border-radius: 12rpx;
+  padding: 16rpx;
+  display: flex;
+  flex-direction: column;
+  gap: 10rpx;
+}
+.circle-rank-row {
+  display: flex;
+  align-items: center;
+  gap: 10rpx;
+}
+.circle-rank-badge {
+  font-size: 24rpx;
+  width: 40rpx;
+  text-align: center;
+}
+.circle-rank-family {
+  flex: 1;
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-gray-700;
+  font-weight: 500;
+}
+.circle-rank-score {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $brand-primary;
+  font-weight: 600;
+}
+.circle-action {
+  display: flex;
+  justify-content: flex-end;
+}
+.circle-action-text {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $brand-primary;
+  font-weight: 500;
+}
+
+/* 无数据:邀请加入样式 */
+.circle-card-invite {
+  background: linear-gradient(135deg, $status-success-tint-2, $status-success-tint);
+}
+.circle-invite-content {
+  padding: 32rpx 24rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 12rpx;
+}
+.circle-invite-icon {
+  font-size: 48rpx;
+  margin-bottom: 4rpx;
+}
+.circle-invite-title {
+  font-size: calc(30rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $status-success-strong;
+}
+.circle-invite-desc {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $facade-text-slate;
+  text-align: center;
+  line-height: 1.5;
+}
+.circle-invite-cta {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $brand-primary;
+  font-weight: 600;
+  margin-top: 4rpx;
+}
+
+/* ===== 五维家庭自检入口卡片(P0-1/P0-2) ===== */
+.selfcheck-entry-section {
+  margin: 0 20rpx 28rpx 20rpx;
+}
+.selfcheck-card-wide {
+  width: 100%;
+}
+.selfcheck-card-content {
+  padding: 28rpx 24rpx;
+  display: flex;
+  align-items: center;
+  gap: 20rpx;
+}
+.selfcheck-icon-wrap {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  flex-shrink: 0;
+}
+// 五维自检图标底色(全局维度令牌)
+.selfcheck-icon-body { background: $dim-body; }
+.selfcheck-icon-wisdom { background: $dim-wisdom; }
+.selfcheck-icon-wealth { background: $dim-wealth; }
+.selfcheck-icon-action { background: $dim-action; }
+.selfcheck-icon-mind { background: $dim-mind; }
+
+.selfcheck-icon-circle {
+  width: 52rpx;
+  height: 52rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: -10rpx;
+  border: 4rpx solid $app-surface;
+  box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.12);
+}
+.selfcheck-icon-circle:last-child {
+  margin-right: 0;
+}
+.selfcheck-icon-char {
+  font-size: 24rpx;
+  font-weight: 700;
+  color: $app-surface;
+}
+.selfcheck-body {
+  flex: 1;
+}
+.selfcheck-title {
+  display: block;
+  font-size: calc(30rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $uni-text-color;
+  line-height: 1.4;
+}
+.selfcheck-subtitle {
+  display: block;
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: $facade-text-slate;
+  margin-top: 4rpx;
+}
+.selfcheck-arrow {
+  font-size: 30rpx;
+  color: $brand-primary;
+  font-weight: 600;
+  flex-shrink: 0;
+}
+
+/* ===== 邀请码二维码弹窗 ===== */
+.qr-code-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.6);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  padding: 40rpx 20rpx;
+  box-sizing: border-box;
+}
+
+.qr-code-modal-content {
+  background: $app-surface;
+  border-radius: 20rpx;
+  padding: 40rpx 32rpx;
+  max-width: 600rpx;
+  width: 100%;
+  box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.2);
+  position: relative;
+}
+
+.qr-code-modal-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 32rpx;
+}
+
+.qr-code-modal-title {
+  font-size: calc(36rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $uni-text-color;
+}
+
+.qr-code-modal-close {
+  font-size: 48rpx;
+  color: $app-text-subtle;
+  line-height: 1;
+  padding: 8rpx;
+  cursor: pointer;
+}
+
+.qr-code-modal-body {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 32rpx;
+}
+
+.qr-code-image-container {
+  width: 280rpx;
+  height: 280rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: $facade-fill-gray-50;
+  border-radius: 16rpx;
+  padding: 20rpx;
+}
+
+.qr-code-image {
+  width: 100%;
+  height: 100%;
+  object-fit: contain;
+}
+
+.qr-code-referral-code {
+  display: flex;
+  align-items: center;
+  gap: 16rpx;
+  padding: 24rpx;
+  background: $facade-fill-gray-50;
+  border-radius: 16rpx;
+  width: 100%;
+  box-sizing: border-box;
+}
+
+.qr-code-label {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $uni-text-color-secondary;
+  font-weight: 500;
+}
+
+.qr-code-code {
+  flex: 1;
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $uni-text-color;
+  word-break: break-all;
+  letter-spacing: 2rpx;
+}
+
+.qr-code-copy {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: $brand-primary;
+  font-weight: 500;
+  padding: 8rpx 16rpx;
+  background: $app-surface;
+  border-radius: 8rpx;
+  border: 1px solid $brand-primary;
+  cursor: pointer;
+}
+
+.qr-code-copy:active {
+  opacity: 0.8;
+}
+
+/* 无家庭成员空状态引导卡 */
+.empty-family-card {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin: 20rpx 24rpx;
+  padding: 48rpx 30rpx;
+  background: $app-surface;
+  border-radius: 24rpx;
+  border: 2rpx dashed $brand-primary-light-2;
+}
+.empty-family-icon {
+  font-size: 88rpx;
+  margin-bottom: 16rpx;
+}
+.empty-family-title {
+  font-size: calc(34rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: $mat-orange-deep;
+  margin-bottom: 10rpx;
+}
+.empty-family-desc {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  color: $facade-text-muted;
+  margin-bottom: 28rpx;
+}
+.empty-family-btn {
+  width: 320rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+  font-size: 30rpx;
+  font-weight: 600;
+  color: $app-surface;
+  background: linear-gradient(135deg, $brand-primary, $dim-body);
+  border-radius: 40rpx;
+  border: none;
+}
+.empty-family-btn::after {
+  border: none;
+}
+
+</style>

+ 1486 - 0
cfc-frontend/pages/home-pages/member-index.vue

@@ -0,0 +1,1486 @@
+<template>
+  <view class="container" :style="{ '--font-scale': fontScale }">
+    <!-- 加载状态 -->
+    <BaseLoading :loading="loading" text="正在加载..." />
+
+    <!-- 撒花庆祝 -->
+    <ConfettiCelebration :show="showConfetti" />
+
+    <!-- 首次登录引导浮层 -->
+    <OnboardingGuide
+      role="child"
+      position="child"
+      :show="showOnboarding"
+      @close="showOnboarding = false" />
+    <!-- 配置驱动弹窗(guide_popup 表,管理端可配置) -->
+    <ConfigurableGuide
+      :popup="guidePopup"
+      :show="showGuidePopup"
+      @done="handleGuidePopupDone" />
+
+    <!-- 空状态 - 无孩子 -->
+    <BaseEmpty
+      v-if="!loading && !hasChildren"
+      icon="🌈"
+      title="欢迎来到知心益家!"
+      description="让爸爸妈妈帮你添加,开始快乐成长之旅"
+    >
+      <template #action>
+        <PlayfulButton variant="clay" size="lg" @click="goToAddChild">
+          ➕ 添加我
+        </PlayfulButton>
+      </template>
+    </BaseEmpty>
+
+<!-- ====== 主内容(有孩子时) ====== -->
+<view class="main-content" v-if="!loading && hasChildren">
+<!-- ===== A) 顶部 Banner ===== -->
+<!-- 通关引导进度条 -->
+<GateGuideBar :unlockStatus="unlockStatus" />
+
+<!-- 五维能量沙盘 -->
+<GateLock v-if="sandboxLocked && sandboxLockGate" :gate="sandboxLockGate" />
+<WuxingSandbox
+  v-else-if="sandboxData"
+  mode="live"
+  :scores="familySandboxScores"
+  :overallScore="familySandboxOverall"
+  :members="familySandboxMembers"
+  :personalData="sandboxPersonalData"
+  :dimensions="energyDimensions"
+  :totalEnergy="totalEnergy"
+  :totalHealthIndex="totalHealthIndex"
+  :familyUnlocked="familyUnlocked"
+  :currentView="currentSandboxView"
+  :memberCount="familyMembersVisible.length"
+  starHeight="400rpx"
+  showDimensionBar
+  @view-change="onSandboxViewChange"
+  @point-click="onDomainClick" />
+<!-- 家庭能量横条 -->
+<FamilyEnergyBar
+  v-if="hasChildren"
+  dimensionCode="child"
+  :sandboxData="sandboxData" />
+
+<!-- 家庭成员选择条 -->
+<MemberSelector
+  variant="strip"
+  v-if="hasChildren && familyMembersVisible.length > 0"
+  :members="familyMembersVisible"
+  :selectedMemberId="selectedMemberId"
+  @select="onFamilyMemberSelect"
+  @removed="loadFamilyMembersVisible" />
+
+<!-- 连续打卡徽章 -->
+<view
+class="streak-badge-wrap animate-slide-up animate-stagger-3"
+v-if="streakDays > 0"
+@click="showStreakDetail"
+hover-class="streak-pressed"
+>
+<text class="streak-fire">🔥</text>
+<view class="streak-info">
+<text class="streak-days">{{ streakDays }}</text>
+<text class="streak-unit">天坚持</text>
+</view>
+</view>
+
+<!-- ============================================================
+B) 积分卡片 — Claymorphism 暖橙黏土风格
+============================================================ -->
+      <view
+        class="points-card animate-bounce-in"
+        @click="goToRewards"
+        hover-class="points-card-pressed"
+      >
+        <!-- 装饰圆点 -->
+        <view class="points-deco points-deco--1"></view>
+        <view class="points-deco points-deco--2"></view>
+
+        <view class="points-top">
+          <view class="points-left">
+            <text class="points-label">🌟 我的星星</text>
+            <text class="points-number">{{ totalPoints }}</text>
+          </view>
+          <view class="points-level-tag">
+            <text class="level-emoji">{{ levelEmoji }}</text>
+            <text class="level-name">{{ levelName }}</text>
+          </view>
+        </view>
+
+        <view class="points-progress">
+          <text class="progress-hint" v-if="pointsToNext > 0">
+            还差 <text class="progress-hint-num">{{ pointsToNext }}</text> 颗星星升到 {{ nextLevelName }}
+          </text>
+          <text class="progress-hint" v-else>
+            🎉 已经是最高等级啦!
+          </text>
+          <view class="progress-track">
+            <view
+              class="progress-fill"
+              :style="'width: ' + levelProgress + '%'"
+            ></view>
+          </view>
+        </view>
+      </view>
+
+      <!-- ============================================================
+      C) 今日任务 — 可水平滑动的黏土卡片
+      ============================================================ -->
+      <view class="tasks-section animate-slide-up animate-stagger-3">
+        <view class="section-header">
+          <text class="section-title">📋 今日任务</text>
+          <text class="section-link" @click="goToTasks">查看全部 →</text>
+        </view>
+
+        <scroll-view
+          class="tasks-scroll"
+          scroll-x
+          enhanced
+          :show-scrollbar="false"
+          v-if="todayTasks.length > 0"
+        >
+          <view
+            class="task-clay-card"
+            v-for="(task, tIdx) in todayTasks"
+            :key="task.id"
+            @click="handleTaskClick(task)"
+            hover-class="task-card-pressed"
+            :style="'animation-delay: ' + (tIdx * 80) + 'ms'"
+          >
+            <view class="task-card-icon-wrap" :class="'task-cat--' + getTaskCategoryClass(task)">
+              <text class="task-card-icon">{{ getTaskIcon(task) }}</text>
+            </view>
+            <text class="task-card-name">{{ task.title }}</text>
+            <text class="task-card-points" v-if="task.points">+{{ task.points }}⭐</text>
+            <view class="task-card-badge">
+              <BaseBadge :variant="getBadgeVariant(task)" size="xs">
+                {{ getStatusText(task) }}
+              </BaseBadge>
+            </view>
+          </view>
+        </scroll-view>
+
+        <!-- 今日任务空状态 -->
+        <view class="tasks-empty" v-else @click="goToTasks">
+          <view class="tasks-empty-icon">🎯</view>
+          <text class="tasks-empty-title">今天没有任务哦</text>
+          <text class="tasks-empty-desc">让爸爸妈妈给你布置任务吧~</text>
+        </view>
+      </view>
+
+      <!-- ============================================================
+      D) 快捷操作 — 2×2 黏土按钮网格
+      ============================================================ -->
+      <view class="quick-section animate-slide-up animate-stagger-4">
+        <view class="section-header">
+          <text class="section-title">🚀 快捷入口</text>
+        </view>
+        <view class="quick-grid">
+          <view
+            class="quick-clay-btn"
+            @click="goToTasks"
+            hover-class="quick-btn-pressed"
+          >
+            <view class="quick-btn-icon quick-icon--tasks">
+              <text class="quick-emoji">📋</text>
+            </view>
+            <text class="quick-btn-label">我的任务</text>
+          </view>
+
+          <view
+            class="quick-clay-btn"
+            @click="goToRewards"
+            hover-class="quick-btn-pressed"
+          >
+            <view class="quick-btn-icon quick-icon--rewards">
+              <text class="quick-emoji">⭐</text>
+            </view>
+            <text class="quick-btn-label">心愿商城</text>
+          </view>
+
+          <view
+            class="quick-clay-btn"
+            @click="goToGrowth"
+            hover-class="quick-btn-pressed"
+          >
+            <view class="quick-btn-icon quick-icon--growth">
+              <text class="quick-emoji">🏆</text>
+            </view>
+            <text class="quick-btn-label">成长记录</text>
+          </view>
+
+          <view
+            class="quick-clay-btn"
+            @click="goToGames"
+            hover-class="quick-btn-pressed"
+          >
+            <view class="quick-btn-icon quick-icon--games">
+              <text class="quick-emoji">🎮</text>
+            </view>
+            <text class="quick-btn-label">小游戏</text>
+          </view>
+
+          <view
+            class="quick-clay-btn"
+            @click="goToSelfQuiz"
+            hover-class="quick-btn-pressed"
+          >
+            <view class="quick-btn-icon quick-icon--quiz">
+              <text class="quick-emoji">✏️</text>
+            </view>
+            <text class="quick-btn-label">自己出题</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- ============================================================
+      E) 最近成就 — 水平滚动徽章
+      ============================================================ -->
+      <view
+        class="achievement-section animate-slide-up animate-stagger-5"
+        v-if="earnedBadges.length > 0"
+      >
+        <view class="section-header">
+          <text class="section-title">🏅 最近成就</text>
+        </view>
+        <scroll-view
+          class="achievement-scroll"
+          scroll-x
+          enhanced
+          :show-scrollbar="false"
+        >
+          <view
+            class="achievement-item"
+            v-for="badge in earnedBadges"
+            :key="badge.id"
+            hover-class="achievement-pressed"
+          >
+            <view class="achievement-icon-wrap">
+              <text class="achievement-icon">{{ badge.icon }}</text>
+            </view>
+            <text class="achievement-name">{{ badge.name }}</text>
+          </view>
+
+          <!-- 锁定占位徽章 -->
+          <view
+            class="achievement-item achievement-item--locked"
+            v-for="lockedBadge in lockedBadges"
+            :key="lockedKey(lockedBadge)"
+          >
+            <view class="achievement-icon-wrap achievement-icon-wrap--locked">
+              <text class="achievement-icon achievement-icon--locked">🔒</text>
+            </view>
+            <text class="achievement-name achievement-name--locked">{{ lockedBadge.name }}</text>
+          </view>
+        </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>
+
+      <!-- ===== 推荐商品+活动(混合信息流) ===== -->
+      <RecommendedFeed
+        v-if="hasChildren"
+        :products="dimensionProducts"
+        :activities="dimensionActivities"
+        @itemClick="onFeedItemClick"
+        @more="goMoreDiscover" />
+
+      <!-- 底部安全留白 -->
+      <view class="bottom-spacer"></view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getChildren, getTodayTasks, getEnergyOverview, getEnergyLogs, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getFamilyMemberList, getGuideConfig, getGuidePopupList, consumeGuidePopup } from '../../utils/api.js'
+import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
+import RecommendedFeed from '../../components/RecommendedFeed.vue'
+import PlayfulCard from '../../components/PlayfulCard.vue'
+import PlayfulButton from '../../components/PlayfulButton.vue'
+import BaseBadge from '../../components/BaseBadge.vue'
+import BaseEmpty from '../../components/BaseEmpty.vue'
+import BaseLoading from '../../components/BaseLoading.vue'
+import GateLock from '../../components/gate-lock.vue'
+import GateGuideBar from '@/pages/home-pages/components/gate-guide-bar.vue'
+import OnboardingGuide from '../../components/OnboardingGuide.vue'
+import ConfigurableGuide from '../../components/ConfigurableGuide.vue'
+import ConfettiCelebration from '@/pages/home-pages/components/ConfettiCelebration.vue'
+import MemberSelector from '@/components/MemberSelector.vue'
+
+/**
+ * 等级配置表
+ * 星星宝宝(0) → 月亮小朋友(200) → 太阳小天使(500) → 宇宙小英雄(1000)
+ */
+var LEVELS = [
+  { min: 0,    max: 200,  name: '星星宝宝',   nextName: '月亮小朋友', emoji: '⭐' },
+  { min: 200,  max: 500,  name: '月亮小朋友', nextName: '太阳小天使', emoji: '🌙' },
+  { min: 500,  max: 1000, name: '太阳小天使', nextName: '宇宙小英雄', emoji: '☀️' },
+  { min: 1000, max: Infinity, name: '宇宙小英雄', nextName: '👑 MAX', emoji: '🦸' }
+]
+
+/**
+ * 任务分类 → 图标映射
+ */
+var TASK_ICONS = {
+  '学习类': '📚',
+  '阅读类': '📖',
+  '生活类': '🏠',
+  '运动类': '⚽',
+  '家务类': '🧹',
+  '艺术类': '🎨',
+  '其他': '⭐'
+}
+import fontScaleMixin from '@/mixins/font-scale.js'
+
+export default {
+  mixins: [fontScaleMixin],
+  components: {
+    FamilyEnergyBar,
+    WuxingSandbox,
+    RecommendedFeed,
+    PlayfulCard,
+    PlayfulButton,
+    BaseBadge,
+    BaseEmpty,
+    BaseLoading,
+    OnboardingGuide,
+    ConfigurableGuide,
+    ConfettiCelebration,
+    MemberSelector,
+    GateLock,
+    GateGuideBar
+  },
+  data() {
+    return {
+      loading: true,
+      showOnboarding: false,
+      hasChildren: false,
+      guidePopup: null,
+      showGuidePopup: false,
+      _guidePopupAttempted: false,
+      nickname: '',
+      memberId: '',
+      totalPoints: 0,
+      streakDays: 0,
+      completedCount: 0,
+      totalTaskCount: 0,
+      todayTasks: [],
+      badges: [
+        { id: 1, icon: '🌟', name: '早起鸟', unlocked: false },
+        { id: 2, icon: '📚', name: '阅读小达人', unlocked: false },
+        { id: 3, icon: '🏃', name: '运动健将', unlocked: false },
+        { id: 4, icon: '🎨', name: '小艺术家', unlocked: false },
+        { id: 5, icon: '🧹', name: '家务小能手', unlocked: false }
+      ],
+      _mounted: false,
+      energyDimensions: [],
+      totalEnergy: 0,
+      totalHealthIndex: 0,
+      energyLogs: [],
+      sandboxData: null,
+      sandboxPersonalData: null,
+      sandboxFamilyData: null,
+      sandboxLocked: false,
+      sandboxLockGate: null,
+      currentSandboxView: 'personal',
+      familyUnlocked: false,
+      unlockStatus: null,
+      dimensionActivities: [],
+      dimensionProducts: [],
+      familyMembersVisible: [],
+      selectedMemberId: null
+    }
+  },
+  computed: {
+    // ---------- 能量沙盘 ----------
+    sandboxScores() {
+      if (!this.sandboxData) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
+      return {
+        body: this.sandboxData.bodyScore || 0,
+        mind: this.sandboxData.mindScore || 0,
+        wisdom: this.sandboxData.wisdomScore || 0,
+        action: this.sandboxData.actionScore || 0,
+        wealth: this.sandboxData.wealthScore || 0
+      }
+    },
+    familySandboxScores() {
+      var d = this.sandboxFamilyData || {}
+      return {
+        body: d.bodyScore || 0,
+        mind: d.mindScore || 0,
+        wisdom: d.wisdomScore || 0,
+        action: d.actionScore || 0,
+        wealth: d.wealthScore || 0
+      }
+    },
+    familySandboxOverall() {
+      return this.sandboxFamilyData ? (this.sandboxFamilyData.overallScore || 0) : 0
+    },
+    familySandboxMembers() {
+      return this.sandboxFamilyData ? (this.sandboxFamilyData.members || []) : []
+    },
+    // ---------- 等级相关 ----------
+    currentLevelIndex() {
+      const pts = this.totalPoints
+      if (pts >= 1000) return 3
+      if (pts >= 500) return 2
+      if (pts >= 200) return 1
+      return 0
+    },
+    levelName() {
+      return LEVELS[this.currentLevelIndex].name
+    },
+    nextLevelName() {
+      return LEVELS[this.currentLevelIndex].nextName
+    },
+    levelEmoji() {
+      return LEVELS[this.currentLevelIndex].emoji
+    },
+    currentLevelMin() {
+      return LEVELS[this.currentLevelIndex].min
+    },
+    nextLevelMax() {
+      return LEVELS[this.currentLevelIndex].max
+    },
+    levelProgress() {
+      if (this.totalPoints >= 1000) return 100
+      const range = this.nextLevelMax - this.currentLevelMin
+      if (range <= 0) return 100
+      const progress = ((this.totalPoints - this.currentLevelMin) / range) * 100
+      return Math.min(100, Math.floor(progress))
+    },
+    pointsToNext() {
+      if (this.totalPoints >= 1000) return 0
+      return this.nextLevelMax - this.totalPoints
+    },
+
+    // ---------- 任务相关 ----------
+    taskProgress() {
+      if (this.totalTaskCount === 0) return 0
+      return Math.floor((this.completedCount / this.totalTaskCount) * 100)
+    },
+
+    // ---------- 庆祝动画 ----------
+    showConfetti() {
+      return this.taskProgress >= 100 && this.totalTaskCount > 0
+    },
+
+    // ---------- 头像表情 ----------
+    avatarEmoji() {
+      const level = this.currentLevelIndex
+      if (level >= 3) return '🦸'
+      if (level >= 2) return '😇'
+      if (level >= 1) return '🌙'
+      return '🐣'
+    },
+
+    // ---------- 问候语 ----------
+    timeGreeting() {
+      const hour = new Date().getHours()
+      if (hour < 6) return '🌛 夜深了,还没睡呀'
+      if (hour < 9) return '🌅 早安!新的一天开始啦'
+      if (hour < 12) return '☀️ 上午好!今天也要加油哦'
+      if (hour < 14) return '🌤️ 中午好!吃饱了才有力气'
+      if (hour < 18) return '🌻 下午好!继续保持好状态'
+      if (hour < 21) return '🌆 晚上好!今天过得开心吗'
+      return '🌛 该休息啦,明天继续加油'
+    },
+
+    // ---------- 成就徽章 ----------
+    earnedBadges() {
+      return this.badges.filter(function(b) { return b.unlocked })
+    },
+    lockedBadges() {
+      return this.badges.filter(function(b) { return !b.unlocked })
+    }
+  },
+  mounted() {
+    // 首次登录引导:异步拉取配置并按版本号判断是否展示
+    this._checkOnboardingShow()
+    this.loadData()
+    this.loadGuidePopup()
+  },
+  onShow() {
+    if (this._mounted) {
+      this.loadData()
+    }
+    this._mounted = true
+  },
+  methods: {
+    onFamilyMemberSelect(member) {
+      this.selectedMemberId = member.id
+    },
+    loadFamilyMembersVisible() {
+      var self = this
+      getFamilyMemberList({ visibleOnly: true }).then(function(res) {
+        self.familyMembersVisible = res.data || []
+      }).catch(function(e) {
+        console.log('获取可见家庭成员失败', e)
+        self.familyMembersVisible = []
+      })
+    },
+    // ======================== 数据加载 ========================
+    async loadData() {
+      this.loading = true
+      try {
+        const userInfo = uni.getStorageSync('userInfo')
+        const role = uni.getStorageSync('currentRole') || 'child'
+        if (role !== 'child') {
+          uni.switchTab({ url: '/pages/index-home/index' })
+          return
+        }
+        this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : '小朋友'
+
+        const childrenRes = await getFamilyMemberList({ visibleOnly: true })
+        var allMembers = childrenRes.data || []
+        if (!Array.isArray(allMembers)) allMembers = []
+        // 同步设置 familyMembersVisible,避免 loadFamilyMembersVisible() 再调一次
+        this.familyMembersVisible = allMembers
+        const children = allMembers.filter(function(m) { return m.effectiveRole === 'child' || m.source !== 'parent' })
+        if (children.length === 0) {
+          this.hasChildren = false
+          this.loading = false
+          return
+        }
+        this.hasChildren = true
+        const child = children[0] || {}
+        this.memberId = child.id || ''
+        this.totalPoints = child.totalPoints || 0
+        this.streakDays = child.streakDays || 0
+
+        const tasksRes = await getTodayTasks()
+        const rawTasks = tasksRes.data || []
+        this.todayTasks = rawTasks
+        this.completedCount = rawTasks.filter(function(t) { return t.status === 'completed' }).length
+        this.totalTaskCount = rawTasks.length
+
+        // 解锁成就
+        if (this.streakDays >= 7) {
+          this.unlockBadge(1)
+        }
+        if (this.totalPoints >= 1000) {
+          this.unlockBadge(2)
+        }
+
+        // 按类别统计已完成任务次数,解锁对应徽章
+        var categoryCompletedCount = { '运动类': 0, '艺术类': 0, '家务类': 0 }
+        for (var i = 0; i < rawTasks.length; i++) {
+          var task = rawTasks[i]
+          if (task.status === 'completed') {
+            var cat = task.category || '其他'
+            if (cat === '运动类') {
+              categoryCompletedCount['运动类']++
+            } else if (cat === '艺术类') {
+              categoryCompletedCount['艺术类']++
+            } else if (cat === '家务类') {
+              categoryCompletedCount['家务类']++
+            }
+          }
+        }
+        if (categoryCompletedCount['运动类'] >= 5) {
+          this.unlockBadge(3)
+        }
+        if (categoryCompletedCount['艺术类'] >= 5) {
+          this.unlockBadge(4)
+        }
+        if (categoryCompletedCount['家务类'] >= 5) {
+          this.unlockBadge(5)
+        }
+
+        // 加载五维能量概览(memberId 由后端从 JWT 派生)
+        try {
+          var overviewRes = await getEnergyOverview()
+          if (overviewRes && overviewRes.data) {
+            this.energyDimensions = overviewRes.data.dimensions || []
+            this.totalEnergy = overviewRes.data.totalEnergy || 0
+            this.totalHealthIndex = overviewRes.data.totalHealthIndex || 0
+          }
+        } catch (e) {
+          console.log('获取能量概览失败', e)
+        }
+
+        // 加载最近能量变动
+        try {
+          var logsRes = await getEnergyLogs('', 1, 5)
+          if (logsRes && logsRes.data) {
+            this.energyLogs = logsRes.data.records || logsRes.data || []
+          }
+        } catch (e) {
+          console.log('获取能量日志失败', e)
+          this.energyLogs = []
+        }
+
+        // 加载通关解锁状态
+        try {
+          var statusRes = await getUnlockStatus()
+          if (statusRes && statusRes.data) {
+            this.unlockStatus = statusRes.data
+          }
+        } catch (e) {
+          console.log('获取通关解锁状态失败', e)
+        }
+
+        // 加载沙盘数据(含通关门禁,一次返回个人+全家)
+        try {
+          var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId') || null
+          var sandboxRes = await getEnergySandbox(this.currentSandboxView, memberId)
+          if (sandboxRes && sandboxRes.data) {
+            var ed = sandboxRes.data
+            if (ed.locked) {
+              this.sandboxLocked = true
+              this.sandboxLockGate = ed.gate || null
+              this.sandboxData = null
+              this.sandboxPersonalData = null
+              this.sandboxFamilyData = null
+            } else {
+              this.sandboxLocked = false
+              this.sandboxLockGate = null
+              this.sandboxPersonalData = ed.personalData || null
+              this.sandboxFamilyData = ed.familyData || null
+              this.sandboxData = this.currentSandboxView === 'family' ? (ed.familyData || null) : (ed.personalData || null)
+              this.familyUnlocked = ed.familyUnlocked || false
+            }
+          }
+        } catch (e) {
+          console.log('获取沙盘数据失败', e)
+        }
+
+        // 家庭成员已在上方 loadData 开头通过 getFamilyMemberList 一次获取,不再重复调用
+        // this.loadFamilyMembersVisible()
+
+        // 加载维度活动
+        try {
+          var actRes = await getActivityList({ dimensionCode: 'child', page: 1, size: 3 })
+          if (actRes && actRes.data) {
+            var actList = Array.isArray(actRes.data) ? actRes.data : (actRes.data.records || [])
+            this.dimensionActivities = actList.slice(0, 3)
+          }
+        } catch (e) {
+          console.log('获取活动数据失败', e)
+        }
+
+        // 加载维度商品
+        try {
+          var prodRes = await getProductsByDomain('child', 1, 4)
+          if (prodRes && prodRes.data) {
+            var prodList = Array.isArray(prodRes.data) ? prodRes.data : (prodRes.data.records || [])
+            this.dimensionProducts = prodList.slice(0, 4)
+          }
+        } catch (e) {
+          console.log('获取商品数据失败', e)
+        }
+      } catch (e) {
+        console.error('加载数据失败', e)
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      } finally {
+        this.loading = false
+        this.$emit('ready')
+      }
+    },
+    unlockBadge(id) {
+      var badge = this.badges.find(function(b) { return b.id === id })
+      if (badge) badge.unlocked = true
+    },
+
+    // ======================== 导航方法 ========================
+    onDomainClick(evt) {
+      var code = evt && evt.code
+      if (!code || !this.memberId) return
+      uni.navigateTo({
+        url: '/pages/energy/detail?memberId=' + this.memberId + '&code=' + code
+      })
+    },
+    /** 切换沙盘个人/全家视角(本地切换,不重新请求) */
+    onSandboxViewChange: function(view) {
+      if (view === this.currentSandboxView) return
+      this.currentSandboxView = view
+      this.sandboxData = view === 'family' ? (this.sandboxFamilyData || null) : (this.sandboxPersonalData || null)
+    },
+    goToRewards() {
+      this.$emit('navigate', { page: 'rewards' })
+      uni.navigateTo({ url: '/pages/rewards/rewards' })
+    },
+    goToTasks() {
+      this.$emit('navigate', { page: 'tasks' })
+      uni.navigateTo({ url: '/pages/tasks/tasks' })
+    },
+    goToGrowth() {
+      this.$emit('navigate', { page: 'growth' })
+      uni.switchTab({ url: '/pages/growth-main/index' })
+    },
+    goToGames() {
+      this.$emit('navigate', { page: 'games' })
+      uni.navigateTo({ url: '/pages/games/games' })
+    },
+    goToSelfQuiz() {
+      this.$emit('navigate', { page: 'self-quiz' })
+      uni.navigateTo({ url: '/pages/wisdom-detail/self-quiz' })
+    },
+    goToAddChild() {
+      this.$emit('navigate', { page: 'add-child' })
+      uni.navigateTo({ url: '/pages/profile-extra/family-members' })
+    },
+    showStreakDetail() {
+      this.$emit('navigate', { page: 'streak-detail' })
+      uni.showToast({ title: '连续打卡 ' + this.streakDays + ' 天啦!太棒了!', icon: 'none' })
+    },
+    goToEnergyDetail() {
+      if (this.memberId) {
+        uni.navigateTo({ url: '/pages/energy/detail?memberId=' + this.memberId + '&code=all' })
+      }
+    },
+    showAchievements() {
+      uni.navigateTo({ url: '/pages/achievement/index' })
+    },
+    switchMode() {
+      uni.showModal({
+        title: '切换模式',
+        content: '确定要切换回家长模式吗?',
+        success: function(res) {
+          if (res.confirm) {
+            uni.switchTab({ url: '/pages/index-home/index' })
+          }
+        }
+      })
+    },
+    _checkOnboardingShow() {
+      var self = this
+      getGuideConfig().then(function(res) {
+        var cfg = (res && res.data) ? res.data : null
+        var cfgVersion = cfg ? (cfg.version || '1.0') : '1.0'
+        // 检查启用
+        if (cfg && cfg.enabled === false) return
+        // 检查位置
+        if (cfg && cfg.positions && cfg.positions.indexOf('child') === -1) return
+        // 比较版本
+        var stored = uni.getStorageSync('_onboardingDone')
+        var seenVersion = '0.0'
+        if (stored) {
+          try {
+            var obj = typeof stored === 'string' ? JSON.parse(stored) : stored
+            if (obj && obj.version) seenVersion = obj.version
+          } catch (e) {}
+        }
+        if (self._compareVersion(seenVersion, cfgVersion) >= 0) return
+        self.showOnboarding = true
+      }).catch(function() {
+        // 接口失败按旧逻辑:无配置则 version='1.0',已看过则不显示
+        var stored = uni.getStorageSync('_onboardingDone')
+        if (stored) {
+          try {
+            var obj = typeof stored === 'string' ? JSON.parse(stored) : stored
+            if (obj && obj.version && self._compareVersion(obj.version, '1.0') >= 0) return
+          } catch (e) {}
+        }
+        self.showOnboarding = true
+      })
+    },
+    loadGuidePopup() {
+      var self = this
+      if (self._guidePopupAttempted) return
+      self._guidePopupAttempted = true
+      getGuidePopupList().then(function(res) {
+        var arr = (res && res.data) || []
+        if (arr && arr.length > 0) {
+          self.guidePopup = arr[0]
+          self.showGuidePopup = true
+        }
+      }).catch(function() {})
+    },
+    handleGuidePopupDone(payload) {
+      var popup = payload && payload.popup
+      var buttonClicked = payload && payload.buttonClicked
+      if (popup && popup.id) {
+        consumeGuidePopup(popup.id, buttonClicked)
+      }
+      this.showGuidePopup = false
+      if (buttonClicked && payload && payload.btnAction) {
+        uni.navigateTo({ url: payload.btnAction })
+      }
+    },
+    _compareVersion(v1, v2) {
+      var parts1 = String(v1).split('.').map(function(s) { return parseInt(s || '0', 10) })
+      var parts2 = String(v2).split('.').map(function(s) { return parseInt(s || '0', 10) })
+      for (var i = 0; i < Math.max(parts1.length, parts2.length); i++) {
+        if ((parts1[i] || 0) !== (parts2[i] || 0)) return (parts1[i] || 0) - (parts2[i] || 0)
+      }
+      return 0
+    },
+    goToDomain(domain) {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
+    goToEnergyDetail(code) {
+      // 跳转到能量详情页(T10 创建)
+      var memberId = this.memberId
+      if (!memberId || !code) return
+      uni.navigateTo({
+        url: '/pages/energy/detail?memberId=' + memberId + '&code=' + code
+      })
+    },
+
+    // ======================== 推荐信息流 ========================
+    onFeedItemClick: function(item) {
+      if (item.type === 'product') {
+        uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + item.id })
+      } else if (item.type === 'activity') {
+        uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + item.id })
+      }
+    },
+    goMoreDiscover: function() {
+      uni.navigateTo({ url: '/pages/discover/index' })
+    },
+
+    // ======================== 任务交互 ========================
+    handleTaskClick(task) {
+      if (!task || !task.id) return
+      this.$emit('navigate', { page: 'task-detail', taskId: task.id })
+
+      // 如果任务尚未完成,弹出确认完成
+      if (task.status !== 'completed' && task.status !== 'overdue') {
+        uni.showModal({
+          title: '完成任务',
+          content: '确定完成「' + task.title + '」吗?',
+          success: function(res) {
+            if (res.confirm) {
+              task.status = 'completed'
+              // 重新计算完成数
+              this.completedCount = this.todayTasks.filter(function(t) { return t.status === 'completed' }).length
+              if (task.points) {
+                uni.showToast({ title: '🎉 获得 ' + task.points + ' 颗星星!', icon: 'success' })
+              }
+            }
+          }.bind(this)
+        })
+      }
+    },
+
+    // ======================== 任务工具方法 ========================
+    getTaskIcon(task) {
+      if (!task) return '📝'
+      var cat = task.category || '其他'
+      return TASK_ICONS[cat] || '📝'
+    },
+    getTaskCategoryClass(task) {
+      if (!task) return 'default'
+      var cat = task.category || '其他'
+      var map = {
+        '学习类': 'study',
+        '阅读类': 'reading',
+        '生活类': 'life',
+        '运动类': 'sports',
+        '家务类': 'chores',
+        '艺术类': 'art'
+      }
+      return map[cat] || 'default'
+    },
+    getTaskStatus(task) {
+      if (!task || !task.status) return 'pending'
+      return task.status
+    },
+    getStatusText(task) {
+      var status = this.getTaskStatus(task)
+      if (status === 'completed') return '✅ 已完成'
+      if (status === 'overdue') return '⏰ 已超时'
+      return '⏳ 待完成'
+    },
+    getBadgeVariant(task) {
+      var status = this.getTaskStatus(task)
+      if (status === 'completed') return 'success'
+      if (status === 'overdue') return 'error'
+      return 'warning'
+    },
+    lockedKey(item) {
+      return 'locked-' + item.id
+    }
+  }
+}
+</script>
+
+<style scoped lang="scss">
+// ===== 孩子首页专属色阶(天空蓝渐变族 + 徽章金;单页专用,约定见 uni.scss §14)=====
+$c-ci-sky:    #E0ECF8; // 顶部渐变终(浅天蓝)
+$c-ci-navy:   #1E4A7A; // 深海军蓝文字
+$c-ci-gold:   #FFB800; // 徽章金渐变终
+$c-ci-blue-1: #DCE8F5; // 空态卡渐变起
+$c-ci-blue-2: #C8DDF0; // 空态卡渐变终
+
+/* ============================================================
+   知心益家 — 孩子端首页 Flat Rounded 设计系统
+   色彩体系:品牌橙($brand-primary)+浅橙($brand-primary-light) | 双重阴影 | 4rpx边框
+   参考:uni.scss --color-primary, --bg, --shadow-clay, --radius-md
+   ============================================================ */
+
+/* ----------------------------------------------------------
+   容器 & 背景
+   ---------------------------------------------------------- */
+.container {
+  min-height: 100vh;
+  background: var(--bg, $uni-bg-color);
+  padding: 0 32rpx 48rpx;
+}
+
+.main-content {
+  padding-top: 16rpx;
+}
+
+.bottom-spacer {
+  height: 48rpx;
+}
+
+/* 连续打卡徽章 — 独立行 */
+.streak-badge-wrap {
+display: flex;
+align-items: center;
+gap: 10rpx;
+justify-content: flex-end;
+margin-bottom: 24rpx;
+padding-top: 4rpx;
+}
+
+.streak-badge {
+  display: flex;
+  align-items: center;
+  gap: 10rpx;
+  background: linear-gradient(145deg, $uni-bg-color, $c-ci-sky);
+  padding: 12rpx 24rpx 12rpx 18rpx;
+  border-radius: 40rpx;
+  border: 4rpx solid $brand-primary-light;
+  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
+              4rpx 4rpx 14rpx rgba(251,146,60,0.30);
+  flex-shrink: 0;
+  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+.streak-badge:active,
+.streak-pressed {
+  transform: scale(0.94);
+}
+
+.streak-fire {
+  font-size: calc(32rpx * var(--font-scale, 1));
+}
+
+.streak-info {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  line-height: 1.2;
+}
+
+.streak-days {
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: 800;
+  color: $c-ci-navy;
+  line-height: 1.1;
+}
+
+.streak-unit {
+  font-size: calc(18rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: $status-info-deep;
+}
+
+/* ----------------------------------------------------------
+   积分卡片 — Flat Rounded 橙色渐变
+   ---------------------------------------------------------- */
+.points-card {
+  position: relative;
+  background: linear-gradient(145deg, $brand-primary-light, $brand-primary);
+  border-radius: 32rpx;
+  padding: 32rpx;
+  margin-bottom: 28rpx;
+  border: 4rpx solid $brand-primary-dark;
+  box-shadow: inset -4rpx -4rpx 14rpx rgba(255,255,255,0.20),
+              8rpx 8rpx 28rpx rgba(249,115,22,0.25);
+  overflow: hidden;
+  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+.points-card:active,
+.points-card-pressed {
+  transform: scale(0.97);
+  box-shadow: inset 2rpx 2rpx 10rpx rgba(0,0,0,0.08),
+              4rpx 4rpx 16rpx rgba(249,115,22,0.18);
+}
+
+/* 装饰圆 */
+.points-deco {
+  position: absolute;
+  border-radius: 50%;
+  background: rgba(255,255,255,0.08);
+  pointer-events: none;
+}
+
+.points-deco--1 {
+  top: -60rpx;
+  right: -40rpx;
+  width: 220rpx;
+  height: 220rpx;
+}
+
+.points-deco--2 {
+  bottom: -30rpx;
+  left: -30rpx;
+  width: 120rpx;
+  height: 120rpx;
+}
+
+.points-top {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 20rpx;
+  position: relative;
+  z-index: 1;
+}
+
+.points-left {
+  display: flex;
+  flex-direction: column;
+}
+
+.points-label {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: rgba(255,255,255,0.85);
+  font-weight: 500;
+  margin-bottom: 4rpx;
+}
+
+.points-number {
+  font-size: calc(64rpx * var(--font-scale, 1));
+  font-weight: 800;
+  color: $app-surface;
+  line-height: 1.1;
+  text-shadow: 0 2rpx 8rpx rgba(0,0,0,0.10);
+}
+
+.points-level-tag {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  background: rgba(255,255,255,0.15);
+  padding: 12rpx 20rpx;
+  border-radius: 24rpx;
+  border: 2rpx solid rgba(255,255,255,0.20);
+  min-width: 120rpx;
+}
+
+.level-emoji {
+  font-size: calc(36rpx * var(--font-scale, 1));
+  line-height: 1;
+}
+
+.level-name {
+  font-size: calc(22rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: $accent-gold;
+  margin-top: 4rpx;
+}
+
+/* 进度条 */
+.points-progress {
+  position: relative;
+  z-index: 1;
+}
+
+.progress-hint {
+  display: block;
+  font-size: calc(22rpx * var(--font-scale, 1));
+  color: rgba(255,255,255,0.75);
+  margin-bottom: 12rpx;
+}
+
+.progress-hint-num {
+  font-weight: 700;
+  color: $accent-gold;
+  font-size: calc(24rpx * var(--font-scale, 1));
+}
+
+.progress-track {
+  height: 14rpx;
+  background: rgba(255,255,255,0.20);
+  border-radius: 7rpx;
+  overflow: hidden;
+  border: 2rpx solid rgba(255,255,255,0.10);
+}
+
+.progress-fill {
+  height: 100%;
+  background: linear-gradient(90deg, $accent-gold, $c-ci-gold);
+  border-radius: 6rpx;
+  transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
+}
+
+/* ----------------------------------------------------------
+   任务区域 — 水平滚动黏土卡片
+   ---------------------------------------------------------- */
+.tasks-section {
+  margin-bottom: 28rpx;
+}
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+
+.section-title {
+  font-size: calc(32rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: var(--text, $uni-text-color);
+}
+
+.section-link {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: var(--color-primary, $brand-primary);
+  padding: 8rpx 4rpx;
+}
+
+.section-link:active {
+  opacity: 0.6;
+}
+
+/* 任务水平滚动容器 */
+.tasks-scroll {
+  display: flex;
+  flex-direction: row;
+  white-space: nowrap;
+  overflow-x: auto;
+  padding: 8rpx 0 16rpx;
+  -webkit-overflow-scrolling: touch;
+}
+
+/* 单张任务黏土卡片 */
+.task-clay-card {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  width: 200rpx;
+  min-height: 260rpx;
+  background: linear-gradient(145deg, $app-surface, var(--bg-grey, $uni-bg-color-grey));
+  border-radius: 28rpx;
+  padding: 24rpx 16rpx 20rpx;
+  margin-right: 20rpx;
+  border: 4rpx solid var(--border, $uni-border-color);
+  box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
+              6rpx 6rpx 20rpx rgba(249,115,22,0.08);
+  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+  animation: slideUp 0.4s cubic-bezier(0.16, 1, 0.3, 1) both;
+  flex-shrink: 0;
+}
+
+.task-clay-card:active,
+.task-card-pressed {
+  transform: scale(0.94);
+}
+
+/* 任务分类图标容器 */
+.task-card-icon-wrap {
+  width: 88rpx;
+  height: 88rpx;
+  border-radius: 24rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 12rpx;
+  border: 3rpx solid var(--border-light, $uni-border-color-light);
+  box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
+              3rpx 3rpx 8rpx rgba(249,115,22,0.06);
+}
+
+.task-card-icon {
+  font-size: 42rpx;
+  line-height: 1;
+}
+
+/* 分类图标底色 */
+.task-cat--study   .task-card-icon-wrap { background: rgba(14,165,233,0.12); border-color: $tint-sky-line; }
+.task-cat--reading .task-card-icon-wrap { background: rgba(34,197,94,0.12);  border-color: $tint-green-line; }
+.task-cat--life    .task-card-icon-wrap { background: rgba(2,132,199,0.12); border-color: $uni-border-color; }
+.task-cat--sports  .task-card-icon-wrap { background: rgba(59,130,246,0.12); border-color: $tint-blue-line; }
+.task-cat--chores  .task-card-icon-wrap { background: rgba(245,158,11,0.12); border-color: $status-warning-light; }
+.task-cat--art     .task-card-icon-wrap { background: rgba(236,72,153,0.12); border-color: $tint-pink-line; }
+.task-cat--default .task-card-icon-wrap { background: rgba(148,163,184,0.12); border-color: $uni-border-color-light; }
+
+.task-card-name {
+  font-size: calc(26rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: var(--text, $uni-text-color);
+  text-align: center;
+  line-height: 1.3;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+  max-width: 100%;
+  margin-bottom: 6rpx;
+}
+
+.task-card-points {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  font-weight: 700;
+  color: var(--color-primary, $brand-primary);
+  margin-bottom: 10rpx;
+}
+
+.task-card-badge {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  min-height: 36rpx;
+}
+
+/* 任务空状态 */
+.tasks-empty {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 48rpx 32rpx;
+  background: var(--surface, $app-surface);
+  border-radius: 28rpx;
+  border: 4rpx solid var(--border, $uni-border-color);
+  box-shadow: var(--shadow-clay);
+  transition: transform 0.2s ease;
+}
+
+.tasks-empty:active {
+  transform: scale(0.98);
+}
+
+.tasks-empty-icon {
+  font-size: 64rpx;
+  margin-bottom: 16rpx;
+}
+
+.tasks-empty-title {
+  font-size: calc(30rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: var(--text, $uni-text-color);
+  margin-bottom: 8rpx;
+}
+
+.tasks-empty-desc {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  color: var(--text-secondary, $uni-text-color-secondary);
+}
+
+/* ----------------------------------------------------------
+   快捷操作 — 2×2 黏土按钮网格
+   ---------------------------------------------------------- */
+.quick-section {
+  margin-bottom: 28rpx;
+}
+
+.quick-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+.quick-grid .quick-clay-btn {
+  width: calc(50% - 10rpx);
+}
+
+.quick-clay-btn {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding: 28rpx 16rpx;
+  background: linear-gradient(145deg, $app-surface, var(--bg-grey, $uni-bg-color-grey));
+  border-radius: 28rpx;
+  border: 4rpx solid var(--border, $uni-border-color);
+  box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
+              6rpx 6rpx 20rpx rgba(249,115,22,0.08);
+  transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
+  min-height: 180rpx;
+}
+
+.quick-clay-btn:active,
+.quick-btn-pressed {
+  transform: scale(0.94);
+  box-shadow: inset 2rpx 2rpx 8rpx rgba(0,0,0,0.06),
+              3rpx 3rpx 10rpx rgba(249,115,22,0.06);
+}
+
+.quick-btn-icon {
+  width: 96rpx;
+  height: 96rpx;
+  border-radius: 28rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 14rpx;
+  border: 3rpx solid var(--border-light, $uni-border-color-light);
+  box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
+              3rpx 3rpx 8rpx rgba(249,115,22,0.06);
+  transition: transform 0.2s ease;
+}
+
+.quick-emoji {
+  font-size: calc(44rpx * var(--font-scale, 1));
+  line-height: 1;
+}
+
+/* 各按钮图标色彩 */
+.quick-icon--tasks   { background: rgba(14,165,233,0.15); border-color: $tint-sky-line; }
+.quick-icon--rewards { background: rgba(245,158,11,0.15); border-color: $status-warning-light; }
+.quick-icon--growth  { background: rgba(2,132,199,0.15); border-color: $uni-border-color; }
+.quick-icon--games   { background: rgba(236,72,153,0.12); border-color: $tint-pink-line; }
+.quick-icon--quiz    { background: rgba(99,102,241,0.15); border-color: $tint-indigo-line; }
+
+.quick-btn-label {
+  font-size: 28rpx;
+  font-weight: 700;
+  color: var(--text, $uni-text-color);
+}
+
+/* ----------------------------------------------------------
+   成就区域 — 水平滚动徽章
+   ---------------------------------------------------------- */
+.achievement-section {
+  margin-bottom: 28rpx;
+}
+
+.achievement-scroll {
+  display: flex;
+  flex-direction: row;
+  white-space: nowrap;
+  overflow-x: auto;
+  padding: 8rpx 0 8rpx;
+  -webkit-overflow-scrolling: touch;
+}
+
+.achievement-item {
+  display: inline-flex;
+  flex-direction: column;
+  align-items: center;
+  width: 160rpx;
+  margin-right: 20rpx;
+  transition: transform 0.2s ease;
+  flex-shrink: 0;
+}
+
+.achievement-item:active,
+.achievement-pressed {
+  transform: scale(0.92);
+}
+
+.achievement-icon-wrap {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  background: linear-gradient(145deg, $c-ci-blue-1, $c-ci-blue-2);
+  border: 4rpx solid var(--border, $uni-border-color);
+  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
+              4rpx 4rpx 14rpx rgba(249,115,22,0.08);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+
+.achievement-icon {
+  font-size: 44rpx;
+  line-height: 1;
+}
+
+.achievement-name {
+  font-size: calc(24rpx * var(--font-scale, 1));
+  font-weight: 600;
+  color: var(--text, $uni-text-color);
+  text-align: center;
+}
+
+/* 锁定徽章 */
+.achievement-item--locked {
+  opacity: 0.50;
+}
+
+.achievement-icon-wrap--locked {
+  background: var(--bg-grey, $uni-bg-color-grey);
+  border-color: var(--border-light, $uni-border-color-light);
+  box-shadow: none;
+}
+
+.achievement-icon--locked {
+  filter: grayscale(1);
+}
+
+.achievement-name--locked {
+  color: var(--muted, $app-text-subtle);
+}
+
+/* ----------------------------------------------------------
+   加载 & 通用
+   ---------------------------------------------------------- */
+.loading-state {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 200rpx 0;
+}
+
+/* ----------------------------------------------------------
+   能量变动日志
+   ---------------------------------------------------------- */
+.energy-log-section { margin-bottom: 28rpx; }
+.energy-log-list { background: $app-surface; 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 $facade-fill;
+}
+.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: calc(26rpx * var(--font-scale, 1)); color: $facade-text-strong; display: block; }
+.log-time { font-size: calc(20rpx * var(--font-scale, 1)); color: $facade-text-muted; margin-top: 4rpx; display: block; }
+.log-amount { font-size: calc(28rpx * var(--font-scale, 1)); font-weight: 700; margin-left: 12rpx; }
+.log-amount--up { color: $status-success; }
+.log-amount--down { color: $status-danger; }
+
+/* 连续打卡徽章的浮动动画 — 使用 uni.scss 全局 float keyframe */
+.streak-badge {
+  animation: float 3s ease-in-out infinite;
+}
+
+/* 等级标签的金色光晕 — 使用 uni.scss 全局 pulseGlow keyframe */
+.points-level-tag {
+  animation: pulseGlow 2.5s ease-in-out infinite;
+}
+</style>

+ 5 - 2285
cfc-frontend/pages/home-pages/parent-index.vue

@@ -1,2294 +1,14 @@
 <template>
-<view class="container" :style="{ '--font-scale': fontScale }">
-<!-- 加载状态 -->
-<BaseLoading :loading="loading" text="正在加载..." />
-
-<template v-if="!loading">
-<!-- ===== a) 顶部 Banner ===== -->
-<!-- ===== 新手任务进度条(onboarding 置顶引导) ===== -->
-<OnboardingProgressBar ref="onboardingProgress" />
-
-<!-- ===== 家庭成员选择条 ===== -->
-<MemberSelector
-  variant="strip"
-  v-if="isLoggedIn"
-  :members="familyMembersVisible"
-  :selectedMemberId="selectedMemberId"
-  :isParent="isFamilyAdmin"
-  :pendingCounts="memberPendingCounts"
-  @select="onFamilyMemberSelect"
-  @switch="onFamilyMemberSwitch"
-  @removed="loadFamilyMembersVisible" />
-
-<!-- ===== 无家庭成员空状态引导卡 ===== -->
-<view class="empty-family-card" v-if="isLoggedIn && familyMembersVisible.length === 0">
-  <view class="empty-family-icon">🏠</view>
-  <text class="empty-family-title">创建我的家庭</text>
-  <text class="empty-family-desc">添加家庭成员,开启五维能量之旅</text>
-  <button class="empty-family-btn" @click="goAddMember">立即添加</button>
-</view>
-
-<!-- ===== 微行动卡片(P0:有效启动) ===== -->
-<MicroActionCard
-  v-if="selectedMemberId && selectedChild && selectedChild.id"
-  :memberId="selectedChild.id"
-  @completed="onMicroActionCompleted" />
-
-<!-- ===== 健康预警横幅(P1-2) ===== -->
-<HealthAlertBanner
-  v-if="selectedMemberId && selectedChild && selectedChild.id"
-  :alerts="healthAlerts"
-  @dismiss="onAlertDismiss" />
-
-      <!-- ===== 家庭健康排行榜(P2-2) ===== -->
-        <FamilyHealthRanking
-          v-if="rankingMembers && rankingMembers.length > 0"
-          :list="rankingMembers"
-          :totalScore="rankingTotalScore" />
-
-      <!-- ===== 家庭挑战区域 ===== -->
-      <view class="challenge-section">
-        <view class="challenge-section-header" @click="goToChallengeManage">
-          <text class="challenge-section-title">家庭挑战</text>
-          <text class="challenge-section-action">管理 →</text>
-        </view>
-        <FamilyChallengeCard
-          v-for="ch in activeChallenges"
-          :key="ch.id"
-          :challenge="ch"
-          @cardClick="goToChallengeManage(ch.id)" />
-      </view>
-
-    <!-- ===== 健康圈入口(P3:社区扩散) ===== -->
-    <view class="circle-entry-section">
-      <!-- 有健康圈数据:显示排行榜预览 -->
-      <PlayfulCard v-if="circleName && circleRankings.length > 0" variant="flat" shadow="sm" :clickable="true" @click="goToHealthCircle" class="circle-card-wide">
-        <view class="circle-card-content">
-          <view class="circle-header">
-            <view class="circle-icon-wrap">
-              <text class="circle-icon">🏅</text>
-            </view>
-            <view class="circle-header-body">
-              <text class="circle-card-title">健康圈</text>
-              <text class="circle-card-subtitle">{{ circleName }}</text>
-            </view>
-          </view>
-          <view class="circle-ranking-preview">
-            <view class="circle-rank-row" v-for="(item, idx) in circleRankings.slice(0, 3)" :key="idx">
-              <text class="circle-rank-badge">{{ rankIcon(idx) }}</text>
-              <text class="circle-rank-family">家庭 {{ idx + 1 }}</text>
-              <text class="circle-rank-score">{{ item.totalScore || 0 }} 分</text>
-            </view>
-          </view>
-          <view class="circle-action">
-            <text class="circle-action-text">查看详情 →</text>
-          </view>
-        </view>
-      </PlayfulCard>
-      <!-- 无健康圈数据:邀请加入 -->
-      <PlayfulCard v-else variant="flat" shadow="sm" :clickable="true" @click="goToHealthCircle" class="circle-card-wide circle-card-invite">
-        <view class="circle-invite-content">
-          <view class="circle-invite-icon">🏅</view>
-          <text class="circle-invite-title">健康圈</text>
-          <text class="circle-invite-desc">加入健康圈,和更多家庭一起健康打卡</text>
-          <text class="circle-invite-cta">去看看 →</text>
-        </view>
-      </PlayfulCard>
-    </view>
-
-    <!-- ===== 五维家庭自检入口(P0-1/P0-2) ===== -->
-    <view class="selfcheck-entry-section">
-      <PlayfulCard variant="flat" shadow="sm" :clickable="true" @click="goToSelfCheck" class="selfcheck-card-wide">
-        <view class="selfcheck-card-content">
-          <view class="selfcheck-icon-wrap">
-            <view class="selfcheck-icon-circle selfcheck-icon-body">
-              <text class="selfcheck-icon-char">身</text>
-            </view>
-            <view class="selfcheck-icon-circle selfcheck-icon-wisdom">
-              <text class="selfcheck-icon-char">智</text>
-            </view>
-            <view class="selfcheck-icon-circle selfcheck-icon-wealth">
-              <text class="selfcheck-icon-char">富</text>
-            </view>
-            <view class="selfcheck-icon-circle selfcheck-icon-action">
-              <text class="selfcheck-icon-char">行</text>
-            </view>
-            <view class="selfcheck-icon-circle selfcheck-icon-mind">
-              <text class="selfcheck-icon-char">心</text>
-            </view>
-          </view>
-          <view class="selfcheck-body">
-            <text class="selfcheck-title">健康自检问卷</text>
-            <text class="selfcheck-subtitle" v-if="selfCheckScore != null">
-              已完成健康自检 · 点击再次填写
-            </text>
-            <text class="selfcheck-subtitle" v-else>
-              4步问卷 · 完成自检解锁能量沙盘
-            </text>
-          </view>
-          <text class="selfcheck-arrow">→</text>
-        </view>
-      </PlayfulCard>
-    </view>
-
-    <!-- ===== 家庭成员关系图谱 ===== -->
-<FamilyRelationGraph
-  v-if="familyMembersVisible.length > 0"
-  dimensionCode="home"
-  :selfId="selfMemberId"
-  :members="familyMembersVisible"
-  :energyMap="energyMapForGraph"
-  :intimacyMap="intimacyMapForGraph"
-  @memberTap="goMemberDetail" />
-
-      <!-- 通关引导进度条 -->
-      <GateGuideBar :unlockStatus="unlockStatus" />
-
-      <!-- ===== 新增:首页广告栏 ===== -->
-      <AdBanner
-        :activities="recommendedActivities"
-        :products="recommendedProducts"
-        :autoPlayInterval="3000"
-        :swiperDuration="500" />
-      <!-- ===== 能量沙盘 ===== -->
-      <GateLock v-if="sandboxLocked && sandboxLockGate" :gate="sandboxLockGate" />
-      <WuxingSandbox
-        v-else-if="sandboxData"
-        mode="live"
-        :scores="familySandboxScores"
-        :overallScore="familySandboxOverall"
-        :members="familySandboxMembers"
-        :personalData="sandboxPersonalData"
-        :dimensions="energyDimensions"
-        :totalEnergy="totalEnergy"
-        :totalHealthIndex="totalHealthIndex"
-        :familyUnlocked="familyUnlocked"
-        :currentView="currentSandboxView"
-        :memberCount="familyMembersVisible.length"
-        starHeight="400rpx"
-        showDimensionBar
-        @view-change="onSandboxViewChange"
-        @point-click="onDomainClick" />
-
-      <!-- 能量数据加载失败兜底 -->
-      <view class="error-state" v-if="errorEnergy && !sandboxData && !sandboxLocked" @click="loadData">
-        <text class="error-state-text">家庭能量数据加载失败,点击重试</text>
-      </view>
-
-      <!-- ===== c) 成长数据 ===== -->
-      <view class="stats-section animate-fade-in animate-stagger-5">
-        <view class="section-title">成长数据</view>
-        <view class="stats-row">
-          <!-- 任务完成率:自己的 + 全家平均 -->
-          <PlayfulCard variant="flat" shadow="sm" :clickable="true" @click="goToCompletionStats" class="stat-card-wide" padding="0">
-            <view class="stat-card-content">
-              <view class="stat-header">
-                <view class="stat-icon-wrap stat-icon-orange">
-                  <text class="stat-icon-symbol">📋</text>
-                </view>
-                <view class="stat-body">
-                  <text class="stat-label-header">任务完成</text>
-                </view>
-              </view>
-              <view class="stat-compare">
-                <view class="stat-compare-row">
-                  <text class="compare-label">自己的</text>
-                  <text class="compare-value accent-color">{{ myCompletionRate }}%</text>
-                </view>
-                <view class="progress-bar-sm">
-                  <view class="progress-fill-sm" :style="{ width: myCompletionRate + '%' }"></view>
-                </view>
-                <view class="stat-compare-row">
-                  <text class="compare-label">全家平均</text>
-                  <text class="compare-value">{{ familyAvgCompletionRate }}%</text>
-                </view>
-                <view class="progress-bar-sm">
-                  <view class="progress-fill-sm muted" :style="{ width: familyAvgCompletionRate + '%' }"></view>
-                </view>
-              </view>
-              <text class="stat-more">查看分析 ›</text>
-            </view>
-          </PlayfulCard>
-
-          <!-- 成长积分:自己的 + 全家共 -->
-          <PlayfulCard variant="flat" shadow="sm" :clickable="true" @click="goToRewards" class="stat-card-wide" padding="0">
-            <view class="stat-card-content">
-              <view class="stat-header">
-                <view class="stat-icon-wrap stat-icon-gold">
-                  <text class="stat-icon-symbol">⭐</text>
-                </view>
-                <view class="stat-body">
-                  <text class="stat-label-header">成长积分</text>
-                </view>
-              </view>
-              <view class="stat-compare">
-                <view class="stat-compare-row">
-                  <text class="compare-label">自己的</text>
-                  <text class="compare-value gold-color">{{ myPoints }}</text>
-                </view>
-                <view class="stat-compare-row">
-                  <text class="compare-label">全家共</text>
-                  <text class="compare-value">{{ familyTotalPoints }}</text>
-                </view>
-              </view>
-              <text class="stat-encourage" v-if="myPoints > 0">太棒了!</text>
-              <text class="stat-encourage muted" v-else>开始赚星星吧</text>
-            </view>
-          </PlayfulCard>
-        </view>
-      </view>
-
-      <!-- ===== d) 今日重点 ===== -->
-      <view class="today-section animate-fade-in animate-stagger-7">
-        <view class="section-title">📋 今日任务</view>
-        <PlayfulCard variant="flat" class="today-card">
-          <template slot="header">
-            <view class="today-header">
-              <text class="today-label">今日待完成</text>
-              <text class="today-count">{{ pendingTasks.length }}项任务</text>
-            </view>
-          </template>
-          <view class="task-list" v-if="pendingTasks.length > 0">
-            <view class="task-item" v-for="task in pendingTasks.slice(0, 3)" :key="task.id">
-              <view class="task-dot" :class="'dot-color-' + (task.id % 5)"></view>
-              <text class="task-title">{{ task.title }}</text>
-            </view>
-          </view>
-          <BaseEmpty v-else icon="🎉" title="今日任务已完成" description="太棒了!" :compact="true" />
-          <view slot="actions" class="task-view-all" @click="goToTaskList">
-            <text>查看全部</text>
-            <text class="arrow">→</text>
-          </view>
-        </PlayfulCard>
-      </view>
-
-      <!-- ===== e) 成长里程碑 ===== -->
-      <view class="milestone-section animate-fade-in animate-stagger-8">
-        <view class="section-title">成长里程碑</view>
-        <PlayfulCard variant="flat" class="milestone-card">
-          <view class="milestone-item" v-if="selectedChild && selectedChild.streakDays >= 7">
-            <view class="milestone-icon-wrap milestone-star">
-              <text class="milestone-icon">🌟</text>
-            </view>
-            <text class="milestone-text">连续7天打卡</text>
-          </view>
-          <view class="milestone-item" v-if="selectedChild && selectedChild.streakDays >= 30">
-            <view class="milestone-icon-wrap milestone-trophy">
-              <text class="milestone-icon">🏆</text>
-            </view>
-            <text class="milestone-text">坚持一个月啦!</text>
-          </view>
-          <view class="milestone-item" v-if="(selectedChild && selectedChild.totalPoints) >= 1000">
-            <view class="milestone-icon-wrap milestone-sparkle">
-              <text class="milestone-icon">💫</text>
-            </view>
-            <text class="milestone-text">积分突破1000</text>
-          </view>
-          <BaseEmpty v-if="!hasMilestones" icon="🌱" title="开始养成好习惯" description="见证成长" :compact="true" />
-        </PlayfulCard>
-      </view>
-
-      <!-- ===== f) 待处理事项 ===== -->
-      <view class="pending-section animate-fade-in animate-stagger-9">
-        <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>
-
-      <!-- ===== 重要关系(外部通讯录) ===== -->
-      <view class="contact-section" v-if="!loading && familyMembersVisible.length > 0">
-        <view class="contact-header">
-          <text class="contact-section-title">重要关系</text>
-          <view class="contact-header-right">
-            <text class="contact-more" @click="loadContacts">刷新</text>
-            <text class="contact-import-btn" @click="onShowImport">+ 导入</text>
-          </view>
-        </view>
-        <scroll-view class="contact-scroll" scroll-x enable-flex>
-          <view class="contact-scroll-inner">
-            <view class="contact-card-wrap" v-for="item in contactList" :key="item.id" @click="onContactClick(item)">
-              <ContactCard :contact="item" />
-            </view>
-            <view class="contact-card-wrap contact-add-card" @click="onShowImport">
-              <view class="add-card-inner">
-                <text class="add-card-icon">+</text>
-                <text class="add-card-label">导入通讯录</text>
-              </view>
-            </view>
-          </view>
-        </scroll-view>
-        <view class="contact-empty" v-if="contactList.length === 0">
-          <text class="contact-empty-text">暂无重要联系人,点击导入添加</text>
-        </view>
-      </view>
-
-      <!-- 导入联系人弹窗 -->
-      <ContactImport
-        :visible="showImportModal"
-        @close="onCloseImport"
-        @success="onImportSuccess" />
-      
-      <!-- 邀请码二维码弹窗 -->
-      <view class="qr-code-modal" v-if="showQrCodeModal" @touchmove.stop.prevent @click="onCloseQrCodeModal">
-        <view class="qr-code-modal-content" @click.stop>
-          <view class="qr-code-modal-header">
-            <text class="qr-code-modal-title">邀请二维码</text>
-            <text class="qr-code-modal-close" @click="onCloseQrCodeModal">×</text>
-          </view>
-          <view class="qr-code-modal-body">
-            <view class="qr-code-image-container">
-              <image :src="(qrCodeData && qrCodeData.qrCodeBase64) || ''" class="qr-code-image" mode="aspectFit" />
-            </view>
-            <view class="qr-code-referral-code">
-              <text class="qr-code-label">邀请码:</text>
-              <text class="qr-code-code">{{ (qrCodeData && qrCodeData.referralCode) || '' }}</text>
-              <text class="qr-code-copy" @click="onCopyReferralCode">复制</text>
-            </view>
-          </view>
-        </view>
-      </view>
-
-      <!-- ===== 推荐商品+活动(混合信息流) ===== -->
-      <RecommendedFeed
-        :products="recommendedProducts"
-        :activities="recommendedActivities"
-        :loading="loadingProducts || loadingActivities"
-        @itemClick="onFeedItemClick"
-        @more="goMoreDiscover" />
-      <view class="error-state" v-if="errorProducts || errorActivities">
-        <text class="error-state-text">推荐加载失败</text>
-      </view>
-
-      <!-- ===== 推荐阅读(书册风格) ===== -->
-      <view class="article-section animate-fade-in animate-stagger-11">
-        <view class="article-header">
-          <view class="section-title">📖 推荐阅读</view>
-          <text class="article-more" @click="goArticles">更多 ›</text>
-        </view>
-        <ArticleBookshelf
-          :articles="indexArticles"
-          :loading="articlesLoading"
-          :isLoggedIn="isLoggedIn"
-          @articleClick="goArticleDetail"
-        />
-      </view>
-
-      <!-- ===== h) 待审核任务 ===== -->
-      <view class="review-section animate-fade-in animate-stagger-11" v-if="pendingReviewTasks.length > 0">
-        <view class="section-title">待审核任务</view>
-        <PlayfulCard
-          variant="flat"
-          shadow="sm"
-          class="review-card"
-          v-for="task in pendingReviewTasks"
-          :key="task.id"
-          padding="0"
-        >
-          <view class="review-card-content">
-            <view class="review-info">
-              <text class="task-name">{{ task.title }}</text>
-              <text class="task-child" v-if="task.childName">提供者: {{ task.childName }}</text>
-            </view>
-            <view class="review-actions">
-              <PlayfulButton variant="success" size="xs" @click="approveTask(task.id)">通过</PlayfulButton>
-              <PlayfulButton variant="error" size="xs" @click="rejectTask(task.id)">拒绝</PlayfulButton>
-            </view>
-          </view>
-        </PlayfulCard>
-      </view>
-
-      <!-- ===== g) 快捷操作 ===== -->
-      <view class="quick-section animate-fade-in animate-stagger-10">
-        <view class="section-title">快捷操作</view>
-        <PlayfulCard variant="flat" class="quick-card">
-          <view class="quick-actions">
-            <view class="action-item" @click="addTask">
-              <view class="action-icon act-add">
-                <text class="action-icon-text">➕</text>
-              </view>
-              <text class="action-label">添加任务</text>
-            </view>
-            <view class="action-item" @click="goToReview">
-              <view class="action-icon act-review">
-                <text class="action-icon-text">📋</text>
-              </view>
-              <text class="action-label">审核任务</text>
-            </view>
-            <view class="action-item" @click="manageChildren">
-              <view class="action-icon act-child">
-                <text class="action-icon-text">👨‍👩‍👧</text>
-              </view>
-              <text class="action-label">成员管理</text>
-            </view>
-            <view class="action-item" @click="goToRewards">
-              <view class="action-icon act-reward">
-                <text class="action-icon-text">🎁</text>
-              </view>
-              <text class="action-label">奖励中心</text>
-            </view>
-            <!-- Added: QR Code button -->
-            <view class="action-item" @click="onShowQrCodeModal">
-              <view class="action-icon act-invite">
-                <text class="action-icon-text">📱</text>
-              </view>
-              <text class="action-label">邀请码</text>
-            </view>
-          </view>
-        </PlayfulCard>
-      </view>
-    </template>
-    <!-- ===== L1 即时反馈:Toast + 庆祝 ===== -->
-    <FeedbackToast
-      :visible="toastVisible"
-      :text="toastText"
-      :icon="toastIcon"
-      :animating="toastAnimating" />
-    <MilestoneCelebration
-      :visible="celebrationVisible"
-      :title="celebrationTitle"
-      :subtitle="celebrationSubtitle"
-      :particles="celebrationParticles"
-      @dismiss="celebrationVisible = false" />
-    <!-- ===== L2 深度反馈:每日健康小语 ===== -->
-    <DailySummary
-      v-if="selectedMemberId && selectedChild && selectedChild.id"
-      :memberId="selectedChild.id"
-      @dismiss="onDailySummaryDismiss" />
-  </view>
+  <view class="redirect-shell"></view>
 </template>
 
 <script>
-import { getFamilyMemberList, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergySandbox, getUnlockStatus, getEnergyOverview, getActivityList, getProductsByDomain, getContactList, getFeaturedArticles, getActiveAlerts, dismissAlert, getFamilyRanking, getChallengeList, getCircleList, getCircleRanking, getInviteQrCode, switchToFamilyMember, getSelfCheckStatus, getTodayTasks } from '../../utils/api.js'
-import store from '../../store/index.js'
-import { BrandColors } from '../../utils/brand-colors.js'
-import PlayfulCard from '../../components/PlayfulCard.vue'
-import PlayfulButton from '../../components/PlayfulButton.vue'
-import BaseBadge from '../../components/BaseBadge.vue'
-import BaseEmpty from '../../components/BaseEmpty.vue'
-import BaseLoading from '../../components/BaseLoading.vue'
-import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
-import WuxingSandbox from '../../components/wuxing-sandbox.vue'
-import ContactCard from '@/pages/home-pages/components/ContactCard.vue'
-import FamilyHealthRanking from '@/pages/home-pages/components/FamilyHealthRanking.vue'
-import FamilyChallengeCard from '../../components/FamilyChallengeCard.vue'
-import ContactImport from '@/pages/home-pages/components/ContactImport.vue'
-import RecommendedFeed from '../../components/RecommendedFeed.vue'
-import MemberSelector from '@/components/MemberSelector.vue'
-import MicroActionCard from '@/pages/home-pages/components/MicroActionCard.vue'
-import FeedbackToast from '@/pages/home-pages/components/FeedbackToast.vue'
-import MilestoneCelebration from '@/pages/home-pages/components/MilestoneCelebration.vue'
-import DailySummary from '@/pages/home-pages/components/DailySummary.vue'
-import HealthAlertBanner from '@/pages/home-pages/components/HealthAlertBanner.vue'
-import CircleRanking from '@/pages/home-pages/components/CircleRanking.vue'
-import AdBanner from '@/pages/home-pages/components/AdBanner.vue'
-import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
-import OnboardingProgressBar from '../../components/OnboardingProgressBar.vue'
-import GateLock from '../../components/gate-lock.vue'
-import GateGuideBar from '@/pages/home-pages/components/gate-guide-bar.vue'
-import feedbackEngine from '../../utils/feedback-engine.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
+// 旧路径跳转壳:/pages/home-pages/parent-index → /pages/home-pages/family-index
+import { redirectToNewPath } from '../../utils/redirect.js'
 
 export default {
-  mixins: [fontScaleMixin],
-  components: { RecommendedFeed, PlayfulCard, PlayfulButton, BaseBadge, BaseEmpty, BaseLoading, FamilyRelationGraph, WuxingSandbox, ContactCard, ContactImport, MemberSelector, MicroActionCard, FeedbackToast, MilestoneCelebration, DailySummary, HealthAlertBanner, FamilyHealthRanking, FamilyChallengeCard, CircleRanking, ArticleBookshelf, OnboardingProgressBar, GateLock, GateGuideBar, AdBanner },
-  data() {
-    return {
-      nickname: '',
-      familyMembers: [],
-      children: [],
-      selectedChild: null,
-      pendingReviewTasks: [],
-      pendingReviewWishes: [],
-      pendingWishesCount: 0,
-      pendingTasks: [],
-      memberPendingCounts: {},
-      hotActivityCount: 0,
-      hasNewReport: false,
-      myCompletionRate: 0,
-      familyAvgCompletionRate: 0,
-      myPoints: 0,
-      familyTotalPoints: 0,
-      // membershipText removed (no real function)
-      loading: true,
-      currentChildId: '',
-      _initialLoadDone: false,
-      _familyId: null,
-      familyMembersVisible: [],
-      selfMemberId: null,
-      energyData: null,
-      energyDimensions: [],
-      totalEnergy: 0,
-      totalHealthIndex: 0,
-      recommendedActivities: [],
-      recommendedProducts: [],
-      sandboxData: null,
-      sandboxPersonalData: null,
-      sandboxFamilyData: null,
-      errorEnergy: false,
-      // 通关解锁状态
-      sandboxLocked: false,
-      sandboxLockGate: null,
-      currentSandboxView: 'personal',
-      familyUnlocked: false,
-      familyLockGate: null,
-      unlockStatus: null,
-      errorTasks: false,
-      errorActivities: false,
-      errorProducts: false,
-      loadingActivities: false,
-      loadingProducts: false,
-      contactList: [],
-      showImportModal: false,
-      // QR Code modal
-      showQrCodeModal: false,
-      qrCodeData: null,
-      // 推荐阅读
-      indexArticles: [],
-      articlesLoading: false,
-      isLoggedIn: false,
-      gradientColors: [
-        'linear-gradient(135deg, ' + BrandColors.success + ', ' + BrandColors.successLight3 + ')',
-        'linear-gradient(135deg, ' + BrandColors.infoSky + ', ' + BrandColors.infoLight + ')',
-        'linear-gradient(135deg, ' + BrandColors.violet + ', ' + BrandColors.violetLighter + ')',
-        'linear-gradient(135deg, ' + BrandColors.primary + ', ' + BrandColors.primaryLight + ')',
-        'linear-gradient(135deg, ' + BrandColors.wisdom + ', ' + BrandColors.wisdomLight + ')'
-      ],
-      selectedMemberId: null,
-      // L1 即时反馈状态
-      toastVisible: false,
-      toastText: '',
-      toastIcon: '✅',
-      toastAnimating: false,
-      celebrationVisible: false,
-      celebrationTitle: '',
-      celebrationSubtitle: '',
-      celebrationParticles: [],
-      // P1-2 健康预警
-      healthAlerts: [],
-      // P2-2 家庭互动
-      rankingMembers: [],
-      rankingTotalScore: 0,
-      activeChallenges: [],
-      // P3 健康圈(社区扩散)
-      circleRankings: [],
-      circleName: '',
-      circleLoading: false,
-      selfCheckScore: null
-    }
-  },
-  computed: {
-    isFamilyAdmin() {
-      if (!this.familyMembersVisible || this.familyMembersVisible.length === 0) return false
-      // isAdmin 语义为"成员本人是否为家庭创建者";自己(isSelf)是创建者即管理员
-      var me = this.familyMembersVisible.filter(function(m) { return m.isSelf })[0]
-      return !!(me && me.isAdmin === true)
-    },
-    topPendingTasks() {
-      return this.pendingTasks.slice(0, 3)
-    },
-    energyScores() {
-      var d = this.energyData
-      if (!d) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
-      return { body: d.bodyScore, mind: d.mindScore, wisdom: d.wisdomScore, action: d.actionScore, wealth: d.wealthScore }
-    },
-    energyOverall() {
-      return this.energyData ? this.energyData.overallScore : 0
-    },
-    energyMembers() {
-      return this.energyData ? this.energyData.members : []
-    },
-    sandboxScores() {
-      if (!this.sandboxData) return { body: 0, mind: 0, wisdom: 0, action: 0, wealth: 0 }
-      return {
-        body: this.sandboxData.bodyScore || 0,
-        mind: this.sandboxData.mindScore || 0,
-        wisdom: this.sandboxData.wisdomScore || 0,
-        action: this.sandboxData.actionScore || 0,
-        wealth: this.sandboxData.wealthScore || 0
-      }
-    },
-    familySandboxScores() {
-      var d = this.sandboxFamilyData || {}
-      return {
-        body: d.bodyScore || 0,
-        mind: d.mindScore || 0,
-        wisdom: d.wisdomScore || 0,
-        action: d.actionScore || 0,
-        wealth: d.wealthScore || 0
-      }
-    },
-    familySandboxOverall() {
-      return this.sandboxFamilyData ? (this.sandboxFamilyData.overallScore || 0) : 0
-    },
-    familySandboxMembers() {
-      return this.sandboxFamilyData ? (this.sandboxFamilyData.members || []) : []
-    },
-    greetingText() {
-      const hour = new Date().getHours()
-      if (hour < 6) return '夜深了'
-      if (hour < 9) return '早上好'
-      if (hour < 12) return '上午好'
-      if (hour < 14) return '中午好'
-      if (hour < 18) return '下午好'
-      if (hour < 22) return '晚上好'
-      return '夜深了'
-    },
-    hasMilestones() {
-      const c = this.selectedChild
-      if (!c) return false
-      return (c.streakDays >= 7) || (c.streakDays >= 30) || (c.totalPoints >= 1000)
-    },
-    energyMapForGraph() {
-      var map = {}
-      if (this.sandboxData && this.sandboxData.members) {
-        for (var i = 0; i < this.sandboxData.members.length; i++) {
-          var m = this.sandboxData.members[i]
-          map[m.memberId || m.id] = {
-            bodyScore: m.bodyScore || 0,
-            mindScore: m.mindScore || 0,
-            actionScore: m.actionScore || 0
-          }
-        }
-      }
-      return map
-    },
-    intimacyMapForGraph() {
-      return {}
-    }
-  },
-  // 使用 mounted 而非 onLoad,因为本组件作为条件渲染子组件(v-if),
-  // page 生命周期钩子(onLoad/onShow)可能在组件挂载前已触发完毕
-  mounted() {
-    if (uni.getStorageSync('token')) {
-      this.isLoggedIn = true
-      this.loadData()
-    }
-  },
-  onShow() {
-    // 首次 onShow 紧接 mounted 之后,跳过避免双重加载
-    if (!this._initialLoadDone) {
-      this._initialLoadDone = true
-      return
-    }
-    const currentRole = uni.getStorageSync('currentRole') || 'parent'
-    if (currentRole === 'child') {
-      uni.reLaunch({ url: '/pages/home-pages/child-index' })
-      return
-    }
-    if (uni.getStorageSync('token')) {
-      this.isLoggedIn = true
-      this.loadData()
-    }
-    if (this.$refs.onboardingProgress) {
-      this.$refs.onboardingProgress.reload()
-    }
-  },
-  onPullDownRefresh() {
-    this.loadData().finally(() => {
-      uni.stopPullDownRefresh()
-    })
-  },
-  methods: {
-    rankIcon: function(idx) {
-      var medals = ['🥇', '🥈', '🥉']
-      return medals[idx] || '#' + (idx + 1)
-    },
-    async loadData() {
-      this.loading = true
-      try {
-        var userInfo = uni.getStorageSync('userInfo')
-        this.nickname = (userInfo && userInfo.nickname) ? userInfo.nickname : ''
-        this.selfMemberId = uni.getStorageSync('userId') || null
-
-        // 优先使用仪表盘接口(包含孩子列表 + 统计数据)
-        let dashboardLoaded = false
-        var _membersVisibleSet = false
-        try {
-          const dashRes = await getParentDashboard()
-          const dashData = dashRes.data || {}
-          const dashChildren = dashData.children || []
-          // 过滤:仅保留孩子角色成员,排除家长/本人
-          var currentUserId = uni.getStorageSync('userId')
-          var filteredChildren = dashChildren.filter(function(c) {
-            return c && c.id && c.id !== currentUserId && (c.role === 'child' || c.memberType === 'child')
-          })
-          if (filteredChildren.length > 0) {
-            this.children = filteredChildren
-            // 不自动选孩子:默认显示家长自己,等待用户从 FamilyMemberStrip 选择
-            this.familyAvgCompletionRate = dashData.avgCompletionRate || 0
-            // 家长自身数据(积分 + 任务完成率)
-            this.myPoints = dashData.parentPoints || 0
-            this.myCompletionRate = dashData.parentCompletionRate || 0
-            // 从仪表盘获取 familyId
-            if (dashData.familyId) {
-              this._familyId = dashData.familyId
-            }
-            // 从仪表盘同步设置 familyMembersVisible(含家长本人),避免后续 loadFamilyMembersVisible() 再调 getFamilyMemberList
-            var dashMembers = dashData.members || dashData.familyMembers || filteredChildren
-            this.familyMembersVisible = dashMembers
-            _membersVisibleSet = true
-            dashboardLoaded = true
-          }
-        } catch (e) {
-          console.log('获取仪表盘数据失败,降级到单独接口', e)
-        }
-
-        // 降级:使用原有单独接口(仅调用一次 getFamilyMemberList,合并 children 与 familyMembers 派生)
-        if (!dashboardLoaded) {
-          try {
-            const currentUserId = uni.getStorageSync('userId')
-            const membersRes = await getFamilyMemberList({ visibleOnly: true, includeFamily: true })
-            const data = membersRes.data || {}
-            var members = data.members || data || []
-            if (!Array.isArray(members)) members = []
-            const parents = members.filter(function(p) { return p.source === 'parent' && p.id && p.id !== currentUserId }).map(function(p) { return { ...p, role: 'parent' } })
-            const children = members.filter(function(c) { return c.source !== 'parent' && c.id }).map(function(c) { return { ...c, role: 'child' } })
-            this.familyMembers = [].concat(parents, children)
-            // 从同一响应派生 children,避免再调 getChildren()(= getFamilyMemberList({}))
-            this.children = children
-            // 同步设置 familyMembersVisible,避免 loadFamilyMembersVisible() 重复调用 getFamilyMemberList
-            this.familyMembersVisible = members
-            _membersVisibleSet = true
-            // 不自动选孩子:默认显示家长自己,等待用户从 FamilyMemberStrip 选择
-            // 保存 familyId 用于后续请求
-            if (data.familyId) {
-              this._familyId = data.familyId
-            }
-          } catch (e) {
-            console.log('获取家庭成员失败', e)
-          }
-        }
-
-        const reviewRes = await getPendingReviewTasks()
-        const allReviewTasks = reviewRes.data || []
-        this.pendingReviewTasks = allReviewTasks.slice(0, 3)
-
-        try {
-          const wishRes = await getPendingWishes()
-          this.pendingReviewWishes = wishRes.data || []
-        } catch (e) {
-          console.log('获取待处理心愿失败', e)
-          this.pendingReviewWishes = []
-        }
-
-        const reviewMap = {}
-        const wishMap = {}
-        for (let i = 0; i < allReviewTasks.length; i++) {
-          const task = allReviewTasks[i]
-          const cid = task.memberId || task.assigneeId
-          if (cid) {
-            reviewMap[cid] = (reviewMap[cid] || 0) + 1
-          }
-        }
-        for (let j = 0; j < (this.pendingReviewWishes || []).length; j++) {
-          const wish = this.pendingReviewWishes[j]
-          const cid = wish.memberId
-          if (cid) {
-            wishMap[cid] = (wishMap[cid] || 0) + 1
-          }
-        }
-        this.children = this.children.map(function(c) {
-          return {
-            ...c,
-            pendingReviewCount: reviewMap[c.id] || 0,
-            pendingWishCount: wishMap[c.id] || 0
-          }
-        })
-
-        // 计算全家总积分(家长 + 所有孩子)
-        var totalPts = this.myPoints
-        for (var pi = 0; pi < this.children.length; pi++) {
-          totalPts += (this.children[pi].totalPoints || 0)
-        }
-        this.familyTotalPoints = totalPts
-
-        // 如果仪表盘未加载到数据,降级:从孩子数据取近似值
-        if (!dashboardLoaded) {
-          // 降级:家长积分取自己的(从缓存或API),完成率取第一个孩子的
-          const targetChild = this.selectedChild || (this.children.length > 0 ? this.children[0] : null)
-          if (targetChild) {
-            try {
-              const statsRes = await getChildCompletionStats({ memberId: targetChild.id })
-              if (statsRes && statsRes.data) {
-                this.myCompletionRate = statsRes.data.completionRate || 0
-              }
-            } catch (e) {
-              console.log('获取个人完成率失败', e)
-            }
-            userInfo = uni.getStorageSync('userInfo')
-            this.myPoints = (userInfo && userInfo.totalPoints) || 0
-            this.familyAvgCompletionRate = this.myCompletionRate
-          }
-        }
-
-        // 待完成任务 - 优先使用后端返回的真实任务列表
-        this.pendingTasks = []
-        if (this.children.length > 0) {
-          const targetChild = this.selectedChild || this.children[0]
-          try {
-            const r2 = await getChildCompletionStats({ memberId: targetChild.id })
-            if (r2 && r2.data && r2.data.pendingTaskList && r2.data.pendingTaskList.length > 0) {
-              this.pendingTasks = r2.data.pendingTaskList.slice(0, 3)
-            } else if (r2 && r2.data && r2.data.pendingTasks > 0) {
-              // 降级:只有数量没有列表时使用占位
-              for (let k = 0; k < Math.min(r2.data.pendingTasks, 3); k++) {
-                this.pendingTasks.push({ id: 'pending-' + k, title: '待完成任务' })
-              }
-            }
-            } catch (e) {
-            this.pendingTasks = []
-          }
-        }
-
-        // 加载通关解锁状态
-        try {
-          var statusRes = await getUnlockStatus()
-          if (statusRes && statusRes.data) {
-            this.unlockStatus = statusRes.data
-          }
-        } catch (e) {
-          console.log('获取通关解锁状态失败', e)
-        }
-
-        // 加载能量沙盘(含通关门禁)
-        try {
-          var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId') || null
-          var energyRes = await getEnergySandbox(this.currentSandboxView, memberId)
-          if (energyRes && energyRes.data) {
-            var ed = energyRes.data
-            if (ed.locked) {
-              this.sandboxLocked = true
-              this.sandboxLockGate = ed.gate || null
-              this.sandboxData = null
-              this.sandboxPersonalData = null
-              this.sandboxFamilyData = null
-              this.energyData = null
-            } else {
-              this.sandboxLocked = false
-              this.sandboxLockGate = null
-              this.sandboxPersonalData = ed.personalData || null
-              this.sandboxFamilyData = ed.familyData || null
-              this.sandboxData = this.currentSandboxView === 'family' ? (ed.familyData || null) : (ed.personalData || null)
-              this.energyData = ed.personalData || null
-              this.familyUnlocked = ed.familyUnlocked || false
-              this.familyLockGate = ed.familyGate || null
-            }
-          }
-        } catch (e) {
-          console.log('获取能量沙盘数据失败', e)
-          this.energyData = null
-          this.sandboxData = null
-          this.errorEnergy = true
-        }
-
-        // 加载全家今日任务(用于成员待完成数角标)
-        getTodayTasks(null, { familyWide: true }).then(function(res) {
-          if (res.code === 200 && Array.isArray(res.data)) {
-            this.memberPendingCounts = this._countTasksByMember(res.data)
-          }
-        }).catch(function() {
-          this.memberPendingCounts = {}
-        }.bind(this))
-
-        // 加载推荐活动
-        this.loadRecommendedActivities()
-        // 加载推荐商品
-        this.loadRecommendedProducts()
-
-        // 加载家庭成员可见列表(降级路径已在上面设置,此处跳过避免重复请求)
-        if (!_membersVisibleSet) {
-          this.loadFamilyMembersVisible()
-        }
-        this.loadSelfCheckStatus()
-        // 加载健康预警(P1-2)
-        this.loadAlerts()
-        // 加载家庭健康互动(P2-2)
-        this.loadFamilyInteraction()
-        // 加载健康圈(P3:社区扩散)
-        this.loadHealthCircle()
-        // 加载外部联系人
-        this.loadContacts()
-        // 加载推荐阅读
-        this.loadFeaturedArticles()
-
-        // 加载五维能量概览(账本模式)
-        var overviewChildId = this.currentChildId
-        if (overviewChildId) {
-          try {
-            var overviewRes = await getEnergyOverview(overviewChildId)
-            if (overviewRes && overviewRes.data) {
-              this.energyDimensions = overviewRes.data.dimensions || []
-              this.totalEnergy = overviewRes.data.totalEnergy || 0
-              this.totalHealthIndex = overviewRes.data.totalHealthIndex || 0
-            }
-          } catch (e) {
-            console.log('获取能量概览失败', e)
-          }
-        }
-      } catch (e) {
-        console.error('加载数据失败', e)
-      } finally {
-        this.loading = false
-        this.$emit('ready')
-      }
-    },
-    async approveTask(taskId) {
-      try {
-        await approveTask(taskId)
-        uni.showToast({ title: '已通过', icon: 'success' })
-        this.loadData()
-      } catch (e) {
-        uni.showToast({ title: '操作失败', icon: 'none' })
-      }
-    },
-    async rejectTask(taskId) {
-      try {
-        await rejectTask(taskId)
-        uni.showToast({ title: '已拒绝', icon: 'success' })
-        this.loadData()
-      } catch (e) {
-        uni.showToast({ title: '操作失败', icon: 'none' })
-      }
-    },
-    // 加载可见家庭成员
-    async loadFamilyMembersVisible() {
-      try {
-        var res = await getFamilyMemberList({ visibleOnly: true })
-        this.familyMembersVisible = res.data || []
-      } catch (e) {
-        console.log('获取可见家庭成员失败', e)
-        this.familyMembersVisible = []
-      }
-    },
-    // 成员选择
-    goToCompletionStats() {
-      if (this.children.length === 1) {
-        uni.navigateTo({ url: '/pages/stats/completion-rate?memberId=' + this.children[0].id })
-      } else {
-        uni.navigateTo({ url: '/pages/stats/completion-rate?familyView=1' })
-      }
-    },
-    onDomainClick(evt) {
-      this.goToDomain(evt.code)
-    },
-    /** 切换沙盘个人/全家视角(本地切换,不重新请求) */
-    onSandboxViewChange: function(view) {
-      if (view === this.currentSandboxView) return
-      this.currentSandboxView = view
-      this.sandboxData = view === 'family' ? (this.sandboxFamilyData || null) : (this.sandboxPersonalData || null)
-    },
-    /** 单独加载沙盘数据(首次进入时使用,一次返回个人+全家) */
-    loadSandboxData: async function() {
-      try {
-        var memberId = uni.getStorageSync('currentMemberId') || uni.getStorageSync('currentChildId') || null
-        var energyRes = await getEnergySandbox(this.currentSandboxView, memberId)
-        if (energyRes && energyRes.data) {
-          var ed = energyRes.data
-          if (ed.locked) {
-            this.sandboxLocked = true
-            this.sandboxLockGate = ed.gate || null
-            this.sandboxData = null
-            this.sandboxPersonalData = null
-            this.sandboxFamilyData = null
-          } else {
-            this.sandboxLocked = false
-            this.sandboxLockGate = null
-            this.sandboxPersonalData = ed.personalData || null
-            this.sandboxFamilyData = ed.familyData || null
-            this.sandboxData = this.currentSandboxView === 'family' ? (ed.familyData || null) : (ed.personalData || null)
-            this.familyUnlocked = ed.familyUnlocked || false
-            this.familyLockGate = ed.familyGate || null
-          }
-        }
-      } catch (e) {
-        console.log('加载沙盘数据失败', e)
-      }
-    },
-    goToDomain(code) {
-      // 跳转到能量详情页
-      var memberId = this.currentChildId
-      if (!memberId || !code) return
-      uni.navigateTo({
-        url: '/pages/energy/detail?memberId=' + memberId + '&code=' + code
-      })
-    },
-    onFamilyMemberSelect(member) {
-      this.selectedMemberId = member.id
-      // 选中的是孩子时,设置 selectedChild 以便子组件加载孩子数据
-      var matched = this.children.find(function(c) { return c.id === member.id })
-      if (matched) {
-        this.selectedChild = matched
-        this.currentChildId = matched.id
-      } else {
-        this.selectedChild = null
-        this.currentChildId = ''
-      }
-    },
-    async onFamilyMemberSwitch(member) {
-      if (!member || !member.id) return
-      var self = this
-      uni.showModal({
-        title: '切换确认',
-        content: '切换到"' + (member.nickname || '该成员') + '"后,退出时需要输入您的密码。是否继续?',
-        success: async function(res) {
-          if (!res.confirm) return
-          try {
-            var result = await switchToFamilyMember(member.id, member.source || 'family_member')
-            if (result.code === 200 && result.data) {
-              store.commit('switchToMember', {
-                memberId: result.data.memberId,
-                effectiveRole: result.data.effectiveRole
-              })
-              uni.showToast({ title: '已切换到' + (member.nickname || '该成员'), icon: 'success' })
-              setTimeout(function() {
-                uni.reLaunch({ url: '/pages/index-home/index' })
-              }, 1000)
-            } else {
-              uni.showToast({ title: result.message || '切换失败', icon: 'none' })
-            }
-          } catch (e) {
-            uni.showToast({ title: '切换失败', icon: 'none' })
-          }
-        }
-      })
-    },
-    onMicroActionCompleted() {
-      this.loadData()
-      feedbackEngine.trigger('micro_action')
-    },
-    goAddMember() {
-      uni.navigateTo({ url: '/pages/family/add-member' })
-    },
-    onDailySummaryDismiss() {
-      // DailySummary 已关闭,无额外操作
-    },
-    loadAlerts: function() {
-      var memberId = this.selectedChild && this.selectedChild.id
-      if (!memberId) return
-      var self = this
-      getActiveAlerts({ memberId: memberId }).then(function(res) {
-        if (res.code === 200) {
-          self.healthAlerts = res.data || []
-        }
-      }).catch(function() {})
-    },
-    handleDismissAlert: function(alertId) {
-      var self = this
-      dismissAlert({ id: alertId }).then(function(res) {
-        if (res.code === 200) {
-          self.healthAlerts = self.healthAlerts.filter(function(a) { return a.id !== alertId })
-        }
-      }).catch(function() {})
-    },
-    loadFamilyInteraction: function() {
-      var familyId = this._familyId
-      if (!familyId) return
-      var self = this
-      getFamilyRanking({}).then(function(res) {
-        if (res.code === 200 && res.data) {
-          self.rankingMembers = res.data.members || []
-          self.rankingTotalScore = res.data.totalScore || 0
-        }
-      }).catch(function() {})
-      getChallengeList({}).then(function(res) {
-        if (res.code === 200 && Array.isArray(res.data)) {
-          self.activeChallenges = res.data
-        }
-      }).catch(function() {})
-    },
-    loadHealthCircle: function() {
-      var familyId = this._familyId
-      if (!familyId) return
-      var self = this
-      self.circleLoading = true
-      getCircleList({}).then(function(res) {
-        if (res.code === 200 && res.data && res.data.length > 0) {
-          var circleId = res.data[0].id
-          getCircleRanking({ circleId: circleId }).then(function(r) {
-            if (r.code === 200 && r.data) {
-              self.circleName = r.data.circleName || ''
-              self.circleRankings = r.data.rankings || []
-            }
-          }).catch(function() {
-            self.circleRankings = []
-            self.circleName = ''
-          }).finally(function() {
-            self.circleLoading = false
-          })
-        } else {
-          self.circleRankings = []
-          self.circleName = ''
-          self.circleLoading = false
-        }
-      }).catch(function() {
-        self.circleRankings = []
-        self.circleName = ''
-        self.circleLoading = false
-      })
-    },
-    goToHealthCircle: function() {
-      uni.navigateTo({ url: '/pages/share/index' })
-    },
-    goToSelfCheck: function() {
-      uni.navigateTo({ url: '/pages/health/health-plan-summary' })
-    },
-    loadSelfCheckStatus: function() {
-      var self = this
-      getSelfCheckStatus().then(function(res) {
-        if (res.code === 200 && res.data && res.data.lastResult) {
-          self.selfCheckScore = res.data.lastResult.totalScore
-        }
-      }).catch(function() {})
-    },
-    goMemberDetail(member) {
-      if (!member || !member.memberId) return
-      uni.navigateTo({
-        url: '/pages/member-detail/member-detail?memberId=' + member.memberId + '&memberType=' + (member.memberType || 'child') + '&entrySource=relation-graph'
-      })
-    },
-    addTask() { uni.navigateTo({ url: '/pages/tasks/create-task' }) },
-    goToChallengeManage: function(id) {
-      uni.navigateTo({ url: '/pages/health/challenge-manage?id=' + id })
-    },
-    goToReview() { uni.navigateTo({ url: '/pages/tasks/review' }) },
-    goToWishApprove() { uni.navigateTo({ url: '/pages/wishes/approve' }) },
-    goToAssessmentReport() { uni.navigateTo({ url: '/pages/assessment/report' }) },
-    goToActivities() { uni.navigateTo({ url: '/pages/activity/index' }) },
-    goToRewards() { uni.navigateTo({ url: '/pages/rewards/rewards' }) },
-    onFeedItemClick(item) {
-      if (item.type === 'product') {
-        uni.navigateTo({ url: '/pages/discover-detail/product-detail/product-detail?id=' + item.id })
-      } else if (item.type === 'activity') {
-        uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + item.id })
-      }
-    },
-    goMoreDiscover() {
-      uni.navigateTo({ url: '/pages/discover/index' })
-    },
-    loadRecommendedActivities() {
-      var self = this
-      getActivityList({ page: 1, size: 6 }).then(function(res) {
-        if (res && res.data) {
-          self.recommendedActivities = res.data.records || []
-        }
-      }).catch(function() {
-        self.errorActivities = true
-      })
-    },
-    loadRecommendedProducts() {
-      var self = this
-      getProductsByDomain('all', 1, 6).then(function(res) {
-        if (res && res.data) {
-          self.recommendedProducts = res.data.records || []
-        }
-      }).catch(function() {
-        self.errorProducts = true
-      })
-    },
-    goToTaskList() { uni.navigateTo({ url: '/pages/tasks/tasks' }) },
-    manageChildren() { uni.navigateTo({ url: '/pages/profile-extra/family-members' }) },
-
-    loadContacts: function() {
-      var self = this
-      getContactList({}).then(function(res) {
-        if (res.code === 200 && res.data) {
-          self.contactList = Array.isArray(res.data) ? res.data : []
-        }
-      }).catch(function(e) {
-        console.log('获取联系人失败', e)
-      })
-    },
-    onContactClick: function(item) {
-      uni.navigateTo({ url: '/pages/action-detail/contact-detail?id=' + item.id })
-    },
-    onShowImport: function() {
-      this.showImportModal = true
-    },
-    onCloseImport: function() {
-      this.showImportModal = false
-    },
-    onImportSuccess: function() {
-      this.showImportModal = false
-      this.loadContacts()
-    },
-    // QR Code modal methods
-    onShowQrCodeModal: async function() {
-      this.showQrCodeModal = true
-      this.qrCodeData = null
-      try {
-        const res = await this.getInviteQrCode()
-        if (res.code === 200) {
-          this.qrCodeData = res.data
-        }
-      } catch (e) {
-        console.error('获取二维码失败', e)
-        uni.showToast({ title: '获取二维码失败', icon: 'none' })
-      }
-    },
-    onCloseQrCodeModal: function() {
-      this.showQrCodeModal = false
-      this.qrCodeData = null
-    },
-    onCopyReferralCode: function() {
-      if (this.qrCodeData && this.qrCodeData.referralCode) {
-        uni.setClipboardData({
-          data: this.qrCodeData.referralCode,
-          success: () => {
-            uni.showToast({ title: '复制成功', icon: 'success' })
-          }
-        })
-      }
-    },
-    // 兼容旧引用
-    getInviteQrCode: function() {
-      return getInviteQrCode()
-    },
-    // ===== 推荐阅读 =====
-    loadFeaturedArticles: function() {
-      var self = this
-      this.articlesLoading = true
-      getFeaturedArticles({ size: 5 }).then(function(res) {
-        self.articlesLoading = false
-        if (res && res.code === 200) {
-          var rawList = res.data && (Array.isArray(res.data) ? res.data : (res.data.data || res.data.records || []))
-          var list = Array.isArray(rawList) ? rawList : []
-          self.indexArticles = list.map(function(item, index) {
-            return {
-              id: item.id,
-              category: item.categoryName || item.category || '热门文章',
-              categoryColor: self.gradientColors[index % self.gradientColors.length],
-              title: item.title || '',
-              summary: item.summary || '',
-              views: item.viewCount ? String(item.viewCount) : '0',
-              date: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
-              coverImage: item.coverImage
-            }
-          })
-        }
-      }).catch(function() {
-        self.articlesLoading = false
-      })
-    },
-    goArticles: function() {
-      uni.navigateTo({ url: '/pages/article-center/index' })
-    },
-    goArticleDetail: function(articleOrId) {
-      var id = typeof articleOrId === 'object' ? articleOrId.id : articleOrId
-      uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
-    },
-    // 按成员统计待完成数(pending + in_progress)
-    _countTasksByMember: function(tasks) {
-      var counts = {}
-      for (var i = 0; i < tasks.length; i++) {
-        var task = tasks[i]
-        if (!task || !task.familyMemberId) continue
-        var memberId = task.familyMemberId
-        if (!counts[memberId]) {
-          counts[memberId] = 0
-        }
-        if (task.status === 'pending' || task.status === 'in_progress') {
-          counts[memberId] += 1
-        }
-      }
-      return counts
-    }
+  onLoad: function () {
+    redirectToNewPath('/pages/home-pages/family-index')
   }
 }
 </script>
-
-<style scoped lang="scss">
-/* ========================================
-   Flat Card Design System — 家长端暖橙扁平
-   ======================================== */
-
-.container {
-  padding: 32rpx;
-  background: var(--bg, $uni-bg-color);
-  min-height: 100vh;
-}
-
-/* ========================================
-   区块标题
-   ======================================== */
-.section-title {
-  font-size: calc(32rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: var(--text, $uni-text-color);
-  margin-bottom: var(--space-md);
-}
-
-/* ========================================
-   c) 成长数据
-   ======================================== */
-.stats-section {
-  margin-bottom: 48rpx;
-}
-
-.stats-row {
-  display: flex;
-  gap: 16rpx;
-}
-
-.stat-card-wide {
-  flex: 1;
-  min-width: 0;
-  box-sizing: border-box;
-}
-
-.stat-card-content {
-  padding: 24rpx;
-}
-
-.stat-header {
-  display: flex;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-
-.stat-icon-wrap {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: var(--radius-sm, 12rpx);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 14rpx;
-  flex-shrink: 0;
-}
-
-.stat-icon-symbol {
-  font-size: 28rpx;
-}
-
-.stat-icon-orange { background: rgba(249,115,22,0.12); }
-.stat-icon-gold { background: rgba(245, 158, 11, 0.15); }
-
-.stat-body {
-  flex: 1;
-  min-width: 0;
-}
-
-.stat-label-header {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: var(--text, $uni-text-color);
-}
-
-/* 对比行布局 */
-.stat-compare {
-  margin-bottom: 12rpx;
-}
-
-.stat-compare-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 4rpx;
-}
-
-.compare-label {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: var(--text-secondary, $uni-text-color-secondary);
-}
-
-.compare-value {
-  font-size: calc(30rpx * var(--font-scale, 1));
-  font-weight: 700;
-}
-
-/* 进度条 - 紧凑版 */
-.progress-bar-sm {
-  height: 6rpx;
-  background: var(--border-light, $uni-border-color-light);
-  border-radius: 999rpx;
-  overflow: hidden;
-  margin-bottom: 8rpx;
-}
-
-.progress-fill-sm {
-  height: 100%;
-  background: linear-gradient(90deg, var(--color-primary, $brand-primary), $dim-wealth);
-  border-radius: 999rpx;
-  transition: width 0.3s ease;
-}
-
-.progress-fill-sm.muted {
-  background: linear-gradient(90deg, $app-text-subtle, $uni-border-color);
-}
-
-.accent-color { color: var(--color-primary, $brand-primary); }
-.gold-color { color: $dim-wealth; }
-
-.stat-more {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: var(--color-accent, $uni-info);
-  font-weight: 500;
-  display: block;
-}
-
-.stat-encourage {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $status-success;
-  font-weight: 500;
-  margin-top: 8rpx;
-  display: block;
-}
-
-.stat-encourage.muted {
-  color: var(--muted, $app-text-subtle);
-}
-
-/* ========================================
-   d) 今日重点
-   ======================================== */
-.today-section {
-  margin-bottom: 48rpx;
-}
-
-.today-card .today-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.today-label {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: var(--text, $uni-text-color);
-}
-
-.today-count {
-  font-size: 24rpx;
-  color: var(--color-primary, $brand-primary);
-  font-weight: 600;
-}
-
-.task-list {
-  margin-bottom: 8rpx;
-}
-
-.task-item {
-  display: flex;
-  align-items: center;
-  padding: 18rpx 0;
-  border-bottom: 1rpx solid var(--border-light, $uni-border-color-light);
-}
-
-.task-item:last-child {
-  border-bottom: none;
-}
-
-.task-dot {
-  width: 14rpx;
-  height: 14rpx;
-  border-radius: 50%;
-  margin-right: 16rpx;
-  flex-shrink: 0;
-}
-
-.dot-color-0 { background: var(--color-primary, $brand-primary); }
-.dot-color-1 { background: $status-success; }
-.dot-color-2 { background: var(--color-accent, $uni-info); }
-.dot-color-3 { background: $dim-wealth; }
-.dot-color-4 { background: var(--color-error, $status-danger); }
-
-.task-title {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: var(--text, $uni-text-color);
-}
-
-.task-view-all {
-  display: flex;
-  justify-content: flex-end;
-  align-items: center;
-  color: var(--text-secondary, $uni-text-color-secondary);
-  font-size: calc(24rpx * var(--font-scale, 1));
-  padding-top: 16rpx;
-  border-top: 1rpx solid var(--border-light, $uni-border-color-light);
-}
-
-.task-view-all .arrow {
-  margin-left: 8rpx;
-}
-
-/* ========================================
-   e) 成长里程碑
-   ======================================== */
-.milestone-section {
-  margin-bottom: 48rpx;
-}
-
-.milestone-card {
-  padding: 24rpx !important;
-}
-
-.milestone-item {
-  display: flex;
-  align-items: center;
-  padding: 16rpx 0;
-  border-bottom: 1rpx solid var(--border-light, $uni-border-color-light);
-}
-
-.milestone-item:last-child {
-  border-bottom: none;
-}
-
-.milestone-icon-wrap {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: var(--radius-sm, 12rpx);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 16rpx;
-  flex-shrink: 0;
-}
-
-.milestone-icon {
-  font-size: 28rpx;
-}
-
-.milestone-star { background: rgba(245, 158, 11, 0.15); }
-.milestone-trophy { background: rgba(14,165,233,0.12); }
-.milestone-sparkle { background: rgba(16, 185, 129, 0.12); }
-
-.milestone-text {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: var(--text, $uni-text-color);
-}
-
-/* ========================================
-   f) 待处理事项
-   ======================================== */
-.pending-section { margin-bottom: 48rpx; }
-.pending-grid {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 16rpx;
-}
-.pending-card {
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-  display: flex;
-  align-items: center;
-  width: calc(50% - 8rpx);
-  box-sizing: border-box;
-}
-.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(14,165,233,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: calc(32rpx * var(--font-scale, 1)); }
-.pending-info { flex: 1; min-width: 0; }
-.pending-count { font-size: 36rpx; font-weight: 800; display: block; }
-.pending-label { font-size: calc(22rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 4rpx; display: block; }
-.pink-color { color: $accent-pink; }
-.orange-color { color: $brand-primary; }
-
-/* ========================================
-   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(14,165,233,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: calc(28rpx * var(--font-scale, 1)); font-weight: 600; color: $facade-text-strong; display: block; }
-.advice-desc { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-muted; margin-top: 4rpx; display: block; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
-.advice-arrow { font-size: 36rpx; color: $facade-text-ghost; margin-left: 12rpx; }
-
-/* ========================================
-   h) 待审核任务
-   ======================================== */
-.review-section {
-  margin-bottom: 48rpx;
-}
-
-.review-card {
-  margin-bottom: 16rpx;
-}
-
-.review-card:last-child {
-  margin-bottom: 0;
-}
-
-.review-card-content {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 24rpx;
-}
-
-.review-info {
-  flex: 1;
-  min-width: 0;
-}
-
-.review-info .task-name {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: var(--text, $uni-text-color);
-  display: block;
-  margin-bottom: 4rpx;
-}
-
-.review-info .task-child {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: var(--muted, $app-text-subtle);
-}
-
-.review-actions {
-  display: flex;
-  gap: 12rpx;
-  flex-shrink: 0;
-  margin-left: 16rpx;
-}
-
-/* ========================================
-   g) 快捷操作
-   ======================================== */
-.quick-section {
-  margin-bottom: 48rpx;
-}
-
-.quick-card {
-  padding: 16rpx 12rpx !important;
-}
-
-.quick-actions {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-.action-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  width: 20%;
-  margin-bottom: 12rpx;
-  transition: transform 0.15s ease;
-}
-
-.action-item:active {
-  transform: scale(0.92);
-}
-
-.action-icon {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: var(--radius-sm, 12rpx);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 10rpx;
-}
-
-.action-icon-text {
-  font-size: 32rpx;
-}
-
-.act-add { background: rgba(249,115,22,0.12); }
-.act-review { background: rgba(16, 185, 129, 0.12); }
-.act-child { background: rgba(14, 165, 233, 0.12); }
-.act-reward { background: rgba(245, 158, 11, 0.15); }
-.act-invite { background: rgba(139, 92, 246, 0.12); }
-
-.action-label {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: var(--text-secondary, $uni-text-color-secondary);
-  font-weight: 500;
-  text-align: center;
-}
-
-/* ===== Error State ===== */
-.error-state {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 32rpx 24rpx;
-  margin-bottom: 24rpx;
-  background: $app-surface;
-  border-radius: 16rpx;
-  border: 1rpx dashed $uni-border-color-light;
-}
-.error-state-text {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $app-text-subtle;
-  text-align: center;
-}
-
-/* ===== 重要关系(通讯录) ===== */
-.contact-section {
-  margin: 20rpx 20rpx 0;
-}
-.contact-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.contact-section-title {
-  font-size: calc(30rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: $facade-text-strong;
-}
-.contact-header-right {
-  display: flex;
-  gap: 16rpx;
-  align-items: center;
-}
-.contact-more {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-}
-.contact-import-btn {
-  font-size: 24rpx;
-  color: $brand-primary;
-  font-weight: 500;
-}
-.contact-scroll {
-  white-space: nowrap;
-  overflow: hidden;
-}
-.contact-scroll-inner {
-  display: flex;
-  flex-direction: row;
-  gap: 16rpx;
-  padding: 8rpx 0 16rpx;
-}
-.contact-card-wrap {
-  flex-shrink: 0;
-  width: 220rpx;
-}
-.contact-card-wrap:active {
-  opacity: 0.8;
-}
-.contact-add-card {
-  width: 160rpx;
-}
-.add-card-inner {
-  width: 160rpx;
-  height: 280rpx;
-  background: $app-surface;
-  border-radius: 16rpx;
-  border: 2rpx dashed $facade-border-soft;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  gap: 12rpx;
-}
-.add-card-icon {
-  font-size: 48rpx;
-  color: $facade-text-ghost;
-}
-.add-card-label {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-}
-.contact-empty {
-  padding: 40rpx 0;
-  text-align: center;
-}
-.contact-empty-text {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $facade-text-ghost;
-}
-
-/* ===== 推荐阅读 ===== */
-.article-section {
-  margin-bottom: 48rpx;
-}
-.article-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: var(--space-md);
-}
-.article-header .section-title {
-  margin-bottom: 0;
-}
-.article-more {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: var(--color-primary, $brand-primary);
-}
-.article-list {
-  display: flex;
-  flex-direction: column;
-  gap: 16rpx;
-}
-.article-card {
-  background: $app-surface;
-  border-radius: 16rpx;
-  padding: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.article-card:active {
-  opacity: 0.8;
-}
-.article-category {
-  display: inline-block;
-  padding: 4rpx 14rpx;
-  border-radius: 8rpx;
-  margin-bottom: 10rpx;
-}
-.article-category-text {
-  font-size: calc(20rpx * var(--font-scale, 1));
-  color: $app-surface;
-}
-.article-title {
-  display: block;
-  font-size: calc(28rpx * var(--font-scale, 1));
-  font-weight: bold;
-  color: $facade-text-strong;
-  margin-bottom: 8rpx;
-  line-height: 1.4;
-}
-.article-summary {
-  display: block;
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-faint;
-  line-height: 1.5;
-  margin-bottom: 12rpx;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  display: -webkit-box;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-}
-.article-meta {
-  display: flex;
-  align-items: center;
-  gap: 16rpx;
-}
-.meta-item {
-  display: flex;
-  align-items: center;
-  gap: 4rpx;
-}
-.meta-icon {
-  font-size: 24rpx;
-}
-.meta-text {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-}
-.article-date {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $facade-text-ghost-light;
-}
-
-/* ===== 家庭挑战区域 ===== */
-.challenge-section {
-  margin-bottom: 28rpx;
-}
-.challenge-section-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin: 0 20rpx 12rpx;
-}
-.challenge-section-title {
-  font-size: calc(32rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $uni-text-color;
-}
-.challenge-section-action {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $status-success;
-  font-weight: 600;
-}
-
-/* ===== 健康圈入口卡片(P3:社区扩散) ===== */
-.circle-entry-section {
-  margin: 0 20rpx 28rpx 20rpx;
-}
-.circle-card-wide {
-  width: 100%;
-}
-.circle-card-content {
-  padding: 24rpx;
-  display: flex;
-  flex-direction: column;
-  gap: 16rpx;
-}
-.circle-header {
-  display: flex;
-  align-items: center;
-  gap: 12rpx;
-}
-.circle-icon-wrap {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: 14rpx;
-  background: linear-gradient(135deg, $status-success, $status-success-light-3);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.circle-icon {
-  font-size: 28rpx;
-}
-.circle-header-body {
-  flex: 1;
-}
-.circle-card-title {
-  display: block;
-  font-size: calc(28rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $uni-text-color;
-  line-height: 1.4;
-}
-.circle-card-subtitle {
-  display: block;
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $facade-text-slate;
-  margin-top: 2rpx;
-}
-.circle-ranking-preview {
-  background: $status-success-tint-2;
-  border-radius: 12rpx;
-  padding: 16rpx;
-  display: flex;
-  flex-direction: column;
-  gap: 10rpx;
-}
-.circle-rank-row {
-  display: flex;
-  align-items: center;
-  gap: 10rpx;
-}
-.circle-rank-badge {
-  font-size: 24rpx;
-  width: 40rpx;
-  text-align: center;
-}
-.circle-rank-family {
-  flex: 1;
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-gray-700;
-  font-weight: 500;
-}
-.circle-rank-score {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $brand-primary;
-  font-weight: 600;
-}
-.circle-action {
-  display: flex;
-  justify-content: flex-end;
-}
-.circle-action-text {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $brand-primary;
-  font-weight: 500;
-}
-
-/* 无数据:邀请加入样式 */
-.circle-card-invite {
-  background: linear-gradient(135deg, $status-success-tint-2, $status-success-tint);
-}
-.circle-invite-content {
-  padding: 32rpx 24rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  gap: 12rpx;
-}
-.circle-invite-icon {
-  font-size: 48rpx;
-  margin-bottom: 4rpx;
-}
-.circle-invite-title {
-  font-size: calc(30rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $status-success-strong;
-}
-.circle-invite-desc {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-slate;
-  text-align: center;
-  line-height: 1.5;
-}
-.circle-invite-cta {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $brand-primary;
-  font-weight: 600;
-  margin-top: 4rpx;
-}
-
-/* ===== 五维家庭自检入口卡片(P0-1/P0-2) ===== */
-.selfcheck-entry-section {
-  margin: 0 20rpx 28rpx 20rpx;
-}
-.selfcheck-card-wide {
-  width: 100%;
-}
-.selfcheck-card-content {
-  padding: 28rpx 24rpx;
-  display: flex;
-  align-items: center;
-  gap: 20rpx;
-}
-.selfcheck-icon-wrap {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  flex-shrink: 0;
-}
-// 五维自检图标底色(全局维度令牌)
-.selfcheck-icon-body { background: $dim-body; }
-.selfcheck-icon-wisdom { background: $dim-wisdom; }
-.selfcheck-icon-wealth { background: $dim-wealth; }
-.selfcheck-icon-action { background: $dim-action; }
-.selfcheck-icon-mind { background: $dim-mind; }
-
-.selfcheck-icon-circle {
-  width: 52rpx;
-  height: 52rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: -10rpx;
-  border: 4rpx solid $app-surface;
-  box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.12);
-}
-.selfcheck-icon-circle:last-child {
-  margin-right: 0;
-}
-.selfcheck-icon-char {
-  font-size: 24rpx;
-  font-weight: 700;
-  color: $app-surface;
-}
-.selfcheck-body {
-  flex: 1;
-}
-.selfcheck-title {
-  display: block;
-  font-size: calc(30rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $uni-text-color;
-  line-height: 1.4;
-}
-.selfcheck-subtitle {
-  display: block;
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $facade-text-slate;
-  margin-top: 4rpx;
-}
-.selfcheck-arrow {
-  font-size: 30rpx;
-  color: $brand-primary;
-  font-weight: 600;
-  flex-shrink: 0;
-}
-
-/* ===== 邀请码二维码弹窗 ===== */
-.qr-code-modal {
-  position: fixed;
-  top: 0;
-  left: 0;
-  width: 100%;
-  height: 100%;
-  background: rgba(0, 0, 0, 0.6);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  z-index: 1000;
-  padding: 40rpx 20rpx;
-  box-sizing: border-box;
-}
-
-.qr-code-modal-content {
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 40rpx 32rpx;
-  max-width: 600rpx;
-  width: 100%;
-  box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.2);
-  position: relative;
-}
-
-.qr-code-modal-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 32rpx;
-}
-
-.qr-code-modal-title {
-  font-size: calc(36rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $uni-text-color;
-}
-
-.qr-code-modal-close {
-  font-size: 48rpx;
-  color: $app-text-subtle;
-  line-height: 1;
-  padding: 8rpx;
-  cursor: pointer;
-}
-
-.qr-code-modal-body {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  gap: 32rpx;
-}
-
-.qr-code-image-container {
-  width: 280rpx;
-  height: 280rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  background: $facade-fill-gray-50;
-  border-radius: 16rpx;
-  padding: 20rpx;
-}
-
-.qr-code-image {
-  width: 100%;
-  height: 100%;
-  object-fit: contain;
-}
-
-.qr-code-referral-code {
-  display: flex;
-  align-items: center;
-  gap: 16rpx;
-  padding: 24rpx;
-  background: $facade-fill-gray-50;
-  border-radius: 16rpx;
-  width: 100%;
-  box-sizing: border-box;
-}
-
-.qr-code-label {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $uni-text-color-secondary;
-  font-weight: 500;
-}
-
-.qr-code-code {
-  flex: 1;
-  font-size: calc(32rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $uni-text-color;
-  word-break: break-all;
-  letter-spacing: 2rpx;
-}
-
-.qr-code-copy {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $brand-primary;
-  font-weight: 500;
-  padding: 8rpx 16rpx;
-  background: $app-surface;
-  border-radius: 8rpx;
-  border: 1px solid $brand-primary;
-  cursor: pointer;
-}
-
-.qr-code-copy:active {
-  opacity: 0.8;
-}
-
-/* 无家庭成员空状态引导卡 */
-.empty-family-card {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin: 20rpx 24rpx;
-  padding: 48rpx 30rpx;
-  background: $app-surface;
-  border-radius: 24rpx;
-  border: 2rpx dashed $brand-primary-light-2;
-}
-.empty-family-icon {
-  font-size: 88rpx;
-  margin-bottom: 16rpx;
-}
-.empty-family-title {
-  font-size: calc(34rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: $mat-orange-deep;
-  margin-bottom: 10rpx;
-}
-.empty-family-desc {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-  margin-bottom: 28rpx;
-}
-.empty-family-btn {
-  width: 320rpx;
-  height: 80rpx;
-  line-height: 80rpx;
-  font-size: 30rpx;
-  font-weight: 600;
-  color: $app-surface;
-  background: linear-gradient(135deg, $brand-primary, $dim-body);
-  border-radius: 40rpx;
-  border: none;
-}
-.empty-family-btn::after {
-  border: none;
-}
-
-</style>

+ 5 - 243
cfc-frontend/pages/parent/appointments.vue

@@ -1,252 +1,14 @@
 <template>
-  <view class="container" :style="{ '--font-scale': fontScale }">
-    <view class="header">
-      <text class="title">我的预约</text>
-      <text class="subtitle">查看测评预约状态</text>
-    </view>
-
-    <!-- 预约列表 -->
-    <view class="appointment-list" v-if="appointments.length > 0">
-      <view class="appointment-card" v-for="item in appointments" :key="item.id">
-        <view class="card-header">
-          <text class="appointment-id">#{{ item.id }}</text>
-          <view class="status-badge" :class="'status-' + item.status">
-            <text>{{ item.statusText || getStatusText(item.status) }}</text>
-          </view>
-        </view>
-
-        <view class="card-body">
-          <view class="info-row">
-            <text class="label">预约日期</text>
-            <text class="value">{{ item.appointmentDate || '待确认' }}</text>
-          </view>
-          <view class="info-row">
-            <text class="label">预约时间</text>
-            <text class="value">{{ item.appointmentTime || '待确认' }}</text>
-          </view>
-          <view class="info-row" v-if="item.notes">
-            <text class="label">备注</text>
-            <text class="value">{{ item.notes }}</text>
-          </view>
-          <view class="info-row" v-if="item.guideName">
-            <text class="label">规划师</text>
-            <text class="value guide-name">{{ item.guideName }}</text>
-          </view>
-          <view class="info-row" v-if="item.status === 1 && !item.guideName">
-            <text class="label">规划师</text>
-            <text class="value">待分配</text>
-          </view>
-          <view class="info-row">
-            <text class="label">创建时间</text>
-            <text class="value">{{ formatTime(item.createdAt) }}</text>
-          </view>
-        </view>
-
-        <view class="card-footer" v-if="item.status === 0">
-          <button class="btn-cancel" size="mini" @click="handleCancel(item.id)">取消预约</button>
-        </view>
-        <view class="card-footer" v-if="item.status === 2">
-          <button class="btn-view-report" size="mini" @click="viewReport(item.id)">查看报告</button>
-        </view>
-      </view>
-    </view>
-
-    <view class="empty-state" v-else>
-      <text class="empty-icon">📋</text>
-      <text class="empty-text">暂无预约记录</text>
-      <text class="empty-hint">申请测评后此处会显示预约状态</text>
-    </view>
-  </view>
+  <view class="redirect-shell"></view>
 </template>
 
 <script>
-import { getMyAppointments, cancelAppointment } from '../../utils/api.js'
-import { parseDate } from '../../utils/format.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
+// 旧路径跳转壳:/pages/parent/appointments → /pages/family/appointments
+import { redirectToNewPath } from '../../utils/redirect.js'
 
 export default {
-  mixins: [fontScaleMixin],
-  data() {
-    return {
-      appointments: []
-    }
-  },
-  onShow() {
-    this.loadAppointments()
-  },
-  methods: {
-    async loadAppointments() {
-      try {
-        const res = await getMyAppointments()
-        if (res.code === 200) {
-          this.appointments = res.data || []
-        }
-      } catch (e) {
-        console.error('加载预约列表失败', e)
-      }
-    },
-
-    getStatusText(status) {
-      const map = { 0: '待确认', 1: '已确认', 2: '已完成', 3: '已取消', 4: '未到' }
-      return map[status] || '未知'
-    },
-
-    formatTime(ts) {
-      if (!ts) return ''
-      const d = parseDate(ts)
-      if (!d) return ''
-      return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}`
-    },
-
-    handleCancel(id) {
-      uni.showModal({
-        title: '取消预约',
-        content: '确定取消此预约?',
-        success: async (res) => {
-          if (res.confirm) {
-            try {
-              const result = await cancelAppointment(id)
-              if (result.code === 200) {
-                uni.showToast({ title: '已取消', icon: 'success' })
-                this.loadAppointments()
-              }
-            } catch (e) {
-              uni.showToast({ title: '操作失败', icon: 'none' })
-            }
-          }
-        }
-      })
-    },
-
-    viewReport(appointmentId) {
-      uni.navigateTo({ url: '/pages/assessment/results' })
-    }
+  onLoad: function () {
+    redirectToNewPath('/pages/family/appointments')
   }
 }
 </script>
-
-<style scoped lang="scss">
-.container {
-  padding: 30rpx;
-  background: $facade-fill-page;
-  min-height: 100vh;
-}
-
-.header {
-  text-align: center;
-  padding: 40rpx 0 30rpx;
-}
-
-.title {
-  font-size: calc(48rpx * var(--font-scale, 1));
-  font-weight: bold;
-  color: $facade-text-strong;
-  display: block;
-  margin-bottom: 16rpx;
-}
-
-.subtitle {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $facade-text-body;
-}
-
-.appointment-list {
-  display: flex;
-  flex-direction: column;
-  gap: 24rpx;
-}
-
-.appointment-card {
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 30rpx;
-}
-
-.card-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 20rpx;
-  padding-bottom: 16rpx;
-  border-bottom: 1rpx solid $facade-hairline-2;
-}
-
-.appointment-id {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-}
-
-.status-badge {
-  padding: 6rpx 16rpx;
-  border-radius: 8rpx;
-  font-size: 22rpx;
-}
-
-.status-0 { background: $fortune-1-tint; color: $mat-orange-base; }
-.status-1 { background: $mat-blue-tint; color: $mat-blue-base; }
-.status-2 { background: $mat-green-tint; color: $mat-green; }
-.status-3 { background: $facade-fill-2; color: #9E9E9E; }
-.status-4 { background: $fortune-0-tint; color: $mat-red; }
-
-.card-body {
-  margin-bottom: 20rpx;
-}
-
-.info-row {
-  display: flex;
-  justify-content: space-between;
-  padding: 10rpx 0;
-  font-size: calc(26rpx * var(--font-scale, 1));
-}
-
-.label { color: $facade-text-muted; }
-.value { color: $facade-text-strong; }
-.guide-name { color: $brand-primary; font-weight: bold; }
-
-.card-footer {
-  padding-top: 16rpx;
-  border-top: 1rpx dashed $facade-hairline-2;
-  display: flex;
-  justify-content: flex-end;
-}
-
-.btn-cancel {
-  background: $facade-fill-2;
-  color: $facade-text-muted;
-  font-size: 26rpx;
-  border-radius: 10rpx;
-  width: 200rpx;
-}
-
-.btn-view-report {
-  background: $accent-indigo;
-  color: $app-surface;
-  font-size: 26rpx;
-  border-radius: 10rpx;
-  width: 200rpx;
-}
-
-.empty-state {
-  text-align: center;
-  padding: 120rpx 0;
-}
-
-.empty-icon {
-  font-size: 80rpx;
-  display: block;
-  margin-bottom: 20rpx;
-}
-
-.empty-text {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-  display: block;
-  margin-bottom: 10rpx;
-}
-
-.empty-hint {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-ghost;
-  display: block;
-}
-</style>

+ 5 - 504
cfc-frontend/pages/parent/child-detail.vue

@@ -1,513 +1,14 @@
 <template>
-  <view class="container" :style="{ '--font-scale': fontScale }">
-    <!-- 加载状态 -->
-    <view class="loading-container" v-if="loading">
-      <text class="loading-text">加载中...</text>
-    </view>
-
-    <template v-if="!loading">
-      <!-- 孩子信息卡片 -->
-      <view class="child-info-card">
-        <view class="child-info-avatar" :class="'child-avatar-color-' + (memberId % 6)">
-          <text class="child-avatar-text">{{ childName ? childName[0] : '?' }}</text>
-        </view>
-        <view class="child-info-body">
-          <text class="child-info-name">{{ childName }}</text>
-          <view class="child-info-stats">
-            <text class="stat-item stat-points">⭐ {{ childInfo.totalPoints || 0 }} 分</text>
-            <text class="stat-item stat-streak" v-if="childInfo.streakDays > 0">🔥 连续 {{ childInfo.streakDays }} 天</text>
-          </view>
-          <view class="child-info-progress" v-if="todayTasks.length > 0">
-            <text class="progress-label">
-              今日任务 {{ completedCount }}/{{ todayTasks.length }} 完成
-            </text>
-            <view class="progress-bar-bg">
-              <view class="progress-bar-fill" :style="{ width: taskProgressPercent + '%' }"></view>
-            </view>
-          </view>
-        </view>
-      </view>
-
-      <!-- 操作区 -->
-      <view class="action-section">
-        <view class="section-title">快捷操作</view>
-        <view class="action-grid">
-          <view class="action-btn" @click="goToCreateTask">
-            <view class="action-btn-icon create-icon">📝</view>
-            <text class="action-btn-label">创建任务</text>
-          </view>
-          <view class="action-btn" @click="goToWishReview">
-            <view class="action-btn-icon wish-icon">💝</view>
-            <text class="action-btn-label">心愿审核</text>
-            <text class="action-btn-badge" v-if="pendingWishCount > 0">{{ pendingWishCount }}</text>
-          </view>
-          <view class="action-btn" @click="goToGrowth">
-            <view class="action-btn-icon growth-icon">📊</view>
-            <text class="action-btn-label">成长记录</text>
-          </view>
-          <view class="action-btn" @click="goToCompletionStats">
-            <view class="action-btn-icon stats-icon">📈</view>
-            <text class="action-btn-label">完成分析</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 今日任务 -->
-      <view class="tasks-section">
-        <view class="section-title">今日任务</view>
-        <view class="tasks-card" v-if="todayTasks.length > 0">
-          <view
-            class="task-item"
-            v-for="task in todayTasks"
-            :key="task.id"
-          >
-            <text class="task-status-icon">{{ task.status === 'completed' ? '✅' : '⏳' }}</text>
-            <view class="task-info">
-              <text class="task-title">{{ task.title }}</text>
-              <text class="task-points" v-if="task.points">+{{ task.points }}CF</text>
-            </view>
-          </view>
-        </view>
-        <view class="empty-card" v-else>
-          <text class="empty-icon">🎉</text>
-          <text class="empty-text">今日暂无任务</text>
-        </view>
-      </view>
-
-      <!-- 待处理 -->
-      <view class="pending-section">
-        <view class="section-title">待处理</view>
-        <view class="pending-card" v-if="pendingReviewTasks.length > 0 || pendingWishes.length > 0">
-          <view
-            class="pending-item"
-            v-for="task in pendingReviewTasks"
-            :key="task.id"
-          >
-            <text class="pending-icon">📋</text>
-            <text class="pending-desc">{{ task.title }}</text>
-            <text class="pending-tag tag-review">待审核</text>
-          </view>
-          <view
-            class="pending-item"
-            v-for="wish in pendingWishes"
-            :key="wish.id"
-          >
-            <text class="pending-icon">💝</text>
-            <text class="pending-desc">{{ wish.title }}</text>
-            <text class="pending-tag tag-wish">{{ wish.status === 'draft' ? '待定价' : '待审批' }}</text>
-          </view>
-        </view>
-        <view class="empty-card" v-else>
-          <text class="empty-icon">✅</text>
-          <text class="empty-text">全部已处理</text>
-        </view>
-      </view>
-    </template>
-  </view>
+  <view class="redirect-shell"></view>
 </template>
 
 <script>
-import { getChildren, getTodayTasks, getPendingReviewTasks, getPendingWishes } from '../../utils/api.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
+// 旧路径跳转壳:/pages/parent/child-detail → /pages/family/member-detail
+import { redirectToNewPath } from '../../utils/redirect.js'
 
 export default {
-  mixins: [fontScaleMixin],
-  data() {
-    return {
-      loading: true,
-      memberId: '',
-      childName: '',
-      childInfo: {},
-      todayTasks: [],
-      pendingReviewTasks: [],
-      pendingWishes: []
-    }
-  },
-  computed: {
-    completedCount() {
-      return this.todayTasks.filter(t => t.status === 'completed').length
-    },
-    taskProgressPercent() {
-      if (this.todayTasks.length === 0) return 0
-      return Math.round((this.completedCount / this.todayTasks.length) * 100)
-    },
-    pendingWishCount() {
-      return this.pendingWishes.length
-    }
-  },
-  onLoad(options) {
-    this.memberId = options.memberId || ''
-    this.childName = options.childName ? decodeURIComponent(options.childName) : '成员'
-    this.loadChildData()
-  },
-  methods: {
-    async loadChildData() {
-      this.loading = true
-      try {
-        // 获取孩子信息
-        const childrenRes = await getChildren()
-        const children = childrenRes.data || []
-        const matched = children.find(c => String(c.id) === String(this.memberId))
-        if (matched) {
-          this.childInfo = matched
-if (!this.childName || this.childName === '成员') {
-    this.childName = matched.nickname || matched.name || '成员'
-          }
-        }
-
-        // 并行加载今日任务和待处理
-        const [tasksRes, reviewRes, wishRes] = await Promise.all([
-          getTodayTasks(this.memberId),
-          getPendingReviewTasks(),
-          getPendingWishes()
-        ])
-
-        this.todayTasks = tasksRes.data || []
-
-        // 过滤属于该孩子的待审核任务
-        const allReviewTasks = reviewRes.data || []
-        this.pendingReviewTasks = allReviewTasks.filter(t => {
-          return String(t.memberId || t.assigneeId) === String(this.memberId)
-        }).slice(0, 5)
-
-        // 过滤属于该孩子的待处理心愿
-        const allWishes = wishRes.data || []
-        this.pendingWishes = allWishes.filter(w => String(w.memberId) === String(this.memberId)).slice(0, 5)
-      } catch (e) {
-        console.error('加载成员详情失败', e)
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      } finally {
-        this.loading = false
-      }
-    },
-    goToCreateTask() {
-      uni.navigateTo({
-        url: '/pages/tasks/create-task?memberId=' + this.memberId
-      })
-    },
-    goToWishReview() {
-      uni.navigateTo({
-        url: '/pages/parent/wishes/index?memberId=' + this.memberId
-      })
-    },
-    goToGrowth() {
-      uni.switchTab({ url: '/pages/growth-main/index' })
-    },
-    goToCompletionStats() {
-      uni.navigateTo({
-        url: '/pages/stats/completion-rate?memberId=' + this.memberId
-      })
-    }
+  onLoad: function () {
+    redirectToNewPath('/pages/family/member-detail')
   }
 }
 </script>
-
-<style scoped lang="scss">
-// ===== 孩子详情页专属色阶(flat 图表青绿 + 米白卡片;单页专用,约定见 uni.scss §14)=====
-$flat-bg:         $flat-bg; // 页面米白底
-$flat-card:       $flat-card; // 暖白卡片底
-$flat-line:       $flat-line; // 暖分隔线
-$flat-ink:        $flat-ink; // 主文字(深灰)
-$flat-muted:      $flat-muted; // 次要文字(灰)
-$flat-muted-2:     $flat-muted-2; // 辅助文字(中灰)
-$c-cd-orange:     #FF7A2E; // 橙渐变终档
-$flat-teal:       $flat-teal; // 青绿主色
-$flat-teal-deep:  $flat-teal-deep; // 青绿深档
-$flat-green:      $flat-green; // 绿浅档
-$flat-green-deep: $flat-green-deep; // 绿深档
-$c-cd-pink:       #F093FB; // 粉紫渐变起
-$c-cd-red:        #F5576C; // 红粉渐变终
-$c-cd-danger-bg:  $status-danger-tint-5; // 警示浅红底
-
-.container {
-  padding: 30rpx;
-  background: $flat-bg;
-  min-height: 100vh;
-}
-
-.loading-container {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 200rpx 0;
-}
-
-.loading-text {
-  font-size: 28rpx;
-  color: $flat-muted;
-}
-
-/* 孩子信息卡片 */
-.child-info-card {
-  display: flex;
-  align-items: flex-start;
-  background: $app-surface;
-  border-radius: 24rpx;
-  padding: 24rpx;
-  margin-bottom: 32rpx;
-  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
-}
-
-.child-info-avatar {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  margin-right: 16rpx;
-}
-
-.child-avatar-text {
-  font-size: calc(32rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: $app-surface;
-}
-
-.child-avatar-color-0 { background: linear-gradient(135deg, $dim-body, $c-cd-orange); }
-.child-avatar-color-1 { background: linear-gradient(135deg, $accent-indigo, $accent-indigo-deep); }
-.child-avatar-color-2 { background: linear-gradient(135deg, $flat-teal, $flat-teal-deep); }
-.child-avatar-color-3 { background: linear-gradient(135deg, $brand-primary, $brand-primary-light); }
-.child-avatar-color-4 { background: linear-gradient(135deg, $flat-green, $flat-green-deep); }
-.child-avatar-color-5 { background: linear-gradient(135deg, $c-cd-pink, $c-cd-red); }
-
-.child-info-body {
-  flex: 1;
-  min-width: 0;
-}
-
-.child-info-name {
-  font-size: calc(36rpx * var(--font-scale, 1));
-  font-weight: 700;
-  color: $flat-ink;
-  display: block;
-}
-
-.child-info-stats {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 12rpx;
-  margin-top: 12rpx;
-}
-
-.stat-item {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  font-weight: 500;
-}
-
-.stat-points { color: $dim-body; }
-.stat-dan { color: $accent-indigo; }
-.stat-streak { color: $brand-primary; }
-
-.child-info-progress {
-  margin-top: 16rpx;
-}
-
-.progress-label {
-  font-size: calc(22rpx * var(--font-scale, 1));
-  color: $flat-muted-2;
-  display: block;
-  margin-bottom: 8rpx;
-}
-
-.progress-bar-bg {
-  height: 10rpx;
-  background: $flat-card;
-  border-radius: 9999rpx;
-  overflow: hidden;
-}
-
-.progress-bar-fill {
-  height: 100%;
-  background: linear-gradient(90deg, $flat-teal, $flat-teal-deep);
-  border-radius: 9999rpx;
-  transition: width 0.3s ease;
-}
-
-/* 区块标题 */
-.section-title {
-  font-size: calc(30rpx * var(--font-scale, 1));
-  font-weight: 600;
-  color: $flat-ink;
-  margin-bottom: 20rpx;
-}
-
-/* 操作区 */
-.action-section {
-  margin-bottom: 40rpx;
-}
-
-.action-grid {
-  display: flex;
-  flex-wrap: wrap;
-}
-
-.action-btn {
-  width: 50%;
-  box-sizing: border-box;
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 24rpx 16rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
-  position: relative;
-  border: 8rpx solid $flat-bg;
-}
-
-.action-btn-icon {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 20rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 36rpx;
-  margin-bottom: 12rpx;
-}
-
-.create-icon { background: rgba(255, 140, 66, 0.15); }
-.wish-icon { background: rgba(255, 107, 107, 0.15); }
-.growth-icon { background: rgba(78, 205, 196, 0.15); }
-.stats-icon { background: rgba(255, 217, 61, 0.15); }
-
-.action-btn-label {
-  font-size: 24rpx;
-  color: $flat-muted-2;
-  font-weight: 500;
-}
-
-.action-btn-badge {
-  position: absolute;
-  top: 12rpx;
-  right: 12rpx;
-  background: $brand-primary;
-  color: $app-surface;
-  font-size: 20rpx;
-  min-width: 32rpx;
-  height: 32rpx;
-  line-height: 32rpx;
-  text-align: center;
-  border-radius: 16rpx;
-  padding: 0 8rpx;
-}
-
-/* 今日任务 */
-.tasks-section {
-  margin-bottom: 40rpx;
-}
-
-.tasks-card {
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 8rpx 24rpx;
-  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
-}
-
-.task-item {
-  display: flex;
-  align-items: center;
-  padding: 20rpx 0;
-  border-bottom: 1rpx solid $flat-line;
-}
-
-.task-item:last-child {
-  border-bottom: none;
-}
-
-.task-status-icon {
-  font-size: 32rpx;
-  margin-right: 16rpx;
-}
-
-.task-info {
-  flex: 1;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-
-.task-title {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $flat-ink;
-}
-
-.task-points {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $dim-body;
-  font-weight: 600;
-}
-
-.empty-card {
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 48rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
-}
-
-.empty-icon {
-  font-size: 48rpx;
-  margin-bottom: 12rpx;
-}
-
-.empty-text {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $flat-muted;
-}
-
-/* 待处理 */
-.pending-section {
-  margin-bottom: 48rpx;
-}
-
-.pending-card {
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 8rpx 24rpx;
-  box-shadow: 0 4px 20px rgba(255, 140, 66, 0.08);
-}
-
-.pending-item {
-  display: flex;
-  align-items: center;
-  padding: 20rpx 0;
-  border-bottom: 1rpx solid $flat-line;
-}
-
-.pending-item:last-child {
-  border-bottom: none;
-}
-
-.pending-icon {
-  font-size: 32rpx;
-  margin-right: 16rpx;
-}
-
-.pending-desc {
-  flex: 1;
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $flat-ink;
-}
-
-.pending-tag {
-  font-size: 22rpx;
-  padding: 4rpx 14rpx;
-  border-radius: 20rpx;
-  font-weight: 500;
-}
-
-.tag-review {
-  background: $flat-card;
-  color: $dim-body;
-}
-
-.tag-wish {
-  background: $c-cd-danger-bg;
-  color: $brand-primary;
-}
-</style>

+ 5 - 111
cfc-frontend/pages/parent/plans/detail.vue

@@ -1,120 +1,14 @@
 <template>
-  <view class="container" :style="{ '--font-scale': fontScale }">
-    <!-- 加载中 -->
-    <view class="loading" v-if="loading">
-      <text>加载中...</text>
-    </view>
-
-    <!-- 计划详情 -->
-    <template v-else-if="plan">
-      <view class="header">
-        <text class="title">{{ plan.title }}</text>
-        <view class="status-badge" :class="'status-' + (plan.status || 0)">
-          <text>{{ statusText(plan.status) }}</text>
-        </view>
-      </view>
-
-      <view class="info-card" v-if="plan.description">
-        <text class="info-label">计划说明</text>
-        <text class="info-value">{{ plan.description }}</text>
-      </view>
-
-      <view class="info-card">
-        <text class="info-label">成员</text>
-        <text class="info-value">{{ plan.childName || '--' }}</text>
-      </view>
-
-      <view class="info-card" v-if="plan.guideName">
-        <text class="info-label">规划师</text>
-        <text class="info-value">{{ plan.guideName }}</text>
-      </view>
-
-      <view class="info-card">
-        <text class="info-label">创建时间</text>
-        <text class="info-value">{{ plan.createTime || '--' }}</text>
-      </view>
-
-      <!-- 计划条目 -->
-      <view class="items-section" v-if="plan.items && plan.items.length > 0">
-        <text class="section-title">计划内容</text>
-        <view class="item-card" v-for="item in plan.items" :key="item.id">
-          <view class="item-top">
-            <text class="item-title">{{ item.title }}</text>
-            <text class="item-status" v-if="item.completed">✅ 已完成</text>
-          </view>
-          <text class="item-desc" v-if="item.description">{{ item.description }}</text>
-        </view>
-      </view>
-    </template>
-
-    <!-- 空状态 -->
-    <view class="empty" v-else>
-      <text class="empty-icon">📋</text>
-      <text class="empty-text">计划不存在</text>
-    </view>
-  </view>
+  <view class="redirect-shell"></view>
 </template>
 
 <script>
-import { getGuidePlanDetail } from '../../../utils/api.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
+// 旧路径跳转壳:/pages/parent/plans/detail → /pages/family/plans/detail
+import { redirectToNewPath } from '../../../utils/redirect.js'
 
 export default {
-  mixins: [fontScaleMixin],
-  data() {
-    return {
-      loading: false,
-      plan: null
-    }
-  },
-  onLoad: function(options) {
-    if (options.planId) {
-      this.planId = options.planId
-      this.loadDetail()
-    }
-  },
-  methods: {
-    loadDetail: function() {
-      if (!this.planId) return
-      this.loading = true
-      var self = this
-      getGuidePlanDetail({ planId: this.planId }).then(function(res) {
-        self.loading = false
-        if (res.code === 200 && res.data) {
-          self.plan = res.data
-        }
-      }).catch(function() {
-        self.loading = false
-      })
-    },
-    statusText: function(status) {
-      var map = { 0: '待审核', 1: '进行中', 2: '已完成', 3: '已取消' }
-      return map[status] || '未知'
-    }
+  onLoad: function () {
+    redirectToNewPath('/pages/family/plans/detail')
   }
 }
 </script>
-
-<style scoped lang="scss">
-.container { min-height: 100vh; background: $facade-fill-page; padding-bottom: 30rpx; }
-.loading, .empty { display: flex; flex-direction: column; align-items: center; padding: 100rpx 0; }
-.empty-icon { font-size: 80rpx; }
-.empty-text { font-size: calc(30rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 20rpx; }
-.header { background: $app-surface; padding: 40rpx 30rpx; display: flex; justify-content: space-between; align-items: center; }
-.title { font-size: calc(36rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; flex: 1; }
-.status-badge { font-size: 24rpx; padding: 6rpx 20rpx; border-radius: 20rpx; }
-.status-0 { background: $fortune-1-tint; color: $brand-primary; }
-.status-1 { background: $mat-green-tint; color: $mat-green; }
-.status-2 { background: $mat-blue-tint; color: $mat-blue-base; }
-.status-3 { background: $facade-fill-2; color: $facade-text-muted; }
-.info-card { background: $app-surface; margin: 20rpx 30rpx; padding: 24rpx 30rpx; border-radius: 16rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.info-label { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-muted; display: block; margin-bottom: 8rpx; }
-.info-value { font-size: calc(28rpx * var(--font-scale, 1)); color: $facade-text-strong; display: block; }
-.items-section { margin: 20rpx 30rpx; }
-.section-title { font-size: calc(30rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; display: block; margin-bottom: 16rpx; }
-.item-card { background: $app-surface; border-radius: 12rpx; padding: 24rpx; margin-bottom: 16rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); }
-.item-top { display: flex; justify-content: space-between; align-items: center; }
-.item-title { font-size: calc(28rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; }
-.item-status { font-size: calc(24rpx * var(--font-scale, 1)); color: $mat-green; }
-.item-desc { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 10rpx; display: block; line-height: 1.5; }
-</style>

+ 5 - 94
cfc-frontend/pages/parent/plans/index.vue

@@ -1,103 +1,14 @@
 <template>
-  <view class="container" :style="{ '--font-scale': fontScale }">
-    <view class="header">
-      <text class="title">我的计划</text>
-      <text class="subtitle">查看和管理家庭成长计划</text>
-    </view>
-
-    <!-- 加载中 -->
-    <view class="loading" v-if="loading">
-      <text>加载中...</text>
-    </view>
-
-    <!-- 空状态 -->
-    <view class="empty" v-else-if="plans.length === 0">
-      <text class="empty-icon">📋</text>
-      <text class="empty-text">暂无计划</text>
-      <text class="empty-hint">规划师可为您创建专属成长计划</text>
-    </view>
-
-    <!-- 计划列表 -->
-    <view class="plan-list" v-else>
-      <view class="plan-card" v-for="plan in plans" :key="plan.id" @click="goDetail(plan.id)">
-        <view class="card-top">
-          <text class="plan-title">{{ plan.title }}</text>
-          <view class="plan-status" :class="'status-' + (plan.status || 0)">
-            <text>{{ statusText(plan.status) }}</text>
-          </view>
-        </view>
-        <text class="plan-desc" v-if="plan.description">{{ plan.description }}</text>
-        <view class="card-meta">
-          <text class="meta-item">成员:{{ plan.childName || '--' }}</text>
-          <text class="meta-item">创建时间:{{ plan.createTime || '--' }}</text>
-        </view>
-      </view>
-    </view>
-  </view>
+  <view class="redirect-shell"></view>
 </template>
 
 <script>
-import { listFamilyPlans } from '../../../utils/api.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
+// 旧路径跳转壳:/pages/parent/plans/index → /pages/family/plans/index
+import { redirectToNewPath } from '../../../utils/redirect.js'
 
 export default {
-  mixins: [fontScaleMixin],
-  data() {
-    return {
-      loading: false,
-      plans: []
-    }
-  },
-  computed: {},
-  onShow() {
-    this.loadPlans()
-  },
-  methods: {
-    loadPlans: function() {
-      this.loading = true
-      var self = this
-      listFamilyPlans({}).then(function(res) {
-        self.loading = false
-        if (res.code === 200 && Array.isArray(res.data)) {
-          self.plans = res.data
-        } else if (res.code === 200 && res.data && res.data.records) {
-          self.plans = res.data.records
-        }
-      }).catch(function() {
-        self.loading = false
-        self.plans = []
-      })
-    },
-    goDetail: function(id) {
-      uni.navigateTo({ url: './detail?planId=' + id })
-    },
-    statusText: function(status) {
-      var map = { 0: '待审核', 1: '进行中', 2: '已完成', 3: '已取消' }
-      return map[status] || '未知'
-    }
+  onLoad: function () {
+    redirectToNewPath('/pages/family/plans/index')
   }
 }
 </script>
-
-<style scoped lang="scss">
-.container { min-height: 100vh; background: $facade-fill-page; padding-bottom: 30rpx; }
-.header { padding: 40rpx 30rpx 20rpx; }
-.title { font-size: calc(36rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; display: block; }
-.subtitle { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-muted; margin-top: 8rpx; display: block; }
-.loading, .empty { display: flex; flex-direction: column; align-items: center; padding: 100rpx 0; }
-.empty-icon { font-size: 80rpx; }
-.empty-text { font-size: calc(30rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 20rpx; }
-.empty-hint { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-ghost-light; margin-top: 10rpx; }
-.plan-list { padding: 0 30rpx; }
-.plan-card { background: $app-surface; border-radius: 16rpx; padding: 30rpx; margin-bottom: 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.card-top { display: flex; justify-content: space-between; align-items: center; }
-.plan-title { font-size: calc(30rpx * var(--font-scale, 1)); font-weight: bold; color: $facade-text-strong; flex: 1; }
-.plan-status { font-size: calc(22rpx * var(--font-scale, 1)); padding: 4rpx 16rpx; border-radius: 20rpx; }
-.status-0 { background: $fortune-1-tint; color: $brand-primary; }
-.status-1 { background: $mat-green-tint; color: $mat-green; }
-.status-2 { background: $mat-blue-tint; color: $mat-blue-base; }
-.status-3 { background: $facade-fill-2; color: $facade-text-muted; }
-.plan-desc { font-size: calc(26rpx * var(--font-scale, 1)); color: $facade-text-body; margin-top: 12rpx; display: block; line-height: 1.5; }
-.card-meta { display: flex; flex-direction: column; margin-top: 16rpx; gap: 6rpx; }
-.meta-item { font-size: calc(24rpx * var(--font-scale, 1)); color: $facade-text-ghost-light; }
-</style>

+ 14 - 558
cfc-frontend/pages/parent/wishes/index.vue

@@ -1,558 +1,14 @@
-<template>
-  <view class="container" :style="{ '--font-scale': fontScale }">
-    <!-- 标签页切换 -->
-    <view class="tabs">
-      <view 
-        class="tab-item" 
-        :class="{ active: activeTab === 'pending' }"
-        @click="activeTab = 'pending'"
-      >
-        待处理
-        <text class="badge" v-if="pendingCount > 0">{{ pendingCount }}</text>
-      </view>
-      <view 
-        class="tab-item" 
-        :class="{ active: activeTab === 'all' }"
-        @click="activeTab = 'all'"
-      >
-        全部心愿
-      </view>
-    </view>
-
-    <!-- 待处理列表 -->
-    <view class="wish-list" v-if="activeTab === 'pending'">
-      <!-- 待定价 -->
-      <view class="section" v-if="draftWishes.length > 0">
-        <view class="section-title">📝 待定价</view>
-        <view 
-          v-for="wish in draftWishes" 
-          :key="wish.id" 
-          class="wish-card"
-        >
-          <view class="wish-info">
-            <text class="wish-child">{{ wish.childName }}</text>
-            <text class="wish-title">{{ wish.title }}</text>
-            <text class="wish-category" v-if="wish.category">{{ wish.category }}</text>
-          </view>
-          <view class="wish-actions">
-            <button class="btn-price" @click="showPriceDialog(wish)">定价</button>
-            <button class="btn-reject" @click="rejectWish(wish)">拒绝</button>
-          </view>
-        </view>
-      </view>
-
-      <!-- 待审批兑换 -->
-      <view class="section" v-if="pendingExchangeWishes.length > 0">
-        <view class="section-title">⏳ 待审批兑换</view>
-        <view 
-          v-for="wish in pendingExchangeWishes" 
-          :key="wish.id" 
-          class="wish-card"
-        >
-          <view class="wish-info">
-            <text class="wish-child">{{ wish.childName }}</text>
-            <text class="wish-title">{{ wish.title }}</text>
-            <text class="wish-points">{{ wish.pointsRequired }}积分</text>
-          </view>
-          <view class="wish-actions">
-            <button class="btn-approve" @click="approveExchange(wish, true)">批准</button>
-            <button class="btn-reject" @click="approveExchange(wish, false)">拒绝</button>
-          </view>
-        </view>
-      </view>
-
-      <!-- 待兑现 -->
-      <view class="section" v-if="approvedWishes.length > 0">
-        <view class="section-title">📦 待兑现</view>
-        <view 
-          v-for="wish in approvedWishes" 
-          :key="wish.id" 
-          class="wish-card"
-        >
-          <view class="wish-info">
-            <text class="wish-child">{{ wish.childName }}</text>
-            <text class="wish-title">{{ wish.title }}</text>
-            <text class="wish-points">已扣{{ wish.pointsRequired }}积分</text>
-          </view>
-          <view class="wish-actions">
-            <button class="btn-fulfill" @click="markFulfilled(wish)">已购买</button>
-          </view>
-        </view>
-      </view>
-
-      <!-- 空状态 -->
-      <view class="empty" v-if="pendingWishes.length === 0">
-        <text class="empty-icon">✅</text>
-        <text class="empty-text">暂无待处理心愿</text>
-      </view>
-    </view>
-
-    <!-- 全部心愿列表 -->
-    <view class="wish-list" v-if="activeTab === 'all'">
-      <view 
-        v-for="wish in allWishes" 
-        :key="wish.id" 
-        class="wish-card"
-        :class="wish.status"
-      >
-        <view class="wish-header">
-          <view class="wish-info-left">
-            <text class="wish-child">{{ wish.childName }}</text>
-            <text class="wish-title">{{ wish.title }}</text>
-          </view>
-          <text class="wish-status" :class="wish.status">{{ wish.statusText }}</text>
-        </view>
-        <view class="wish-meta">
-          <text class="wish-points" v-if="wish.pointsRequired">{{ wish.pointsRequired }}积分</text>
-          <text class="wish-time">{{ wish.createdAt }}</text>
-        </view>
-      </view>
-
-      <!-- 空状态 -->
-      <view class="empty" v-if="allWishes.length === 0">
-        <text class="empty-icon">🎁</text>
-        <text class="empty-text">暂无心愿</text>
-      </view>
-    </view>
-
-    <!-- 定价弹窗 -->
-    <view class="modal-mask" v-if="priceDialogVisible" @click="priceDialogVisible = false">
-      <view class="modal" @click.stop>
-        <view class="modal-title">设置积分</view>
-        <view class="modal-content">
-          <text class="modal-wish-title">{{ currentWish && currentWish.title }}</text>
-          <view class="input-row">
-            <text class="inputLabel">所需积分:</text>
-            <input 
-              type="number" 
-              v-model="priceValue" 
-              placeholder="请输入积分"
-              class="price-input"
-            />
-          </view>
-        </view>
-        <view class="modal-btns">
-          <button class="btn-cancel" @click="priceDialogVisible = false">取消</button>
-          <button class="btn-confirm" @click="submitPrice">确定</button>
-        </view>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { 
-  getPendingWishes, 
-  getWishes, 
-  setWishPrice, 
-  rejectWish,
-  approveWishExchange,
-  markWishFulfilled
-} from '../../../utils/api.js'
-import fontScaleMixin from '@/mixins/font-scale.js'
-
-export default {
-  mixins: [fontScaleMixin],
-  data() {
-    return {
-      activeTab: 'pending',
-      memberId: '',
-      childName: '',
-      pendingWishes: [],
-      allWishes: [],
-      priceDialogVisible: false,
-      currentWish: null,
-      priceValue: ''
-    }
-  },
-  computed: {
-    pendingCount() {
-      return this.pendingWishes.length
-    },
-    draftWishes() {
-      return this.pendingWishes.filter(w => w.status === 'draft')
-    },
-    pendingExchangeWishes() {
-      return this.pendingWishes.filter(w => w.status === 'pending_exchange')
-    },
-    approvedWishes() {
-      return this.pendingWishes.filter(w => w.status === 'approved')
-    }
-  },
-  onShow() {
-    this.loadData()
-  },
-  onLoad(options) {
-    if (options.memberId) {
-      this.memberId = options.memberId
-      this.childName = options.childName || '该成员'
-    }
-  },
-  methods: {
-    async loadData() {
-      try {
-        // 获取待处理列表
-        const pendingRes = await getPendingWishes()
-        let wishes = pendingRes.data || []
-
-        // 如果指定了 memberId,按成员过滤
-        if (this.memberId) {
-          wishes = wishes.filter(w => String(w.memberId) === String(this.memberId))
-        }
-        this.pendingWishes = wishes
-        
-        // 获取全部心愿
-        const allRes = await getWishes({})
-        let allWishes = allRes.data || []
-        if (this.memberId) {
-          allWishes = allWishes.filter(w => String(w.memberId) === String(this.memberId))
-        }
-        this.allWishes = allWishes
-      } catch (e) {
-        console.error('加载数据失败', e)
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      }
-    },
-    
-    showPriceDialog(wish) {
-      this.currentWish = wish
-      this.priceValue = ''
-      this.priceDialogVisible = true
-    },
-    
-    async submitPrice() {
-      const points = parseInt(this.priceValue)
-      if (!points || points <= 0) {
-        uni.showToast({ title: '请输入有效积分', icon: 'none' })
-        return
-      }
-      
-      try {
-        await setWishPrice(this.currentWish.id, points)
-        uni.showToast({ title: '定价成功', icon: 'success' })
-        this.priceDialogVisible = false
-        this.loadData()
-      } catch (e) {
-        uni.showToast({ title: e.message || '定价失败', icon: 'none' })
-      }
-    },
-    
-    async rejectWish(wish) {
-      uni.showModal({
-        title: '拒绝心愿',
-        content: `确定要拒绝"${wish.title}"吗?`,
-        success: async (res) => {
-          if (res.confirm) {
-            try {
-              await rejectWish(wish.id, '不合适')
-              uni.showToast({ title: '已拒绝', icon: 'success' })
-              this.loadData()
-            } catch (e) {
-              uni.showToast({ title: '操作失败', icon: 'none' })
-            }
-          }
-        }
-      })
-    },
-    
-    async approveExchange(wish, approved) {
-      const action = approved ? '批准' : '拒绝'
-      uni.showModal({
-        title: `${action}兑换`,
-        content: `确定要${action}"${wish.title}"的兑换申请吗?`,
-        success: async (res) => {
-          if (res.confirm) {
-            try {
-              await approveWishExchange(wish.id, approved)
-              uni.showToast({ title: `已${action}`, icon: 'success' })
-              this.loadData()
-            } catch (e) {
-              uni.showToast({ title: e.message || '操作失败', icon: 'none' })
-            }
-          }
-        }
-      })
-    },
-    
-    async markFulfilled(wish) {
-      uni.showModal({
-        title: '标记已购买',
-        content: `确定已购买"${wish.title}"吗?`,
-        success: async (res) => {
-          if (res.confirm) {
-            try {
-              await markWishFulfilled(wish.id)
-              uni.showToast({ title: '已标记', icon: 'success' })
-              this.loadData()
-            } catch (e) {
-              uni.showToast({ title: '操作失败', icon: 'none' })
-            }
-          }
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style scoped lang="scss">
-.container {
-  padding: 30rpx;
-  min-height: 100vh;
-  background: $facade-fill-2;
-}
-
-/* 标签页 */
-.tabs {
-  display: flex;
-  background: $app-surface;
-  border-radius: 50rpx;
-  padding: 8rpx;
-  margin-bottom: 30rpx;
-}
-
-.tab-item {
-  flex: 1;
-  text-align: center;
-  padding: 16rpx;
-  border-radius: 40rpx;
-  font-size: 28rpx;
-  color: $facade-text-body;
-  position: relative;
-}
-
-.tab-item.active {
-  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
-  color: $app-surface;
-}
-
-.badge {
-  position: absolute;
-  top: 4rpx;
-  right: 20rpx;
-  background: $brand-primary;
-  color: $app-surface;
-  font-size: 20rpx;
-  padding: 2rpx 10rpx;
-  border-radius: 20rpx;
-  min-width: 30rpx;
-  text-align: center;
-}
-
-.tab-item.active .badge {
-  background: $app-surface;
-  color: $brand-primary;
-}
-
-/* 分组 */
-.section {
-  margin-bottom: 30rpx;
-}
-
-.section-title {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $facade-text-body;
-  margin-bottom: 15rpx;
-  padding-left: 10rpx;
-}
-
-/* 心愿卡片 */
-.wish-card {
-  background: $app-surface;
-  border-radius: 15rpx;
-  padding: 25rpx;
-  margin-bottom: 15rpx;
-}
-
-.wish-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-start;
-}
-
-.wish-info {
-  flex: 1;
-}
-
-.wish-info-left {
-  flex: 1;
-}
-
-.wish-child {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $brand-primary;
-  background: $status-danger-tint;
-  padding: 4rpx 12rpx;
-  border-radius: 10rpx;
-  margin-right: 10rpx;
-}
-
-.wish-title {
-  font-size: calc(30rpx * var(--font-scale, 1));
-  color: $facade-text-strong;
-  font-weight: bold;
-  margin-left: 10rpx;
-}
-
-.wish-category {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-body;
-  background: $facade-fill-2;
-  padding: 4rpx 12rpx;
-  border-radius: 10rpx;
-  margin-left: 10rpx;
-}
-
-.wish-meta {
-  display: flex;
-  justify-content: space-between;
-  margin-top: 15rpx;
-}
-
-.wish-points {
-  font-size: calc(26rpx * var(--font-scale, 1));
-  color: $brand-primary;
-  font-weight: bold;
-}
-
-.wish-time {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-}
-
-.wish-status {
-  font-size: calc(24rpx * var(--font-scale, 1));
-  padding: 6rpx 16rpx;
-  border-radius: 20rpx;
-  background: $facade-fill;
-  color: $facade-text-body;
-}
-
-.wish-status.active { background: $status-success-soft; color: $status-success-deep; }
-.wish-status.pending_exchange { background: $status-warning-soft; color: $status-warning-deep; }
-.wish-status.approved { background: $status-info-soft; color: $status-info-deep; }
-.wish-status.exchanging { background: $status-info-soft; color: $status-info-deep; }
-.wish-status.fulfilled { background: $violet-tint-100; color: $facade-violet-deep; }
-
-.wish-actions {
-  display: flex;
-  gap: 15rpx;
-  margin-top: 20rpx;
-}
-
-.btn-price, .btn-approve, .btn-fulfill {
-  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
-  color: $app-surface;
-  font-size: 26rpx;
-  padding: 12rpx 24rpx;
-  border-radius: 30rpx;
-  margin: 0;
-}
-
-.btn-reject {
-  background: $app-surface;
-  color: $facade-text-muted;
-  border: 1rpx solid $facade-border-soft;
-  font-size: 26rpx;
-  padding: 12rpx 24rpx;
-  border-radius: 30rpx;
-  margin: 0;
-}
-
-/* 空状态 */
-.empty {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 80rpx;
-}
-
-.empty-icon {
-  font-size: 80rpx;
-  margin-bottom: 20rpx;
-}
-
-.empty-text {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $facade-text-muted;
-}
-
-/* 弹窗 */
-.modal-mask {
-  position: fixed;
-  top: 0;
-  left: 0;
-  right: 0;
-  bottom: 0;
-  background: rgba(0,0,0,0.5);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.modal {
-  background: $app-surface;
-  border-radius: 20rpx;
-  padding: 40rpx;
-  width: 80%;
-}
-
-.modal-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  text-align: center;
-  margin-bottom: 30rpx;
-}
-
-.modal-content {
-  margin-bottom: 30rpx;
-}
-
-.modal-wish-title {
-  font-size: 28rpx;
-  color: $facade-text-strong;
-  display: block;
-  text-align: center;
-  margin-bottom: 30rpx;
-}
-
-.input-row {
-  display: flex;
-  align-items: center;
-}
-
-.inputLabel {
-  font-size: calc(28rpx * var(--font-scale, 1));
-  color: $facade-text-strong;
-}
-
-.price-input {
-  flex: 1;
-  background: $facade-fill-2;
-  border-radius: 15rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-}
-
-.modal-btns {
-  display: flex;
-  gap: 20rpx;
-}
-
-.btn-cancel, .btn-confirm {
-  flex: 1;
-  padding: 20rpx;
-  border-radius: 40rpx;
-  font-size: 28rpx;
-}
-
-.btn-cancel {
-  background: $facade-fill-2;
-  color: $facade-text-body;
-}
-
-.btn-confirm {
-  background: linear-gradient(135deg, $brand-primary, $brand-primary-light);
-  color: $app-surface;
-}
-</style>
+<template>
+  <view class="redirect-shell"></view>
+</template>
+
+<script>
+// 旧路径跳转壳:/pages/parent/wishes/index → /pages/family/wishes/index
+import { redirectToNewPath } from '../../../utils/redirect.js'
+
+export default {
+  onLoad: function () {
+    redirectToNewPath('/pages/family/wishes/index')
+  }
+}
+</script>

+ 1 - 1
cfc-frontend/pages/profile-extra/gates.vue

@@ -147,7 +147,7 @@ export default {
           url = '/pages/health/report-upload'
           break
         case 'MICRO_ACTION':
-          url = '/pages/home-pages/parent-index'
+          url = '/pages/home-pages/family-index'
           break
         case 'STREAK_DAYS':
           url = '/pages/health/daily-checkin'

+ 1 - 1
cfc-frontend/pages/profile-main/profile.vue

@@ -753,7 +753,7 @@ export default {
       uni.navigateTo({ url: '/pages/home-pages/member-home-detail?memberId=' + member.id })
     },
     goToAppointments: function() {
-      uni.navigateTo({ url: '/pages/parent/appointments' })
+      uni.navigateTo({ url: '/pages/family/appointments' })
     },
     goToOrders: function() {
       uni.navigateTo({ url: '/pages/shop/order-list/order-list' })

+ 1 - 1
cfc-frontend/pages/profile/components/ProfileMenu.vue

@@ -247,7 +247,7 @@ var res = await getReferralCode()
       uni.navigateTo({ url: '/pages/profile-extra/family-members' })
     },
     goToAppointments() {
-      uni.navigateTo({ url: '/pages/parent/appointments' })
+      uni.navigateTo({ url: '/pages/family/appointments' })
     },
     goToDailyTasks() {
       uni.navigateTo({ url: '/pages/tasks/daily-tasks' })

+ 91 - 31
cfc-frontend/utils/api.js

@@ -565,13 +565,19 @@ export const switchMode = (mode) => {
 	return request('/api/user/switch-mode', 'POST', { mode })
 }
 
-export const switchToChild = (memberId) => {
+// 切换到指定家庭成员视角(旧接口路径不变:/api/user/switch-to-child)
+export const switchToMemberView = (memberId) => {
 	return request('/api/user/switch-to-child', 'POST', { memberId })
 }
+// @deprecated 兼容旧名,转发到 switchToMemberView
+export const switchToChild = (memberId) => switchToMemberView(memberId)
 
-export const switchBackToParent = () => {
+// 切回本人视角
+export const switchBackToSelf = () => {
 	return request('/api/user/switch-back-to-parent', 'POST', {})
 }
+// @deprecated 兼容旧名,转发到 switchBackToSelf
+export const switchBackToParent = () => switchBackToSelf()
 
 export const switchBackVerify = (password) => {
 	return request('/api/family/user/switch-back-verify', 'POST', { password })
@@ -636,26 +642,35 @@ export const getAuthorizations = () => {
 	return request('/api/parent/authorizations', 'POST')
 }
 
-// @deprecated 别名:统一使用 getFamilyMemberList,避免同 URL 多封装导致重复调用
-// @deprecated 使用 getFamilyMemberList 代替。getChildren 与 getFamilyMemberList 调用同一接口 /api/family/member/list,
-// 同一页面同时调用两者会导致重复请求。新代码请直接使用 getFamilyMemberList。
-export const getChildren = () => getFamilyMemberList({})
+// 家庭成员列表(统一成员语义,底层仍走 /api/family/member/list)
+export const getFamilyMembers = () => getFamilyMemberList({})
+// @deprecated 兼容旧名,转发到 getFamilyMembers
+export const getChildren = () => getFamilyMembers()
 
-export const getParentDashboard = () => {
+// 家庭首页概览
+export const getFamilyDashboard = () => {
   return request('/api/parent/dashboard', 'POST')
 }
+// @deprecated 兼容旧名,转发到 getFamilyDashboard
+export const getParentDashboard = () => getFamilyDashboard()
 
 export const getSystemBalance = (memberId) => {
   return request('/api/points/system-balance', 'POST', { memberId })
 }
 
-export const createChild = (data) => {
+// 新增家庭成员
+export const createFamilyMember = (data) => {
   return request('/api/family/user/children', 'POST', data)
 }
+// @deprecated 兼容旧名,转发到 createFamilyMember
+export const createChild = (data) => createFamilyMember(data)
 
-export const updateChild = (memberId, data) => {
+// 更新家庭成员资料
+export const updateMemberProfile = (memberId, data) => {
   return request('/api/family/user/children/update', 'POST', Object.assign({}, data, { memberId: memberId }))
 }
+// @deprecated 兼容旧名,转发到 updateMemberProfile
+export const updateChild = (memberId, data) => updateMemberProfile(memberId, data)
 
 // 家庭成员模块(统一添加成员)
 export const addFamilyMember = (data) => {
@@ -805,13 +820,18 @@ export const getExchangeHistory = (memberId, page = 1, size = 10) => {
 // ===
 // 家长任务模块(孩子分配给家长的任务)
 // ===
-export const getTodayParentTasks = () => {
+// 家庭管理者待办任务(成员分配给本人的任务)
+export const getTodayOwnerTasks = () => {
   return request('/api/tasks/today-parent', 'POST')
 }
+// @deprecated 兼容旧名,转发到 getTodayOwnerTasks
+export const getTodayParentTasks = () => getTodayOwnerTasks()
 
-export const completeParentTask = (taskId) => {
+export const completeOwnerTask = (taskId) => {
   return request(`/api/tasks/${taskId}/complete-parent`, 'POST', {})
 }
+// @deprecated 兼容旧名,转发到 completeOwnerTask
+export const completeParentTask = (taskId) => completeOwnerTask(taskId)
 
 // ===
 // 家庭成员任务分配模块
@@ -827,26 +847,35 @@ export const acceptTask = (taskId, memberId) => {
 // ===
 // 家长心愿单模块
 // ===
-export const createParentWishlist = (data) => {
+// 家庭管理者心愿单
+export const createFamilyOwnerWishlist = (data) => {
   return request('/api/parent/wishlist', 'POST', data)
 }
+// @deprecated 兼容旧名,转发到 createFamilyOwnerWishlist
+export const createParentWishlist = (data) => createFamilyOwnerWishlist(data)
 
-export const getParentWishlist = () => {
+export const getFamilyOwnerWishlist = () => {
   return request('/api/parent/wishlist/list', 'POST')
 }
+// @deprecated 兼容旧名,转发到 getFamilyOwnerWishlist
+export const getParentWishlist = () => getFamilyOwnerWishlist()
 
 export const getFamilyWishlist = () => {
   return request('/api/parent/wishlist/family', 'POST')
 }
 
-export const exchangeParentWishlist = (id) => {
+export const exchangeFamilyOwnerWishlist = (id) => {
   return request(`/api/parent/wishlist/${id}/exchange`, 'POST', {})
 }
+// @deprecated 兼容旧名,转发到 exchangeFamilyOwnerWishlist
+export const exchangeParentWishlist = (id) => exchangeFamilyOwnerWishlist(id)
 
-// 家长审批孩子的心愿并填写积分
-export const approveParentWishlist = (id, pointsRequired) => {
+// 家庭管理者审批成员心愿并填写积分
+export const approveFamilyOwnerWishlist = (id, pointsRequired) => {
   return request(`/api/parent/wishlist/${id}/approve`, 'POST', { pointsRequired })
 }
+// @deprecated 兼容旧名,转发到 approveFamilyOwnerWishlist
+export const approveParentWishlist = (id, pointsRequired) => approveFamilyOwnerWishlist(id, pointsRequired)
 
 // ===
 // 家长心愿管理模块
@@ -1379,9 +1408,12 @@ export const createGrowthRecord = (data) => {
 export const getGrowthRecordList = () => {
   return request('/api/growth/record/list', 'POST')
 }
-export const getChildRecords = (memberId) => {
+// 成员成长记录(旧接口路径不变:/api/growth/record/child/{memberId})
+export const getMemberGrowthRecords = (memberId) => {
   return request('/api/growth/record/child/' + memberId, 'POST')
 }
+// @deprecated 兼容旧名,转发到 getMemberGrowthRecords
+export const getChildRecords = (memberId) => getMemberGrowthRecords(memberId)
 export const getGrowthRecordDetail = (id) => {
   return request('/api/growth/record/' + id, 'POST')
 }
@@ -1453,10 +1485,12 @@ export const getOrderStats = () => {
   return request('/api/guide/orders/stats', 'POST')
 }
 
-// 任务完成率统计
-export const getChildCompletionStats = (params) => {
+// 成员任务完成率统计
+export const getMemberCompletionStats = (params) => {
   return request('/api/stats/child-completion', 'POST', params)
 }
+// @deprecated 兼容旧名,转发到 getMemberCompletionStats
+export const getChildCompletionStats = (params) => getMemberCompletionStats(params)
 
 export const getFamilyCompletionStats = (params) => {
   return request('/api/stats/family-completion', 'POST', params)
@@ -1478,13 +1512,18 @@ export const acceptInviteCard = (code, phone) => {
 }
 
 // 瑙勫垝甯堥個璇峰闀?
-export const generateParentInvite = () => {
+// 规划师邀请家庭加入(旧接口路径不变:/api/guide/parent-invite/*)
+export const generateGuideInvite = () => {
   return request('/api/guide/parent-invite/generate', 'POST', {})
 }
+// @deprecated 兼容旧名,转发到 generateGuideInvite
+export const generateParentInvite = () => generateGuideInvite()
 
-export const acceptParentInvite = (code) => {
+export const acceptGuideInvite = (code) => {
   return request('/api/guide/parent-invite/accept', 'POST', { code })
 }
+// @deprecated 兼容旧名,转发到 acceptGuideInvite
+export const acceptParentInvite = (code) => acceptGuideInvite(code)
 
 // 踢出家庭成员(仅管理员)
 export const kickFamilyMember = (targetUserId) => {
@@ -1778,9 +1817,12 @@ export const adminConfigAll = (params) => {
 
 
 //  统计报表
-export const getChildOverview = (memberId) => {
+// 成员数据总览(旧接口路径不变:/api/stats/child/overview)
+export const getMemberOverview = (memberId) => {
   return request('/api/stats/child/overview', 'POST', { memberId })
 }
+// @deprecated 兼容旧名,转发到 getMemberOverview
+export const getChildOverview = (memberId) => getMemberOverview(memberId)
 
 export const getFamilySummary = () => {
   return request('/api/stats/family/summary', 'POST', {})
@@ -1803,17 +1845,22 @@ export const getBadgeDetail = (id) => {
   return request('/api/badges/detail', 'POST', { id })
 }
 
-export const getChildBadges = (memberId) => {
+// 成员徽章列表(旧接口路径不变:/api/badges/child/list)
+export const getMemberBadges = (memberId) => {
   return request('/api/badges/child/list', 'POST', { memberId })
 }
+// @deprecated 兼容旧名,转发到 getMemberBadges
+export const getChildBadges = (memberId) => getMemberBadges(memberId)
 
 export const toggleBadgeFavorite = (memberId, badgeId) => {
   return request('/api/badges/favorite', 'POST', { memberId, badgeId })
 }
 
-export const getChildBadgeStats = (memberId) => {
+export const getMemberBadgeStats = (memberId) => {
   return request('/api/badges/statistics/child', 'POST', { memberId })
 }
+// @deprecated 兼容旧名,转发到 getMemberBadgeStats
+export const getChildBadgeStats = (memberId) => getMemberBadgeStats(memberId)
 
 export const getFamilyBadgeRanking = () => {
   return request('/api/badges/ranking/family', 'POST', {})
@@ -1990,8 +2037,14 @@ export const submitQuiz = (data) => request('/api/articles/quiz/submit', 'POST',
 
 // ===== EMI蹇冪悊鎶ュ憡 =====
 export const getEmiReport = (memberId) => request('/api/emireport/latest', 'POST', { memberId })
-export const getEmiFamilyReports = (childIds) => request('/api/emireport/family', 'POST', { childIds })
-export const getCognitiveChildScores = (memberId) => request('/api/cognitive/child', 'POST', { memberId })
+// 家庭成员 EMI 报告(请求字段名保持后端契约 childIds)
+export const getEmiFamilyMemberReports = (memberIds) => request('/api/emireport/family', 'POST', { childIds: memberIds })
+// @deprecated 兼容旧名,转发到 getEmiFamilyMemberReports
+export const getEmiFamilyReports = (childIds) => getEmiFamilyMemberReports(childIds)
+// 成员认知训练分数(旧接口路径不变:/api/cognitive/child)
+export const getCognitiveMemberScores = (memberId) => request('/api/cognitive/child', 'POST', { memberId })
+// @deprecated 兼容旧名,转发到 getCognitiveMemberScores
+export const getCognitiveChildScores = (memberId) => getCognitiveMemberScores(memberId)
 export const getCognitiveFamilyScores = () => request('/api/cognitive/family', 'POST', {})
 export const submitCognitiveSelfTest = (memberId, dimension, score) => request('/api/cognitive/self-test', 'POST', { memberId, dimension, score })
 
@@ -2555,7 +2608,10 @@ export const getNutritionProfile = (data) => request('/api/nutrition/profile/get
 export const saveNutritionProfile = (data) => request('/api/nutrition/profile/save', 'POST', data)
 
 // ===== 钀ュ吇妗f涓庣己涔忓垎鏋?=====
-export const getChildNutritionProfile = (memberId) => request('/api/health/nutrition/profile', 'POST', { memberId })
+// 成员营养档案
+export const getMemberNutritionProfile = (memberId) => request('/api/health/nutrition/profile', 'POST', { memberId })
+// @deprecated 兼容旧名,转发到 getMemberNutritionProfile
+export const getChildNutritionProfile = (memberId) => getMemberNutritionProfile(memberId)
 
 // ===== 家庭天盘 =====
 export const getFamilyFortune = () => request('/api/mind/fortune', 'POST')
@@ -2779,8 +2835,10 @@ export const discardTongue = (data) => request('/api/health/report/discard?type=
 export const parseReportDraft = (draftId) => request('/api/health/report/parse', 'POST', { draftId })
 export const confirmAssessmentReportConfirmInbox = (resultId, overallScore) => 
   request('/api/dan-assessment/result/' + resultId + '/confirm-inbox', 'POST', { overallScore })
-export const getMyAssessmentPlans = (familyId, childId) => 
-  request('/api/dan-assessment/result/plans/list', 'POST', { familyId, childId })
+// 成员的测评方案列表(请求字段名保持后端契约 childId)
+export const getMyMemberAssessmentPlans = (familyId, memberId) =>
+  request('/api/dan-assessment/result/plans/list', 'POST', { familyId, childId: memberId })
+export const getMyAssessmentPlans = (familyId, childId) => getMyMemberAssessmentPlans(familyId, childId)
 // 鎸囨爣瀹氫箟
 export const getIndicatorDefinitions = (data) => request('/api/admin/indicator/list', 'POST', data)
 
@@ -2895,11 +2953,13 @@ export const danConfirmReport = (data) => {
 }
 
 /**
- * 获取某孩子某维度的 DAN 报告列表
+ * 获取某成员某维度的 DAN 报告列表(旧接口路径不变:/api/dan-assessment/report/child)
  */
-export const getDanReportByChild = (memberId, dimension) => {
+export const getDanReportByMember = (memberId, dimension) => {
   return request('/api/dan-assessment/report/child', 'POST', { memberId, dimension })
 }
+// @deprecated 兼容旧名,转发到 getDanReportByMember
+export const getDanReportByChild = (memberId, dimension) => getDanReportByMember(memberId, dimension)
 
 export const supplierProducts = (data) => {
   return request('/api/supplier/products', 'POST', data)

+ 48 - 0
cfc-frontend/utils/redirect.js

@@ -0,0 +1,48 @@
+/**
+ * 旧页面路径跳转壳工具
+ *
+ * 背景:G2 契约/目录中性化把 pages/parent、pages/child 迁到 pages/family,
+ * home-pages/parent-index.vue、child-index.vue 改名为 family-index.vue、member-index.vue。
+ * 已发布的小程序版本仍可能跳旧路径,因此旧路径保留一个跳转壳,重定向到新路径。
+ *
+ * 规则:
+ * - 保留原页面的查询参数(memberId 等),避免跳转后丢上下文;
+ * - 优先 redirectTo(保留页面栈),目标为 tabBar 页时 redirectTo 会失败,回退 reLaunch。
+ */
+
+function buildQuery(options) {
+  if (!options) return '';
+  var keys = Object.keys(options);
+  if (!keys.length) return '';
+  var parts = [];
+  for (var i = 0; i < keys.length; i++) {
+    var key = keys[i];
+    var value = options[key];
+    if (value === undefined || value === null) continue;
+    parts.push(encodeURIComponent(key) + '=' + encodeURIComponent(value));
+  }
+  return parts.length ? '?' + parts.join('&') : '';
+}
+
+/**
+ * 跳转到新页面路径
+ * @param {string} targetPath 新页面路径,如 '/pages/family/appointments'
+ * @param {object} [options] 查询参数;缺省时自动从当前页面实例读取
+ */
+export function redirectToNewPath(targetPath, options) {
+  var opts = options;
+  if (!opts) {
+    var pages = typeof getCurrentPages === 'function' ? getCurrentPages() : [];
+    var current = pages && pages.length ? pages[pages.length - 1] : null;
+    opts = (current && current.options) || {};
+  }
+  var url = targetPath + buildQuery(opts);
+  uni.redirectTo({
+    url: url,
+    fail: function () {
+      uni.reLaunch({ url: url });
+    }
+  });
+}
+
+export default redirectToNewPath;

+ 22 - 6
cfc-web/src/api/admin.js

@@ -161,38 +161,54 @@ export function deleteTaskTemplate(id) {
   })
 }
 
-// 孩子管理
-export function getChildrenList(params) {
+// 家庭成员管理(中性语义别名;后端接口路径仍为 /api/admin/children,child_id 列迁移完成后再评估 URL)
+export function getMemberList(params) {
   return request({
     url: '/api/admin/children',
     method: 'post',
     data: params
   })
 }
+// @deprecated 兼容旧名,转发到 getMemberList
+export function getChildrenList(params) {
+  return getMemberList(params)
+}
 
-export function getChildDetail(id) {
+export function getMemberDetail(id) {
   return request({
     url: `/api/admin/children/${id}`,
     method: 'post'
   })
 }
+// @deprecated 兼容旧名,转发到 getMemberDetail
+export function getChildDetail(id) {
+  return getMemberDetail(id)
+}
 
-export function updateChild(id, data) {
+export function updateMember(id, data) {
   return request({
     url: `/api/admin/children/${id}/update`,
     method: 'post',
     data
   })
 }
+// @deprecated 兼容旧名,转发到 updateMember
+export function updateChild(id, data) {
+  return updateMember(id, data)
+}
 
-export function deleteChild(id) {
+export function deleteMember(id) {
   return request({
     url: `/api/admin/children/${id}/delete`,
     method: 'post'
   })
 }
+// @deprecated 兼容旧名,转发到 deleteMember
+export function deleteChild(id) {
+  return deleteMember(id)
+}
 
-export function adjustChildPoints(id, data) {
+export function adjustMemberPoints(id, data) {
   return request({
     url: `/api/admin/children/${id}/adjust-points`,
     method: 'post',

+ 6 - 2
cfc-web/src/api/guideTask.js

@@ -15,13 +15,17 @@ export function fetchFamilyOverview(familyId) {
 }
 
 /**
- * 成长规划师获取孩子任务列表
+ * 成长规划师获取成员任务列表(中性语义;接口路径仍为 /children/)
  */
-export function fetchChildTasks(familyId, memberId, page = 1, size = 10, status, sort) {
+export function fetchMemberTasks(familyId, memberId, page = 1, size = 10, status, sort) {
   const data = { page, size, status }
   if (sort) data.sort = sort
   return request.post(`/api/guide/families/${familyId}/children/${memberId}/tasks`, data)
 }
+// @deprecated 兼容旧名,转发到 fetchMemberTasks
+export function fetchChildTasks(familyId, memberId, page = 1, size = 10, status, sort) {
+  return fetchMemberTasks(familyId, memberId, page, size, status, sort)
+}
 
 /**
  * 成长规划师获取待审核任务

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

@@ -55,6 +55,13 @@ const routes = [
         component: () => import('@/views/Children.vue'),
         meta: { title: '孩子管理', perm: 'family:children' }
       },
+      // 家庭成员管理(中性语义别名路由,旧路径 children 保留兼容)
+      {
+        path: 'family-members',
+        name: 'FamilyMembers',
+        component: () => import('@/views/Children.vue'),
+        meta: { title: '成员管理', perm: 'family:children' }
+      },
       {
         path: 'points',
         name: 'Points',

+ 1 - 0
cfc-web/src/views/admin/ActivityRegistrationReview.vue

@@ -99,6 +99,7 @@ export default {
       var kw = this.keyword.toLowerCase()
       return this.list.filter(function(item) {
         return (item.userId && String(item.userId).includes(kw)) ||
+               (item.memberId && String(item.memberId).includes(kw)) ||
                (item.childId && String(item.childId).includes(kw)) ||
                (item.registrantName && String(item.registrantName).toLowerCase().includes(kw)) ||
                (item.registrantPhone && String(item.registrantPhone).includes(kw))

+ 1 - 1
tests/e2e/family-member-management.spec.js

@@ -272,7 +272,7 @@ test.describe('【场景流程】家庭成员管理全流程', () => {
     const childIndicator = page.locator('.child-view, .child-mode, .switched-indicator');
     const hasSwitched = await childIndicator.isVisible() ||
       page.url().includes('child') ||
-      page.url().includes('child-index');
+      page.url().includes('member-index');
 
     expect(hasSwitched).toBeTruthy();
   });