Przeglądaj źródła

Merge remote-tracking branch 'origin/feature/terminology-neutralize-l2-l3' into merge/cfclub-all-20261031

# Conflicts:
#	cfc-backend/src/main/java/com/etotem/cfc/entity/ActivityRegistration.java
#	cfc-backend/src/main/java/com/etotem/cfc/service/ActivityOrderService.java
#	cfc-backend/src/main/java/com/etotem/cfc/service/ActivityRegistrationService.java
#	cfc-frontend/pages/child/rewards.vue
#	cfc-frontend/pages/child/tasks.vue
#	cfc-frontend/pages/home-pages/child-index.vue
#	cfc-frontend/pages/home-pages/parent-index.vue
#	cfc-frontend/pages/parent/appointments.vue
#	cfc-frontend/pages/parent/child-detail.vue
#	cfc-frontend/pages/parent/plans/detail.vue
#	cfc-frontend/pages/parent/plans/index.vue
#	cfc-frontend/pages/parent/wishes/index.vue
#	cfc-frontend/utils/api.js
Agnes Agent 2 tygodni temu
rodzic
commit
ee86caa74d
28 zmienionych plików z 6470 dodań i 30 usunięć
  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 3
      cfc-backend/src/main/java/com/etotem/cfc/service/InsurancePlanningService.java
  6. 4 2
      cfc-backend/src/main/resources/schema.sql
  7. 58 3
      cfc-frontend/pages.json
  8. 1 1
      cfc-frontend/pages/assessment/results.vue
  9. 252 0
      cfc-frontend/pages/family/appointments.vue
  10. 513 0
      cfc-frontend/pages/family/member-detail.vue
  11. 120 0
      cfc-frontend/pages/family/plans/detail.vue
  12. 103 0
      cfc-frontend/pages/family/plans/index.vue
  13. 161 0
      cfc-frontend/pages/family/rewards.vue
  14. 783 0
      cfc-frontend/pages/family/tasks.vue
  15. 558 0
      cfc-frontend/pages/family/wishes/index.vue
  16. 1 1
      cfc-frontend/pages/growth-main/index.vue
  17. 1 1
      cfc-frontend/pages/guide/bind/invite.vue
  18. 2294 0
      cfc-frontend/pages/home-pages/family-index.vue
  19. 1486 0
      cfc-frontend/pages/home-pages/member-index.vue
  20. 1 1
      cfc-frontend/pages/profile-extra/gates.vue
  21. 1 1
      cfc-frontend/pages/profile-main/profile.vue
  22. 1 1
      cfc-frontend/pages/profile/components/ProfileMenu.vue
  23. 48 0
      cfc-frontend/utils/redirect.js
  24. 22 6
      cfc-web/src/api/admin.js
  25. 6 2
      cfc-web/src/api/guideTask.js
  26. 7 0
      cfc-web/src/router/index.js
  27. 1 0
      cfc-web/src/views/admin/ActivityRegistrationReview.vue
  28. 1 1
      tests/e2e/family-member-management.spec.js

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

@@ -8173,7 +8173,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());
@@ -11347,6 +11347,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 (" +
@@ -13155,6 +13158,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 - 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

@@ -2093,8 +2093,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',
@@ -2109,6 +2110,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

@@ -264,7 +264,7 @@
         {
           "path": "child-detail",
           "style": {
-            "navigationBarTitleText": "孩子详情"
+            "navigationBarTitleText": "成员详情"
           }
         }
       ]
@@ -275,13 +275,13 @@
         {
           "path": "tasks",
           "style": {
-            "navigationBarTitleText": "孩子任务"
+            "navigationBarTitleText": "成员任务"
           }
         },
         {
           "path": "rewards",
           "style": {
-            "navigationBarTitleText": "孩子心愿单"
+            "navigationBarTitleText": "成员心愿单"
           }
         }
       ]
@@ -1516,6 +1516,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": "心愿管理"
+          }
         }
       ]
     },
@@ -1811,6 +1853,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' })
     }
   }
 }

+ 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

@@ -360,7 +360,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' }) },
     goToFoods: 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' })
     }
   }
 }

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

+ 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

@@ -754,7 +754,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' })

+ 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

@@ -168,38 +168,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();
   });