Ver Fonte

feat: add energy/body/mind/promotion/stats pages, update existing pages for energy system

New page sets:
- body/: 身体健康维度页面 (body dimension)
- energy/: 能量中心 (energy center hub + history)
- mind/: 心理健康维度页面 (mental health dimension)
- promotion/: 推广中心 (referral/promotion hub + invites + commissions + team)
- games/records.vue + stats.vue: 游戏记录/统计
- rewards/badge.vue: 徽章展示页
- stats/index.vue: 统计概览

Updated pages: index (parent/child), discover, profile, login, shop, membership, tasks (child/review), wishes, games/list, config.js

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
User há 3 meses atrás
pai
commit
7d3963adbd

+ 34 - 18
cfc-frontend/config.js

@@ -1,29 +1,45 @@
 /**
  * 应用全局配置
  *
- * 环境切换方式:
- * 1. 开发环境(默认):从 VUE_APP_API_BASE 环境变量读取,或回退到默认值
- * 2. 测试环境:配置 VUE_APP_API_BASE=https://test-api.xxx.com
- * 3. 生产环境:配置 VUE_APP_API_BASE=https://api.xxx.com
+ * 环境自动检测(基于微信小程序官方 API):
+ *   uni.getAccountInfoSync().miniProgram.envVersion
+ *     'develop'  → 开发版(工具内预览)
+ *     'trial'    → 体验版(上传到体验版)
+ *     'release'  → 正式版(线上发布)
  *
- * uni-app vue-cli 项目通过 .env 文件设置环境变量:
- *   .env.development    — VUE_APP_API_BASE=http://localhost:9080
- *   .env.production     — VUE_APP_API_BASE=https://api.xxx.com
- *   .env.staging        — VUE_APP_API_BASE=https://staging-api.xxx.com
- *
- * 如果未配置环境变量,默认使用开发环境地址。
+ * 无需手动修改任何变量,发布到对应环境自动生效。
  */
 
-// #ifndef VUE_APP_API_BASE
-const API_BASE_URL = process.env.VUE_APP_API_BASE || 'http://localhost:9080'
-// #endif
+// 默认值(兜底)
+let API_BASE_URL = 'http://localhost:9080'
+let DANSHOP_BASE_URL = 'http://localhost:8081'
 
-// #ifdef VUE_APP_API_BASE
-const API_BASE_URL = VUE_APP_API_BASE
-// #endif
+try {
+  const accountInfo = uni.getAccountInfoSync()
+  const env = accountInfo && accountInfo.miniProgram && accountInfo.miniProgram.envVersion
 
-// DAN测评服务(独立服务,可能不同端口)
-const DANSHOP_BASE_URL = process.env.VUE_APP_DANSHOP_BASE || 'http://localhost:8081'
+  if (env) {
+    switch (env) {
+      case 'develop':
+        // 开发版 - 本地开发
+        API_BASE_URL = 'http://localhost:9080'
+        DANSHOP_BASE_URL = 'http://localhost:8081'
+        break
+      case 'trial':
+        // 体验版
+        API_BASE_URL = 'https://dev.iwintrue.com/num'
+        DANSHOP_BASE_URL = 'https://dev.iwintrue.com/danshop'
+        break
+      case 'release':
+        // 正式版
+        API_BASE_URL = 'https://num.etotem.com'
+        DANSHOP_BASE_URL = 'https://danshop.etotem.com'
+        break
+    }
+  }
+} catch (e) {
+  // uni.getAccountInfoSync() 不可用(非小程序环境),使用默认值
+}
 
 export default {
   API_BASE_URL,

+ 317 - 0
cfc-frontend/pages/body/index.vue

@@ -0,0 +1,317 @@
+<template>
+  <view class="body-container">
+    <!-- 品牌头部 -->
+    <view class="brand-header">
+      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
+      <text class="brand-title">身体能量</text>
+      <text class="brand-slogan">关注儿童身心健康,从每一天开始</text>
+    </view>
+
+    <!-- 功能入口 -->
+    <view class="func-section" v-if="sectionVisible('func_entries')">
+      <view class="func-grid">
+        <view class="func-item" v-for="item in funcList" :key="item.label">
+          <view class="func-icon-wrap">
+            <text class="func-icon">{{ item.icon }}</text>
+          </view>
+          <text class="func-label">{{ item.label }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 今日身体数据 -->
+    <view class="section" v-if="sectionVisible('daily_stats')">
+      <view class="section-header">
+        <text class="section-title">今日身体数据</text>
+      </view>
+      <view class="stats-card">
+        <view class="stat-item">
+          <text class="stat-value">{{ dailyStats.steps }}</text>
+          <text class="stat-unit">步</text>
+          <text class="stat-label">今日步数</text>
+        </view>
+        <view class="stat-divider"></view>
+        <view class="stat-item">
+          <text class="stat-value">{{ dailyStats.water }}</text>
+          <text class="stat-unit">L</text>
+          <text class="stat-label">饮水量</text>
+        </view>
+        <view class="stat-divider"></view>
+        <view class="stat-item">
+          <text class="stat-value">{{ dailyStats.sleep }}</text>
+          <text class="stat-unit">h</text>
+          <text class="stat-label">睡眠时长</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 健康小贴士 -->
+    <view class="section" v-if="sectionVisible('health_tips')">
+      <view class="section-header">
+        <text class="section-title">健康小贴士</text>
+      </view>
+      <view class="tips-list">
+        <view class="tip-card" v-for="item in healthTips" :key="item.id">
+          <text class="tip-title">{{ item.title }}</text>
+          <text class="tip-summary">{{ item.summary }}</text>
+          <view class="tip-footer">
+            <text class="tip-date">{{ item.date }}</text>
+            <text class="tip-tag">阅读更多</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 规划师推荐(服务商可见,后台可配置) -->
+    <view class="section" v-if="sectionVisible('teacher_tips')">
+      <view class="section-header">
+        <text class="section-title">👨‍🏫 规划师推荐</text>
+      </view>
+      <view class="tips-list">
+        <view class="tip-card" v-for="item in teacherTips" :key="item.id">
+          <text class="tip-title">{{ item.title }}</text>
+          <text class="tip-summary">{{ item.summary }}</text>
+          <view class="tip-footer">
+            <text class="tip-date">{{ item.date }}</text>
+            <text class="tip-tag">查看详情</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import { getVisibleSections } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      visibleSections: [],
+      funcList: [
+        { icon: '\u{1F3C3}', label: '运动' },
+        { icon: '\u{1F957}', label: '饮食' },
+        { icon: '\u{1F634}', label: '作息' },
+        { icon: '\u{1F9A0}', label: '菌群' },
+        { icon: '\u{1F9D8}', label: '冥想' }
+      ],
+      dailyStats: {
+        steps: '6,582',
+        water: '1.2',
+        sleep: '8.5'
+      },
+      healthTips: [
+        { id: 1, title: '儿童每日运动指南', summary: '不同年龄段儿童每天需要多少运动量?科学运动助力健康成长。', date: '2024-01-15' },
+        { id: 2, title: '五行饮食搭配法则', summary: '根据五行理论合理搭配膳食,均衡营养从每一餐开始。', date: '2024-01-12' },
+        { id: 3, title: '优质睡眠从小养成', summary: '帮助孩子建立规律的作息习惯,提升睡眠质量。', date: '2024-01-10' },
+        { id: 4, title: '肠道菌群与免疫力', summary: '了解益生菌对儿童健康的重要作用,从内部提升抵抗力。', date: '2024-01-08' }
+      ],
+      teacherTips: [
+        { id: 1, title: '儿童体态评估与纠正', summary: '专业体态评估方法,及早发现并纠正不良体态。', date: '2024-01-14' },
+        { id: 2, title: '感统训练家庭方案', summary: '针对不同年龄段的感觉统合训练建议,在家也能做。', date: '2024-01-11' }
+      ]
+    }
+  },
+  onShow() {
+    this.resetTabBar()
+    this.loadSectionConfig()
+  },
+  methods: {
+    resetTabBar() {
+      uni.setTabBarItem({ index: 1, text: '身体' })
+      uni.setTabBarItem({ index: 2, text: '心智' })
+    },
+    async loadSectionConfig() {
+      var token = uni.getStorageSync('token')
+      var role = null
+      if (token) {
+        role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      }
+      var res
+      try {
+        res = await getVisibleSections({ pageKey: 'body', role: role })
+        if (res.code === 200 && res.data) {
+          this.visibleSections = res.data.map(function(s) { return s.sectionKey })
+        }
+      } catch (e) {
+        this.visibleSections = ['func_entries', 'daily_stats', 'health_tips']
+      }
+    },
+    sectionVisible(sectionKey) {
+      return this.visibleSections.indexOf(sectionKey) !== -1
+    }
+  }
+}
+</script>
+
+<style scoped>
+.body-container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+/* ===== 品牌头部 ===== */
+.brand-header {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 30rpx 30rpx 16rpx;
+  background: linear-gradient(180deg, #1A2A4A 0%, #1E3A5F 60%, #f5f7fa 100%);
+}
+.brand-logo {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 18rpx;
+  margin-bottom: 8rpx;
+}
+.brand-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #8FC5E8;
+  letter-spacing: 4rpx;
+}
+.brand-slogan {
+  font-size: 22rpx;
+  color: rgba(143, 197, 232, 0.6);
+  margin-top: 6rpx;
+}
+
+/* ===== 功能入口 ===== */
+.func-section {
+  margin: 20rpx 30rpx;
+}
+.func-grid {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.func-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  flex: 1;
+}
+.func-icon-wrap {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 40rpx;
+  background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+.func-icon {
+  font-size: 40rpx;
+}
+.func-label {
+  font-size: 22rpx;
+  color: #666;
+  font-weight: 500;
+}
+
+/* ===== 通用区块 ===== */
+.section {
+  margin: 20rpx 30rpx;
+}
+.section-header {
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+/* ===== 今日数据 ===== */
+.stats-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx 20rpx;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.stat-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.stat-value {
+  font-size: 44rpx;
+  font-weight: bold;
+  color: #5B9BD5;
+}
+.stat-unit {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: -4rpx;
+}
+.stat-label {
+  font-size: 24rpx;
+  color: #666;
+  margin-top: 8rpx;
+}
+.stat-divider {
+  width: 1rpx;
+  height: 80rpx;
+  background: #f0f0f0;
+}
+
+/* ===== 健康小贴士 ===== */
+.tips-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+.tip-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.tip-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 10rpx;
+}
+.tip-summary {
+  font-size: 24rpx;
+  color: #888;
+  line-height: 1.5;
+  display: block;
+  margin-bottom: 16rpx;
+}
+.tip-footer {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
+.tip-date {
+  font-size: 22rpx;
+  color: #bbb;
+}
+.tip-tag {
+  font-size: 22rpx;
+  color: #5B9BD5;
+  font-weight: 500;
+}
+
+/* ===== 底部 ===== */
+.bottom-spacer {
+  height: 120rpx;
+}
+</style>

+ 14 - 14
cfc-frontend/pages/child/tasks.vue

@@ -374,7 +374,7 @@ export default {
    ============================================= */
 .page-bg {
   min-height: 100vh;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, #F5F9FC);
   padding-bottom: 40rpx;
 }
 
@@ -389,7 +389,7 @@ export default {
   padding: 24rpx;
   margin: 24rpx 32rpx 0;
   background: var(--surface, #FFFFFF);
-  border: 2rpx solid var(--border, #FED7AA);
+  border: 2rpx solid var(--border, #CBD5E1);
   border-radius: var(--radius-lg, 32rpx);
   box-shadow: var(--shadow-sm);
 }
@@ -405,7 +405,7 @@ export default {
 
 .exit-switch__text {
   font-size: 28rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   font-weight: var(--font-weight-medium, 500);
 }
 
@@ -436,19 +436,19 @@ export default {
 }
 
 .tab-item--hover {
-  background: rgba(249, 115, 22, 0.06);
+  background: rgba(91, 155, 213, 0.06);
 }
 
 .tab-item--active {
-  background: linear-gradient(145deg, var(--color-primary-light, #FB923C), var(--color-primary, #F97316));
+  background: linear-gradient(145deg, var(--color-primary-light, #8FC5E8), var(--color-primary, #5B9BD5));
   box-shadow: inset -2rpx -2rpx 8rpx rgba(255, 255, 255, 0.3),
-    4rpx 4rpx 12rpx rgba(249, 115, 22, 0.2);
+    4rpx 4rpx 12rpx rgba(91, 155, 213, 0.2);
 }
 
 .tab-item__label {
   font-size: 26rpx;
   font-weight: var(--font-weight-medium, 500);
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   transition: color var(--transition-fast, 0.15s ease);
 }
 
@@ -465,8 +465,8 @@ export default {
   height: 36rpx;
   padding: 0 8rpx;
   border-radius: 999rpx;
-  background: rgba(249, 115, 22, 0.12);
-  color: var(--color-primary, #F97316);
+  background: rgba(91, 155, 213, 0.12);
+  color: var(--color-primary, #5B9BD5);
   font-size: 20rpx;
   font-weight: var(--font-weight-bold, 600);
   line-height: 1;
@@ -553,12 +553,12 @@ export default {
   width: 88rpx;
   height: 88rpx;
   border-radius: var(--radius-md, 20rpx);
-  background: linear-gradient(145deg, #FFF7ED, #FFEDD5);
+  background: linear-gradient(145deg, #F5F9FC, #E2E8F0);
   display: flex;
   align-items: center;
   justify-content: center;
   flex-shrink: 0;
-  border: 2rpx solid var(--border-light, #FFEDD5);
+  border: 2rpx solid var(--border-light, #E2E8F0);
 }
 
 .task-card--completed .task-icon {
@@ -587,7 +587,7 @@ export default {
 .task-title {
   font-size: 32rpx;
   font-weight: var(--font-weight-bold, 600);
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   line-height: 1.4;
   margin-bottom: 10rpx;
   overflow: hidden;
@@ -597,7 +597,7 @@ export default {
 
 .task-card--completed .task-title {
   text-decoration: line-through;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
 }
 
 .task-meta {
@@ -615,7 +615,7 @@ export default {
 
 .task-time {
   font-size: 22rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   line-height: 1.4;
 }
 

+ 176 - 35
cfc-frontend/pages/discover/index.vue

@@ -3,8 +3,10 @@
     <!-- 品牌头部 -->
     <view class="brand-header">
       <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
-      <text class="brand-title">汐艾福</text>
-      <text class="brand-slogan">遵循五行节律,陪伴孩子自然成长</text>
+      <view class="brand-text-wrap">
+        <text class="brand-title">浠艾福</text>
+        <text class="brand-subtitle">家庭健康成长俱乐部</text>
+      </view>
     </view>
 
     <!-- 五行沙盘预览 -->
@@ -51,10 +53,30 @@
       </view>
     </view>
 
-    <!-- 为何选择汐艾福 -->
+    <!-- 推荐文章(未登录可见) -->
+    <view class="article-section">
+      <view class="section-header">
+        <text class="section-title">📖 成长文章</text>
+        <text class="section-more" @click="goAllArticles">查看全部 ›</text>
+      </view>
+      <view class="article-list">
+        <view v-for="(item, index) in featuredArticles" :key="item.id" class="article-card" @click="goArticleDetail(item.id)">
+          <view class="article-cover" :style="{ background: coverGradients[index % coverGradients.length] }">
+            <text class="article-cover-icon">{{ coverIcons[index % coverIcons.length] }}</text>
+          </view>
+          <view class="article-info">
+            <text class="article-title">{{ item.title }}</text>
+            <text class="article-desc">{{ item.summary || item.title }}</text>
+            <text class="article-meta">{{ item.publishedAt ? item.publishedAt.slice(0, 10) : '' }} · {{ item.readTime || 3 }}分钟阅读</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 为何选择浠艾福 -->
     <view class="why-section">
       <view class="section-header">
-        <text class="section-title">💡 为什么选择汐艾福</text>
+        <text class="section-title">💡 为什么选择艾福</text>
       </view>
       <view class="feature-grid">
         <view class="feature-item">
@@ -85,15 +107,22 @@
       <button class="login-btn" @click="handleLogin">登录 / 注册</button>
       <text class="login-footer-text">登录即表示同意《用户协议》和《隐私政策》</text>
     </view>
+
+    <!-- 未登录态自定义底部导航 -->
+    <bottom-nav current="index" />
+
+    <!-- 底部占位 -->
+    <view style="height: 20rpx;"></view>
   </view>
 </template>
 
 <script>
-import { productList } from '@/utils/api.js'
+import { productList, getFeaturedArticles } from '@/utils/api.js'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
+import BottomNav from '../../components/bottom-nav.vue'
 
 export default {
-  components: { WuxingSandbox },
+  components: { WuxingSandbox, BottomNav },
   data() {
     return {
       typeList: [
@@ -106,12 +135,22 @@ export default {
       ],
       currentType: '',
       products: [],
-      loading: false
+      loading: false,
+      featuredArticles: [],
+      coverGradients: [
+        'linear-gradient(135deg, #D6EAF8, #8FC5E8)',
+        'linear-gradient(135deg, #E8F5E9, #81C784)',
+        'linear-gradient(135deg, #FFF3E0, #FFB74D)',
+        'linear-gradient(135deg, #F3E5F5, #CE93D8)',
+        'linear-gradient(135deg, #FCE4EC, #F06292)'
+      ],
+      coverIcons: ['🌱', '🏃', '🎯', '🌟', '📚']
     }
   },
   onShow() {
     this.resetTabBar()
     this.loadProducts()
+    this.loadFeaturedArticles()
   },
   methods: {
     resetTabBar() {
@@ -138,13 +177,45 @@ export default {
       this.loadProducts()
     },
     goShop() {
-      uni.switchTab({ url: '/pages/shop/index' })
+      const token = uni.getStorageSync('token')
+      if (token) {
+        uni.switchTab({ url: '/pages/shop/index' })
+      } else {
+        uni.redirectTo({ url: '/pages/shop/index' })
+      }
     },
     goDetail(id) {
-      uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
+      const token = uni.getStorageSync('token')
+      if (token) {
+        uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/discover/index') })
+      }
     },
     handleLogin() {
-      uni.navigateTo({ url: '/pages/login/login' })
+      uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/discover/index') })
+    },
+    async loadFeaturedArticles() {
+      try {
+        const res = await getFeaturedArticles({ size: 3 })
+        if (res.code === 200 && res.data) {
+          let list = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          this.featuredArticles = list
+        }
+      } catch (e) {
+        // 静默处理
+      }
+    },
+    goArticleDetail(id) {
+      var token = uni.getStorageSync('token')
+      if (token) {
+        uni.navigateTo({ url: '/pages/mind/article-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/discover/index') })
+      }
+    },
+    goAllArticles() {
+      uni.navigateTo({ url: '/pages/mind/articles' })
     }
   }
 }
@@ -160,27 +231,34 @@ export default {
 /* ===== 品牌头部 ===== */
 .brand-header {
   display: flex;
-  flex-direction: column;
+  flex-direction: row;
   align-items: center;
-  padding: 60rpx 30rpx 30rpx;
-  background: linear-gradient(180deg, #1a1a2e 0%, #16213e 60%, #f5f7fa 100%);
+  padding: 20rpx 30rpx 12rpx;
+  background: linear-gradient(180deg, #EBF2FA 0%, #f5f9fc 100%);
 }
 .brand-logo {
-  width: 120rpx;
-  height: 120rpx;
-  border-radius: 24rpx;
-  margin-bottom: 16rpx;
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 14rpx;
+  flex-shrink: 0;
+}
+.brand-text-wrap {
+  display: flex;
+  flex-direction: column;
+  margin-left: 14rpx;
 }
 .brand-title {
-  font-size: 44rpx;
+  font-size: 32rpx;
   font-weight: bold;
-  color: #FFD700;
-  letter-spacing: 8rpx;
+  color: #2C5282;
+  letter-spacing: 4rpx;
+  line-height: 1.2;
 }
-.brand-slogan {
-  font-size: 24rpx;
-  color: rgba(255, 215, 0, 0.7);
-  margin-top: 10rpx;
+.brand-subtitle {
+  font-size: 20rpx;
+  color: #5B9BD5;
+  margin-top: 2rpx;
+  line-height: 1.2;
 }
 
 /* ===== 推荐 ===== */
@@ -200,7 +278,7 @@ export default {
 }
 .section-more {
   font-size: 24rpx;
-  color: #F97316;
+  color: #5B9BD5;
 }
 .type-filter-scroll {
   white-space: nowrap;
@@ -216,7 +294,7 @@ export default {
   margin-right: 16rpx;
 }
 .filter-chip.active {
-  background: #F97316;
+  background: #5B9BD5;
   color: #fff;
 }
 .loading-state,
@@ -266,7 +344,7 @@ export default {
 }
 .product-price {
   font-size: 28rpx;
-  color: #F97316;
+  color: #5B9BD5;
   font-weight: bold;
   margin-right: 10rpx;
 }
@@ -292,7 +370,7 @@ export default {
 }
 .card-banner {
   padding: 14rpx 20rpx;
-  background: #F97316;
+  background: #5B9BD5;
   color: #fff;
   font-size: 22rpx;
   font-weight: bold;
@@ -322,36 +400,99 @@ export default {
 }
 .card-price {
   font-size: 26rpx;
-  color: #F97316;
+  color: #5B9BD5;
   font-weight: bold;
 }
 .card-tag {
   font-size: 18rpx;
-  color: #F97316;
-  background: rgba(255,107,107,0.1);
+  color: #5B9BD5;
+  background: rgba(91,155,213,0.1);
   padding: 2rpx 10rpx;
   border-radius: 10rpx;
 }
 
+/* ===== 推荐文章 ===== */
+.article-section {
+  margin: 20rpx 30rpx 0;
+}
+.article-list {
+  display: flex;
+  flex-direction: column;
+  gap: 16rpx;
+}
+.article-card {
+  display: flex;
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+  cursor: pointer;
+}
+.article-cover {
+  width: 180rpx;
+  flex-shrink: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.article-cover-icon {
+  font-size: 48rpx;
+}
+.article-info {
+  flex: 1;
+  padding: 16rpx;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+.article-title {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #333;
+  line-height: 1.4;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.article-desc {
+  font-size: 22rpx;
+  color: #999;
+  line-height: 1.3;
+  margin-top: 6rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.article-meta {
+  font-size: 20rpx;
+  color: #ccc;
+  margin-top: 6rpx;
+}
+
 /* ===== 为何选择 ===== */
 .why-section {
   margin: 30rpx;
 }
 .feature-grid {
-  display: grid;
-  grid-template-columns: 1fr 1fr;
-  gap: 20rpx;
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
 }
 .feature-item {
+  width: calc(50% - 10rpx);
+  box-sizing: border-box;
   background: #fff;
   border-radius: 20rpx;
   padding: 24rpx;
+  margin-bottom: 20rpx;
   display: flex;
   flex-direction: column;
   align-items: center;
   text-align: center;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
+
 .feature-icon {
   font-size: 48rpx;
   margin-bottom: 10rpx;
@@ -378,7 +519,7 @@ export default {
   width: 100%;
   height: 88rpx;
   line-height: 88rpx;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
   color: #fff;
   font-size: 32rpx;
   font-weight: bold;

+ 214 - 0
cfc-frontend/pages/energy/detail.vue

@@ -0,0 +1,214 @@
+<template>
+  <view class="container">
+    <!-- 头部:维度信息 -->
+    <view class="dim-header" v-if="dimInfo">
+      <view class="dim-icon-wrap" :class="'dim-icon--' + elementColor">
+        <text class="dim-icon">{{ dimInfo.icon }}</text>
+      </view>
+      <text class="dim-name">{{ dimInfo.name }}</text>
+      <text class="dim-element">{{ dimInfo.element }} · {{ dimInfo.code }}</text>
+      <view class="dim-energy">
+        <text class="dim-energy-value">{{ dimEnergy }}</text>
+        <text class="dim-energy-label">能量值</text>
+      </view>
+    </view>
+
+    <!-- 加载状态 -->
+    <BaseLoading :loading="loading" text="加载中..." />
+
+    <!-- 流水列表 -->
+    <view class="logs-section" v-if="!loading">
+      <view class="section-header">
+        <text class="section-title">📋 能量流水</text>
+      </view>
+
+      <view class="log-item" v-for="(log, idx) in logs" :key="idx" v-if="logs.length > 0">
+        <view class="log-left">
+          <text :class="log.amount > 0 ? 'log-amount earn' : 'log-amount spend'">
+            {{ log.amount > 0 ? '+' : '' }}{{ log.amount }}
+          </text>
+          <text class="log-desc">{{ log.description || '能量变动' }}</text>
+        </view>
+        <view class="log-right">
+          <text class="log-balance">余额 {{ log.balanceAfter }}</text>
+          <text class="log-time">{{ formatTime(log.createdAt) }}</text>
+        </view>
+      </view>
+
+      <BaseEmpty v-if="logs.length === 0" icon="💧" title="暂无能量流水" description="完成任务可以获取能量值" />
+
+      <view class="load-more" v-if="hasMore" @click="loadMore">
+        <text>加载更多...</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getEnergyLogs } from '../../utils/api.js'
+import BaseLoading from '../../components/BaseLoading.vue'
+import BaseEmpty from '../../components/BaseEmpty.vue'
+
+var ELEMENT_MAP = {
+  'mind': 'fire', 'body': 'earth', 'wisdom': 'metal',
+  'action': 'wood', 'wealth': 'water'
+}
+
+export default {
+  components: { BaseLoading, BaseEmpty },
+  data() {
+    return {
+      childId: '',
+      code: '',
+      dimInfo: null,
+      dimEnergy: 0,
+      loading: true,
+      logs: [],
+      page: 1,
+      pageSize: 20,
+      hasMore: true,
+      elementColor: 'fire'
+    }
+  },
+  onLoad(options) {
+    if (options.childId) this.childId = options.childId
+    if (options.code) {
+      this.code = options.code
+      this.elementColor = ELEMENT_MAP[options.code] || 'fire'
+    }
+    // 如果有传入 dimData(URL编码的JSON),解析维度信息
+    if (options.dimData) {
+      try {
+        var parsed = JSON.parse(decodeURIComponent(options.dimData))
+        if (parsed) {
+          this.dimInfo = parsed
+          this.dimEnergy = parsed.energy || 0
+        }
+      } catch (e) { /* ignore */ }
+    }
+    this.loadLogs()
+  },
+  methods: {
+    async loadLogs() {
+      this.loading = true
+      try {
+        var res = await getEnergyLogs(this.childId, this.code, this.page, this.pageSize)
+        if (res && res.data) {
+          var records = res.data.records || []
+          if (this.page === 1) {
+            this.logs = records
+          } else {
+            this.logs = this.logs.concat(records)
+          }
+          this.hasMore = records.length >= this.pageSize
+        }
+      } catch (e) {
+        console.log('获取能量流水失败', e)
+      } finally {
+        this.loading = false
+      }
+    },
+    loadMore() {
+      this.page++
+      this.loadLogs()
+    },
+    formatTime(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      var month = d.getMonth() + 1
+      var day = d.getDate()
+      var hour = d.getHours()
+      var min = d.getMinutes()
+      if (month < 10) month = '0' + month
+      if (day < 10) day = '0' + day
+      if (hour < 10) hour = '0' + hour
+      if (min < 10) min = '0' + min
+      return month + '-' + day + ' ' + hour + ':' + min
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #0F1B2D;
+  padding-bottom: 40rpx;
+}
+
+/* ---- 头部 ---- */
+.dim-header {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 60rpx 0 40rpx;
+  position: relative;
+}
+.dim-header::after {
+  content: '';
+  position: absolute;
+  bottom: 0;
+  left: 10%;
+  width: 80%;
+  height: 1rpx;
+  background: linear-gradient(90deg, transparent, rgba(143,197,232,0.2), transparent);
+}
+.dim-icon-wrap {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 16rpx;
+  box-shadow: 0 0 40rpx rgba(255,255,255,0.15);
+}
+.dim-icon--fire { background: linear-gradient(135deg, #FF6B35, #E65100); }
+.dim-icon--water { background: linear-gradient(135deg, #42A5F5, #1565C0); }
+.dim-icon--earth { background: linear-gradient(135deg, #8D6E63, #5D4037); }
+.dim-icon--metal { background: linear-gradient(135deg, #FFD700, #FF8F00); }
+.dim-icon--wood { background: linear-gradient(135deg, #4CAF50, #2E7D32); }
+.dim-icon { font-size: 44rpx; }
+.dim-name { font-size: 36rpx; font-weight: bold; color: #FFFFFF; }
+.dim-element { font-size: 22rpx; color: rgba(143,197,232,0.5); margin-top: 6rpx; }
+.dim-energy {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  margin-top: 20rpx;
+}
+.dim-energy-value { font-size: 56rpx; font-weight: bold; color: #F97316; }
+.dim-energy-label { font-size: 20rpx; color: rgba(143,197,232,0.4); }
+
+/* ---- 流水 ---- */
+.logs-section {
+  padding: 30rpx 30rpx 0;
+}
+.section-header { margin-bottom: 20rpx; }
+.section-title { font-size: 28rpx; font-weight: bold; color: #8FC5E8; }
+
+.log-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 24rpx 20rpx;
+  background: rgba(255,255,255,0.04);
+  border-radius: 16rpx;
+  margin-bottom: 12rpx;
+}
+.log-left { display: flex; flex-direction: column; }
+.log-amount { font-size: 32rpx; font-weight: bold; }
+.log-amount.earn { color: #4CAF50; }
+.log-amount.spend { color: #FF6B35; }
+.log-desc { font-size: 22rpx; color: rgba(255,255,255,0.5); margin-top: 4rpx; }
+.log-right { display: flex; flex-direction: column; align-items: flex-end; }
+.log-balance { font-size: 22rpx; color: rgba(255,255,255,0.4); }
+.log-time { font-size: 20rpx; color: rgba(255,255,255,0.25); margin-top: 4rpx; }
+
+.load-more {
+  text-align: center;
+  padding: 24rpx;
+  color: rgba(143,197,232,0.4);
+  font-size: 24rpx;
+}
+</style>

+ 41 - 0
cfc-frontend/pages/games/list.vue

@@ -5,6 +5,17 @@
       <text class="subtitle">完成游戏获得积分奖励</text>
     </view>
 
+    <view class="nav-bar">
+      <view class="nav-item" @click="goRecords">
+        <text class="nav-icon">📋</text>
+        <text class="nav-text">训练记录</text>
+      </view>
+      <view class="nav-item" @click="goStats">
+        <text class="nav-icon">📊</text>
+        <text class="nav-text">训练统计</text>
+      </view>
+    </view>
+
     <view class="game-list">
       <view 
         class="game-card" 
@@ -51,6 +62,12 @@ export default {
         console.error('加载游戏列表失败', e)
       }
     },
+    goRecords() {
+      uni.navigateTo({ url: '/pages/games/records' })
+    },
+    goStats() {
+      uni.navigateTo({ url: '/pages/games/stats' })
+    },
     playGame(game) {
       // 跳转到对应的游戏页面
       const pageMap = {
@@ -100,6 +117,30 @@ export default {
   margin-top: 10rpx;
 }
 
+.nav-bar {
+  display: flex;
+  gap: 20rpx;
+  margin-bottom: 30rpx;
+}
+.nav-item {
+  flex: 1;
+  background: rgba(255,255,255,0.15);
+  border-radius: 16rpx;
+  padding: 24rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  border: 1rpx solid rgba(255,255,255,0.1);
+}
+.nav-icon {
+  font-size: 48rpx;
+  margin-bottom: 8rpx;
+}
+.nav-text {
+  font-size: 24rpx;
+  color: rgba(255,255,255,0.8);
+}
+
 .game-list {
   display: flex;
   flex-direction: column;

+ 241 - 0
cfc-frontend/pages/games/records.vue

@@ -0,0 +1,241 @@
+<template>
+  <view class="records-container">
+    <view class="header">
+      <text class="back-btn" @click="goBack">‹ 返回</text>
+      <text class="title">训练记录</text>
+    </view>
+
+    <view class="filter-bar">
+      <view
+        v-for="item in gameFilters"
+        :key="item.code"
+        :class="['filter-chip', currentFilter === item.code ? 'active' : '']"
+        @click="onFilterChange(item.code)"
+      >
+        {{ item.label }}
+      </view>
+    </view>
+
+    <view v-if="loading" class="loading-state">
+      <text>加载中...</text>
+    </view>
+
+    <view v-else-if="records.length === 0" class="empty-state">
+      <text class="empty-text">暂无训练记录</text>
+      <text class="empty-desc">完成小游戏后记录将出现在这里</text>
+    </view>
+
+    <view v-else class="record-list">
+      <view class="record-card" v-for="item in records" :key="item.id">
+        <view class="record-left">
+          <text class="game-icon">{{ iconMap[item.gameCode] || '🎮' }}</text>
+        </view>
+        <view class="record-mid">
+          <text class="game-name">{{ gameNameMap[item.gameCode] || item.gameCode }}</text>
+          <text class="record-meta">{{ formatTime(item.playedAt) }}</text>
+        </view>
+        <view class="record-right">
+          <text class="score-text">{{ item.score }}分</text>
+          <text class="points-text">+{{ item.pointsEarned }}积分</text>
+        </view>
+      </view>
+    </view>
+
+    <view class="load-more" v-if="hasMore">
+      <text class="load-text" @click="loadMore">加载更多</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getGameHistory } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      records: [],
+      currentFilter: '',
+      page: 1,
+      size: 20,
+      hasMore: true,
+      loading: false,
+      gameFilters: [
+        { code: '', label: '全部' },
+        { code: 'schulte', label: '舒尔特' },
+        { code: '1a2b', label: '猜数字' },
+        { code: 'sudoku', label: '数独' }
+      ],
+      gameNameMap: {
+        'schulte': '舒尔特方格',
+        '1a2b': '猜数字',
+        'sudoku': '数独'
+      },
+      iconMap: {
+        'schulte': '🔢',
+        '1a2b': '🔍',
+        'sudoku': '🧩'
+      }
+    }
+  },
+  onLoad() {
+    this.loadRecords()
+  },
+  methods: {
+    async loadRecords() {
+      const childId = uni.getStorageSync('currentChildId')
+      if (!childId) return
+
+      this.loading = true
+      try {
+        const params = { childId, page: this.page, size: this.size }
+        if (this.currentFilter) {
+          params.gameCode = this.currentFilter
+        }
+        const res = await getGameHistory(params)
+        const data = res.data || {}
+        const newRecords = data.records || []
+        this.records = this.page === 1 ? newRecords : [...this.records, ...newRecords]
+        this.hasMore = newRecords.length >= this.size
+      } catch (e) {
+        console.error('加载记录失败', e)
+      } finally {
+        this.loading = false
+      }
+    },
+    onFilterChange(code) {
+      if (this.currentFilter === code) return
+      this.currentFilter = code
+      this.page = 1
+      this.loadRecords()
+    },
+    loadMore() {
+      this.page++
+      this.loadRecords()
+    },
+    formatTime(dateStr) {
+      if (!dateStr) return ''
+      const d = new Date(dateStr)
+      const month = (d.getMonth() + 1).toString().padStart(2, '0')
+      const day = d.getDate().toString().padStart(2, '0')
+      const hour = d.getHours().toString().padStart(2, '0')
+      const min = d.getMinutes().toString().padStart(2, '0')
+      return month + '-' + day + ' ' + hour + ':' + min
+    },
+    goBack() {
+      uni.navigateBack()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.records-container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 40rpx;
+}
+.header {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 30rpx;
+  background: #fff;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.back-btn {
+  font-size: 28rpx;
+  color: #F97316;
+  padding-right: 20rpx;
+}
+.title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+.filter-bar {
+  display: flex;
+  padding: 20rpx 30rpx;
+  gap: 16rpx;
+  background: #fff;
+}
+.filter-chip {
+  padding: 8rpx 24rpx;
+  border-radius: 30rpx;
+  font-size: 24rpx;
+  color: #666;
+  background: #f5f5f5;
+}
+.filter-chip.active {
+  color: #fff;
+  background: #F97316;
+}
+.loading-state, .empty-state {
+  text-align: center;
+  padding: 120rpx 30rpx;
+}
+.empty-text {
+  display: block;
+  font-size: 28rpx;
+  color: #999;
+  margin-bottom: 10rpx;
+}
+.empty-desc {
+  display: block;
+  font-size: 24rpx;
+  color: #ccc;
+}
+.record-list {
+  padding: 20rpx 30rpx;
+}
+.record-card {
+  display: flex;
+  align-items: center;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.record-left {
+  margin-right: 20rpx;
+}
+.game-icon {
+  font-size: 48rpx;
+}
+.record-mid {
+  flex: 1;
+}
+.game-name {
+  display: block;
+  font-size: 28rpx;
+  color: #333;
+  font-weight: bold;
+  margin-bottom: 4rpx;
+}
+.record-meta {
+  display: block;
+  font-size: 22rpx;
+  color: #999;
+}
+.record-right {
+  text-align: right;
+}
+.score-text {
+  display: block;
+  font-size: 32rpx;
+  color: #F97316;
+  font-weight: bold;
+}
+.points-text {
+  display: block;
+  font-size: 22rpx;
+  color: #4ECDC4;
+}
+.load-more {
+  text-align: center;
+  padding: 30rpx;
+}
+.load-text {
+  font-size: 26rpx;
+  color: #F97316;
+}
+</style>

+ 297 - 0
cfc-frontend/pages/games/stats.vue

@@ -0,0 +1,297 @@
+<template>
+  <view class="stats-container">
+    <view class="header">
+      <text class="back-btn" @click="goBack">‹ 返回</text>
+      <text class="title">训练统计</text>
+    </view>
+
+    <view v-if="loading" class="loading-state">
+      <text>加载中...</text>
+    </view>
+
+    <template v-else>
+      <!-- 概览卡片 -->
+      <view class="overview-section">
+        <view class="overview-card">
+          <view class="overview-item">
+            <text class="overview-num">{{ stats.totalGames || 0 }}</text>
+            <text class="overview-label">总训练次数</text>
+          </view>
+          <view class="overview-item">
+            <text class="overview-num">{{ stats.totalPoints || 0 }}</text>
+            <text class="overview-label">获得积分</text>
+          </view>
+          <view class="overview-item">
+            <text class="overview-num">{{ stats.bestScore || 0 }}</text>
+            <text class="overview-label">最高分</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 各游戏最佳成绩 -->
+      <view class="section">
+        <text class="section-title">各游戏成绩</text>
+        <view class="best-card" v-for="item in bestScores" :key="item.game_code">
+          <text class="best-icon">{{ iconMap[item.game_code] || '🎮' }}</text>
+          <view class="best-info">
+            <text class="best-name">{{ gameNameMap[item.game_code] || item.game_code }}</text>
+            <text class="best-stats">
+              最佳 {{ item.best_score }}分 · 共 {{ item.total_plays }}次 · 累计 {{ item.total_points }}分
+            </text>
+          </view>
+          <text class="best-score">{{ item.best_score }}</text>
+        </view>
+        <view class="no-data" v-if="bestScores.length === 0">
+          <text>暂无数据,完成游戏后查看</text>
+        </view>
+      </view>
+
+      <!-- 排行榜 -->
+      <view class="section">
+        <text class="section-title">游戏排行榜</text>
+        <view class="leaderboard-tabs" v-if="gameCodes.length > 0">
+          <view
+            v-for="code in gameCodes"
+            :key="code"
+            :class="['lb-tab', currentLb === code ? 'active' : '']"
+            @click="onLeaderboardChange(code)"
+          >
+            {{ gameNameMap[code] || code }}
+          </view>
+        </view>
+        <view class="leaderboard-list">
+          <view class="lb-item" v-for="(entry, idx) in leaderboard" :key="idx">
+            <text :class="['lb-rank', idx < 3 ? 'top' : '']">{{ idx + 1 }}</text>
+            <text class="lb-name">{{ entry.child_name || '未知' }}</text>
+            <text class="lb-score">{{ entry.score }}分</text>
+          </view>
+        </view>
+        <view class="no-data" v-if="leaderboard.length === 0">
+          <text>排行榜暂无数据</text>
+        </view>
+      </view>
+    </template>
+  </view>
+</template>
+
+<script>
+import { getGameBestScores, getGameStats, getGameLeaderboard } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      loading: false,
+      stats: {},
+      bestScores: [],
+      leaderboard: [],
+      currentLb: 'schulte',
+      gameCodes: ['schulte', '1a2b', 'sudoku'],
+      gameNameMap: {
+        'schulte': '舒尔特方格',
+        '1a2b': '猜数字',
+        'sudoku': '数独'
+      },
+      iconMap: {
+        'schulte': '🔢',
+        '1a2b': '🔍',
+        'sudoku': '🧩'
+      }
+    }
+  },
+  onLoad() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      const childId = uni.getStorageSync('currentChildId')
+      if (!childId) return
+
+      this.loading = true
+      try {
+        const [statsRes, bestRes] = await Promise.all([
+          getGameStats({ childId }),
+          getGameBestScores({ childId })
+        ])
+        this.stats = statsRes.data || {}
+        this.bestScores = bestRes.data || []
+        this.loadLeaderboard()
+      } catch (e) {
+        console.error('加载统计数据失败', e)
+      } finally {
+        this.loading = false
+      }
+    },
+    async loadLeaderboard() {
+      try {
+        const res = await getGameLeaderboard({ gameCode: this.currentLb, limit: 10 })
+        this.leaderboard = res.data || []
+      } catch (e) {
+        console.error('加载排行榜失败', e)
+      }
+    },
+    onLeaderboardChange(code) {
+      this.currentLb = code
+      this.loadLeaderboard()
+    },
+    goBack() {
+      uni.navigateBack()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.stats-container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 40rpx;
+}
+.header {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 30rpx;
+  background: #fff;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.back-btn {
+  font-size: 28rpx;
+  color: #F97316;
+  padding-right: 20rpx;
+}
+.title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+.loading-state {
+  text-align: center;
+  padding: 120rpx 30rpx;
+  font-size: 28rpx;
+  color: #999;
+}
+.overview-section {
+  padding: 30rpx;
+}
+.overview-card {
+  display: flex;
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  border-radius: 20rpx;
+  padding: 40rpx 20rpx;
+}
+.overview-item {
+  flex: 1;
+  text-align: center;
+}
+.overview-num {
+  display: block;
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-bottom: 8rpx;
+}
+.overview-label {
+  display: block;
+  font-size: 24rpx;
+  color: rgba(255,255,255,0.8);
+}
+.section {
+  margin: 0 30rpx 30rpx;
+}
+.section-title {
+  display: block;
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 20rpx;
+}
+.best-card {
+  display: flex;
+  align-items: center;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 12rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.best-icon {
+  font-size: 48rpx;
+  margin-right: 20rpx;
+}
+.best-info {
+  flex: 1;
+}
+.best-name {
+  display: block;
+  font-size: 28rpx;
+  color: #333;
+  font-weight: bold;
+  margin-bottom: 4rpx;
+}
+.best-stats {
+  display: block;
+  font-size: 22rpx;
+  color: #999;
+}
+.best-score {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #F97316;
+}
+.leaderboard-tabs {
+  display: flex;
+  gap: 16rpx;
+  margin-bottom: 20rpx;
+}
+.lb-tab {
+  padding: 8rpx 24rpx;
+  border-radius: 30rpx;
+  font-size: 24rpx;
+  color: #666;
+  background: #f5f5f5;
+}
+.lb-tab.active {
+  color: #fff;
+  background: #F97316;
+}
+.leaderboard-list {
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+}
+.lb-item {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 30rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.lb-item:last-child {
+  border-bottom: none;
+}
+.lb-rank {
+  width: 50rpx;
+  font-size: 28rpx;
+  color: #999;
+  font-weight: bold;
+}
+.lb-rank.top {
+  color: #F97316;
+}
+.lb-name {
+  flex: 1;
+  font-size: 26rpx;
+  color: #333;
+}
+.lb-score {
+  font-size: 28rpx;
+  color: #F97316;
+  font-weight: bold;
+}
+.no-data {
+  text-align: center;
+  padding: 60rpx 30rpx;
+  font-size: 26rpx;
+  color: #999;
+  background: #fff;
+  border-radius: 16rpx;
+}
+</style>

+ 180 - 140
cfc-frontend/pages/index/child-index.vue

@@ -20,42 +20,55 @@
       </template>
     </BaseEmpty>
 
-    <!-- ====== 主内容(有孩子时) ====== -->
-    <view class="main-content" v-if="!loading && hasChildren">
-      <!-- ============================================================
-      A) 顶栏:头像 + 问候 + 连续打卡徽章
-      ============================================================ -->
-      <view class="welcome-section animate-slide-up animate-stagger-1">
-        <view class="welcome-left">
-          <view class="avatar-row">
-            <view class="avatar-circle">
-              <text class="avatar-emoji">{{ avatarEmoji }}</text>
-            </view>
-            <view class="greeting-block">
-              <text class="greeting-time">{{ timeGreeting }}</text>
-              <text class="greeting-name">{{ nickname || '小朋友' }}</text>
-            </view>
-          </view>
-        </view>
-
-        <!-- 连续打卡徽章 -->
-        <view
-          class="streak-badge"
-          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>
-      </view>
-
-      <!-- ============================================================
-      B) 积分卡片 — Claymorphism 暖橙黏土风格
-      ============================================================ -->
+<!-- ====== 主内容(有孩子时) ====== -->
+<view class="main-content" v-if="!loading && hasChildren">
+<!-- ============================================================
+A) 五维理念区
+============================================================ -->
+<view class="philosophy-section animate-fade-in animate-stagger-1">
+<view class="philosophy-banner">
+<text class="philosophy-title">浠艾福</text>
+<view class="philosophy-five">
+<text class="five-item five-body">🌏 身泰</text>
+<text class="five-item five-wisdom">⚔️ 智达</text>
+<text class="five-item five-mind">🔥 心怡</text>
+<text class="five-item five-action">🌿 行远</text>
+<text class="five-item five-wealth">💧 富沛</text>
+</view>
+</view>
+</view>
+
+<!-- 五维能量沙盘 -->
+<view class="animate-fade-in animate-stagger-2">
+<wuxing-sandbox mode="live"
+  :scores="{}"
+  :overall-score="0"
+  :members="[]"
+  :dimensions="energyDimensions"
+  :total-energy="totalEnergy"
+  :total-health-index="totalHealthIndex"
+  show-dimension-bar="false"
+  star-height="400rpx"
+  @point-click="goToEnergyDetail" />
+</view>
+
+<!-- 连续打卡徽章 -->
+<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"
@@ -240,13 +253,14 @@
 </template>
 
 <script>
-import { getChildren, getTodayTasks } from '../../utils/api.js'
+import { getChildren, getTodayTasks, getEnergyOverview } from '../../utils/api.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 ConfettiCelebration from '../../components/ConfettiCelebration.vue'
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 
 /**
  * 等级配置表
@@ -279,7 +293,8 @@ export default {
     BaseBadge,
     BaseEmpty,
     BaseLoading,
-    ConfettiCelebration
+    ConfettiCelebration,
+    WuxingSandbox
   },
   data() {
     return {
@@ -299,7 +314,10 @@ export default {
         { id: 4, icon: '🎨', name: '小艺术家', unlocked: false },
         { id: 5, icon: '🧹', name: '家务小能手', unlocked: false }
       ],
-      _mounted: false
+      _mounted: false,
+      energyDimensions: [],
+      totalEnergy: 0,
+      totalHealthIndex: 0
     }
   },
   computed: {
@@ -426,6 +444,21 @@ export default {
         if (this.totalPoints >= 1000) {
           this.unlockBadge(2)
         }
+
+        // 加载五维能量概览
+        var energyChildId = this.childId
+        if (energyChildId) {
+          try {
+            var overviewRes = await getEnergyOverview(energyChildId)
+            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)
         uni.showToast({ title: '加载失败', icon: 'none' })
@@ -480,6 +513,14 @@ export default {
     goToDomain(domain) {
       uni.showToast({ title: '即将上线', icon: 'none' })
     },
+    goToEnergyDetail(code) {
+      // 跳转到能量详情页(T10 创建)
+      var childId = this.childId
+      if (!childId || !code) return
+      uni.navigateTo({
+        url: '/pages/energy/detail?childId=' + childId + '&code=' + code
+      })
+    },
 
     // ======================== 任务交互 ========================
     handleTaskClick(task) {
@@ -549,8 +590,8 @@ export default {
 
 <style scoped>
 /* ============================================================
-   知心益家 — 孩子端首页 Claymorphism 设计系统
-   色彩体系:暖橙(F97316)+天蓝(0EA5E9) | 双重阴影 | 4rpx边框
+   知心益家 — 孩子端首页 Flat Rounded 设计系统
+   色彩体系:品牌蓝(#5B9BD5)+浅蓝(#8FC5E8) | 双重阴影 | 4rpx边框
    参考:uni.scss --color-primary, --bg, --shadow-clay, --radius-md
    ============================================================ */
 
@@ -559,7 +600,7 @@ export default {
    ---------------------------------------------------------- */
 .container {
   min-height: 100vh;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, #F5F9FC);
   padding: 0 32rpx 48rpx;
 }
 
@@ -572,79 +613,78 @@ export default {
 }
 
 /* ----------------------------------------------------------
-   欢迎区 — 头像 + 问候 + 连续打卡
-   ---------------------------------------------------------- */
-.welcome-section {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 28rpx;
-  padding-top: 12rpx;
-}
-
-.welcome-left {
-  flex: 1;
-  min-width: 0;
-}
-
-.avatar-row {
-  display: flex;
-  align-items: center;
-  gap: 20rpx;
-}
-
-.avatar-circle {
-  width: 88rpx;
-  height: 88rpx;
-  border-radius: 50%;
-  background: linear-gradient(145deg, #FFE4CC, #FFD4B0);
-  border: 4rpx solid var(--border, #FED7AA);
-  box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.6),
-              4rpx 4rpx 12rpx rgba(249,115,22,0.10);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-}
-
-.avatar-emoji {
-  font-size: 44rpx;
-  line-height: 1;
-}
-
-.greeting-block {
-  display: flex;
-  flex-direction: column;
-  min-width: 0;
-}
-
-.greeting-time {
-  font-size: 24rpx;
-  color: var(--text-secondary, #6B5A4A);
-  line-height: 1.4;
-}
-
-.greeting-name {
-  font-size: 40rpx;
-  font-weight: 700;
-  color: var(--text, #3D2E1E);
-  line-height: 1.3;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
+A) 五维理念区
+---------------------------------------------------------- */
+.philosophy-section {
+margin-bottom: 32rpx;
+}
+
+.philosophy-banner {
+background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
+border-radius: 28rpx;
+padding: 32rpx 28rpx 24rpx;
+text-align: center;
+border: 3rpx solid rgba(147, 197, 253, 0.2);
+box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
+}
+
+.philosophy-title {
+font-size: 36rpx;
+font-weight: 800;
+color: #FFD700;
+letter-spacing: 8rpx;
+display: block;
+margin-bottom: 24rpx;
+text-shadow: 0 2rpx 12rpx rgba(255, 215, 0, 0.3);
+}
+
+.philosophy-five {
+display: flex;
+justify-content: space-around;
+align-items: center;
+flex-wrap: wrap;
+gap: 12rpx 8rpx;
+}
+
+.five-item {
+font-size: 26rpx;
+font-weight: 700;
+color: #FFFFFF;
+display: inline-flex;
+align-items: center;
+gap: 6rpx;
+padding: 10rpx 16rpx;
+border-radius: 24rpx;
+background: rgba(255, 255, 255, 0.08);
+border: 2rpx solid rgba(255, 255, 255, 0.12);
+}
+
+.five-body { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
+.five-wisdom { border-color: rgba(251, 191, 36, 0.4); color: #FDE68A; }
+.five-mind { border-color: rgba(251, 146, 60, 0.4); color: #FDBA74; }
+.five-action { border-color: rgba(134, 239, 172, 0.4); color: #BBF7D0; }
+.five-wealth { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
+
+/* 连续打卡徽章 — 独立行 */
+.streak-badge-wrap {
+display: flex;
+align-items: center;
+gap: 10rpx;
+justify-content: flex-end;
+margin-bottom: 24rpx;
+padding-top: 4rpx;
 }
 
-/* 连续打卡徽章 — Clay 风格 */
 .streak-badge {
   display: flex;
   align-items: center;
   gap: 10rpx;
-  background: linear-gradient(145deg, #FFF7ED, #FFE8CC);
+  background: linear-gradient(145deg, #F5F9FC, #E0ECF8);
   padding: 12rpx 24rpx 12rpx 18rpx;
   border-radius: 40rpx;
-  border: 4rpx solid #F5D97A;
+  border: 4rpx solid #8FC5E8;
   box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
-              4rpx 4rpx 14rpx rgba(245,217,122,0.30);
+              4rpx 4rpx 14rpx rgba(143,197,232,0.30);
   flex-shrink: 0;
   transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
 }
@@ -668,28 +708,28 @@ export default {
 .streak-days {
   font-size: 32rpx;
   font-weight: 800;
-  color: #C2410C;
+  color: #1E4A7A;
   line-height: 1.1;
 }
 
 .streak-unit {
   font-size: 18rpx;
   font-weight: 600;
-  color: #B45309;
+  color: #2563EB;
 }
 
 /* ----------------------------------------------------------
-   积分卡片 — Claymorphism 暖橙渐变
+   积分卡片 — Flat Rounded 蓝色渐变
    ---------------------------------------------------------- */
 .points-card {
   position: relative;
-  background: linear-gradient(145deg, #FF8C42, #F97316);
+  background: linear-gradient(145deg, #8FC5E8, #5B9BD5);
   border-radius: 32rpx;
   padding: 32rpx;
   margin-bottom: 28rpx;
-  border: 4rpx solid #E07A3A;
+  border: 4rpx solid #3A7CC4;
   box-shadow: inset -4rpx -4rpx 14rpx rgba(255,255,255,0.20),
-              8rpx 8rpx 28rpx rgba(249,115,22,0.25);
+              8rpx 8rpx 28rpx rgba(91,155,213,0.25);
   overflow: hidden;
   transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
 }
@@ -698,7 +738,7 @@ export default {
 .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);
+              4rpx 4rpx 16rpx rgba(91,155,213,0.18);
 }
 
 /* 装饰圆 */
@@ -826,13 +866,13 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: 700;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
 }
 
 .section-link {
   font-size: 24rpx;
   font-weight: 600;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
   padding: 8rpx 4rpx;
 }
 
@@ -857,13 +897,13 @@ export default {
   align-items: center;
   width: 200rpx;
   min-height: 260rpx;
-  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #FFF2E4));
+  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
   border-radius: 28rpx;
   padding: 24rpx 16rpx 20rpx;
   margin-right: 20rpx;
-  border: 4rpx solid var(--border, #FED7AA);
+  border: 4rpx solid var(--border, #CBD5E1);
   box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
-              6rpx 6rpx 20rpx rgba(249,115,22,0.08);
+              6rpx 6rpx 20rpx rgba(91,155,213,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;
@@ -883,9 +923,9 @@ export default {
   align-items: center;
   justify-content: center;
   margin-bottom: 12rpx;
-  border: 3rpx solid var(--border-light, #FFEDD5);
+  border: 3rpx solid var(--border-light, #E2E8F0);
   box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
-              3rpx 3rpx 8rpx rgba(249,115,22,0.06);
+              3rpx 3rpx 8rpx rgba(91,155,213,0.06);
 }
 
 .task-card-icon {
@@ -896,7 +936,7 @@ export default {
 /* 分类图标底色 */
 .task-cat--study   .task-card-icon-wrap { background: rgba(14,165,233,0.12); border-color: #BAE6FD; }
 .task-cat--reading .task-card-icon-wrap { background: rgba(34,197,94,0.12);  border-color: #BBF7D0; }
-.task-cat--life    .task-card-icon-wrap { background: rgba(249,115,22,0.12); border-color: #FED7AA; }
+.task-cat--life    .task-card-icon-wrap { background: rgba(91,155,213,0.12); border-color: #CBD5E1; }
 .task-cat--sports  .task-card-icon-wrap { background: rgba(59,130,246,0.12); border-color: #BFDBFE; }
 .task-cat--chores  .task-card-icon-wrap { background: rgba(245,158,11,0.12); border-color: #FDE68A; }
 .task-cat--art     .task-card-icon-wrap { background: rgba(236,72,153,0.12); border-color: #FBCFE8; }
@@ -905,7 +945,7 @@ export default {
 .task-card-name {
   font-size: 26rpx;
   font-weight: 600;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   text-align: center;
   line-height: 1.3;
   overflow: hidden;
@@ -918,7 +958,7 @@ export default {
 .task-card-points {
   font-size: 24rpx;
   font-weight: 700;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
   margin-bottom: 10rpx;
 }
 
@@ -938,7 +978,7 @@ export default {
   padding: 48rpx 32rpx;
   background: var(--surface, #FFFFFF);
   border-radius: 28rpx;
-  border: 4rpx solid var(--border, #FED7AA);
+  border: 4rpx solid var(--border, #CBD5E1);
   box-shadow: var(--shadow-clay);
   transition: transform 0.2s ease;
 }
@@ -955,13 +995,13 @@ export default {
 .tasks-empty-title {
   font-size: 30rpx;
   font-weight: 600;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   margin-bottom: 8rpx;
 }
 
 .tasks-empty-desc {
   font-size: 24rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
 }
 
 /* ----------------------------------------------------------
@@ -986,11 +1026,11 @@ export default {
   align-items: center;
   justify-content: center;
   padding: 28rpx 16rpx;
-  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #FFF2E4));
+  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
   border-radius: 28rpx;
-  border: 4rpx solid var(--border, #FED7AA);
+  border: 4rpx solid var(--border, #CBD5E1);
   box-shadow: inset -4rpx -4rpx 10rpx rgba(255,255,255,0.6),
-              6rpx 6rpx 20rpx rgba(249,115,22,0.08);
+              6rpx 6rpx 20rpx rgba(91,155,213,0.08);
   transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
   min-height: 180rpx;
 }
@@ -999,7 +1039,7 @@ export default {
 .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);
+              3rpx 3rpx 10rpx rgba(91,155,213,0.06);
 }
 
 .quick-btn-icon {
@@ -1010,9 +1050,9 @@ export default {
   align-items: center;
   justify-content: center;
   margin-bottom: 14rpx;
-  border: 3rpx solid var(--border-light, #FFEDD5);
+  border: 3rpx solid var(--border-light, #E2E8F0);
   box-shadow: inset -2rpx -2rpx 6rpx rgba(255,255,255,0.5),
-              3rpx 3rpx 8rpx rgba(249,115,22,0.06);
+              3rpx 3rpx 8rpx rgba(91,155,213,0.06);
   transition: transform 0.2s ease;
 }
 
@@ -1024,13 +1064,13 @@ export default {
 /* 各按钮图标色彩 */
 .quick-icon--tasks   { background: rgba(14,165,233,0.15); border-color: #BAE6FD; }
 .quick-icon--rewards { background: rgba(245,158,11,0.15); border-color: #FDE68A; }
-.quick-icon--growth  { background: rgba(249,115,22,0.15); border-color: #FED7AA; }
+.quick-icon--growth  { background: rgba(91,155,213,0.15); border-color: #CBD5E1; }
 .quick-icon--games   { background: rgba(236,72,153,0.12); border-color: #FBCFE8; }
 
 .quick-btn-label {
   font-size: 28rpx;
   font-weight: 700;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
 }
 
 /* ----------------------------------------------------------
@@ -1068,10 +1108,10 @@ export default {
   width: 100rpx;
   height: 100rpx;
   border-radius: 50%;
-  background: linear-gradient(145deg, #FFE4CC, #FFD4B0);
-  border: 4rpx solid var(--border, #FED7AA);
+  background: linear-gradient(145deg, #DCE8F5, #C8DDF0);
+  border: 4rpx solid var(--border, #CBD5E1);
   box-shadow: inset -3rpx -3rpx 8rpx rgba(255,255,255,0.5),
-              4rpx 4rpx 14rpx rgba(249,115,22,0.08);
+              4rpx 4rpx 14rpx rgba(91,155,213,0.08);
   display: flex;
   align-items: center;
   justify-content: center;
@@ -1086,7 +1126,7 @@ export default {
 .achievement-name {
   font-size: 24rpx;
   font-weight: 600;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   text-align: center;
 }
 
@@ -1096,8 +1136,8 @@ export default {
 }
 
 .achievement-icon-wrap--locked {
-  background: var(--bg-grey, #FFF2E4);
-  border-color: var(--border-light, #FFEDD5);
+  background: var(--bg-grey, #EDF2F7);
+  border-color: var(--border-light, #E2E8F0);
   box-shadow: none;
 }
 

+ 373 - 38
cfc-frontend/pages/index/index.vue

@@ -1,18 +1,111 @@
 <template>
 <view>
-  <!-- 家长端首页 -->
-  <parent-index v-if="currentRole === 'parent'" />
+  <!-- 未登录态 — 发现页/引流内容,直接内嵌,不跳转 -->
+  <view v-if="!currentRole" class="discover-container">
+    <!-- 品牌头部 -->
+    <view class="brand-header">
+      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
+      <view class="brand-text-wrap">
+        <text class="brand-title">浠艾福</text>
+        <text class="brand-subtitle">家庭健康成长俱乐部</text>
+      </view>
+    </view>
 
-  <!-- 孩子端首页 -->
-  <child-index v-else-if="currentRole === 'child'" />
+    <!-- 五行沙盘预览 -->
+    <wuxing-sandbox mode="preview" badgeText="登录查看完整报告" @point-click="handleLogin" />
 
-  <!-- 成长规划师首页 -->
-  <teacher-index v-else-if="currentRole === 'teacher'" />
+    <!-- 活动/商品推荐 -->
+    <view class="recommend-section">
+      <view class="section-header">
+        <text class="section-title">🔥 热门推荐</text>
+        <text class="section-more" @click="goShop">查看更多 ›</text>
+      </view>
+      <scroll-view scroll-x enable-flex show-scrollbar="false" class="type-filter-scroll">
+        <view
+          v-for="item in typeList"
+          :key="item.value"
+          :class="['filter-chip', currentType === item.value ? 'active' : '']"
+          @click="onTypeChange(item.value)"
+        >
+          {{ item.label }}
+        </view>
+      </scroll-view>
+      <view v-if="loading" class="loading-state">
+        <text class="loading-text">加载中...</text>
+      </view>
+      <view v-else-if="products.length === 0" class="empty-state">
+        <text class="empty-text">暂无商品,去商城看看吧</text>
+      </view>
+      <view v-else class="product-grid">
+        <view
+          v-for="item in products"
+          :key="item.id"
+          class="product-card"
+          @click="goDetail(item.id)"
+        >
+          <image class="product-cover" :src="item.coverImage || '/static/default-product.png'" mode="aspectFill" />
+          <view class="product-info">
+            <text class="product-name">{{ item.name }}</text>
+            <view class="product-price-row">
+              <text class="product-price">¥{{ item.price }}</text>
+              <text v-if="item.memberPrice" class="product-member">会员¥{{ item.memberPrice }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
 
-  <!-- 默认显示加载中或错误提示 -->
-  <view v-else class="loading-container">
-    <text>加载中...</text>
+    <!-- 为何选择浠艾福 -->
+    <view class="why-section">
+      <view class="section-header">
+        <text class="section-title">💡 为什么选择浠艾福</text>
+      </view>
+      <view class="feature-grid">
+        <view class="feature-item">
+          <text class="feature-icon">🎯</text>
+          <text class="feature-title">科学评估</text>
+          <text class="feature-desc">DAN少儿核心素养评估体系</text>
+        </view>
+        <view class="feature-item">
+          <text class="feature-icon">🏆</text>
+          <text class="feature-title">积分激励</text>
+          <text class="feature-desc">完成任务得积分,兑换心愿</text>
+        </view>
+        <view class="feature-item">
+          <text class="feature-icon">👨‍👩‍👧‍👦</text>
+          <text class="feature-title">家庭参与</text>
+          <text class="feature-desc">全家一起见证成长每一步</text>
+        </view>
+        <view class="feature-item">
+          <text class="feature-icon">🌟</text>
+          <text class="feature-title">专业规划</text>
+          <text class="feature-desc">成长规划师一对一指导</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 底部登录按钮 -->
+    <view class="login-footer">
+      <button class="login-btn" @click="handleLogin">登录 / 注册</button>
+      <text class="login-footer-text">登录即表示同意《用户协议》和《隐私政策》</text>
+    </view>
+
+    <!-- 底部占位 -->
+    <view style="height: 120rpx;"></view>
+
+    <!-- 未登录态自定义底栏(仅 首页/行动) -->
+    <bottom-nav v-if="!currentRole" current="index" />
   </view>
+
+  <!-- 登录态 — 按角色渲染 -->
+  <template v-else>
+    <parent-index v-if="currentRole === 'parent'" />
+    <child-index v-else-if="currentRole === 'child'" />
+    <teacher-index v-else-if="currentRole === 'teacher'" />
+    <view v-else class="loading-container">
+      <text>加载中...</text>
+    </view>
+  </template>
 </view>
 </template>
 
@@ -20,54 +113,69 @@
 import ParentIndex from './parent-index.vue'
 import ChildIndex from './child-index.vue'
 import TeacherIndex from '../teacher/index.vue'
-import { acceptParentInvite } from '../../utils/api.js'
+import WuxingSandbox from '../../components/wuxing-sandbox.vue'
+import BottomNav from '../../components/bottom-nav.vue'
+import { acceptParentInvite, productList } from '../../utils/api.js'
 
 export default {
   components: {
     ParentIndex,
     ChildIndex,
-    TeacherIndex
+    TeacherIndex,
+    WuxingSandbox,
+    BottomNav
   },
   data() {
     return {
-      currentRole: ''
+      currentRole: '',
+      typeList: [
+        { label: '全部', value: '' },
+        { label: '活动', value: 'activity' },
+        { label: '课程', value: 'course' },
+        { label: '实物', value: 'physical' },
+        { label: '数字', value: 'digital' },
+        { label: '服务', value: 'service' }
+      ],
+      currentType: '',
+      products: [],
+      loading: false
     }
   },
-onLoad(options) {
-      if (!this.isLoggedIn()) {
-        this.goDiscover()
-        return
-      }
-      this.syncRoleAndTabBar()
-      // 检查是否有邀请码参数
-      if (options.inviteCode && options.type === 'parent_invite') {
-        this.handleParentInvite(options.inviteCode)
-      } else {
-        // 检查是否有存储的邀请码(登录后返回)
-        const storedCode = uni.getStorageSync('inviteCode')
-        const storedType = uni.getStorageSync('inviteType')
-        if (storedCode && storedType === 'parent_invite') {
-          this.handleParentInvite(storedCode)
-          // 清除存储
-          uni.removeStorageSync('inviteCode')
-          uni.removeStorageSync('inviteType')
-        }
+  onLoad(options) {
+    if (!this.isLoggedIn()) {
+      this.currentRole = ''
+      setTimeout(() => { uni.hideTabBar() }, 100)
+      return
+    }
+    this.syncRoleAndTabBar()
+    // 检查是否有邀请码参数
+    if (options.inviteCode && options.type === 'parent_invite') {
+      this.handleParentInvite(options.inviteCode)
+    } else {
+      // 检查是否有存储的邀请码(登录后返回)
+      const storedCode = uni.getStorageSync('inviteCode')
+      const storedType = uni.getStorageSync('inviteType')
+      if (storedCode && storedType === 'parent_invite') {
+        this.handleParentInvite(storedCode)
+        // 清除存储
+        uni.removeStorageSync('inviteCode')
+        uni.removeStorageSync('inviteType')
       }
-    },
+    }
+  },
   onShow() {
     if (!this.isLoggedIn()) {
-      this.goDiscover()
+      this.currentRole = ''
+      setTimeout(() => { uni.hideTabBar() }, 100)
       return
     }
     this.syncRoleAndTabBar()
+    setTimeout(() => { uni.showTabBar() }, 100)
   },
   methods: {
     isLoggedIn() {
       return !!uni.getStorageSync('token')
     },
-    goDiscover() {
-      uni.switchTab({ url: '/pages/discover/index' })
-    },
     normalizeCurrentRole() {
       const role = uni.getStorageSync('role') || 'parent'
       const currentRole = uni.getStorageSync('currentRole')
@@ -95,11 +203,40 @@ onLoad(options) {
       uni.setTabBarItem({ index: 1, text: secondText })
       uni.setTabBarItem({ index: 2, text: thirdText })
     },
-syncRoleAndTabBar() {
+    syncRoleAndTabBar() {
       this.currentRole = this.normalizeCurrentRole()
       uni.setStorageSync('currentRole', this.currentRole)
       this.syncTabBarText(this.currentRole)
     },
+    // ===== 发现页 / 未登录态方法 =====
+    loadProducts() {
+      this.loading = true
+      const params = { page: 1, size: 10 }
+      if (this.currentType) {
+        params.productType = this.currentType
+      }
+      productList(params).then(res => {
+        this.loading = false
+        if (res.code === 200 && res.data) {
+          this.products = res.data.records || []
+        }
+      }).catch(() => {
+        this.loading = false
+      })
+    },
+    onTypeChange(value) {
+      this.currentType = value
+      this.loadProducts()
+    },
+    goShop() {
+      uni.switchTab({ url: '/pages/shop/index' })
+    },
+    goDetail(id) {
+      uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
+    },
+    handleLogin() {
+      uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/index/index') })
+    },
     async handleParentInvite(code) {
       // 延迟显示,等待登录完成
       setTimeout(async () => {
@@ -141,11 +278,209 @@ syncRoleAndTabBar() {
         })
       }, 500)
     }
-    }
+  }
 }
 </script>
 
 <style>
+.discover-container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 100rpx;
+}
+
+/* ===== 品牌头部 ===== */
+.brand-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 20rpx 30rpx 12rpx;
+  background: linear-gradient(180deg, #EBF2FA 0%, #f5f9fc 100%);
+}
+.brand-logo {
+  width: 64rpx;
+  height: 64rpx;
+  border-radius: 14rpx;
+  flex-shrink: 0;
+}
+.brand-text-wrap {
+  display: flex;
+  flex-direction: column;
+  margin-left: 14rpx;
+}
+.brand-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #2C5282;
+  letter-spacing: 4rpx;
+  line-height: 1.2;
+}
+.brand-subtitle {
+  font-size: 20rpx;
+  color: #5B9BD5;
+  margin-top: 2rpx;
+  line-height: 1.2;
+}
+
+/* ===== 推荐 ===== */
+.recommend-section {
+  margin: 20rpx 30rpx;
+}
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+.section-more {
+  font-size: 24rpx;
+  color: #5B9BD5;
+}
+.type-filter-scroll {
+  white-space: nowrap;
+  margin-bottom: 20rpx;
+}
+.filter-chip {
+  display: inline-block;
+  padding: 8rpx 24rpx;
+  font-size: 24rpx;
+  color: #666;
+  background: #f0f0f0;
+  border-radius: 30rpx;
+  margin-right: 16rpx;
+}
+.filter-chip.active {
+  background: #5B9BD5;
+  color: #fff;
+}
+.loading-state,
+.empty-state {
+  display: flex;
+  justify-content: center;
+  padding: 60rpx 0;
+}
+.loading-text,
+.empty-text {
+  font-size: 26rpx;
+  color: #999;
+}
+.product-grid {
+  display: flex;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+.product-card {
+  width: 48%;
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
+}
+.product-cover {
+  width: 100%;
+  height: 200rpx;
+  background: #f0f0f0;
+}
+.product-info {
+  padding: 14rpx;
+}
+.product-name {
+  display: block;
+  font-size: 26rpx;
+  color: #333;
+  margin-bottom: 10rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.product-price-row {
+  display: flex;
+  align-items: baseline;
+}
+.product-price {
+  font-size: 28rpx;
+  color: #5B9BD5;
+  font-weight: bold;
+  margin-right: 10rpx;
+}
+.product-member {
+  font-size: 20rpx;
+  color: #999;
+}
+
+/* ===== 为何选择 ===== */
+.why-section {
+  margin: 30rpx;
+}
+.feature-grid {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+.feature-item {
+  width: calc(50% - 10rpx);
+  box-sizing: border-box;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.feature-icon {
+  font-size: 48rpx;
+  margin-bottom: 10rpx;
+}
+.feature-title {
+  font-size: 24rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 6rpx;
+}
+.feature-desc {
+  font-size: 20rpx;
+  color: #999;
+}
+
+/* ===== 底部登录 ===== */
+.login-footer {
+  margin: 40rpx 30rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+.login-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+  border-radius: 44rpx;
+  text-align: center;
+  border: none;
+}
+.login-btn::after {
+  border: none;
+}
+.login-footer-text {
+  font-size: 20rpx;
+  color: #bbb;
+  margin-top: 16rpx;
+}
+
+/* ===== 加载占位 ===== */
 .loading-container {
   display: flex;
   justify-content: center;

+ 157 - 83
cfc-frontend/pages/index/parent-index.vue

@@ -1,21 +1,24 @@
 <template>
-  <view class="container">
-    <!-- 加载状态 -->
-    <BaseLoading :loading="loading" text="正在加载..." />
-
-    <template v-if="!loading">
-      <!-- ===== a) 顶部欢迎区 ===== -->
-      <view class="welcome-section animate-fade-in animate-stagger-1">
-        <view class="welcome-header">
-          <view class="welcome-text">
-            <text class="greeting">{{ greetingText }}!</text>
-            <text class="sub-greeting">{{ nickname || '家长' }}</text>
-          </view>
-          <view class="greeting-emoji">☀️</view>
-        </view>
-      </view>
-
-      <!-- ===== b) 孩子卡片 ===== -->
+<view class="container">
+<!-- 加载状态 -->
+<BaseLoading :loading="loading" text="正在加载..." />
+
+<template v-if="!loading">
+<!-- ===== a) 五维理念区 ===== -->
+<view class="philosophy-section animate-fade-in animate-stagger-1">
+<view class="philosophy-banner">
+<text class="philosophy-title">浠艾福</text>
+<view class="philosophy-five">
+<text class="five-item five-body">🌏 身泰</text>
+<text class="five-item five-wisdom">⚔️ 智达</text>
+<text class="five-item five-mind">🔥 心怡</text>
+<text class="five-item five-action">🌿 行远</text>
+<text class="five-item five-wealth">💧 富沛</text>
+</view>
+</view>
+</view>
+
+<!-- ===== b) 孩子卡片 ===== -->
       <view class="children-row animate-fade-in animate-stagger-2">
         <template v-if="children.length > 0">
           <scroll-view
@@ -77,12 +80,19 @@
         </PlayfulCard>
       </view>
 
-      <!-- 五行能量沙盘(汐艾福) -->
+      <!-- 五行能量沙盘(浠艾福 — 全家庭版) -->
       <view class="animate-fade-in animate-stagger-3">
-        <wuxing-sandbox mode="parent" badgeText="综合成长力 0%" @point-click="goToDomain" />
+    <wuxing-sandbox mode="live"
+      :scores="energyScores"
+      :overall-score="energyOverall"
+      :members="energyMembers"
+      :dimensions="energyDimensions"
+      :total-energy="totalEnergy"
+      :total-health-index="totalHealthIndex"
+      @point-click="goToDomain" />
       </view>
 
-      <!-- 会员Banner(汐福俱乐部) -->
+      <!-- 会员Banner(浠艾福俱乐部) -->
       <view class="membership-wrapper animate-fade-in animate-stagger-4">
         <view class="membership-banner" @click="goToMembership">
           <view class="banner-content">
@@ -282,7 +292,7 @@
 </template>
 
 <script>
-import { getFamilyMembers, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard } from '../../utils/api.js'
+import { getFamilyMembers, getChildren, getPendingReviewTasks, getPendingWishes, approveTask, rejectTask, getChildCompletionStats, getParentDashboard, getFamilyEnergySandbox, getEnergyOverview } from '../../utils/api.js'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import PlayfulCard from '../../components/PlayfulCard.vue'
 import PlayfulButton from '../../components/PlayfulButton.vue'
@@ -307,10 +317,25 @@ export default {
       loading: true,
       currentChildId: '',
       _initialLoadDone: false,
-      _familyId: null
+      _familyId: null,
+      energyData: null,
+      energyDimensions: [],
+      totalEnergy: 0,
+      totalHealthIndex: 0
     }
   },
   computed: {
+    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 : []
+    },
     greetingText() {
       const hour = new Date().getHours()
       if (hour < 6) return '夜深了'
@@ -471,10 +496,34 @@ export default {
                 this.pendingTasks.push({ id: 100 + k, title: '待完成任务 ' + (k + 1) })
               }
             }
-          } catch (e) {
+            } catch (e) {
             this.pendingTasks = []
           }
         }
+
+        // 加载家庭能量沙盘数据(五维 + 所有成员)
+        try {
+          const energyRes = await getFamilyEnergySandbox()
+          this.energyData = energyRes.data || null
+        } catch (e) {
+          console.log('获取能量沙盘数据失败', e)
+          this.energyData = null
+        }
+
+        // 加载五维能量概览(账本模式)
+        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 {
@@ -506,8 +555,13 @@ export default {
         uni.navigateTo({ url: '/pages/stats/completion-rate?familyView=1' })
       }
     },
-    goToDomain(domain) {
-      uni.showToast({ title: '即将上线', icon: 'none' })
+    goToDomain(code) {
+      // 跳转到能量详情页(T10 创建)
+      var childId = this.currentChildId
+      if (!childId || !code) return
+      uni.navigateTo({
+        url: '/pages/energy/detail?childId=' + childId + '&code=' + code
+      })
     },
     goToMembership() {
       uni.showToast({ title: '即将上线', icon: 'none' })
@@ -533,7 +587,7 @@ export default {
 
 .container {
   padding: 32rpx;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, #F5F9FC);
   min-height: 100vh;
 }
 
@@ -543,46 +597,66 @@ export default {
 .section-title {
   font-size: 32rpx;
   font-weight: 700;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   margin-bottom: var(--space-md);
 }
 
 /* ========================================
-   a) 顶部欢迎区
-   ======================================== */
-.welcome-section {
-  margin-bottom: 48rpx;
-}
-
-.welcome-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: flex-start;
-  margin-bottom: 24rpx;
-}
-
-.greeting {
-  font-size: 40rpx;
-  font-weight: 700;
-  color: var(--text, #3D2E1E);
-  display: block;
-  line-height: 1.2;
-}
-
-.sub-greeting {
-  font-size: 28rpx;
-  color: var(--text-secondary, #6B5A4A);
-  display: block;
-  margin-top: 8rpx;
-}
-
-.greeting-emoji {
-  font-size: 64rpx;
-}
+a) 五维理念区
+======================================== */
+.philosophy-section {
+margin-bottom: 40rpx;
+}
+
+.philosophy-banner {
+background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
+border-radius: var(--radius-lg, 28rpx);
+padding: 32rpx 28rpx 24rpx;
+text-align: center;
+border: 3rpx solid rgba(147, 197, 253, 0.2);
+box-shadow: 0 8rpx 32rpx rgba(0, 0, 0, 0.15);
+}
+
+.philosophy-title {
+font-size: 36rpx;
+font-weight: 800;
+color: #FFD700;
+letter-spacing: 8rpx;
+display: block;
+margin-bottom: 24rpx;
+text-shadow: 0 2rpx 12rpx rgba(255, 215, 0, 0.3);
+}
+
+.philosophy-five {
+display: flex;
+justify-content: space-around;
+align-items: center;
+flex-wrap: wrap;
+gap: 12rpx 8rpx;
+}
+
+.five-item {
+font-size: 26rpx;
+font-weight: 700;
+color: #FFFFFF;
+display: inline-flex;
+align-items: center;
+gap: 6rpx;
+padding: 10rpx 16rpx;
+border-radius: 24rpx;
+background: rgba(255, 255, 255, 0.08);
+border: 2rpx solid rgba(255, 255, 255, 0.12);
+}
+
+.five-body { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
+.five-wisdom { border-color: rgba(251, 191, 36, 0.4); color: #FDE68A; }
+.five-mind { border-color: rgba(251, 146, 60, 0.4); color: #FDBA74; }
+.five-action { border-color: rgba(134, 239, 172, 0.4); color: #BBF7D0; }
+.five-wealth { border-color: rgba(147, 197, 253, 0.4); color: #BFDBFE; }
 
 /* ========================================
-   b) 孩子卡片
-   ======================================== */
+b) 孩子卡片
+======================================== */
 .children-row {
   margin-bottom: 48rpx;
 }
@@ -622,7 +696,7 @@ export default {
   flex-shrink: 0;
 }
 
-.child-avatar-color-0 { background: linear-gradient(135deg, var(--color-primary, #F97316), var(--color-primary-dark, #EA580C)); }
+.child-avatar-color-0 { background: linear-gradient(135deg, var(--color-primary, #5B9BD5), var(--color-primary-dark, #3A7CC4)); }
 .child-avatar-color-1 { background: linear-gradient(135deg, var(--color-accent, #0EA5E9), #6366F1); }
 .child-avatar-color-2 { background: linear-gradient(135deg, #10B981, #059669); }
 .child-avatar-color-3 { background: linear-gradient(135deg, #EC4899, #E11D48); }
@@ -644,7 +718,7 @@ export default {
 .child-card-name {
   font-size: 30rpx;
   font-weight: 600;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   display: block;
   line-height: 1.3;
 }
@@ -658,7 +732,7 @@ export default {
 
 .child-card-points {
   font-size: 24rpx;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
   font-weight: 600;
 }
 
@@ -714,7 +788,7 @@ export default {
 .empty-children-title {
   font-size: 28rpx;
   font-weight: 600;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   display: block;
 }
 
@@ -737,7 +811,7 @@ export default {
   margin-bottom: 48rpx;
 }
 
-/* 福俱乐部 - 会员Banner */
+/* 浠艾福俱乐部 - 会员Banner */
 .membership-banner {
   padding: 30rpx 32rpx;
   background: linear-gradient(135deg, #FFD700, #FFA500);
@@ -821,7 +895,7 @@ export default {
   font-size: 28rpx;
 }
 
-.stat-icon-orange { background: rgba(249, 115, 22, 0.12); }
+.stat-icon-orange { background: rgba(91, 155, 213, 0.12); }
 .stat-icon-gold { background: rgba(245, 158, 11, 0.15); }
 .stat-icon-red { background: rgba(239, 68, 68, 0.12); }
 .stat-icon-teal { background: rgba(16, 185, 129, 0.12); }
@@ -840,12 +914,12 @@ export default {
 
 .stat-label {
   font-size: 22rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   display: block;
   margin-top: 4rpx;
 }
 
-.accent-color { color: var(--color-primary, #F97316); }
+.accent-color { color: var(--color-primary, #5B9BD5); }
 .gold-color { color: #F59E0B; }
 .red-color { color: var(--color-error, #EF4444); }
 .teal-color { color: #10B981; }
@@ -856,14 +930,14 @@ export default {
 
 .progress-bar {
   height: 8rpx;
-  background: var(--border-light, #FFEDD5);
+  background: var(--border-light, #E2E8F0);
   border-radius: 999rpx;
   overflow: hidden;
 }
 
 .progress-fill {
   height: 100%;
-  background: linear-gradient(90deg, var(--color-primary, #F97316), #F59E0B);
+  background: linear-gradient(90deg, var(--color-primary, #5B9BD5), #F59E0B);
   border-radius: 999rpx;
   transition: width 0.3s ease;
 }
@@ -903,12 +977,12 @@ export default {
 .today-label {
   font-size: 28rpx;
   font-weight: 600;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
 }
 
 .today-count {
   font-size: 24rpx;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
   font-weight: 600;
 }
 
@@ -920,7 +994,7 @@ export default {
   display: flex;
   align-items: center;
   padding: 18rpx 0;
-  border-bottom: 1rpx solid var(--border-light, #FFEDD5);
+  border-bottom: 1rpx solid var(--border-light, #E2E8F0);
 }
 
 .task-item:last-child {
@@ -935,7 +1009,7 @@ export default {
   flex-shrink: 0;
 }
 
-.dot-color-0 { background: var(--color-primary, #F97316); }
+.dot-color-0 { background: var(--color-primary, #5B9BD5); }
 .dot-color-1 { background: #10B981; }
 .dot-color-2 { background: var(--color-accent, #0EA5E9); }
 .dot-color-3 { background: #F59E0B; }
@@ -943,17 +1017,17 @@ export default {
 
 .task-title {
   font-size: 28rpx;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
 }
 
 .task-view-all {
   display: flex;
   justify-content: flex-end;
   align-items: center;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   font-size: 24rpx;
   padding-top: 16rpx;
-  border-top: 1rpx solid var(--border-light, #FFEDD5);
+  border-top: 1rpx solid var(--border-light, #E2E8F0);
 }
 
 .task-view-all .arrow {
@@ -975,7 +1049,7 @@ export default {
   display: flex;
   align-items: center;
   padding: 16rpx 0;
-  border-bottom: 1rpx solid var(--border-light, #FFEDD5);
+  border-bottom: 1rpx solid var(--border-light, #E2E8F0);
 }
 
 .milestone-item:last-child {
@@ -998,12 +1072,12 @@ export default {
 }
 
 .milestone-star { background: rgba(245, 158, 11, 0.15); }
-.milestone-trophy { background: rgba(249, 115, 22, 0.12); }
+.milestone-trophy { background: rgba(91, 155, 213, 0.12); }
 .milestone-sparkle { background: rgba(16, 185, 129, 0.12); }
 
 .milestone-text {
   font-size: 28rpx;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
 }
 
 /* ========================================
@@ -1035,7 +1109,7 @@ export default {
 
 .review-info .task-name {
   font-size: 28rpx;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   display: block;
   margin-bottom: 4rpx;
 }
@@ -1095,7 +1169,7 @@ export default {
   font-size: 32rpx;
 }
 
-.act-add { background: rgba(249, 115, 22, 0.12); }
+.act-add { background: rgba(91, 155, 213, 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); }
@@ -1103,7 +1177,7 @@ export default {
 
 .action-label {
   font-size: 22rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   font-weight: 500;
   text-align: center;
 }

+ 15 - 3
cfc-frontend/pages/login/login.vue

@@ -81,7 +81,8 @@ export default {
       countdown: 0,
       agreed: false,
       nickname: '',
-      avatar: ''
+      avatar: '',
+      redirectUrl: ''
     }
   },
   onLoad(options) {
@@ -90,6 +91,10 @@ export default {
       uni.setStorageSync('inviteCode', options.inviteCode)
       uni.setStorageSync('inviteType', options.inviteType || 'family')
     }
+    // 存储登录后跳转地址
+    if (options.redirect) {
+      this.redirectUrl = decodeURIComponent(options.redirect)
+    }
   },
   methods: {
     handleImageError(e) {
@@ -284,8 +289,15 @@ async handleWechatPhoneLogin(e) {
     }
     },
     navigateToHome(role) {
-      // 通过 TabBar 首页跳转,让 index.vue 根据角色显示不同内容
-      uni.reLaunch({ url: '/pages/index/index' })
+      if (this.redirectUrl) {
+        // 有 redirect 则跳回来源页(登录后已恢复原生 TabBar)
+        const redirect = this.redirectUrl
+        this.redirectUrl = ''
+        uni.reLaunch({ url: redirect })
+      } else {
+        // 默认通过 TabBar 首页跳转
+        uni.reLaunch({ url: '/pages/index/index' })
+      }
     }
   }
 }

+ 2 - 2
cfc-frontend/pages/membership/index.vue

@@ -148,7 +148,7 @@ export default {
 }
 
 .membership-card {
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #5B9BD5 0%, #3A7CC4 100%);
   border-radius: 20rpx;
   padding: 40rpx;
   margin-bottom: 30rpx;
@@ -275,7 +275,7 @@ export default {
 
 .level-price {
   font-size: 26rpx;
-  color: #F97316;
+  color: #5B9BD5;
 }
 
 .level-price.free {

+ 309 - 0
cfc-frontend/pages/mind/article-detail.vue

@@ -0,0 +1,309 @@
+<template>
+  <view class="detail-container">
+    <!-- 加载状态 -->
+    <view v-if="loading" class="loading-wrap">
+      <view class="loading-spinner"></view>
+      <text class="loading-text">加载中...</text>
+    </view>
+
+    <!-- 错误状态 -->
+    <view v-else-if="error" class="error-wrap">
+      <text class="error-icon">📄</text>
+      <text class="error-text">{{ errorMsg }}</text>
+      <button class="retry-btn" @click="loadDetail(articleId)">重新加载</button>
+    </view>
+
+    <!-- 文章内容 -->
+    <view v-else-if="article" class="detail-content">
+      <!-- 封面图 -->
+      <image
+        v-if="article.coverImage"
+        class="detail-cover"
+        :src="article.coverImage"
+        mode="widthFix"
+      />
+
+      <!-- 文章标题 -->
+      <text class="detail-title">{{ article.title }}</text>
+
+      <!-- 元信息 -->
+      <view class="detail-meta">
+        <text class="meta-author">{{ article.author || '浠艾福' }}</text>
+        <text class="meta-separator">|</text>
+        <text class="meta-date">{{ getFormattedDate() }}</text>
+        <text class="meta-separator">|</text>
+        <text class="meta-read">{{ article.readTime || 3 }} 分钟阅读</text>
+      </view>
+
+      <!-- 分类标签 -->
+      <view class="detail-category-row">
+        <text class="detail-category">{{ article.categoryName || '' }}</text>
+      </view>
+
+      <!-- 文章标签 -->
+      <view v-if="tagList.length" class="detail-tags">
+        <text v-for="(tag, idx) in tagList" :key="idx" class="tag-item">{{ tag }}</text>
+      </view>
+
+      <!-- 分割线 -->
+      <view class="divider"></view>
+
+      <!-- 文章正文(富文本) -->
+      <view class="detail-body">
+        <rich-text :nodes="article.content" />
+      </view>
+
+      <!-- 底部完成按钮 -->
+      <view class="detail-footer">
+        <button class="read-btn" @click="onReadComplete">阅读完成</button>
+      </view>
+    </view>
+
+    <!-- 空状态 -->
+    <view v-else class="error-wrap">
+      <text class="error-icon">📄</text>
+      <text class="error-text">文章不存在</text>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getArticleDetail, recordArticleRead } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      articleId: '',
+      article: null,
+      loading: true,
+      error: false,
+      errorMsg: '',
+      tagList: []
+    }
+  },
+  onLoad(options) {
+    if (options && options.id) {
+      this.articleId = options.id
+      this.loadDetail(options.id)
+    } else {
+      this.error = true
+      this.errorMsg = '参数错误'
+      this.loading = false
+    }
+  },
+  methods: {
+    async loadDetail(id) {
+      this.loading = true
+      this.error = false
+      try {
+        const res = await getArticleDetail({ id: id })
+        if (res.code === 200 && res.data) {
+          this.article = res.data
+          // 解析标签 JSON 字符串
+          if (res.data.tags) {
+            try {
+              let parsed = JSON.parse(res.data.tags)
+              this.tagList = Array.isArray(parsed) ? parsed : []
+            } catch (e) {
+              this.tagList = []
+            }
+          }
+        } else {
+          this.error = true
+          this.errorMsg = '文章不存在或无权限查看'
+        }
+      } catch (e) {
+        this.error = true
+        this.errorMsg = '加载失败,请稍后重试'
+      } finally {
+        this.loading = false
+      }
+    },
+    getFormattedDate() {
+      if (this.article && this.article.publishedAt) {
+        return this.article.publishedAt.slice(0, 10)
+      }
+      return ''
+    },
+    async onReadComplete() {
+      try {
+        await recordArticleRead({ id: this.article.id })
+        uni.showToast({ title: '阅读记录已保存', icon: 'success' })
+      } catch (e) {
+        // 静默处理
+      }
+      setTimeout(function() {
+        uni.navigateBack()
+      }, 1500)
+    }
+  }
+}
+</script>
+
+<style scoped>
+.detail-container {
+  min-height: 100vh;
+  background: #fff;
+  padding-bottom: 120rpx;
+}
+
+/* ===== 加载状态 ===== */
+.loading-wrap {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  padding-top: 300rpx;
+}
+.loading-spinner {
+  width: 60rpx;
+  height: 60rpx;
+  border: 4rpx solid #e0e0e0;
+  border-top-color: #5B9BD5;
+  border-radius: 50%;
+  animation: spin 0.8s linear infinite;
+  margin-bottom: 20rpx;
+}
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+.loading-text {
+  font-size: 26rpx;
+  color: #999;
+}
+
+/* ===== 错误状态 ===== */
+.error-wrap {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding-top: 300rpx;
+}
+.error-icon {
+  font-size: 100rpx;
+  margin-bottom: 24rpx;
+}
+.error-text {
+  font-size: 28rpx;
+  color: #999;
+  margin-bottom: 30rpx;
+}
+.retry-btn {
+  width: 240rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  background: #5B9BD5;
+  color: #fff;
+  font-size: 28rpx;
+  border-radius: 36rpx;
+  text-align: center;
+  border: none;
+}
+.retry-btn::after {
+  border: none;
+}
+
+/* ===== 内容区 ===== */
+.detail-cover {
+  width: 100%;
+  max-height: 500rpx;
+  display: block;
+}
+.detail-title {
+  display: block;
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #333;
+  line-height: 1.4;
+  padding: 30rpx 30rpx 0;
+}
+
+/* ===== 元信息 ===== */
+.detail-meta {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 16rpx 30rpx 0;
+  font-size: 22rpx;
+  color: #999;
+}
+.meta-author {
+  color: #5B9BD5;
+}
+.meta-separator {
+  margin: 0 12rpx;
+  color: #ddd;
+}
+.meta-date,
+.meta-read {
+  color: #999;
+}
+
+/* ===== 分类标签 ===== */
+.detail-category-row {
+  padding: 16rpx 30rpx 0;
+}
+.detail-category {
+  display: inline-block;
+  font-size: 20rpx;
+  color: #5B9BD5;
+  background: rgba(91, 155, 213, 0.1);
+  padding: 4rpx 16rpx;
+  border-radius: 8rpx;
+}
+
+/* ===== 标签 ===== */
+.detail-tags {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  padding: 12rpx 30rpx 0;
+  gap: 12rpx;
+}
+.tag-item {
+  font-size: 20rpx;
+  color: #8B5CF6;
+  background: rgba(139, 92, 246, 0.08);
+  padding: 4rpx 14rpx;
+  border-radius: 8rpx;
+}
+
+/* ===== 分割线 ===== */
+.divider {
+  height: 1rpx;
+  background: #eee;
+  margin: 24rpx 30rpx;
+}
+
+/* ===== 正文 ===== */
+.detail-body {
+  padding: 0 30rpx;
+  font-size: 28rpx;
+  color: #444;
+  line-height: 1.8;
+}
+.detail-body rich-text {
+  word-break: break-word;
+}
+
+/* ===== 底部按钮 ===== */
+.detail-footer {
+  padding: 40rpx 60rpx;
+}
+.read-btn {
+  width: 100%;
+  height: 88rpx;
+  line-height: 88rpx;
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  color: #fff;
+  font-size: 32rpx;
+  font-weight: bold;
+  border-radius: 44rpx;
+  text-align: center;
+  border: none;
+}
+.read-btn::after {
+  border: none;
+}
+</style>

+ 384 - 0
cfc-frontend/pages/mind/articles.vue

@@ -0,0 +1,384 @@
+<template>
+  <view class="container">
+    <!-- 品牌头部 -->
+    <view class="brand-header">
+      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
+      <view class="brand-text-wrap">
+        <text class="brand-title">心智成长</text>
+        <text class="brand-subtitle">探索内心世界,培养健康心智</text>
+      </view>
+    </view>
+
+    <!-- 分类筛选 -->
+    <view class="filter-section">
+      <scroll-view scroll-x enable-flex show-scrollbar="false" class="category-scroll">
+        <view class="filter-chips">
+          <view
+            v-for="cat in categoryList"
+            :key="cat.value"
+            :class="['filter-chip', currentCategory === cat.value ? 'active' : '']"
+            @click="onCategoryChange(cat.value)"
+          >
+            {{ cat.label }}
+          </view>
+        </view>
+      </scroll-view>
+    </view>
+
+    <!-- 文章列表 -->
+    <scroll-view scroll-y class="article-list" @scrolltolower="onLoadMore">
+      <view v-if="loading && articles.length === 0" class="loading-wrap">
+        <text class="loading-text">加载中...</text>
+      </view>
+      <view v-else-if="articles.length === 0" class="empty-wrap">
+        <text class="empty-icon">📝</text>
+        <text class="empty-text">暂无相关文章</text>
+      </view>
+      <view v-else class="article-grid">
+        <view
+          v-for="item in articles"
+          :key="item.id"
+          class="article-card"
+          @click="goDetail(item.id)"
+        >
+          <image
+            class="article-cover"
+            :src="item.coverImage || '/static/default-article.png'"
+            mode="aspectFill"
+          />
+          <view class="article-body">
+            <view class="article-meta">
+              <text class="article-category">{{ item.categoryName || '心理健康' }}</text>
+              <text class="article-date">{{ item.publishDate || '' }}</text>
+            </view>
+            <text class="article-title">{{ item.title }}</text>
+            <text class="article-summary">{{ item.summary || item.content }}</text>
+            <view class="article-footer">
+              <text class="article-author">{{ item.author || '浠艾福' }}</text>
+              <text class="article-read-count">阅读 {{ item.readCount || 0 }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+      <view v-if="loadingMore" class="loading-more">
+        <text class="loading-text">加载中...</text>
+      </view>
+      <view v-if="noMore && articles.length > 0" class="no-more">
+        <text class="no-more-text">— 没有更多了 —</text>
+      </view>
+      <!-- 未登录提示 -->
+      <view v-if="!isLoggedIn" class="login-hint-bar">
+        <text class="login-hint-text">🔒 登录后解锁全部文章</text>
+      </view>
+      <!-- 底部占位 -->
+      <view class="bottom-spacer"></view>
+    </scroll-view>
+
+    <!-- 自定义底栏(3个tab:首页/行动/心智) -->
+    <bottom-nav v-if="!isLoggedIn" current="mind" />
+  </view>
+</template>
+
+<script>
+import BottomNav from '../../components/bottom-nav.vue'
+import { getArticleCategories, getArticleList } from '../../utils/api.js'
+
+export default {
+  components: { BottomNav },
+  data() {
+    return {
+      categoryList: [{ id: '', name: '全部' }],
+      categoryMap: {},
+      currentCategory: '',
+      articles: [],
+      page: 1,
+      size: 10,
+      total: 0,
+      loading: false,
+      noMore: false,
+      isLoggedIn: false
+    }
+  },
+  onLoad() {
+    this.isLoggedIn = !!uni.getStorageSync('token')
+    this.loadCategories()
+  },
+  onShow() {
+    this.isLoggedIn = !!uni.getStorageSync('token')
+  },
+  methods: {
+    async loadCategories() {
+      try {
+        const res = await getArticleCategories()
+        if (res.code === 200 && res.data) {
+          const cats = Array.isArray(res.data) ? res.data : []
+          let map = {}
+          cats.forEach(function(c) { map[c.id] = c.name })
+          this.categoryMap = map
+          this.categoryList = [{ id: '', name: '全部' }].concat(cats)
+        }
+      } catch (e) {
+        // 默认分类列表
+        this.categoryList = [
+          { id: '', name: '全部' },
+          { id: 1, name: '心理健康' },
+          { id: 2, name: '情绪管理' }
+        ]
+      }
+      // 分类加载完成后加载文章
+      this.loadArticles()
+    },
+    onCategoryChange(id) {
+      this.currentCategory = id
+      this.page = 1
+      this.articles = []
+      this.noMore = false
+      this.loadArticles()
+    },
+    async loadArticles() {
+      if (this.loading) return
+      this.loading = true
+      try {
+        let params = { page: this.page, size: this.size }
+        if (this.currentCategory) {
+          params.categoryId = this.currentCategory
+        }
+        const res = await getArticleList(params)
+        if (res.code === 200 && res.data) {
+          let pageData = res.data
+          let list = pageData.records || []
+          let mapped = list.map(function(item) {
+            return {
+              id: item.id,
+              title: item.title || '',
+              summary: item.summary || '',
+              content: item.content || '',
+              coverImage: item.coverImage || '',
+              author: item.author || '浠艾福',
+              categoryName: item.categoryName || item.category || '',
+              publishDate: item.publishedAt ? item.publishedAt.slice(0, 10) : '',
+              readCount: item.viewCount || 0
+            }
+          })
+          if (this.page === 1) {
+            this.articles = mapped
+          } else {
+            this.articles = this.articles.concat(mapped)
+          }
+          this.total = pageData.total || 0
+          this.noMore = this.articles.length >= this.total
+        } else {
+          if (this.page === 1) {
+            this.articles = []
+          }
+        }
+      } catch (e) {
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      } finally {
+        this.loading = false
+      }
+    },
+    onLoadMore() {
+      if (this.noMore || this.loading) return
+      this.page++
+      this.loadArticles()
+    },
+    goDetail(id) {
+      if (this.isLoggedIn) {
+        uni.navigateTo({ url: '/pages/mind/article-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/articles') })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  display: flex;
+  flex-direction: column;
+  min-height: 100vh;
+  background: #f5f7fa;
+}
+
+/* 品牌头部 */
+.brand-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 24rpx 30rpx 16rpx;
+  background: linear-gradient(180deg, #EBF2FA 0%, #f5f9fc 100%);
+}
+.brand-logo {
+  width: 56rpx;
+  height: 56rpx;
+  border-radius: 12rpx;
+  flex-shrink: 0;
+}
+.brand-text-wrap {
+  display: flex;
+  flex-direction: column;
+  margin-left: 14rpx;
+}
+.brand-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #2C5282;
+  letter-spacing: 2rpx;
+}
+.brand-subtitle {
+  font-size: 20rpx;
+  color: #5B9BD5;
+  margin-top: 2rpx;
+}
+
+/* 分类筛选 */
+.filter-section {
+  background: #fff;
+  padding: 16rpx 0 12rpx;
+  border-bottom: 1rpx solid #eee;
+}
+.category-scroll {
+  white-space: nowrap;
+}
+.filter-chips {
+  display: flex;
+  padding: 0 20rpx;
+  gap: 16rpx;
+}
+.filter-chip {
+  display: inline-block;
+  padding: 8rpx 24rpx;
+  font-size: 24rpx;
+  color: #666;
+  background: #f0f0f0;
+  border-radius: 30rpx;
+  flex-shrink: 0;
+}
+.filter-chip.active {
+  background: #5B9BD5;
+  color: #fff;
+}
+
+/* 文章列表 */
+.article-list {
+  flex: 1;
+  height: calc(100vh - 220rpx);
+}
+.loading-wrap,
+.empty-wrap {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding-top: 120rpx;
+}
+.loading-text {
+  font-size: 26rpx;
+  color: #999;
+}
+.empty-icon {
+  font-size: 100rpx;
+  margin-bottom: 24rpx;
+}
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+}
+.article-grid {
+  padding: 20rpx 24rpx;
+}
+.article-card {
+  background: #fff;
+  border-radius: 16rpx;
+  overflow: hidden;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
+}
+.article-cover {
+  width: 100%;
+  height: 280rpx;
+  background: linear-gradient(135deg, #667eea, #764ba2);
+}
+.article-body {
+  padding: 20rpx;
+}
+.article-meta {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10rpx;
+}
+.article-category {
+  font-size: 20rpx;
+  color: #5B9BD5;
+  background: rgba(91,155,213,0.1);
+  padding: 2rpx 12rpx;
+  border-radius: 8rpx;
+  margin-right: 12rpx;
+}
+.article-date {
+  font-size: 20rpx;
+  color: #999;
+}
+.article-title {
+  display: block;
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 8rpx;
+  line-height: 1.4;
+}
+.article-summary {
+  display: block;
+  font-size: 24rpx;
+  color: #666;
+  line-height: 1.5;
+  margin-bottom: 12rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+.article-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+.article-author {
+  font-size: 20rpx;
+  color: #999;
+}
+.article-read-count {
+  font-size: 20rpx;
+  color: #bbb;
+}
+.loading-more,
+.no-more {
+  text-align: center;
+  padding: 30rpx;
+}
+.no-more-text {
+  font-size: 24rpx;
+  color: #ccc;
+}
+
+/* 未登录提示 */
+.login-hint-bar {
+  text-align: center;
+  padding: 20rpx;
+  background: #fef9e7;
+  border-top: 1rpx solid #f0e6c0;
+  margin: 20rpx 24rpx 0;
+  border-radius: 12rpx;
+}
+.login-hint-text {
+  font-size: 24rpx;
+  color: #b8860b;
+}
+
+/* 底部占位 */
+.bottom-spacer {
+  height: 120rpx;
+}
+</style>

+ 380 - 0
cfc-frontend/pages/mind/index.vue

@@ -0,0 +1,380 @@
+<template>
+  <view class="container">
+    <!-- 品牌头部 -->
+    <view class="brand-header">
+      <image class="brand-logo" src="/static/logo.png" mode="aspectFit" />
+      <text class="brand-title">心智成长</text>
+      <text class="brand-slogan">探索内心世界,培养健康心智</text>
+    </view>
+
+    <!-- 功能入口 -->
+    <view class="func-section" v-if="sectionVisible('func_entries')">
+      <view class="func-grid">
+        <view class="func-item" v-for="item in funcList" :key="item.label">
+          <view class="func-icon-wrap">
+            <text class="func-icon">{{ item.icon }}</text>
+          </view>
+          <text class="func-label">{{ item.label }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 每日心理 -->
+    <view class="section" v-if="sectionVisible('daily_tip')">
+      <view class="section-header">
+        <text class="section-title">每日心理</text>
+      </view>
+      <view class="tip-card">
+        <view class="tip-left-bar"></view>
+        <view class="tip-body">
+          <text class="tip-title">{{ dailyTip.title }}</text>
+          <text class="tip-content">{{ dailyTip.content }}</text>
+          <view class="tip-footer">
+            <text class="tip-source">{{ dailyTip.source }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 推荐阅读 -->
+    <view class="section" v-if="sectionVisible('recommended_reading')">
+      <view class="section-header">
+        <text class="section-title">推荐阅读</text>
+      </view>
+      <view class="article-list">
+        <view class="article-card" v-for="article in articles" :key="article.id" @click="goArticleDetail(article.id)">
+          <view class="article-category" :style="{ background: article.categoryColor }">
+            <text class="article-category-text">{{ article.category }}</text>
+          </view>
+          <text class="article-title">{{ article.title }}</text>
+          <text class="article-summary">{{ article.summary }}</text>
+          <view class="article-meta">
+            <view class="meta-item">
+              <text class="meta-icon">📖</text>
+              <text class="meta-text">{{ article.views }}</text>
+            </view>
+            <view class="meta-item">
+              <text class="meta-icon">👍</text>
+              <text class="meta-text">{{ article.likes }}</text>
+            </view>
+            <text class="article-date">{{ article.date }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- 会员专属内容(后台可配置,登录后可见) -->
+    <view class="section" v-if="sectionVisible('premium_content')">
+      <view class="section-header">
+        <text class="section-title">👑 会员专属</text>
+      </view>
+      <view class="premium-card">
+        <text class="premium-text">深度心智测评报告、个性化成长方案等会员专属内容</text>
+      </view>
+    </view>
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import { getVisibleSections, getFeaturedArticles } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      visibleSections: [],
+      funcList: [
+        { icon: '\u{1F4D6}', label: '阅读' },
+        { icon: '\u{1F9E9}', label: '认知' },
+        { icon: '\u{1F4AA}', label: '专注' },
+        { icon: '\u{1F3AF}', label: '决策' },
+        { icon: '\u{1F31F}', label: '心智测评' }
+      ],
+      dailyTip: {
+        title: '成长型思维',
+        content: '拥有成长型思维的孩子相信自己的能力可以通过努力提升,他们更愿意接受挑战,从失败中学习。作为家长,多鼓励孩子的努力而非天赋,可以帮助孩子建立成长型思维。',
+        source: '—— 卡罗尔·德韦克《终身成长》'
+      },
+      articles: []
+    }
+  },
+  onShow() {
+    this.resetTabBar()
+    this.loadSectionConfig()
+    this.loadFeaturedArticles()
+  },
+  methods: {
+    resetTabBar() {
+      uni.setTabBarItem({ index: 1, text: '身体' })
+      uni.setTabBarItem({ index: 2, text: '心智' })
+    },
+    async loadSectionConfig() {
+      var token = uni.getStorageSync('token')
+      var role = null
+      if (token) {
+        role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
+      }
+      var res
+      try {
+        res = await getVisibleSections({ pageKey: 'mind', role: role })
+        if (res.code === 200 && res.data) {
+          this.visibleSections = res.data.map(function(s) { return s.sectionKey })
+        }
+      } catch (e) {
+        // 默认显示所有公开区块
+        this.visibleSections = ['func_entries', 'daily_tip', 'recommended_reading']
+      }
+    },
+    sectionVisible(sectionKey) {
+      return this.visibleSections.indexOf(sectionKey) !== -1
+    },
+    async loadFeaturedArticles() {
+      try {
+        const res = await getFeaturedArticles({ size: 5 })
+        if (res.code === 200 && res.data) {
+          const gradientColors = [
+            'linear-gradient(135deg, #8B5CF6, #A78BFA)',
+            'linear-gradient(135deg, #5B9BD5, #8FC5E8)',
+            'linear-gradient(135deg, #10B981, #34D399)',
+            'linear-gradient(135deg, #8B5CF6, #C084FC)',
+            'linear-gradient(135deg, #F97316, #FB923C)'
+          ]
+          let list = Array.isArray(res.data) ? res.data : (res.data.records || [])
+          this.articles = list.map(function(item, index) {
+            return {
+              id: item.id,
+              category: item.categoryName || '成长文章',
+              categoryColor: gradientColors[index % gradientColors.length],
+              title: item.title || '',
+              summary: item.summary || '',
+              views: item.viewCount ? String(item.viewCount) : '0',
+              likes: '0',
+              date: item.publishedAt ? item.publishedAt.slice(0, 10) : ''
+            }
+          })
+        }
+      } catch (e) {
+        // 静默处理,保持空列表
+      }
+    },
+    goArticleDetail(id) {
+      var token = uni.getStorageSync('token')
+      if (token) {
+        uni.navigateTo({ url: '/pages/mind/article-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/mind/index') })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+/* ===== 品牌头部 ===== */
+.brand-header {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 30rpx 30rpx 16rpx;
+  background: linear-gradient(180deg, #1A2A4A 0%, #1E3A5F 60%, #f5f7fa 100%);
+}
+.brand-logo {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 18rpx;
+  margin-bottom: 8rpx;
+}
+.brand-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #8FC5E8;
+  letter-spacing: 4rpx;
+}
+.brand-slogan {
+  font-size: 22rpx;
+  color: rgba(143, 197, 232, 0.6);
+  margin-top: 6rpx;
+}
+
+/* ===== 功能入口 ===== */
+.func-section {
+  margin: 20rpx 30rpx;
+}
+.func-grid {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.func-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  flex: 1;
+}
+.func-icon-wrap {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 40rpx;
+  background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 10rpx;
+}
+.func-icon {
+  font-size: 40rpx;
+}
+.func-label {
+  font-size: 22rpx;
+  color: #666;
+  font-weight: 500;
+}
+
+/* ===== 通用区块 ===== */
+.section {
+  margin: 20rpx 30rpx;
+}
+.section-header {
+  margin-bottom: 20rpx;
+}
+.section-title {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+/* ===== 每日心理 ===== */
+.tip-card {
+  background: #fff;
+  border-radius: 20rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  display: flex;
+  flex-direction: row;
+  overflow: hidden;
+}
+.tip-left-bar {
+  width: 6rpx;
+  background: linear-gradient(180deg, #5B9BD5, #8FC5E8);
+  flex-shrink: 0;
+}
+.tip-body {
+  flex: 1;
+  padding: 28rpx 24rpx;
+}
+.tip-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #5B9BD5;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.tip-content {
+  font-size: 24rpx;
+  color: #666;
+  line-height: 1.6;
+  display: block;
+  margin-bottom: 16rpx;
+}
+.tip-footer {
+  display: flex;
+  flex-direction: row;
+  justify-content: flex-end;
+}
+.tip-source {
+  font-size: 22rpx;
+  color: #bbb;
+}
+
+/* ===== 推荐阅读 ===== */
+.article-list {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+.article-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 28rpx 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.article-category {
+  display: inline-block;
+  padding: 4rpx 18rpx;
+  border-radius: 20rpx;
+  margin-bottom: 14rpx;
+}
+.article-category-text {
+  font-size: 20rpx;
+  color: #fff;
+  font-weight: 500;
+}
+.article-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 10rpx;
+}
+.article-summary {
+  font-size: 24rpx;
+  color: #888;
+  line-height: 1.5;
+  display: block;
+  margin-bottom: 20rpx;
+}
+.article-meta {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
+.meta-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-right: 20rpx;
+}
+.meta-icon {
+  font-size: 22rpx;
+  margin-right: 4rpx;
+}
+.meta-text {
+  font-size: 22rpx;
+  color: #bbb;
+}
+.article-date {
+  font-size: 22rpx;
+  color: #bbb;
+  margin-left: auto;
+}
+
+/* ===== 会员专属 ===== */
+.premium-card {
+  background: linear-gradient(135deg, #D6EAF8, #EBF2FA);
+  border-radius: 20rpx;
+  padding: 30rpx 24rpx;
+  text-align: center;
+}
+.premium-text {
+  font-size: 26rpx;
+  color: #3A7CC4;
+  line-height: 1.5;
+}
+
+/* ===== 底部 ===== */
+.bottom-spacer {
+  height: 120rpx;
+}
+</style>

+ 12 - 5
cfc-frontend/pages/profile/profile.vue

@@ -3,7 +3,7 @@
     <!-- ===== 未登录状态 ===== -->
     <view v-if="!isLoggedIn" class="login-prompt">
       <view class="prompt-icon">👤</view>
-      <text class="prompt-title">登录艾福</text>
+      <text class="prompt-title">登录艾福</text>
       <text class="prompt-desc">登录后可查看个人资料、积分记录等</text>
       <button class="login-btn" @click="goLogin">登录 / 注册</button>
     </view>
@@ -59,7 +59,7 @@
         <text>👨‍🏫 邀请规划师</text>
         <text class="arrow">›</text>
       </button>
-      <!-- 会员中心(福俱乐部) -->
+      <!-- 会员中心(浠艾福俱乐部) -->
       <view class="menu-item" @click="goToMembership">
         <text>👑 会员中心</text>
         <text class="arrow">›</text>
@@ -69,6 +69,10 @@
         <text>🏪 服务商中心</text>
         <text class="arrow">›</text>
       </view>
+      <view class="menu-item" @click="goToPromotion">
+        <text>📢 推广中心</text>
+        <text class="arrow">›</text>
+      </view>
       <view class="menu-item" @click="logout">
         <text>🚪 退出登录</text>
         <text class="arrow">›</text>
@@ -338,6 +342,9 @@ export default {
     goToVendorCenter() {
       uni.navigateTo({ url: '/pages/vendor/center' })
     },
+    goToPromotion() {
+      uni.navigateTo({ url: '/pages/promotion/index' })
+    },
     async checkVendorStatus() {
       try {
         const res = await vendorStatus()
@@ -382,7 +389,7 @@ export default {
   padding: 30rpx;
 }
 .user-card {
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background: linear-gradient(135deg, #5B9BD5 0%, #3A7CC4 100%);
   border-radius: 20rpx;
   padding: 40rpx;
   display: flex;
@@ -406,7 +413,7 @@ export default {
 }
 .system-points {
   font-size: 22rpx;
-  color: #667eea;
+  color: #D6EAF8;
   margin-top: 8rpx;
   font-weight: 500;
 }
@@ -524,7 +531,7 @@ export default {
   width: 60%;
   height: 80rpx;
   line-height: 80rpx;
-  background: linear-gradient(135deg, #F97316, #FB923C);
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
   color: #fff;
   font-size: 30rpx;
   font-weight: bold;

+ 195 - 0
cfc-frontend/pages/promotion/commission.vue

@@ -0,0 +1,195 @@
+<template>
+  <view class="container">
+    <!-- 头部总览 -->
+    <view class="header-card">
+      <view class="header-row">
+        <view class="header-item">
+          <text class="header-label">累计佣金</text>
+          <text class="header-value orange">¥{{ summary.totalCommission || '0.00' }}</text>
+        </view>
+        <view class="header-item">
+          <text class="header-label">可提现</text>
+          <text class="header-value blue">¥{{ summary.availableAmount || '0.00' }}</text>
+        </view>
+      </view>
+    </view>
+
+    <!-- 佣金列表 -->
+    <view class="list-section">
+      <view class="list-item" v-for="item in list" :key="item.id">
+        <view class="item-left">
+          <text :class="['type-tag', item.commissionType === 'member' ? 'tag-orange' : 'tag-blue']">
+            {{ item.commissionType === 'member' ? '会员佣金' : '利润佣金' }}
+          </text>
+          <text class="item-desc">{{ getOrderDesc(item) }}</text>
+        </view>
+        <view class="item-right">
+          <text class="item-amount">+¥{{ item.commissionAmount || '0.00' }}</text>
+          <text class="item-time">{{ formatTime(item.createdAt) }}</text>
+        </view>
+      </view>
+      <view class="empty-state" v-if="!loading && list.length === 0">
+        <text>暂无佣金记录</text>
+      </view>
+      <view class="loading-state" v-if="loading">
+        <text>加载中...</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getCommissionList, getCommissionSummary } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      list: [],
+      summary: {},
+      page: 1,
+      hasMore: true,
+      loading: false
+    }
+  },
+  onLoad() {
+    this.loadSummary()
+    this.loadList()
+  },
+  onReachBottom() {
+    if (this.hasMore && !this.loading) {
+      this.page++
+      this.loadList()
+    }
+  },
+  methods: {
+    async loadSummary() {
+      try {
+        const res = await getCommissionSummary()
+        if (res.data) {
+          this.summary = res.data
+        }
+      } catch (e) {
+        console.error('获取佣金总览失败', e)
+      }
+    },
+    async loadList() {
+      this.loading = true
+      try {
+        const res = await getCommissionList(this.page)
+        if (res.data && res.data.records) {
+          this.list = this.page === 1 ? res.data.records : [...this.list, ...res.data.records]
+          this.hasMore = res.data.records.length >= 20
+        } else {
+          this.hasMore = false
+        }
+      } catch (e) {
+        console.error('获取佣金明细失败', e)
+      }
+      this.loading = false
+    },
+    getOrderDesc(item) {
+      const typeMap = {
+        membership: '会员',
+        product: '商品',
+        assessment: '测评',
+        package: '套餐'
+      }
+      return (typeMap[item.orderType] || item.orderType) + '订单 #' + item.orderId
+    },
+    formatTime(time) {
+      if (!time) return ''
+      const t = new Date(time)
+      return t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  min-height: 100vh;
+  background: #f5f7fa;
+}
+.header-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
+}
+.header-row {
+  display: flex;
+}
+.header-item {
+  flex: 1;
+  text-align: center;
+}
+.header-label {
+  font-size: 26rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.header-value {
+  font-size: 40rpx;
+  font-weight: bold;
+}
+.header-value.orange { color: #F97316; }
+.header-value.blue { color: #3A7CC4; }
+.list-section {
+  background: #fff;
+  border-radius: 20rpx;
+  overflow: hidden;
+}
+.list-item {
+  display: flex;
+  justify-content: space-between;
+  padding: 30rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.item-left {
+  flex: 1;
+}
+.type-tag {
+  font-size: 22rpx;
+  padding: 4rpx 12rpx;
+  border-radius: 6rpx;
+  display: inline-block;
+  margin-bottom: 8rpx;
+}
+.tag-orange {
+  background: #FEF3E2;
+  color: #F97316;
+}
+.tag-blue {
+  background: #E8F0FE;
+  color: #3A7CC4;
+}
+.item-desc {
+  font-size: 26rpx;
+  color: #666;
+  display: block;
+}
+.item-right {
+  text-align: right;
+}
+.item-amount {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #F97316;
+  display: block;
+}
+.item-time {
+  font-size: 22rpx;
+  color: #999;
+  display: block;
+  margin-top: 6rpx;
+}
+.empty-state, .loading-state {
+  text-align: center;
+  padding: 60rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+</style>

+ 225 - 0
cfc-frontend/pages/promotion/index.vue

@@ -0,0 +1,225 @@
+<template>
+  <view class="container">
+    <!-- 邀请码区域 -->
+    <view class="invite-card">
+      <view class="code-section">
+        <text class="code-label">我的邀请码</text>
+        <view class="code-value" @click="copyCode">
+          <text class="code-text">{{ referralCode || '加载中...' }}</text>
+          <text class="copy-icon">📋</text>
+        </view>
+      </view>
+      <button class="share-btn" @click="shareCard">📤 分享邀请卡</button>
+    </view>
+
+    <!-- 收益概览 -->
+    <view class="earnings-card">
+      <view class="earning-item">
+        <text class="earning-label">累计收益</text>
+        <text class="earning-value orange">¥{{ totalEarnings || '0.00' }}</text>
+      </view>
+      <view class="earning-divider"></view>
+      <view class="earning-item">
+        <text class="earning-label">可提现</text>
+        <text class="earning-value blue">¥{{ availableAmount || '0.00' }}</text>
+      </view>
+    </view>
+
+    <!-- 功能入口 -->
+    <view class="menu-grid">
+      <view class="menu-item" @click="goCommission">
+        <text class="menu-icon">📋</text>
+        <text class="menu-text">佣金明细</text>
+      </view>
+      <view class="menu-item" @click="goWithdraw">
+        <text class="menu-icon">💰</text>
+        <text class="menu-text">立即提现</text>
+      </view>
+      <view class="menu-item" @click="goInvite">
+        <text class="menu-icon">👥</text>
+        <text class="menu-text">邀请记录</text>
+      </view>
+      <view class="menu-item" @click="showTutorial">
+        <text class="menu-icon">📖</text>
+        <text class="menu-text">推广教程</text>
+      </view>
+    </view>
+
+    <bottom-nav v-if="!isLoggedIn" current="promotion" />
+  </view>
+</template>
+
+<script>
+import { getReferralCode, getReferralSummary } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      referralCode: '',
+      totalEarnings: '0.00',
+      availableAmount: '0.00'
+    }
+  },
+  computed: {
+    isLoggedIn() {
+      return !!uni.getStorageSync('token')
+    }
+  },
+  onLoad() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      try {
+        const codeRes = await getReferralCode()
+        if (codeRes.data) {
+          this.referralCode = codeRes.data.referralCode || codeRes.data.code || ''
+        }
+      } catch (e) {
+        console.error('获取邀请码失败', e)
+      }
+      try {
+        const summaryRes = await getReferralSummary()
+        if (summaryRes.data) {
+          this.totalEarnings = summaryRes.data.totalEarnings || '0.00'
+        }
+      } catch (e) {
+        console.error('获取邀请统计失败', e)
+      }
+      try {
+        const { getCommissionSummary } = require('../../utils/api.js')
+        const comRes = await getCommissionSummary()
+        if (comRes.data) {
+          this.availableAmount = comRes.data.availableAmount || '0.00'
+        }
+      } catch (e) {
+        console.error('获取佣金总览失败', e)
+      }
+    },
+    copyCode() {
+      if (!this.referralCode) return
+      uni.setClipboardData({
+        data: this.referralCode,
+        success: () => {
+          uni.showToast({ title: '邀请码已复制', icon: 'success' })
+        }
+      })
+    },
+    shareCard() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
+    goCommission() {
+      uni.navigateTo({ url: '/pages/promotion/commission' })
+    },
+    goWithdraw() {
+      uni.navigateTo({ url: '/pages/promotion/withdraw' })
+    },
+    goInvite() {
+      uni.navigateTo({ url: '/pages/promotion/invite' })
+    },
+    showTutorial() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  min-height: 100vh;
+  background: #f5f7fa;
+}
+.invite-card {
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  border-radius: 20rpx;
+  padding: 40rpx;
+  color: #fff;
+  margin-bottom: 30rpx;
+}
+.code-section {
+  margin-bottom: 30rpx;
+}
+.code-label {
+  font-size: 26rpx;
+  opacity: 0.9;
+}
+.code-value {
+  display: flex;
+  align-items: center;
+  margin-top: 16rpx;
+}
+.code-text {
+  font-size: 40rpx;
+  font-weight: bold;
+  letter-spacing: 4rpx;
+}
+.copy-icon {
+  font-size: 32rpx;
+  margin-left: 16rpx;
+}
+.share-btn {
+  background: rgba(255,255,255,0.2);
+  color: #fff;
+  font-size: 28rpx;
+  border-radius: 40rpx;
+  height: 72rpx;
+  line-height: 72rpx;
+  border: 1rpx solid rgba(255,255,255,0.3);
+}
+.share-btn::after { border: none; }
+.earnings-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  display: flex;
+  align-items: center;
+  margin-bottom: 30rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
+}
+.earning-item {
+  flex: 1;
+  text-align: center;
+}
+.earning-label {
+  font-size: 26rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.earning-value {
+  font-size: 44rpx;
+  font-weight: bold;
+}
+.earning-value.orange { color: #F97316; }
+.earning-value.blue { color: #3A7CC4; }
+.earning-divider {
+  width: 1rpx;
+  height: 80rpx;
+  background: #eee;
+}
+.menu-grid {
+  display: flex;
+  flex-wrap: wrap;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 20rpx 0;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05);
+}
+.menu-item {
+  width: 50%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 30rpx 0;
+  box-sizing: border-box;
+}
+.menu-icon {
+  font-size: 48rpx;
+  margin-bottom: 12rpx;
+}
+.menu-text {
+  font-size: 28rpx;
+  color: #333;
+}
+</style>

+ 250 - 0
cfc-frontend/pages/promotion/invite.vue

@@ -0,0 +1,250 @@
+<template>
+  <view class="container">
+    <!-- 邀请码展示 -->
+    <view class="code-card">
+      <text class="code-title">我的邀请码</text>
+      <text class="code-value">{{ referralCode || '加载中...' }}</text>
+      <view class="code-actions">
+        <button class="action-btn" @click="copyCode">复制邀请码</button>
+        <button class="action-btn share" @click="shareInvite">分享给好友</button>
+      </view>
+    </view>
+
+    <!-- 绑定推荐人 -->
+    <view class="bind-card">
+      <text class="bind-title">我有推荐人</text>
+      <view class="bind-row">
+        <input v-model="bindCode" type="text" placeholder="输入推荐人的邀请码" class="bind-input" />
+        <button class="bind-btn" @click="bindReferral">绑定</button>
+      </view>
+    </view>
+
+    <!-- 已邀请列表 -->
+    <view class="list-section">
+      <text class="section-title">已邀请好友 ({{ total || 0 }})</text>
+      <view class="empty-state" v-if="!loading && list.length === 0">
+        <text>还没有邀请好友,快去分享吧</text>
+      </view>
+      <view class="invite-item" v-for="item in list" :key="item.id">
+        <text class="invite-avatar">&#x1F464;</text>
+        <view class="invite-info">
+          <text class="invite-name">{{ item.nickname || '好友' }}</text>
+          <text class="invite-time">{{ formatTime(item.createdAt) }}</text>
+        </view>
+      </view>
+      <view class="loading-state" v-if="loading">
+        <text>加载中...</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getReferralCode, bindReferral, getReferralList } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      referralCode: '',
+      bindCode: '',
+      list: [],
+      total: 0,
+      page: 1,
+      loading: false,
+      hasMore: true
+    }
+  },
+  onLoad() {
+    this.loadCode()
+    this.loadList()
+  },
+  onReachBottom() {
+    if (this.hasMore && !this.loading) {
+      this.page++
+      this.loadList()
+    }
+  },
+  methods: {
+    async loadCode() {
+      try {
+        const res = await getReferralCode()
+        if (res.data) {
+          this.referralCode = res.data.referralCode || res.data.code || ''
+        }
+      } catch (e) {
+        console.error('获取邀请码失败', e)
+      }
+    },
+    async loadList() {
+      this.loading = true
+      try {
+        const res = await getReferralList(this.page)
+        if (res.data) {
+          if (res.data.records) {
+            this.list = this.page === 1 ? res.data.records : [...this.list, ...res.data.records]
+          }
+          this.total = res.data.total || 0
+          if (res.data.records) {
+            this.hasMore = res.data.records.length >= 10
+          } else {
+            this.hasMore = false
+          }
+        }
+      } catch (e) {
+        console.error('获取邀请列表失败', e)
+      }
+      this.loading = false
+    },
+    copyCode() {
+      if (!this.referralCode) return
+      uni.setClipboardData({
+        data: this.referralCode,
+        success: () => {
+          uni.showToast({ title: '已复制邀请码', icon: 'success' })
+        }
+      })
+    },
+    shareInvite() {
+      uni.showToast({ title: '即将上线', icon: 'none' })
+    },
+    async bindReferral() {
+      if (!this.bindCode) {
+        uni.showToast({ title: '请输入邀请码', icon: 'none' })
+        return
+      }
+      if (this.bindCode === this.referralCode) {
+        uni.showToast({ title: '不能绑定自己的邀请码', icon: 'none' })
+        return
+      }
+      try {
+        await bindReferral(this.bindCode)
+        uni.showToast({ title: '绑定成功', icon: 'success' })
+        this.bindCode = ''
+      } catch (e) {
+        uni.showToast({ title: e.message || '绑定失败', icon: 'none' })
+      }
+    },
+    formatTime(time) {
+      if (!time) return ''
+      const t = new Date(time)
+      return t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  min-height: 100vh;
+  background: #f5f7fa;
+}
+.code-card {
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  border-radius: 20rpx;
+  padding: 40rpx;
+  text-align: center;
+  color: #fff;
+  margin-bottom: 30rpx;
+}
+.code-title {
+  font-size: 26rpx;
+  opacity: 0.9;
+}
+.code-value {
+  font-size: 48rpx;
+  font-weight: bold;
+  letter-spacing: 6rpx;
+  margin: 20rpx 0 30rpx;
+  display: block;
+}
+.code-actions {
+  display: flex;
+  gap: 20rpx;
+}
+.action-btn {
+  flex: 1;
+  background: rgba(255,255,255,0.2);
+  color: #fff;
+  font-size: 26rpx;
+  border-radius: 40rpx;
+  height: 64rpx;
+  line-height: 64rpx;
+  border: 1rpx solid rgba(255,255,255,0.3);
+}
+.action-btn::after { border: none; }
+.bind-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+  margin-bottom: 30rpx;
+}
+.bind-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 16rpx;
+}
+.bind-row {
+  display: flex;
+  gap: 20rpx;
+}
+.bind-input {
+  flex: 1;
+  border: 1rpx solid #ddd;
+  border-radius: 10rpx;
+  padding: 20rpx;
+  font-size: 28rpx;
+}
+.bind-btn {
+  background: #3A7CC4;
+  color: #fff;
+  font-size: 26rpx;
+  border-radius: 10rpx;
+  height: 64rpx;
+  line-height: 64rpx;
+  padding: 0 30rpx;
+}
+.bind-btn::after { border: none; }
+.list-section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+}
+.section-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+.invite-item {
+  display: flex;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.invite-item:last-child { border-bottom: none; }
+.invite-avatar {
+  font-size: 48rpx;
+  margin-right: 20rpx;
+}
+.invite-info { flex: 1; }
+.invite-name {
+  font-size: 28rpx;
+  color: #333;
+  display: block;
+}
+.invite-time {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 4rpx;
+}
+.empty-state, .loading-state {
+  text-align: center;
+  padding: 60rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+</style>

+ 244 - 0
cfc-frontend/pages/promotion/withdraw.vue

@@ -0,0 +1,244 @@
+<template>
+  <view class="container">
+    <!-- 可提现余额 -->
+    <view class="balance-card">
+      <text class="balance-label">可提现金额</text>
+      <text class="balance-value">¥{{ availableAmount || '0.00' }}</text>
+    </view>
+
+    <!-- 提现表单 -->
+    <view class="form-card">
+      <view class="form-item">
+        <text class="form-label">提现金额</text>
+        <view class="input-row">
+          <input v-model="amount" type="digit" placeholder="输入提现金额" class="form-input" />
+          <text class="unit">元</text>
+          <text class="btn-all" @click="amount = availableAmount">全部提现</text>
+        </view>
+      </view>
+      <view class="form-item">
+        <text class="form-label">支付宝账号</text>
+        <input v-model="accountInfo" type="text" placeholder="请输入支付宝账号" class="form-input" />
+      </view>
+      <view class="form-tip">最低提现金额:1元</view>
+      <button class="submit-btn" @click="submitWithdraw">提交申请</button>
+    </view>
+
+    <!-- 提现历史 -->
+    <view class="history-section" v-if="history.length > 0">
+      <text class="section-title">提现记录</text>
+      <view class="history-item" v-for="item in history" :key="item.id">
+        <view class="history-left">
+          <text class="history-amount">¥{{ item.amount }}</text>
+          <text class="history-time">{{ formatTime(item.createdAt) }}</text>
+        </view>
+        <text :class="['history-status', statusClass(item.status)]">{{ statusText(item.status) }}</text>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getCommissionSummary, applyWithdrawal, getWithdrawalHistory } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      availableAmount: '0.00',
+      amount: '',
+      accountInfo: '',
+      history: [],
+      historyPage: 1
+    }
+  },
+  onLoad() {
+    this.loadBalance()
+    this.loadHistory()
+  },
+  methods: {
+    async loadBalance() {
+      try {
+        const res = await getCommissionSummary()
+        if (res.data) {
+          this.availableAmount = res.data.availableAmount || '0.00'
+        }
+      } catch (e) {
+        console.error('获取佣金总览失败', e)
+      }
+    },
+    async loadHistory() {
+      try {
+        const res = await getWithdrawalHistory(this.historyPage)
+        if (res.data && res.data.records) {
+          this.history = res.data.records
+        }
+      } catch (e) {
+        console.error('获取提现记录失败', e)
+      }
+    },
+    submitWithdraw() {
+      const amountNum = parseFloat(this.amount)
+      if (!this.amount || isNaN(amountNum) || amountNum < 1) {
+        uni.showToast({ title: '提现金额不能少于1元', icon: 'none' })
+        return
+      }
+      if (amountNum > parseFloat(this.availableAmount)) {
+        uni.showToast({ title: '提现金额超过可提现余额', icon: 'none' })
+        return
+      }
+      if (!this.accountInfo) {
+        uni.showToast({ title: '请输入支付宝账号', icon: 'none' })
+        return
+      }
+      uni.showModal({
+        title: '确认提现',
+        content: '确认提现 ¥' + amountNum.toFixed(2) + ' 到 ' + this.accountInfo + ' ?',
+        success: async (res) => {
+          if (res.confirm) {
+            try {
+              await applyWithdrawal(amountNum, this.accountInfo)
+              uni.showToast({ title: '提现申请已提交', icon: 'success' })
+              this.amount = ''
+              this.accountInfo = ''
+              this.loadBalance()
+              this.loadHistory()
+            } catch (e) {
+              uni.showToast({ title: e.message || '提现失败', icon: 'none' })
+            }
+          }
+        }
+      })
+    },
+    statusText(status) {
+      const map = { pending: '待审核', approved: '已通过', rejected: '已拒绝' }
+      return map[status] || status
+    },
+    statusClass(status) {
+      if (status === 'approved') return 'text-success'
+      if (status === 'rejected') return 'text-danger'
+      return 'text-warning'
+    },
+    formatTime(time) {
+      if (!time) return ''
+      const t = new Date(time)
+      return t.getFullYear() + '-' + String(t.getMonth()+1).padStart(2,'0') + '-' + String(t.getDate()).padStart(2,'0')
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  padding: 30rpx;
+  min-height: 100vh;
+  background: #f5f7fa;
+}
+.balance-card {
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  border-radius: 20rpx;
+  padding: 40rpx;
+  text-align: center;
+  color: #fff;
+  margin-bottom: 30rpx;
+}
+.balance-label {
+  font-size: 26rpx;
+  opacity: 0.9;
+}
+.balance-value {
+  font-size: 56rpx;
+  font-weight: bold;
+  margin-top: 12rpx;
+}
+.form-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  margin-bottom: 30rpx;
+}
+.form-item {
+  margin-bottom: 30rpx;
+}
+.form-label {
+  font-size: 28rpx;
+  color: #333;
+  display: block;
+  margin-bottom: 16rpx;
+}
+.input-row {
+  display: flex;
+  align-items: center;
+  border: 1rpx solid #ddd;
+  border-radius: 10rpx;
+  padding: 20rpx;
+}
+.form-input {
+  flex: 1;
+  font-size: 28rpx;
+}
+.unit {
+  font-size: 28rpx;
+  color: #999;
+  margin: 0 16rpx;
+}
+.btn-all {
+  font-size: 24rpx;
+  color: #3A7CC4;
+  padding: 6rpx 12rpx;
+  border: 1rpx solid #3A7CC4;
+  border-radius: 6rpx;
+}
+.form-tip {
+  font-size: 24rpx;
+  color: #999;
+  margin-bottom: 30rpx;
+}
+.submit-btn {
+  background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
+  color: #fff;
+  font-size: 30rpx;
+  border-radius: 40rpx;
+  height: 80rpx;
+  line-height: 80rpx;
+}
+.submit-btn::after { border: none; }
+.history-section {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 30rpx;
+}
+.section-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 20rpx;
+}
+.history-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 20rpx 0;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.history-item:last-child { border-bottom: none; }
+.history-left {
+  flex: 1;
+}
+.history-amount {
+  font-size: 28rpx;
+  font-weight: bold;
+  display: block;
+}
+.history-time {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 4rpx;
+}
+.history-status {
+  font-size: 24rpx;
+}
+.text-success { color: #52c41a; }
+.text-warning { color: #faad14; }
+.text-danger { color: #ff4d4f; }
+</style>

+ 412 - 0
cfc-frontend/pages/rewards/badge.vue

@@ -0,0 +1,412 @@
+<template>
+  <view class="badge-container">
+    <!-- 统计概览 -->
+    <view class="stats-card">
+      <view class="stat-item">
+        <text class="stat-num">{{ stats.total || 0 }}</text>
+        <text class="stat-label">获得勋章</text>
+      </view>
+      <view class="stat-item">
+        <text class="stat-num">{{ stats.active || 0 }}</text>
+        <text class="stat-label">有效</text>
+      </view>
+      <view class="stat-item">
+        <text class="stat-num">{{ totalBadges || 0 }}</text>
+        <text class="stat-label">总勋章</text>
+      </view>
+    </view>
+
+    <!-- 分类筛选 -->
+    <view class="filter-bar">
+      <view
+        v-for="cat in categories"
+        :key="cat.code"
+        :class="['filter-chip', currentCategory === cat.code ? 'active' : '']"
+        @click="onCategoryChange(cat.code)"
+      >
+        {{ cat.label }}
+      </view>
+    </view>
+
+    <!-- 勋章网格 -->
+    <view class="badge-grid">
+      <view
+        v-for="item in badgeList"
+        :key="item._key"
+        class="badge-item"
+        @click="showBadgeDetail(item)"
+      >
+        <view :class="['badge-icon', item._earned ? 'earned' : 'locked']">
+          <image v-if="item._icon" :src="item._icon" mode="aspectFit" />
+          <text v-else class="icon-placeholder">{{ getBadgeSymbol(item._badge) }}</text>
+        </view>
+        <text class="badge-name">{{ item._name }}</text>
+        <text v-if="item._earned" class="badge-status">已获得</text>
+      </view>
+    </view>
+
+    <view v-if="badgeList.length === 0" class="empty-state">
+      <text class="empty-text">暂无可用的勋章</text>
+    </view>
+
+    <!-- 详情弹窗 -->
+    <view class="modal-mask" v-if="showDetail" @click="showDetail = false">
+      <view class="modal" @click.stop>
+        <view class="modal-badge-icon">
+          <image v-if="detailIcon" :src="detailIcon" mode="aspectFit" />
+          <text v-else class="big-icon">{{ getBadgeSymbol(detailBadge) }}</text>
+        </view>
+        <text class="modal-badge-name">{{ detailBadge && detailBadge.name }}</text>
+        <text class="modal-badge-desc">{{ (detailBadge && detailBadge.description) || '暂无描述' }}</text>
+        <view class="modal-meta">
+          <text class="meta-tag">{{ detailLevel }}</text>
+          <text class="meta-tag">{{ detailRarity }}</text>
+          <text class="meta-tag">{{ detailCategory }}</text>
+        </view>
+        <view class="modal-earned" v-if="detailEarned">
+          <text>获得时间:{{ detailEarnedDate }}</text>
+        </view>
+        <view class="modal-btns">
+          <button v-if="detailEarned" :class="['btn-fav', detailIsFav ? 'active' : '']" @click="onToggleFavorite">
+            {{ detailIsFav ? '已收藏' : '收藏' }}
+          </button>
+          <button class="btn-close" @click="showDetail = false">关闭</button>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import { getActiveBadges, getChildBadges, getChildBadgeStats, toggleBadgeFavorite } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      badges: [],
+      childBadges: [],
+      badgeList: [],
+      earnedMap: {},
+      stats: {},
+      totalBadges: 0,
+      currentCategory: '',
+      categories: [
+        { code: '', label: '全部' },
+        { code: '学习', label: '学习' },
+        { code: '运动', label: '运动' },
+        { code: '家务', label: '家务' },
+        { code: '阅读', label: '阅读' },
+        { code: '打卡', label: '打卡' },
+        { code: '综合', label: '综合' }
+      ],
+      levelMap: { 'bronze': '铜牌', 'silver': '银牌', 'gold': '金牌', 'diamond': '钻石' },
+      rarityMap: { 'common': '普通', 'rare': '稀有', 'epic': '史诗', 'legendary': '传说' },
+      categoryMap: { '学习': '📚', '运动': '⚽', '家务': '🧹', '阅读': '📖', '打卡': '✅', '综合': '⭐' },
+      showDetail: false,
+      detailBadge: null,
+      detailEarned: false,
+      detailIsFav: false,
+      detailEarnedDate: ''
+    }
+  },
+  computed: {
+    detailIcon() {
+      return this.detailBadge ? this.detailBadge.icon : ''
+    },
+    detailLevel() {
+      if (!this.detailBadge) return ''
+      return this.levelMap[this.detailBadge.level] || this.detailBadge.level || ''
+    },
+    detailRarity() {
+      if (!this.detailBadge) return ''
+      return this.rarityMap[this.detailBadge.rarity] || this.detailBadge.rarity || ''
+    },
+    detailCategory() {
+      if (!this.detailBadge) return ''
+      return this.categoryMap[this.detailBadge.category] || this.detailBadge.category || ''
+    }
+  },
+  onShow() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      try {
+        const [badgesRes, childBadgesRes, statsRes] = await Promise.all([
+          getActiveBadges(),
+          getChildBadges(this.getChildId()),
+          getChildBadgeStats(this.getChildId())
+        ])
+        this.badges = badgesRes.data || []
+        this.childBadges = childBadgesRes.data || []
+        this.stats = statsRes.data || {}
+        this.totalBadges = this.badges.length
+        this.buildEarnedMap()
+        this.applyFilter()
+      } catch (e) {
+        console.error('加载勋章数据失败', e)
+      }
+    },
+    getChildId() {
+      return uni.getStorageSync('currentChildId') || ''
+    },
+    buildEarnedMap() {
+      const map = {}
+      for (const item of this.childBadges) {
+        const badge = item.badge || {}
+        const badgeId = badge.id || item.badgeId
+        if (badgeId) map[badgeId] = true
+      }
+      this.earnedMap = map
+    },
+    onCategoryChange(code) {
+      if (this.currentCategory === code) return
+      this.currentCategory = code
+      this.applyFilter()
+    },
+    applyFilter() {
+      let filtered = this.badges.slice()
+      if (this.currentCategory) {
+        filtered = filtered.filter(function(b) { return b.category === this.currentCategory }.bind(this))
+      }
+      this.badgeList = filtered.map(function(b) {
+        const badgeId = b.id
+        const earned = this.earnedMap[badgeId] || false
+        return {
+          _key: badgeId,
+          _badge: b,
+          _name: b.name || '',
+          _icon: b.icon || '',
+          _earned: earned,
+          _badgeId: badgeId
+        }
+      }.bind(this))
+    },
+    showBadgeDetail(item) {
+      const badge = item._badge || item
+      this.detailBadge = badge
+      const found = this.childBadges.find(function(cb) {
+        const cbBadge = cb.badge || {}
+        return (cbBadge.id || cb.badgeId) === badge.id
+      })
+      this.detailEarned = !!found
+      this.detailIsFav = found ? (found.isFavorite || (found.childBadge && found.childBadge.isFavorite)) : false
+      this.detailEarnedDate = found ? this.formatDate(found.earnedAt || (found.childBadge && found.childBadge.earnedAt)) : ''
+      this.showDetail = true
+    },
+    async onToggleFavorite() {
+      const childId = this.getChildId()
+      const badgeId = this.detailBadge.id
+      try {
+        await toggleBadgeFavorite(childId, badgeId)
+        uni.showToast({ title: this.detailIsFav ? '已取消收藏' : '已收藏', icon: 'success' })
+        await this.loadData()
+        this.showDetail = false
+      } catch (e) {
+        console.error('操作失败', e)
+      }
+    },
+    getBadgeSymbol(badge) {
+      if (!badge) return '🏅'
+      const symbols = {
+        '学习': '📚', '运动': '⚽', '家务': '🧹', '阅读': '📖', '打卡': '✅', '综合': '⭐'
+      }
+      return symbols[badge.category] || '🏅'
+    },
+    formatDate(dateStr) {
+      if (!dateStr) return ''
+      var d = new Date(dateStr)
+      return d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.badge-container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 40rpx;
+}
+.stats-card {
+  display: flex;
+  background: linear-gradient(135deg, #F97316, #fb923c);
+  padding: 30rpx;
+  margin: 20rpx;
+  border-radius: 16rpx;
+  justify-content: space-around;
+}
+.stat-item {
+  text-align: center;
+}
+.stat-num {
+  display: block;
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #fff;
+}
+.stat-label {
+  display: block;
+  font-size: 22rpx;
+  color: rgba(255,255,255,0.85);
+  margin-top: 6rpx;
+}
+.filter-bar {
+  display: flex;
+  padding: 16rpx 20rpx;
+  gap: 12rpx;
+  flex-wrap: wrap;
+}
+.filter-chip {
+  padding: 8rpx 24rpx;
+  border-radius: 30rpx;
+  font-size: 24rpx;
+  color: #666;
+  background: #fff;
+}
+.filter-chip.active {
+  color: #fff;
+  background: #F97316;
+}
+.badge-grid {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 10rpx 20rpx;
+  gap: 16rpx;
+}
+.badge-item {
+  width: calc(25% - 12rpx);
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 20rpx 10rpx;
+  text-align: center;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.badge-icon {
+  width: 80rpx;
+  height: 80rpx;
+  margin: 0 auto 10rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 48rpx;
+}
+.badge-icon.earned {
+  background: linear-gradient(135deg, #fef3c7, #fde68a);
+}
+.badge-icon.locked {
+  background: #f0f0f0;
+  filter: grayscale(1);
+  opacity: 0.5;
+}
+.badge-icon image {
+  width: 60rpx;
+  height: 60rpx;
+}
+.badge-name {
+  display: block;
+  font-size: 22rpx;
+  color: #333;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.badge-status {
+  display: block;
+  font-size: 18rpx;
+  color: #F97316;
+  margin-top: 4rpx;
+}
+.empty-state {
+  text-align: center;
+  padding: 120rpx 30rpx;
+}
+.empty-text {
+  font-size: 28rpx;
+  color: #999;
+}
+.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;
+  z-index: 100;
+}
+.modal {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 40rpx;
+  width: 75%;
+  text-align: center;
+}
+.modal-badge-icon {
+  width: 120rpx;
+  height: 120rpx;
+  margin: 0 auto 20rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #fef3c7, #fde68a);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.big-icon {
+  font-size: 72rpx;
+}
+.modal-badge-name {
+  display: block;
+  font-size: 34rpx;
+  font-weight: bold;
+  color: #333;
+  margin-bottom: 12rpx;
+}
+.modal-badge-desc {
+  display: block;
+  font-size: 26rpx;
+  color: #666;
+  margin-bottom: 20rpx;
+}
+.modal-meta {
+  display: flex;
+  justify-content: center;
+  gap: 12rpx;
+  margin-bottom: 20rpx;
+}
+.meta-tag {
+  padding: 6rpx 20rpx;
+  border-radius: 20rpx;
+  font-size: 22rpx;
+  background: #f5f5f5;
+  color: #666;
+}
+.modal-earned {
+  font-size: 24rpx;
+  color: #999;
+  margin-bottom: 20rpx;
+}
+.modal-btns {
+  display: flex;
+  gap: 20rpx;
+}
+.modal-btns button {
+  flex: 1;
+  font-size: 28rpx;
+  padding: 16rpx;
+  border-radius: 40rpx;
+  border: none;
+}
+.btn-fav {
+  background: #F97316;
+  color: #fff;
+}
+.btn-fav.active {
+  background: #ddd;
+  color: #999;
+}
+.btn-close {
+  background: #f5f5f5;
+  color: #666;
+}
+</style>

+ 172 - 94
cfc-frontend/pages/shop/index.vue

@@ -1,32 +1,44 @@
 <template>
   <view class="container">
-    <view class="filter-section">
-      <scroll-view scroll-x class="type-tabs">
-        <view class="tab-row">
-          <view
-            v-for="item in typeList"
-            :key="item.value"
-            :class="['tab-item', currentType === item.value ? 'active' : '']"
-            @click="onTypeChange(item.value)"
-          >
-            {{ item.label }}
-          </view>
-        </view>
-      </scroll-view>
-      <scroll-view scroll-x class="domain-tabs">
-        <view class="tab-row">
-          <view
-            v-for="item in domainList"
-            :key="item.value"
-            :class="['tab-item small', currentDomain === item.value ? 'active' : '']"
-            @click="onDomainChange(item.value)"
-          >
-            {{ item.label }}
-          </view>
+    <!-- Level 1 分类:2列横排网格 -->
+    <view class="cat-level1">
+      <view
+        v-for="cat in categories"
+        :key="cat.id"
+        :class="['cat1-item', currentCat1 && currentCat1.id === cat.id ? 'active' : '']"
+        @click="onCat1Select(cat)"
+      >
+        <image v-if="cat.image" class="cat1-icon" :src="cat.image" mode="aspectFit" />
+        <view v-else class="cat1-icon-placeholder">
+          <text class="cat1-icon-text">{{ cat.name.substr(0, 1) }}</text>
         </view>
-      </scroll-view>
+        <text class="cat1-name">{{ cat.name }}</text>
+      </view>
     </view>
 
+    <!-- Level 2 子分类:横向滚动chips -->
+    <scroll-view
+      v-if="currentCat1 && currentCat1.children && currentCat1.children.length"
+      scroll-x
+      enable-flex
+      show-scrollbar="false"
+      class="cat-level2-scroll"
+    >
+      <view class="cat2-chips">
+        <view
+          :class="['cat2-chip', !currentCat2 ? 'active' : '']"
+          @click="onCat2Select(null)"
+        >全部</view>
+        <view
+          v-for="child in currentCat1.children"
+          :key="child.id"
+          :class="['cat2-chip', currentCat2 && currentCat2.id === child.id ? 'active' : '']"
+          @click="onCat2Select(child)"
+        >{{ child.name }}</view>
+      </view>
+    </scroll-view>
+
+    <!-- 商品列表 -->
     <scroll-view
       scroll-y
       class="product-list"
@@ -51,7 +63,7 @@
         >
           <image
             class="cover"
-            :src="item.coverImage || '/static/default-product.png'"
+            :src="item.image || item.coverImage || '/static/default-product.png'"
             mode="aspectFill"
           />
           <view class="card-body">
@@ -60,7 +72,7 @@
               <text class="price">¥{{ item.price }}</text>
               <text v-if="item.memberPrice" class="member-price">会员¥{{ item.memberPrice }}</text>
             </view>
-            <text class="vendor-name">{{ item.vendorName || '平台官方' }}</text>
+            <text class="vendor-name">{{ item.brandName || '平台官方' }}</text>
           </view>
         </view>
       </view>
@@ -70,56 +82,64 @@
       <view v-if="noMore && products.length > 0" class="no-more">
         <text class="no-more-text">— 没有更多了 —</text>
       </view>
+      <view v-if="!isLoggedIn" class="login-hint-bar">
+        <text class="login-hint-text">🔒 登录查看全部商品和会员价</text>
+      </view>
+      <view class="bottom-spacer"></view>
     </scroll-view>
+
+    <bottom-nav v-if="!isLoggedIn" current="action" />
   </view>
 </template>
 
 <script>
-import { productList } from '@/utils/api.js'
+import { goodsCategory, productList } from '@/utils/api.js'
+import BottomNav from '../../components/bottom-nav.vue'
 
 export default {
+  components: { BottomNav },
   data() {
     return {
-      typeList: [
-        { label: '全部', value: '' },
-        { label: '活动', value: 'activity' },
-        { label: '课程', value: 'course' },
-        { label: '实物', value: 'physical' },
-        { label: '数字', value: 'digital' },
-        { label: '服务', value: 'service' }
-      ],
-      domainList: [
-        { label: '全部', value: '' },
-        { label: '行动', value: 'action' },
-        { label: '心智', value: 'mind' },
-        { label: '身体', value: 'body' },
-        { label: '智慧', value: 'wisdom' },
-        { label: '财富', value: 'wealth' }
-      ],
-      currentType: '',
-      currentDomain: '',
+      categories: [],
+      currentCat1: null,
+      currentCat2: null,
       products: [],
       page: 1,
       size: 20,
       loading: false,
       loadingMore: false,
       refreshing: false,
-      noMore: false
+      noMore: false,
+      isLoggedIn: false
     }
   },
   onLoad() {
+    this.isLoggedIn = !!uni.getStorageSync('token')
+    this.loadCategories()
     this.loadProducts()
   },
+  onShow() {
+    this.isLoggedIn = !!uni.getStorageSync('token')
+  },
   methods: {
-    onTypeChange(value) {
-      this.currentType = value
+    loadCategories() {
+      goodsCategory('0').then(res => {
+        if (res.code === 200 && res.data) {
+          this.categories = res.data || []
+        }
+      }).catch(() => {})
+    },
+    onCat1Select(cat) {
+      if (this.currentCat1 && this.currentCat1.id === cat.id) return
+      this.currentCat1 = cat
+      this.currentCat2 = null
       this.page = 1
       this.products = []
       this.noMore = false
       this.loadProducts()
     },
-    onDomainChange(value) {
-      this.currentDomain = value
+    onCat2Select(cat) {
+      this.currentCat2 = cat
       this.page = 1
       this.products = []
       this.noMore = false
@@ -128,15 +148,11 @@ export default {
     loadProducts() {
       if (this.loading) return
       this.loading = true
-      const params = {
-        page: this.page,
-        size: this.size
-      }
-      if (this.currentType) {
-        params.productType = this.currentType
-      }
-      if (this.currentDomain) {
-        params.domain = this.currentDomain
+      const params = { page: this.page, size: this.size }
+      if (this.currentCat2) {
+        params.categoryId = this.currentCat2.id
+      } else if (this.currentCat1) {
+        params.categoryId = this.currentCat1.id
       }
       productList(params).then(res => {
         this.loading = false
@@ -164,17 +180,13 @@ export default {
     },
     onLoadMore() {
       if (this.noMore || this.loadingMore) return
-      this.page = this.page + 1
+      this.page++
       this.loadingMore = true
-      const params = {
-        page: this.page,
-        size: this.size
-      }
-      if (this.currentType) {
-        params.productType = this.currentType
-      }
-      if (this.currentDomain) {
-        params.domain = this.currentDomain
+      const params = { page: this.page, size: this.size }
+      if (this.currentCat2) {
+        params.categoryId = this.currentCat2.id
+      } else if (this.currentCat1) {
+        params.categoryId = this.currentCat1.id
       }
       productList(params).then(res => {
         this.loadingMore = false
@@ -189,9 +201,12 @@ export default {
       })
     },
     goDetail(id) {
-      uni.navigateTo({
-        url: '/pages/discover/product-detail/product-detail?id=' + id
-      })
+      const token = uni.getStorageSync('token')
+      if (token) {
+        uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/shop/index') })
+      }
     }
   }
 }
@@ -204,44 +219,107 @@ export default {
   height: 100vh;
   background: #f5f5f5;
 }
-.filter-section {
+/* ==================== 分类布局 ==================== */
+/* Level 1 分类:2列横排网格 */
+.cat-level1 {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 16rpx 16rpx 8rpx;
   background: #fff;
-  padding: 20rpx 0 10rpx;
   border-bottom: 1rpx solid #eee;
-  position: sticky;
-  top: 0;
-  z-index: 10;
 }
-.type-tabs {
-  white-space: nowrap;
-  margin-bottom: 16rpx;
+.cat1-item {
+  width: 50%;
+  box-sizing: border-box;
+  padding: 8rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  text-align: center;
+  border-radius: 12rpx;
+  margin-bottom: 8rpx;
 }
-.tab-row {
+.cat1-item.active {
+  background: rgba(249, 115, 22, 0.08);
+}
+.cat1-icon {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  margin-bottom: 6rpx;
+  background: #f5f5f5;
+}
+.cat1-icon-placeholder {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 50%;
+  margin-bottom: 6rpx;
+  background: linear-gradient(135deg, #667eea, #764ba2);
   display: flex;
-  padding: 0 20rpx;
+  align-items: center;
+  justify-content: center;
+}
+.cat1-icon-text {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #fff;
+}
+.cat1-name {
+  font-size: 24rpx;
+  color: #333;
+  max-width: 140rpx;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+.cat1-item.active .cat1-name {
+  color: #F97316;
+  font-weight: bold;
+}
+
+/* Level 2 子分类:横向滚动chips */
+.cat-level2-scroll {
+  background: #fff;
+  white-space: nowrap;
+  padding: 12rpx 16rpx;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.cat2-chips {
+  display: inline-flex;
+  gap: 12rpx;
+  padding: 0 4rpx;
 }
-.tab-item {
+.cat2-chip {
   display: inline-block;
-  padding: 12rpx 28rpx;
-  font-size: 28rpx;
+  padding: 8rpx 24rpx;
+  font-size: 24rpx;
   color: #666;
-  margin-right: 16rpx;
-  border-radius: 40rpx;
   background: #f0f0f0;
+  border-radius: 30rpx;
   flex-shrink: 0;
 }
-.tab-item.small {
-  padding: 8rpx 20rpx;
-  font-size: 24rpx;
-  margin-right: 12rpx;
-}
-.tab-item.active {
+.cat2-chip.active {
   background: #F97316;
   color: #fff;
 }
+
+/* ==================== 商品列表 ==================== */
 .product-list {
   flex: 1;
-  height: calc(100vh - 200rpx);
+  height: calc(100vh - 220rpx);
+}
+.login-hint-bar {
+  text-align: center;
+  padding: 20rpx;
+  background: #fef9e7;
+  border-top: 1rpx solid #f0e6c0;
+}
+.login-hint-text {
+  font-size: 24rpx;
+  color: #b8860b;
+}
+.bottom-spacer {
+  height: 120rpx;
 }
 .grid {
   display: flex;

+ 329 - 0
cfc-frontend/pages/stats/index.vue

@@ -0,0 +1,329 @@
+<template>
+  <view class="report-container">
+    <!-- 顶部标题 -->
+    <view class="header">
+      <text class="header-title">数据统计</text>
+    </view>
+
+    <view v-if="loading" class="loading-state">
+      <text>加载中...</text>
+    </view>
+
+    <scroll-view v-else scroll-y class="scroll-area">
+      <!-- 概览卡片 -->
+      <view class="overview-grid">
+        <view class="overview-item">
+          <text class="ov-num">{{ overview.totalPoints || 0 }}</text>
+          <text class="ov-label">总积分</text>
+        </view>
+        <view class="overview-item">
+          <text class="ov-num">{{ (overview.taskStats && overview.taskStats.completed) || 0 }}/{{ (overview.taskStats && overview.taskStats.total) || 0 }}</text>
+          <text class="ov-label">任务完成</text>
+        </view>
+        <view class="overview-item">
+          <text class="ov-num">{{ (overview.gameStats && overview.gameStats.total) || 0 }}</text>
+          <text class="ov-label">游戏次数</text>
+        </view>
+        <view class="overview-item" @click="navTo('/pages/stats/completion-rate')">
+          <text class="ov-num">{{ (overview.taskStats && overview.taskStats.completionRate) || 0 }}%</text>
+          <text class="ov-label">完成率 ›</text>
+        </view>
+      </view>
+
+      <!-- 积分趋势 -->
+      <view class="section">
+        <view class="section-header">
+          <text class="section-title">积分趋势(近7天)</text>
+        </view>
+        <view class="trend-chart">
+          <view class="bar-item" v-for="(item, idx) in overview.pointsTrend || []" :key="idx">
+            <view class="bar-wrapper">
+              <view class="bar" :style="{ height: barHeight(item.points) + 'rpx' }"></view>
+            </view>
+            <text class="bar-label">{{ formatDay(item.date) }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 任务统计 -->
+      <view class="section">
+        <view class="section-header">
+          <text class="section-title">任务统计</text>
+          <text class="section-more" @click="navTo('/pages/stats/completion-rate')">查看详情 ›</text>
+        </view>
+        <view class="stat-cards">
+          <view class="stat-row">
+            <view class="stat-chip pending">
+              <text class="chip-num">{{ (overview.taskStats && overview.taskStats.pending) || 0 }}</text>
+              <text class="chip-label">待完成</text>
+            </view>
+            <view class="stat-chip review">
+              <text class="chip-num">{{ (overview.taskStats && overview.taskStats.inReview) || 0 }}</text>
+              <text class="chip-label">待审核</text>
+            </view>
+            <view class="stat-chip done">
+              <text class="chip-num">{{ (overview.taskStats && overview.taskStats.completed) || 0 }}</text>
+              <text class="chip-label">已完成</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 游戏统计 -->
+      <view class="section">
+        <view class="section-header">
+          <text class="section-title">专注训练统计</text>
+          <text class="section-more" @click="navTo('/pages/games/stats')">查看详情 ›</text>
+        </view>
+        <view class="stat-cards">
+          <view class="stat-row">
+            <view class="stat-chip game">
+              <text class="chip-num">{{ (overview.gameStats && overview.gameStats.total) || 0 }}</text>
+              <text class="chip-label">总次数</text>
+            </view>
+            <view class="stat-chip game">
+              <text class="chip-num">{{ (overview.gameStats && overview.gameStats.bestScore) || 0 }}</text>
+              <text class="chip-label">最高分</text>
+            </view>
+            <view class="stat-chip game">
+              <text class="chip-num">{{ (overview.gameStats && overview.gameStats.totalPoints) || 0 }}</text>
+              <text class="chip-label">获得积分</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 快捷入口 -->
+      <view class="section">
+        <view class="section-header">
+          <text class="section-title">更多报表</text>
+        </view>
+        <view class="quick-links">
+          <view class="quick-link" @click="navTo('/pages/games/records')">
+            <text class="ql-icon">📋</text>
+            <text class="ql-text">训练记录</text>
+          </view>
+          <view class="quick-link" @click="navTo('/pages/games/stats')">
+            <text class="ql-icon">📊</text>
+            <text class="ql-text">成绩统计</text>
+          </view>
+          <view class="quick-link" @click="navTo('/pages/rewards/badge')">
+            <text class="ql-icon">🏅</text>
+            <text class="ql-text">勋章墙</text>
+          </view>
+          <view class="quick-link" @click="navTo('/pages/stats/completion-rate')">
+            <text class="ql-icon">✅</text>
+            <text class="ql-text">完成率</text>
+          </view>
+        </view>
+      </view>
+    </scroll-view>
+  </view>
+</template>
+
+<script>
+import { getChildOverview } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      loading: true,
+      overview: {}
+    }
+  },
+  onShow() {
+    this.loadData()
+  },
+  methods: {
+    async loadData() {
+      this.loading = true
+      try {
+        const childId = uni.getStorageSync('currentChildId')
+        if (childId) {
+          const res = await getChildOverview(childId)
+          this.overview = res.data || {}
+        }
+      } catch (e) {
+        console.error('加载报表数据失败', e)
+      } finally {
+        this.loading = false
+      }
+    },
+    barHeight(points) {
+      const max = Math.max(...(this.overview.pointsTrend || []).map(i => i.points), 1)
+      return Math.max(8, (points / max) * 160)
+    },
+    formatDay(dateStr) {
+      if (!dateStr) return ''
+      const parts = dateStr.split('-')
+      return (parts[1] || '') + '/' + (parts[2] || '')
+    },
+    navTo(path) {
+      uni.navigateTo({ url: path })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.report-container {
+  min-height: 100vh;
+  background: #f5f7fa;
+}
+.header {
+  padding: 20rpx 30rpx;
+  background: #fff;
+  border-bottom: 1rpx solid #f0f0f0;
+}
+.header-title {
+  font-size: 34rpx;
+  font-weight: bold;
+  color: #333;
+}
+.loading-state {
+  text-align: center;
+  padding: 200rpx 30rpx;
+  color: #999;
+  font-size: 28rpx;
+}
+.scroll-area {
+  padding: 0 20rpx 40rpx;
+}
+
+/* 概览 */
+.overview-grid {
+  display: flex;
+  margin: 20rpx 0;
+  gap: 16rpx;
+}
+.overview-item {
+  flex: 1;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx 16rpx;
+  text-align: center;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.ov-num {
+  display: block;
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #F97316;
+}
+.ov-label {
+  display: block;
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 6rpx;
+}
+
+/* 积分趋势 */
+.section {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
+}
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.section-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+}
+.section-more {
+  font-size: 24rpx;
+  color: #F97316;
+}
+.trend-chart {
+  display: flex;
+  align-items: flex-end;
+  height: 200rpx;
+  gap: 12rpx;
+  padding-top: 20rpx;
+}
+.bar-item {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  height: 100%;
+}
+.bar-wrapper {
+  flex: 1;
+  width: 100%;
+  display: flex;
+  align-items: flex-end;
+  justify-content: center;
+}
+.bar {
+  width: 32rpx;
+  background: linear-gradient(180deg, #F97316, #fb923c);
+  border-radius: 8rpx 8rpx 0 0;
+  min-height: 8rpx;
+  transition: height 0.3s;
+}
+.bar-label {
+  font-size: 20rpx;
+  color: #999;
+  margin-top: 8rpx;
+}
+
+/* 统计卡片 */
+.stat-cards {
+  margin-top: 8rpx;
+}
+.stat-row {
+  display: flex;
+  gap: 16rpx;
+}
+.stat-chip {
+  flex: 1;
+  text-align: center;
+  padding: 20rpx;
+  border-radius: 12rpx;
+}
+.stat-chip.pending { background: #fef3c7; }
+.stat-chip.review { background: #dbeafe; }
+.stat-chip.done { background: #d1fae5; }
+.stat-chip.game { background: #f3e8ff; }
+.chip-num {
+  display: block;
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+.chip-label {
+  display: block;
+  font-size: 22rpx;
+  color: #666;
+  margin-top: 4rpx;
+}
+
+/* 快捷入口 */
+.quick-links {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 16rpx;
+}
+.quick-link {
+  width: calc(25% - 12rpx);
+  text-align: center;
+  padding: 20rpx 0;
+}
+.ql-icon {
+  display: block;
+  font-size: 48rpx;
+  margin-bottom: 8rpx;
+}
+.ql-text {
+  display: block;
+  font-size: 22rpx;
+  color: #666;
+}
+</style>

+ 13 - 13
cfc-frontend/pages/tasks/review.vue

@@ -149,7 +149,7 @@ export default {
 <style scoped>
 .review-page {
   min-height: 100vh;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, #F5F9FC);
   padding: 24rpx var(--page-padding, 32rpx);
 }
 
@@ -163,8 +163,8 @@ export default {
   border-radius: var(--radius-md, 20rpx);
   padding: 8rpx;
   margin-bottom: 32rpx;
-  box-shadow: var(--shadow-sm, 0 4rpx 12rpx rgba(249, 115, 22, 0.06));
-  border: 1px solid var(--border-light, #FFEDD5);
+  box-shadow: var(--shadow-sm, 0 4rpx 12rpx rgba(91, 155, 213, 0.06));
+  border: 1px solid var(--border-light, #E2E8F0);
 }
 
 .tab-bar__item {
@@ -185,18 +185,18 @@ export default {
 }
 
 .tab-bar__item--active {
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, #F5F9FC);
 }
 
 .tab-bar__label {
   font-size: 28rpx;
   font-weight: 600;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   transition: color var(--transition-base, 0.25s ease);
 }
 
 .tab-bar__item--active .tab-bar__label {
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
 }
 
 .tab-bar__item--active::after {
@@ -207,12 +207,12 @@ export default {
   transform: translateX(-50%);
   width: 40%;
   height: 4rpx;
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, #5B9BD5);
   border-radius: 4rpx;
 }
 
 .tab-bar__badge {
-  background: var(--color-primary, #F97316);
+  background: var(--color-primary, #5B9BD5);
   border-radius: 999rpx;
   min-width: 36rpx;
   height: 36rpx;
@@ -221,7 +221,7 @@ export default {
   justify-content: center;
   padding: 0 8rpx;
   margin-left: 8rpx;
-  box-shadow: 0 2rpx 8rpx rgba(249, 115, 22, 0.3);
+  box-shadow: 0 2rpx 8rpx rgba(91, 155, 213, 0.3);
 }
 
 .tab-bar__badge-text {
@@ -253,7 +253,7 @@ export default {
 .card-title {
   font-size: 30rpx;
   font-weight: 700;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   line-height: 1.4;
 }
 
@@ -267,7 +267,7 @@ export default {
 
 .card-desc {
   font-size: 26rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   line-height: 1.5;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -289,11 +289,11 @@ export default {
 }
 
 .card-provider {
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
 }
 
 .card-points {
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
   font-weight: 600;
 }
 

+ 35 - 35
cfc-frontend/pages/wishes/index.vue

@@ -587,7 +587,7 @@ export default {
    ============================================================ */
 .page {
   min-height: 100vh;
-  background: var(--bg, #FFF7ED);
+  background: var(--bg, #F5F9FC);
   padding-bottom: 180rpx;
 }
 
@@ -607,7 +607,7 @@ export default {
   right: -30%;
   width: 300rpx;
   height: 300rpx;
-  background: radial-gradient(circle, rgba(249, 115, 22, 0.15), transparent 70%);
+  background: radial-gradient(circle, rgba(91, 155, 213, 0.15), transparent 70%);
   pointer-events: none;
 }
 
@@ -615,11 +615,11 @@ export default {
   position: relative;
   padding: 32rpx 36rpx 28rpx;
   border-radius: 32rpx;
-  border: 4rpx solid var(--border, #FED7AA);
-  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #FFF2E4));
+  border: 4rpx solid var(--border, #CBD5E1);
+  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
   box-shadow:
-    inset -4rpx -4rpx 12rpx rgba(249, 115, 22, 0.05),
-    6rpx 6rpx 18rpx rgba(249, 115, 22, 0.10);
+    inset -4rpx -4rpx 12rpx rgba(91, 155, 213, 0.05),
+    6rpx 6rpx 18rpx rgba(91, 155, 213, 0.10);
 }
 
 .points-card__header {
@@ -636,14 +636,14 @@ export default {
 .points-card__label {
   font-size: 28rpx;
   font-weight: 600;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
 }
 
 .points-card__value {
   display: block;
   font-size: 72rpx;
   font-weight: 800;
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
   line-height: 1.1;
   margin-bottom: 20rpx;
   letter-spacing: -2rpx;
@@ -657,7 +657,7 @@ export default {
 
 .milestone__track {
   height: 12rpx;
-  background: var(--border-light, #FFEDD5);
+  background: var(--border-light, #E2E8F0);
   border-radius: 999rpx;
   overflow: hidden;
   margin-bottom: 12rpx;
@@ -666,13 +666,13 @@ export default {
 .milestone__fill {
   height: 100%;
   border-radius: 999rpx;
-  background: linear-gradient(90deg, var(--color-primary-light, #FB923C), var(--color-primary, #F97316));
+  background: linear-gradient(90deg, var(--color-primary-light, #8FC5E8), var(--color-primary, #5B9BD5));
   transition: width 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
 }
 
 .milestone__text {
   font-size: 24rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   line-height: 1.5;
 }
 
@@ -698,19 +698,19 @@ export default {
   padding: 16rpx 28rpx;
   border-radius: 999rpx;
   background: var(--surface, #FFFFFF);
-  border: 2rpx solid var(--border-light, #FFEDD5);
+  border: 2rpx solid var(--border-light, #E2E8F0);
   font-size: 26rpx;
   font-weight: 500;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   transition: all 0.25s ease;
   flex-shrink: 0;
 }
 
 .filter-tab--active {
-  background: var(--color-primary, #F97316);
-  border-color: var(--color-primary, #F97316);
+  background: var(--color-primary, #5B9BD5);
+  border-color: var(--color-primary, #5B9BD5);
   color: #FFFFFF;
-  box-shadow: 0 4rpx 16rpx rgba(249, 115, 22, 0.25);
+  box-shadow: 0 4rpx 16rpx rgba(91, 155, 213, 0.25);
 }
 
 .filter-tab:active {
@@ -799,7 +799,7 @@ export default {
 .wish-card__title {
   font-size: 32rpx;
   font-weight: 700;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   flex: 1;
   overflow: hidden;
   text-overflow: ellipsis;
@@ -817,7 +817,7 @@ export default {
 
 .wish-card__points-label {
   font-size: 24rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
 }
 
 .wish-card__points-value {
@@ -853,7 +853,7 @@ export default {
 .wish-card__status-msg {
   font-size: 24rpx;
   font-weight: 500;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
 }
 
 .wish-card__status-text--warning .wish-card__status-msg {
@@ -905,11 +905,11 @@ export default {
   display: flex;
   align-items: center;
   justify-content: center;
-  background: linear-gradient(145deg, var(--color-primary-light, #FB923C), var(--color-primary, #F97316));
-  border: 4rpx solid var(--border, #FED7AA);
+  background: linear-gradient(145deg, var(--color-primary-light, #8FC5E8), var(--color-primary, #5B9BD5));
+  border: 4rpx solid var(--border, #CBD5E1);
   box-shadow:
     inset -3rpx -3rpx 10rpx rgba(255, 255, 255, 0.3),
-    4rpx 4rpx 16rpx rgba(249, 115, 22, 0.3);
+    4rpx 4rpx 16rpx rgba(91, 155, 213, 0.3);
   z-index: 100;
   transition: transform 0.15s ease;
 }
@@ -922,7 +922,7 @@ export default {
   position: absolute;
   inset: -8rpx;
   border-radius: 50%;
-  background: rgba(249, 115, 22, 0.15);
+  background: rgba(91, 155, 213, 0.15);
   animation: pulseGlow 2s ease-in-out infinite;
   pointer-events: none;
 }
@@ -970,7 +970,7 @@ export default {
 .modal__title {
   font-size: 36rpx;
   font-weight: 700;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
 }
 
 .modal__close {
@@ -994,7 +994,7 @@ export default {
   display: block;
   font-size: 26rpx;
   font-weight: 600;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   margin-bottom: 16rpx;
 }
 
@@ -1011,15 +1011,15 @@ export default {
   align-items: center;
   padding: 20rpx 12rpx;
   border-radius: 20rpx;
-  background: var(--bg, #FFF7ED);
-  border: 2rpx solid var(--border-light, #FFEDD5);
+  background: var(--bg, #F5F9FC);
+  border: 2rpx solid var(--border-light, #E2E8F0);
   transition: all 0.2s ease;
 }
 
 .emoji-item--active {
-  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #FFF2E4));
-  border-color: var(--color-primary, #F97316);
-  box-shadow: 0 4rpx 12rpx rgba(249, 115, 22, 0.15);
+  background: linear-gradient(145deg, #FFFFFF, var(--bg-grey, #EDF2F7));
+  border-color: var(--color-primary, #5B9BD5);
+  box-shadow: 0 4rpx 12rpx rgba(91, 155, 213, 0.15);
 }
 
 .emoji-item:active {
@@ -1034,24 +1034,24 @@ export default {
 
 .emoji-item__label {
   font-size: 24rpx;
-  color: var(--text-secondary, #6B5A4A);
+  color: var(--text-secondary, #64748B);
   font-weight: 500;
 }
 
 .emoji-item--active .emoji-item__label {
-  color: var(--color-primary, #F97316);
+  color: var(--color-primary, #5B9BD5);
   font-weight: 600;
 }
 
 /* 输入框 */
 .form-input {
   width: 100%;
-  background: var(--bg, #FFF7ED);
-  border: 2rpx solid var(--border-light, #FFEDD5);
+  background: var(--bg, #F5F9FC);
+  border: 2rpx solid var(--border-light, #E2E8F0);
   border-radius: 20rpx;
   padding: 24rpx 28rpx;
   font-size: 30rpx;
-  color: var(--text, #3D2E1E);
+  color: var(--text, #1E293B);
   box-sizing: border-box;
 }