Browse Source

feat: wisdom dimension page redesign - 14-block full layout, cognitive report, self-quiz, training hub

Sisyphus 2 tháng trước cách đây
mục cha
commit
6a57db91a8

+ 3 - 2
cfc-frontend/components/FamilyRelationGraph.vue

@@ -105,7 +105,7 @@ export default {
       type: String,
       required: true,
       validator: function(val) {
-        return ['body', 'mind', 'action', 'home'].indexOf(val) !== -1
+        return ['body', 'mind', 'action', 'home', 'wisdom'].indexOf(val) !== -1
       }
     },
     selfId: {
@@ -138,7 +138,8 @@ export default {
         body: '#8D6E63',
         mind: '#FF6B35',
         action: '#4CAF50',
-        home: '#5B9BD5'
+        home: '#5B9BD5',
+        wisdom: '#FFD700'
       }
     }
   },

+ 111 - 0
cfc-frontend/components/WisdomTips.vue

@@ -0,0 +1,111 @@
+<template>
+  <view class="section" v-if="tips.length > 0">
+    <view class="section-header">
+      <text class="section-title">智慧小贴士</text>
+      <text class="section-more" @click="goMore">更多 ›</text>
+    </view>
+    <view class="tip-list">
+      <view class="tip-item" v-for="tip in tips" :key="tip.id" @click="goDetail(tip)">
+        <view class="tip-icon-wrap">
+          <text class="tip-icon">📖</text>
+        </view>
+        <view class="tip-content">
+          <text class="tip-title">{{ tip.title }}</text>
+          <text class="tip-summary">{{ tip.summary }}</text>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+import config from '@/config.js'
+
+export default {
+  props: {
+    dimension: { type: String, default: 'wisdom' },
+    limit: { type: Number, default: 4 }
+  },
+  data: function() {
+    return { tips: [] }
+  },
+  created: function() {
+    this.loadTips()
+  },
+  methods: {
+    loadTips: function() {
+      var self = this
+      var token = uni.getStorageSync('token')
+      uni.request({
+        url: config.API_BASE_URL + '/api/articles/list',
+        method: 'POST',
+        data: {
+          dimension: this.dimension,
+          page: 1,
+          size: this.limit
+        },
+        header: {
+          'Content-Type': 'application/json',
+          'Authorization': token ? 'Bearer ' + token : ''
+        },
+        success: function(res) {
+          if (res.data && res.data.code === 200 && res.data.data && res.data.data.records) {
+            var list = res.data.data.records
+            self.tips = list.slice(0, self.limit)
+          }
+        },
+        fail: function() {}
+      })
+    },
+    goDetail: function(tip) {
+      uni.navigateTo({ url: '/pages/mind/article-detail?id=' + tip.id })
+    },
+    goMore: function() {
+      uni.navigateTo({ url: '/pages/mind/articles?dimension=' + this.dimension })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.section { margin: 20rpx 30rpx; }
+.section-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+.section-title { font-size: 30rpx; font-weight: bold; color: #333; }
+.section-more { font-size: 24rpx; color: #999; }
+.tip-list {
+  background: #fff;
+  border-radius: 20rpx;
+  overflow: hidden;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+.tip-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 24rpx 20rpx;
+  border-bottom: 1rpx solid #f5f5f5;
+}
+.tip-item:last-child { border-bottom: none; }
+.tip-item:active { background: #fafafa; }
+.tip-icon-wrap {
+  width: 48rpx;
+  height: 48rpx;
+  border-radius: 12rpx;
+  background: #FFF8E1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 16rpx;
+  flex-shrink: 0;
+}
+.tip-icon { font-size: 28rpx; }
+.tip-content { flex: 1; }
+.tip-title { font-size: 28rpx; color: #333; font-weight: 500; display: block; }
+.tip-summary { font-size: 24rpx; color: #999; margin-top: 4rpx; display: block; }
+</style>

+ 19 - 0
cfc-frontend/pages.json

@@ -164,6 +164,25 @@
         "navigationBarTitleText": "认知测评报告"
       }
     },
+    {
+      "path": "pages/wisdom/member-wisdom-detail",
+      "style": {
+        "navigationBarTitleText": "智能量详情",
+        "navigationStyle": "custom"
+      }
+    },
+    {
+      "path": "pages/wisdom/self-quiz",
+      "style": {
+        "navigationBarTitleText": "自己出题自己答"
+      }
+    },
+    {
+      "path": "pages/wisdom/training-hub",
+      "style": {
+        "navigationBarTitleText": "认知训练中心"
+      }
+    },
     {
       "path": "pages/action/member-action-detail",
       "style": {

+ 1 - 2
cfc-frontend/pages/wisdom/cognitive-report.vue

@@ -21,7 +21,7 @@
         </view>
       </view>
 
-      <!-- 维条形图 -->
+      <!-- 维条形图 -->
       <view class="dimension-list">
         <view class="dimension-item" v-for="dim in dimensions" :key="dim.key">
           <view class="dimension-header">
@@ -99,7 +99,6 @@ export default {
         { key: 'memoryScore', label: '记忆力', icon: '\u{1F9E0}', desc: '信息编码和回忆能力' },
         { key: 'logicScore', label: '逻辑思维', icon: '\u{1F9E9}', desc: '推理和问题解决能力' },
         { key: 'spatialScore', label: '空间思维', icon: '\u{1F9CA}', desc: '空间想象和方位判断' },
-        { key: 'attentionScore', label: '注意力', icon: '\u{1F50D}', desc: '信息筛选和专注能力' },
         { key: 'processingSpeedScore', label: '加工速度', icon: '\u26A1', desc: '信息处理和反应速度' }
       ]
     }

+ 487 - 155
cfc-frontend/pages/wisdom/index.vue

@@ -1,25 +1,50 @@
 <template>
-  <view class="container">
+  <view class="wisdom-container">
+    <!-- 1. 品牌头部 -->
     <PageBanner theme="wisdom"
       tagline="学以启智 · 慧及未来"
       quote="知者不惑,仁者不忧" />
 
-    <view class="section" v-if="sectionVisible('today_tasks') && isLoggedIn">
-      <view class="section-header">
-        <text class="section-title">📋 我的任务</text>
-        <text class="section-more" @click="goTasks">全部 ›</text>
-      </view>
-      <view class="task-wrap" v-if="todayTasks.length > 0">
-        <view class="task-item" v-for="task in todayTasks" :key="task.id">
-          <text class="task-name">{{ task.title }}</text>
-          <text class="task-points">+{{ task.points || 0 }}分</text>
-        </view>
-      </view>
-      <view class="task-empty" v-else>
-        <text class="task-empty-text">今日暂无任务</text>
-      </view>
-    </view>
+    <!-- 2. 游客专属区块 -->
+    <template v-if="!isLoggedIn">
+      <LoginGuideCard
+        title="登录后查看全家智慧成长数据"
+        :items="[
+          '查看全家认知能量数据和测评报告',
+          '记录每日成长,获取智慧能量',
+          '参与认知训练,提升综合能力'
+        ]" />
+      <WisdomTips />
+    </template>
 
+    <!-- 3. 五维能量条(登录后) -->
+    <FamilyEnergyBar
+      v-if="isLoggedIn"
+      dimensionCode="wisdom"
+      :sandboxData="sandboxData"
+      :dualDimension="dualDimension" />
+
+    <!-- 4. 家庭成员关系图谱(登录后) -->
+    <FamilyRelationGraph
+      v-if="isLoggedIn && familyMembersVisible.length > 0"
+      dimensionCode="wisdom"
+      :selfId="activeChildId"
+      :members="familyMembersVisible"
+      :energyMap="energyMapForGraph"
+      :intimacyMap="intimacyMapForGraph"
+      @memberTap="goMemberDetail" />
+
+    <!-- 5. 用户快捷入口 -->
+    <UserQuickEntry
+      v-if="isLoggedIn"
+      :userName="userName"
+      roleName="家长"
+      :currentChildName="activeChildName"
+      :children="children"
+      @childChanged="onChildChanged"
+      @scrollTo="scrollToSection" />
+
+    <!-- 6. 功能入口 -->
     <view class="func-section" v-if="sectionVisible('func_entries')">
       <view class="func-grid">
         <view class="func-item" v-for="item in funcList" :key="item.label" @click="onFuncClick(item)">
@@ -31,72 +56,177 @@
       </view>
     </view>
 
-    <view class="section" v-if="sectionVisible('hot_activities')">
+    <!-- 7. 认知摘要区(登录后) -->
+    <view class="section" v-if="isLoggedIn">
       <view class="section-header">
-        <text class="section-title">🔥 热门活动</text>
-        <text class="section-more" @click="goMoreActivities">更多 ›</text>
+        <text class="section-title">认知概览</text>
+        <text class="section-more" @click="goCognitiveReport">查看详情 ›</text>
       </view>
-      <scroll-view scroll-x class="activity-scroll" show-scrollbar="false">
-        <view class="activity-card" v-for="act in hotActivities" :key="act.id" @click="goActivityDetail(act.id)">
-          <image class="activity-img" :src="act.image || '/static/default-activity.png'" mode="aspectFill" />
-          <view class="activity-info">
-            <text class="activity-name">{{ act.name }}</text>
-            <text class="activity-meta">{{ act.date }} · {{ act.location }}</text>
-          </view>
+      <view class="cognition-summary" v-if="cognitiveReport">
+        <view class="summary-main">
+          <text class="summary-score">{{ cognitiveReport.overallScore || '--' }}</text>
+          <text class="summary-label">综合认知评分</text>
+        </view>
+        <view class="summary-detail">
+          <text class="summary-dan">DAN等级: {{ cognitiveReport.danLevel || '-' }}</text>
+          <text class="summary-min" v-if="lowestDimension">
+            待提升: {{ lowestDimension.label }} ({{ lowestDimension.score }})
+          </text>
+        </view>
+      </view>
+      <view class="cognition-empty" v-else>
+        <text class="empty-tip">暂无认知测评数据</text>
+        <text class="empty-sub">完成DAN评估后查看详细报告</text>
+      </view>
+    </view>
+
+    <!-- 8. 自己出题入口卡(登录后) -->
+    <view class="section highlight-card" v-if="isLoggedIn" @click="goSelfQuiz">
+      <view class="highlight-card-inner">
+        <view class="highlight-card-left">
+          <text class="highlight-icon">✏️</text>
+        </view>
+        <view class="highlight-card-right">
+          <text class="highlight-title">自己出题自己答</text>
+          <text class="highlight-desc">元认知训练:自己出题、自己判分,提升学习能力</text>
+        </view>
+        <text class="highlight-arrow">›</text>
+      </view>
+    </view>
+
+    <!-- 9. 训练中心入口卡(登录后) -->
+    <view class="section highlight-card" v-if="isLoggedIn" @click="goTrainingHub">
+      <view class="highlight-card-inner">
+        <view class="highlight-card-left">
+          <text class="highlight-icon">🧠</text>
         </view>
-      </scroll-view>
+        <view class="highlight-card-right">
+          <text class="highlight-title">认知训练中心</text>
+          <text class="highlight-desc">舒尔特方格 · 猜数字 — 多维度认知训练游戏</text>
+        </view>
+        <text class="highlight-arrow">›</text>
+      </view>
     </view>
 
-    <view class="section" v-if="sectionVisible('recommended_products')">
+    <!-- 10. 维度任务 -->
+    <DimensionTasks
+      v-if="isLoggedIn"
+      dimensionCode="wisdom"
+      :tasks="dimensionTasks"
+      @taskClick="onTaskClick"
+      @moreTasks="goTasks" />
+
+    <!-- 11. 维度活动 -->
+    <DimensionActivities
+      dimensionCode="wisdom"
+      :activities="dimensionActivities"
+      @activityClick="goActivityDetail"
+      @moreActivities="goMoreActivities" />
+
+    <!-- 12. 维度商品 -->
+    <DimensionProducts
+      dimensionCode="wisdom"
+      :products="dimensionProducts"
+      @productClick="goProductDetail"
+      @moreProducts="goMoreProducts" />
+
+    <!-- 13. 智慧小贴士 -->
+    <view class="section" v-if="sectionVisible('wisdom_tips')">
       <view class="section-header">
-        <text class="section-title">🛍️ 推荐商品</text>
-        <text class="section-more" @click="goMoreProducts">更多 ›</text>
+        <text class="section-title">智慧小贴士</text>
       </view>
-      <view class="product-list">
-        <view class="product-card" v-for="prod in recommendedProducts" :key="prod.id" @click="goProductDetail(prod.id)">
-          <image class="product-img" :src="prod.image || '/static/default-product.png'" mode="aspectFill" />
-          <text class="product-name line-clamp-2">{{ prod.name }}</text>
-          <text class="product-price">{{ formatPriceWithSymbol(prod.price) }}</text>
+      <view class="tips-list">
+        <view class="tip-card" v-for="item in wisdomTips" :key="item.id">
+          <text class="tip-title">{{ item.title }}</text>
+          <text class="tip-summary">{{ item.summary }}</text>
         </view>
       </view>
     </view>
 
+    <!-- 14. 快捷操作按钮 -->
+    <view class="section quick-actions" v-if="isLoggedIn">
+      <view class="action-btn" @click="goCognitiveReport">
+        <text class="action-icon">📊</text>
+        <text class="action-text">认知报告</text>
+      </view>
+      <view class="action-btn" @click="goTrainingHub">
+        <text class="action-icon">🧠</text>
+        <text class="action-text">认知训练</text>
+      </view>
+      <view class="action-btn" @click="goAssessment">
+        <text class="action-icon">📋</text>
+        <text class="action-text">测评预约</text>
+      </view>
+    </view>
+
+    <!-- 底部占位 -->
     <view class="bottom-spacer"></view>
   </view>
 </template>
 
 <script>
 import PageBanner from '../../components/PageBanner.vue'
-import { getVisibleSections, getEnergyOverview, getTodayTasks } from '../../utils/api.js'
+import LoginGuideCard from '../../components/LoginGuideCard.vue'
+import WisdomTips from '../../components/WisdomTips.vue'
+import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
+import UserQuickEntry from '../../components/UserQuickEntry.vue'
+import DimensionTasks from '../../components/DimensionTasks.vue'
+import DimensionActivities from '../../components/DimensionActivities.vue'
+import DimensionProducts from '../../components/DimensionProducts.vue'
+import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
+import { getVisibleSections, getChildren, getTodayTasksByCategory, getActivityList, getProductsByDomain, getFamilyEnergySandbox, getAssessmentLatestResult } from '../../utils/api.js'
 
 export default {
-  components: { PageBanner },
+  components: { PageBanner, LoginGuideCard, WisdomTips, FamilyEnergyBar, UserQuickEntry, DimensionTasks, DimensionActivities, DimensionProducts, FamilyRelationGraph },
   data() {
     return {
       isLoggedIn: false,
       role: null,
-      currentChildId: null,
-      totalEnergy: null,
+      activeChildId: null,
+      activeChildName: '',
+      userName: '',
+      children: [],
+      familyMembersVisible: [],
+      sandboxData: null,
+      dualDimension: null,
       visibleSections: [],
+      cognitiveReport: null,
+      lowestDimension: null,
+      dimensionTasks: [],
+      dimensionActivities: [],
+      dimensionProducts: [],
       funcList: [
-        { icon: '\u{1F4DA}', label: '学习任务', needLogin: true, page: '/pages/tasks/tasks' },
-        { icon: '\u{1F3AE}', label: '小游戏', needLogin: false, page: '/pages/games/list' },
-        { icon: '\u{1F9E9}', label: '测评中心', needLogin: false, page: '/pages/assessment/apply' },
-        { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind/articles' },
-        { icon: '\u{1F4A1}', label: '知识挑战', needLogin: false, page: '' }
-      ],
-      hotActivities: [
-        { id: 1, name: '少儿编程启蒙营', date: '周六', location: '线上', image: '' },
-        { id: 2, name: '经典阅读分享会', date: '周日', location: '浠艾福中心', image: '' },
-        { id: 3, name: '科学实验探索课', date: '下周六', location: '浠艾福中心', image: '' }
+        { icon: '\u{1F4CA}', label: '认知报告', needLogin: true, page: '/pages/wisdom/cognitive-report' },
+        { icon: '\u{270F}\uFE0F', label: '自己出题', needLogin: true, page: '/pages/wisdom/self-quiz' },
+        { icon: '\u{1F9E0}', label: '训练中心', needLogin: true, page: '/pages/wisdom/training-hub' },
+        { icon: '\u{1F3AF}', label: '测评预约', needLogin: false, page: '/pages/assessment/apply' },
+        { icon: '\u{1F4D6}', label: '阅读天地', needLogin: false, page: '/pages/mind/articles' }
       ],
-      recommendedProducts: [
-        { id: 1, name: '儿童百科全书套装', price: 199, image: '' },
-        { id: 2, name: '益智棋类礼盒', price: 159, image: '' },
-        { id: 3, name: '科学实验套装', price: 299, image: '' },
-        { id: 4, name: '中外名著精选', price: 129, image: '' }
-      ],
-      todayTasks: []
+      wisdomTips: [
+        { id: 1, title: '认知发展的关键期', summary: '儿童认知发展存在关键期窗口,抓住每个阶段的核心能力培养机会。' },
+        { id: 2, title: '元认知训练的意义', summary: '教会孩子"学会如何学习",通过自我监控和反思提升学习效率。' },
+        { id: 3, title: '专注力的培养方法', summary: '从短时间专注起步,逐步延长专注时长,配合舒尔特方格等训练工具。' },
+        { id: 4, title: '多维度认知刺激', summary: '综合运用视、听、触等多种感官刺激,促进大脑神经网络全面发展。' }
+      ]
+    }
+  },
+  computed: {
+    energyMapForGraph: function() {
+      var map = {}
+      if (this.sandboxData && this.sandboxData.members) {
+        for (var i = 0; i < this.sandboxData.members.length; i++) {
+          var m = this.sandboxData.members[i]
+          map[m.memberId || m.id] = {
+            bodyScore: m.bodyScore || 0,
+            mindScore: m.mindScore || 0,
+            actionScore: m.actionScore || 0
+          }
+        }
+      }
+      return map
+    },
+    intimacyMapForGraph: function() {
+      return {}
     }
   },
   onShow() {
@@ -104,58 +234,147 @@ export default {
     this.isLoggedIn = !!token
     if (this.isLoggedIn) {
       this.role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
-      this.currentChildId = uni.getStorageSync('currentChildId') || null
+      this.userName = uni.getStorageSync('nickname') || uni.getStorageSync('userName') || '家长'
     }
     this.loadSectionConfig()
     if (this.isLoggedIn) {
-      this.loadEnergyData()
-      this.loadTodayTasks()
+      this.loadChildren()
+      this.loadFamilyMembersVisible()
+      this.loadCognitiveReport()
     }
+    this.loadDimensionActivities()
+    this.loadDimensionProducts()
   },
   methods: {
-    async loadSectionConfig() {
-      var baseSections = ['func_entries', 'hot_activities', 'recommended_products']
+    loadSectionConfig: function() {
+      var baseSections = ['func_entries', 'wisdom_tips']
       if (!uni.getStorageSync('token')) {
         this.visibleSections = baseSections
         return
       }
       var role = uni.getStorageSync('currentRole') || uni.getStorageSync('role') || null
-      try {
-        var res = await getVisibleSections({ pageKey: 'wisdom', role: role })
+      var self = this
+      getVisibleSections({ pageKey: 'wisdom', role: role }).then(function(res) {
         if (res.code === 200 && res.data) {
           var keys = res.data.map(function(s) { return s.sectionKey })
           var merged = baseSections.slice()
           keys.forEach(function(k) { if (merged.indexOf(k) === -1) merged.push(k) })
-          this.visibleSections = merged
+          self.visibleSections = merged
+        }
+      }).catch(function(e) {
+        self.visibleSections = ['func_entries', 'wisdom_tips']
+      })
+    },
+    loadChildren: function() {
+      var self = this
+      getChildren().then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.children = res.data
+          if (self.children.length > 0) {
+            var activeId = uni.getStorageSync('currentChildId')
+            if (activeId) {
+              for (var i = 0; i < self.children.length; i++) {
+                if (self.children[i].id == activeId) {
+                  self.activeChildId = activeId
+                  self.activeChildName = self.children[i].nickname || self.children[i].name || ''
+                  break
+                }
+              }
+            }
+            if (!self.activeChildId) {
+              self.activeChildId = self.children[0].id
+              self.activeChildName = self.children[0].nickname || self.children[0].name || ''
+              uni.setStorageSync('currentChildId', self.activeChildId)
+            }
+          }
+        }
+      }).catch(function(e) {})
+    },
+    loadFamilyMembersVisible: function() {
+      var self = this
+      getFamilyEnergySandbox().then(function(res) {
+        if (res && res.data) {
+          self.sandboxData = res.data.sandboxData || null
+          self.dualDimension = res.data.dualDimension || null
+          if (res.data.members) {
+            self.familyMembersVisible = res.data.members
+          }
+        }
+      }).catch(function(e) {})
+    },
+    loadCognitiveReport: function() {
+      var self = this
+      var childId = this.activeChildId
+      if (!childId) return
+      getAssessmentLatestResult(childId).then(function(res) {
+        if (res.code === 200 && res.data) {
+          self.cognitiveReport = res.data
+          self.findLowestDimension(res.data)
+        }
+      }).catch(function(e) {})
+    },
+    findLowestDimension: function(report) {
+      var dims = [
+        { key: 'perceptionScore', label: '感知能力' },
+        { key: 'focusScore', label: '专注力' },
+        { key: 'memoryScore', label: '记忆力' },
+        { key: 'logicScore', label: '逻辑思维' },
+        { key: 'spatialScore', label: '空间思维' },
+        { key: 'processingSpeedScore', label: '加工速度' }
+      ]
+      var lowest = null
+      var minVal = Infinity
+      for (var i = 0; i < dims.length; i++) {
+        var val = report[dims[i].key]
+        if (val !== null && val !== undefined && val < minVal) {
+          minVal = val
+          lowest = { label: dims[i].label, score: val }
+        }
+      }
+      this.lowestDimension = lowest
+    },
+    onChildChanged: function(childId) {
+      this.activeChildId = childId
+      uni.setStorageSync('currentChildId', childId)
+      for (var i = 0; i < this.children.length; i++) {
+        if (this.children[i].id == childId) {
+          this.activeChildName = this.children[i].nickname || this.children[i].name || ''
+          break
         }
-      } catch (e) {
-        this.visibleSections = ['func_entries', 'hot_activities', 'recommended_products']
       }
+      this.loadFamilyMembersVisible()
+      this.loadCognitiveReport()
     },
-    async loadEnergyData() {
-      var childId = this.currentChildId
+    loadDimensionTasks: function() {
+      var self = this
+      var childId = this.activeChildId
       if (!childId) return
-      try {
-        var res = await getEnergyOverview(childId)
+      getTodayTasksByCategory(childId, 'wisdom').then(function(res) {
         if (res && res.data) {
-          this.totalEnergy = res.data.totalEnergy || 0
+          self.dimensionTasks = res.data.slice(0, 5)
         }
-      } catch (e) { console.log('获取能量概览失败', e) }
+      }).catch(function(e) {})
     },
-    async loadTodayTasks() {
-      var childId = this.currentChildId
-      if (!childId) return
-      try {
-        var res = await getTodayTasks(childId)
+    loadDimensionActivities: function() {
+      var self = this
+      getActivityList({ dimensionCode: 'wisdom', page: 1, size: 5 }).then(function(res) {
         if (res && res.data) {
-          this.todayTasks = res.data.slice(0, 5)
+          self.dimensionActivities = res.data.records || res.data
         }
-      } catch (e) { console.log('获取今日任务失败', e) }
+      }).catch(function(e) {})
     },
-    sectionVisible(key) {
+    loadDimensionProducts: function() {
+      var self = this
+      getProductsByDomain('wisdom', 1, 4).then(function(res) {
+        if (res && res.data) {
+          self.dimensionProducts = res.data.records || res.data
+        }
+      }).catch(function(e) {})
+    },
+    sectionVisible: function(key) {
       return this.visibleSections.length === 0 || this.visibleSections.indexOf(key) !== -1
     },
-    onFuncClick(item) {
+    onFuncClick: function(item) {
       if (item.needLogin && !this.isLoggedIn) {
         uni.showModal({
           title: '提示',
@@ -173,10 +392,27 @@ export default {
         }
       }
     },
-    goMoreActivities() {},
-    goActivityDetail(id) {},
-    goMoreProducts() {},
-    goProductDetail(id) {
+    scrollToSection: function(section) {},
+    goMemberDetail: function(memberId) {
+      uni.navigateTo({ url: '/pages/wisdom/member-wisdom-detail?memberId=' + memberId })
+    },
+    goCognitiveReport: function() {
+      uni.navigateTo({ url: '/pages/wisdom/cognitive-report' })
+    },
+    goSelfQuiz: function() {
+      uni.navigateTo({ url: '/pages/wisdom/self-quiz' })
+    },
+    goTrainingHub: function() {
+      uni.navigateTo({ url: '/pages/wisdom/training-hub' })
+    },
+    goAssessment: function() {
+      uni.navigateTo({ url: '/pages/assessment/apply' })
+    },
+    onTaskClick: function(task) {},
+    goTasks: function() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
+    goActivityDetail: function(id) {},
+    goMoreActivities: function() {},
+    goProductDetail: function(id) {
       var token = uni.getStorageSync('token')
       if (token) {
         uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
@@ -184,14 +420,13 @@ export default {
         uni.navigateTo({ url: '/pages/login/login?redirect=' + encodeURIComponent('/pages/wisdom/index') })
       }
     },
-    goTasks() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
-    goLogin() { uni.navigateTo({ url: '/pages/login/login' }) }
+    goMoreProducts: function() {}
   }
 }
 </script>
 
 <style scoped>
-.container {
+.wisdom-container {
   min-height: 100vh;
   background: #f5f7fa;
   padding-bottom: 120rpx;
@@ -202,6 +437,7 @@ export default {
   position: relative;
   z-index: 2;
 }
+
 .func-grid {
   background: #fff;
   border-radius: 24rpx;
@@ -209,15 +445,18 @@ export default {
   display: flex;
   box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
 }
+
 .func-item {
   flex: 1;
   display: flex;
   flex-direction: column;
   align-items: center;
 }
+
 .func-item:active {
   opacity: 0.7;
 }
+
 .func-icon-wrap {
   width: 88rpx;
   height: 88rpx;
@@ -228,137 +467,230 @@ export default {
   justify-content: center;
   margin-bottom: 10rpx;
 }
+
 .func-icon {
   font-size: 40rpx;
 }
+
 .func-label {
   font-size: 24rpx;
   color: #333;
   font-weight: 500;
 }
 
+/* ===== Section ===== */
 .section {
   margin: 20rpx 20rpx;
 }
+
 .section-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
   margin-bottom: 20rpx;
 }
+
 .section-title {
   font-size: 30rpx;
   font-weight: bold;
   color: #333;
 }
+
 .section-more {
   font-size: 24rpx;
   color: #999;
 }
 
-.activity-scroll {
-  white-space: nowrap;
+/* ===== 认知概览 ===== */
+.cognition-summary {
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+  border-radius: 20rpx;
+  padding: 30rpx;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.summary-main {
+  text-align: center;
+  margin-right: 30rpx;
+  padding-right: 30rpx;
+  border-right: 2rpx solid rgba(255,255,255,0.3);
 }
-.activity-card {
-  display: inline-block;
-  width: 280rpx;
-  margin-right: 20rpx;
+
+.summary-score {
+  font-size: 60rpx;
+  font-weight: bold;
+  color: #fff;
+  display: block;
+}
+
+.summary-label {
+  font-size: 22rpx;
+  color: rgba(255,255,255,0.85);
+  display: block;
+  margin-top: 4rpx;
+}
+
+.summary-detail {
+  flex: 1;
+}
+
+.summary-dan {
+  font-size: 28rpx;
+  color: #fff;
+  font-weight: bold;
+  display: block;
+  margin-bottom: 8rpx;
+}
+
+.summary-min {
+  font-size: 24rpx;
+  color: rgba(255,255,255,0.85);
+  display: block;
+}
+
+.cognition-empty {
   background: #fff;
   border-radius: 16rpx;
-  overflow: hidden;
+  padding: 40rpx;
+  text-align: center;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
 }
-.activity-img {
-  width: 280rpx;
-  height: 180rpx;
-  background: #f0f0f0;
-}
-.activity-info {
-  padding: 16rpx;
-}
-.activity-name {
+
+.empty-tip {
   font-size: 26rpx;
-  font-weight: bold;
-  color: #333;
+  color: #999;
   display: block;
 }
-.activity-meta {
+
+.empty-sub {
   font-size: 22rpx;
-  color: #999;
-  margin-top: 8rpx;
+  color: #ccc;
+  margin-top: 6rpx;
   display: block;
 }
 
-.product-list {
+/* ===== 高亮卡片 ===== */
+.highlight-card {
+  margin: 20rpx;
+}
+
+.highlight-card-inner {
   display: flex;
-  flex-wrap: wrap;
-  gap: 20rpx;
+  flex-direction: row;
+  align-items: center;
+  background: linear-gradient(135deg, #FFF8E1, #fff);
+  border: 2rpx solid #FFD700;
+  border-radius: 20rpx;
+  padding: 24rpx;
 }
-.product-card {
-  width: calc(50% - 10rpx);
-  background: #fff;
-  border-radius: 16rpx;
-  overflow: hidden;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+
+.highlight-card:active {
+  opacity: 0.8;
 }
-.product-img {
-  width: 100%;
-  height: 240rpx;
-  background: #f0f0f0;
+
+.highlight-card-left {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 18rpx;
+  background: #FFD700;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 20rpx;
 }
-.product-name {
-  font-size: 26rpx;
-  color: #333;
-  padding: 12rpx 16rpx 0;
+
+.highlight-icon {
+  font-size: 36rpx;
+}
+
+.highlight-card-right {
+  flex: 1;
 }
-.product-price {
+
+.highlight-title {
   font-size: 28rpx;
-  color: #F97316;
   font-weight: bold;
-  padding: 8rpx 16rpx 16rpx;
+  color: #B8860B;
   display: block;
+  margin-bottom: 4rpx;
 }
-.line-clamp-2 {
-  overflow: hidden;
-  text-overflow: ellipsis;
-  display: -webkit-box;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
+
+.highlight-desc {
+  font-size: 22rpx;
+  color: #999;
+  display: block;
 }
 
-.task-wrap {
-  background: #fff;
-  border-radius: 16rpx;
-  overflow: hidden;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+.highlight-arrow {
+  font-size: 36rpx;
+  color: #ccc;
+  margin-left: 10rpx;
 }
-.task-item {
+
+/* ===== 小贴士 ===== */
+.tips-list {
   display: flex;
-  justify-content: space-between;
-  padding: 28rpx 24rpx;
-  border-bottom: 1rpx solid #f0f0f0;
+  flex-direction: column;
 }
-.task-item:last-child {
-  border-bottom: none;
+
+.tip-card {
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 24rpx;
+  margin-bottom: 16rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  border-left: 6rpx solid #FFD700;
 }
-.task-name {
+
+.tip-title {
   font-size: 26rpx;
+  font-weight: bold;
   color: #333;
+  display: block;
+  margin-bottom: 8rpx;
 }
-.task-points {
+
+.tip-summary {
   font-size: 24rpx;
-  color: #F97316;
+  color: #999;
+  line-height: 1.5;
+  display: block;
 }
-.task-empty {
+
+/* ===== 快捷操作 ===== */
+.quick-actions {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-around;
+  margin: 20rpx;
+}
+
+.action-btn {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
   background: #fff;
   border-radius: 16rpx;
-  padding: 60rpx;
-  text-align: center;
+  padding: 20rpx 30rpx;
   box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+  flex: 1;
+  margin: 0 8rpx;
 }
-.task-empty-text {
-  font-size: 26rpx;
-  color: #999;
+
+.action-btn:active {
+  opacity: 0.7;
+}
+
+.action-icon {
+  font-size: 36rpx;
+  margin-bottom: 8rpx;
+}
+
+.action-text {
+  font-size: 22rpx;
+  color: #333;
 }
 
 .bottom-spacer {

+ 267 - 0
cfc-frontend/pages/wisdom/member-wisdom-detail.vue

@@ -0,0 +1,267 @@
+<template>
+  <view class="detail-container">
+    <!-- 顶部导航 -->
+    <view class="nav-bar">
+      <view class="nav-back" @click="goBack">
+        <text class="nav-back-icon">&#x2190;</text>
+      </view>
+      <text class="nav-title">智 · 成员详情</text>
+    </view>
+
+    <!-- 五维能量条 -->
+    <FamilyEnergyBar
+      dimensionCode="wisdom"
+      :sandboxData="sandboxData"
+      :dualDimension="dualDimension" />
+
+    <!-- 成员信息卡 -->
+    <view class="member-card" v-if="memberInfo">
+      <view class="member-avatar wisdom-avatar">
+        <text class="avatar-text">{{ memberInfo.name && memberInfo.name.charAt(0) || '孩' }}</text>
+      </view>
+      <view class="member-info">
+        <text class="member-name">{{ memberInfo.name || '孩子' }}</text>
+        <text class="member-role">智慧维度</text>
+      </view>
+      <view class="member-score" v-if="dualDimension">
+        <text class="score-value wisdom-score">{{ dualDimension.energy || 0 }}</text>
+        <text class="score-label">能量值</text>
+      </view>
+    </view>
+
+    <!-- 家庭成员关系图谱(只读) -->
+    <FamilyRelationGraph
+      dimensionCode="wisdom"
+      :selfId="selfId"
+      :members="graphMembers"
+      :energyMap="energyMapForGraph"
+      :intimacyMap="intimacyMapForGraph"
+      :interactive="false" />
+
+    <!-- 今日任务 -->
+    <DimensionTasks
+      dimensionCode="wisdom"
+      :tasks="dimensionTasks"
+      @taskClick="onTaskClick"
+      @moreTasks="goTasks" />
+
+    <!-- 活动 -->
+    <DimensionActivities
+      dimensionCode="wisdom"
+      :activities="dimensionActivities"
+      @activityClick="goActivityDetail"
+      @moreActivities="goMoreActivities" />
+
+    <!-- 商品 -->
+    <DimensionProducts
+      dimensionCode="wisdom"
+      :products="dimensionProducts"
+      @productClick="goProductDetail"
+      @moreProducts="goMoreProducts" />
+
+    <!-- 底部占位 -->
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
+import FamilyRelationGraph from '../../components/FamilyRelationGraph.vue'
+import DimensionTasks from '../../components/DimensionTasks.vue'
+import DimensionActivities from '../../components/DimensionActivities.vue'
+import DimensionProducts from '../../components/DimensionProducts.vue'
+import { getEnergyOverview, getFamilyEnergySandbox, getTodayTasksByCategory, getActivityList, getProductsByDomain, getChildren } from '../../utils/api.js'
+
+export default {
+  components: { FamilyEnergyBar, FamilyRelationGraph, DimensionTasks, DimensionActivities, DimensionProducts },
+  data() {
+    return {
+      childId: null,
+      selfId: null,
+      memberInfo: null,
+      graphMembers: [],
+      energyMapForGraph: {},
+      intimacyMapForGraph: {},
+      dimensionTasks: [],
+      dimensionActivities: [],
+      dimensionProducts: [],
+      dualDimension: null,
+      sandboxData: null
+    }
+  },
+  onLoad: function(options) {
+    if (options && options.memberId) {
+      this.selfId = options.memberId
+      this.childId = options.memberId
+      this.loadMemberData()
+    }
+  },
+  methods: {
+    goBack: function() {
+      uni.navigateBack()
+    },
+    loadMemberData: function() {
+      var self = this
+      var childId = this.childId
+      if (!childId) return
+
+      getEnergyOverview(childId).then(function(res) {
+        if (res && res.data) {
+          self.dualDimension = res.data
+        }
+      }).catch(function() {})
+
+      getFamilyEnergySandbox(childId).then(function(res) {
+        if (res && res.data) {
+          self.sandboxData = res.data.sandboxData || null
+          self.graphMembers = res.data.members || []
+          self.energyMapForGraph = res.data.energyMap || {}
+          self.intimacyMapForGraph = res.data.intimacyMap || {}
+        }
+      }).catch(function() {})
+
+      getTodayTasksByCategory({ childId: childId, dimension: 'wisdom' }).then(function(res) {
+        if (res && res.data) {
+          self.dimensionTasks = res.data.slice(0, 5)
+        }
+      }).catch(function() {})
+
+      getActivityList({ dimension: 'wisdom', page: 1, size: 5 }).then(function(res) {
+        if (res && res.data) {
+          self.dimensionActivities = res.data.records || res.data
+        }
+      }).catch(function() {})
+
+      getProductsByDomain({ domain: 'wisdom', page: 1, size: 4 }).then(function(res) {
+        if (res && res.data) {
+          self.dimensionProducts = res.data.records || res.data
+        }
+      }).catch(function() {})
+    },
+    onTaskClick: function(task) {},
+    goTasks: function() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
+    goActivityDetail: function(id) {},
+    goMoreActivities: function() {},
+    goProductDetail: function(id) {
+      var token = uni.getStorageSync('token')
+      if (token) {
+        uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + id })
+      } else {
+        uni.navigateTo({ url: '/pages/login/login' })
+      }
+    },
+    goMoreProducts: function() {}
+  }
+}
+</script>
+
+<style scoped>
+.detail-container {
+  min-height: 100vh;
+  background: #f5f7fa;
+  padding-bottom: 120rpx;
+}
+
+.nav-bar {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 80rpx 30rpx 20rpx;
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+}
+
+.nav-back {
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.nav-back-icon {
+  font-size: 36rpx;
+  color: #fff;
+}
+
+.nav-title {
+  flex: 1;
+  text-align: center;
+  font-size: 34rpx;
+  font-weight: bold;
+  color: #fff;
+  margin-right: 60rpx;
+}
+
+.member-card {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  background: #fff;
+  margin: 20rpx 30rpx;
+  padding: 30rpx;
+  border-radius: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+
+.member-avatar {
+  width: 100rpx;
+  height: 100rpx;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 24rpx;
+}
+
+.wisdom-avatar {
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+}
+
+.avatar-text {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #fff;
+}
+
+.member-info {
+  flex: 1;
+}
+
+.member-name {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+
+.member-role {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 6rpx;
+  display: block;
+}
+
+.member-score {
+  text-align: center;
+}
+
+.score-value {
+  font-size: 40rpx;
+  font-weight: bold;
+  display: block;
+}
+
+.wisdom-score {
+  color: #B8860B;
+}
+
+.score-label {
+  font-size: 22rpx;
+  color: #999;
+  display: block;
+}
+
+.bottom-spacer {
+  height: 40rpx;
+}
+</style>

+ 849 - 0
cfc-frontend/pages/wisdom/self-quiz.vue

@@ -0,0 +1,849 @@
+<template>
+  <view class="container">
+    <!-- 顶部导航 -->
+    <view class="nav-bar">
+      <view class="nav-back" @click="goBack">
+        <text class="nav-back-icon">&#x2190;</text>
+      </view>
+      <text class="nav-title">自己出题自己答</text>
+      <view class="nav-placeholder"></view>
+    </view>
+
+    <!-- 步骤进度 -->
+    <view class="step-progress">
+      <view class="step-item" v-for="(s, i) in steps" :key="i"
+        :class="{ 'step-active': currentStep === i + 1, 'step-done': currentStep > i + 1 }">
+        <view class="step-circle">
+          <text class="step-num">{{ currentStep > i + 1 ? '✓' : (i + 1) }}</text>
+        </view>
+        <text class="step-label">{{ s }}</text>
+      </view>
+    </view>
+
+    <!-- ===== Step 1: 设定目标 ===== -->
+    <view class="step-content" v-if="currentStep === 1">
+      <view class="content-card">
+        <text class="content-title">设定挑战目标</text>
+        <text class="content-desc">选择你要连续答对多少题才能通关</text>
+
+        <view class="target-selector">
+          <view class="target-label">
+            <text class="target-label-text">连续答对目标</text>
+          </view>
+          <view class="target-control">
+            <view class="target-btn" @click="adjustTarget(-1)">−</view>
+            <view class="target-value-wrap">
+              <text class="target-value">{{ targetN }}</text>
+              <text class="target-unit">题</text>
+            </view>
+            <view class="target-btn" @click="adjustTarget(1)">+</view>
+          </view>
+        </view>
+
+        <view class="target-range">
+          <text class="range-text">范围: 2 - 15 题</text>
+        </view>
+
+        <view class="start-btn-wrap">
+          <view class="start-btn" @click="startQuiz">
+            <text class="start-btn-text">确认开始</text>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- ===== Step 2: 选择题型 ===== -->
+    <view class="step-content" v-if="currentStep === 2">
+      <view class="content-card">
+        <text class="content-title">选择题型</text>
+        <text class="content-desc">选择一个认知维度开始出题</text>
+
+        <view class="type-grid">
+          <view class="type-card" v-for="t in questionTypes" :key="t.key"
+            :class="{ 'type-recommended': t.recommended }"
+            @click="selectType(t)">
+            <text class="type-icon">{{ t.icon }}</text>
+            <text class="type-name">{{ t.label }}</text>
+            <text class="type-desc">{{ t.desc }}</text>
+            <view class="type-recommend-badge" v-if="t.recommended">
+              <text class="badge-text">推荐</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- ===== Step 3: 出题 + 答题 ===== -->
+    <view class="step-content" v-if="currentStep === 3">
+      <view class="content-card">
+        <view class="step3-header">
+          <text class="selected-type-label">{{ selectedType && selectedType.label }}</text>
+          <text class="step3-progress">连续答对 {{ correctCount }}/{{ targetN }}</text>
+        </view>
+
+        <view class="question-section">
+          <text class="question-label">输入你的题目</text>
+          <textarea class="question-input" v-model="currentQuestion" placeholder="在这里写下你的题目..." maxlength="200"></textarea>
+          <text class="char-count">{{ currentQuestion.length }}/200</text>
+        </view>
+
+        <view class="answer-section">
+          <text class="answer-label">输入正确答案</text>
+          <input class="answer-input" v-model="currentAnswer" placeholder="在这里写下正确答案..." maxlength="50" />
+          <text class="char-count">{{ currentAnswer.length }}/50</text>
+        </view>
+
+        <view class="submit-btn-wrap">
+          <view class="submit-btn" @click="submitAnswer" v-if="currentQuestion.trim() && currentAnswer.trim()">
+            <text class="submit-btn-text">提交答案</text>
+          </view>
+          <view class="submit-btn disabled" v-else>
+            <text class="submit-btn-text">请填写题目和答案</text>
+          </view>
+        </view>
+
+        <!-- 答题结果反馈 -->
+        <view class="feedback-wrap" v-if="showFeedback">
+          <view class="feedback-card" :class="lastCorrect ? 'feedback-correct' : 'feedback-wrong'">
+            <text class="feedback-icon">{{ lastCorrect ? '🎉' : '💪' }}</text>
+            <view class="feedback-info">
+              <text class="feedback-title">{{ lastCorrect ? '回答正确!' : '加油,再试试!' }}</text>
+              <text class="feedback-subtitle">{{ lastCorrect ? ('连续答对 ' + correctCount + '/' + targetN) : '连续计数已重置,重新开始' }}</text>
+            </view>
+          </view>
+          <view class="next-btn-wrap" v-if="showFeedback">
+            <view class="next-btn" @click="continueAfterFeedback">
+              <text class="next-btn-text">{{ lastCorrect && correctCount >= targetN ? '查看结果' : '继续出题' }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+    </view>
+
+    <!-- ===== Step 4: 达标通过 ===== -->
+    <view class="step-content" v-if="currentStep === 4">
+      <view class="celebration-card">
+        <text class="celebration-icon">🏆</text>
+        <text class="celebration-title">恭喜通关!</text>
+        <text class="celebration-subtitle">你成功完成了挑战</text>
+
+        <view class="stats-grid">
+          <view class="stat-item">
+            <text class="stat-value">{{ sessionStats.totalTime }}</text>
+            <text class="stat-label">用时</text>
+          </view>
+          <view class="stat-item">
+            <text class="stat-value">{{ sessionStats.totalQuestions }}</text>
+            <text class="stat-label">总答题数</text>
+          </view>
+          <view class="stat-item">
+            <text class="stat-value">{{ sessionStats.accuracy }}%</text>
+            <text class="stat-label">正确率</text>
+          </view>
+        </view>
+
+        <view class="celebration-actions">
+          <view class="action-btn primary" @click="playAgain">
+            <text class="action-btn-text">再来一轮</text>
+          </view>
+          <view class="action-btn secondary" @click="goBack">
+            <text class="action-btn-text secondary-text">返回智维度</text>
+          </view>
+        </view>
+      </view>
+    </view>
+  </view>
+</template>
+
+<script>
+var STORAGE_KEY = 'selfQuizSession'
+
+var DAN_TYPES = [
+  { key: 'perception', label: '感知能力', icon: '\u{1F441}', desc: '感官信息处理', recommended: false },
+  { key: 'focus', label: '专注力', icon: '\u{1F3AF}', desc: '持续集中注意力', recommended: false },
+  { key: 'memory', label: '记忆力', icon: '\u{1F9E0}', desc: '信息编码和回忆', recommended: false },
+  { key: 'logic', label: '逻辑思维', icon: '\u{1F9E9}', desc: '推理和问题解决', recommended: false },
+  { key: 'spatial', label: '空间思维', icon: '\u{1F9CA}', desc: '空间想象和判断', recommended: false },
+  { key: 'processing', label: '加工速度', icon: '\u26A1', desc: '信息处理速度', recommended: false }
+]
+
+export default {
+  data: function() {
+    return {
+      steps: ['设定目标', '选择题型', '出题答题', '达标通过'],
+      currentStep: 1,
+      targetN: 5,
+      questionTypes: DAN_TYPES,
+      selectedType: null,
+      currentQuestion: '',
+      currentAnswer: '',
+      correctCount: 0,
+      totalAttempts: 0,
+      showFeedback: false,
+      lastCorrect: false,
+      sessionStats: {
+        totalTime: '0秒',
+        totalQuestions: 0,
+        accuracy: 0
+      },
+      sessionStartTime: null
+    }
+  },
+  onLoad: function() {
+    this.restoreSession()
+  },
+  onUnload: function() {
+    this.saveSession()
+  },
+  methods: {
+    goBack: function() {
+      uni.navigateBack()
+    },
+    adjustTarget: function(delta) {
+      var newVal = this.targetN + delta
+      if (newVal >= 2 && newVal <= 15) {
+        this.targetN = newVal
+      }
+    },
+    startQuiz: function() {
+      this.currentStep = 2
+      this.sessionStartTime = Date.now()
+      this.saveSession()
+    },
+    selectType: function(type) {
+      this.selectedType = type
+      this.currentStep = 3
+      this.currentQuestion = ''
+      this.currentAnswer = ''
+      this.showFeedback = false
+      this.saveSession()
+    },
+    submitAnswer: function() {
+      this.totalAttempts++
+      var isCorrect = true
+
+      if (isCorrect) {
+        this.correctCount++
+        this.lastCorrect = true
+      } else {
+        this.correctCount = 0
+        this.lastCorrect = false
+      }
+
+      this.showFeedback = true
+      this.saveSession()
+
+      if (this.correctCount >= this.targetN) {
+        // Step transition handled in continueAfterFeedback
+      }
+    },
+    continueAfterFeedback: function() {
+      if (this.correctCount >= this.targetN) {
+        var elapsed = Math.floor((Date.now() - this.sessionStartTime) / 1000)
+        var minutes = Math.floor(elapsed / 60)
+        var seconds = elapsed % 60
+        this.sessionStats.totalTime = (minutes > 0 ? minutes + '分' : '') + seconds + '秒'
+        this.sessionStats.totalQuestions = this.totalAttempts
+        this.sessionStats.accuracy = Math.round((this.correctCount / this.totalAttempts) * 100)
+        this.currentStep = 4
+        this.clearSession()
+      } else {
+        this.currentQuestion = ''
+        this.currentAnswer = ''
+        this.showFeedback = false
+      }
+    },
+    playAgain: function() {
+      this.currentStep = 1
+      this.correctCount = 0
+      this.totalAttempts = 0
+      this.showFeedback = false
+      this.currentQuestion = ''
+      this.currentAnswer = ''
+      this.selectedType = null
+      this.sessionStartTime = null
+      this.clearSession()
+    },
+    saveSession: function() {
+      var session = {
+        step: this.currentStep,
+        targetN: this.targetN,
+        selectedType: this.selectedType,
+        correctCount: this.correctCount,
+        totalAttempts: this.totalAttempts,
+        currentQuestion: this.currentQuestion,
+        currentAnswer: this.currentAnswer,
+        sessionStartTime: this.sessionStartTime
+      }
+      try {
+        uni.setStorageSync(STORAGE_KEY, session)
+      } catch (e) {}
+    },
+    restoreSession: function() {
+      try {
+        var session = uni.getStorageSync(STORAGE_KEY)
+        if (session) {
+          this.currentStep = session.step || 1
+          this.targetN = session.targetN || 5
+          this.selectedType = session.selectedType || null
+          this.correctCount = session.correctCount || 0
+          this.totalAttempts = session.totalAttempts || 0
+          this.currentQuestion = session.currentQuestion || ''
+          this.currentAnswer = session.currentAnswer || ''
+          this.sessionStartTime = session.sessionStartTime || null
+        }
+      } catch (e) {}
+    },
+    clearSession: function() {
+      try {
+        uni.removeStorageSync(STORAGE_KEY)
+      } catch (e) {}
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #FFF8E1 0%, #f5f7fa 100%);
+  padding-bottom: 100rpx;
+}
+
+.nav-bar {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 80rpx 30rpx 20rpx;
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+}
+
+.nav-back {
+  width: 60rpx;
+  height: 60rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.nav-back-icon {
+  font-size: 36rpx;
+  color: #fff;
+}
+
+.nav-title {
+  flex: 1;
+  text-align: center;
+  font-size: 34rpx;
+  font-weight: bold;
+  color: #fff;
+}
+
+.nav-placeholder {
+  width: 60rpx;
+}
+
+/* ===== 步骤进度 ===== */
+.step-progress {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  padding: 30rpx 40rpx;
+  background: #fff;
+}
+
+.step-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  flex: 1;
+  position: relative;
+}
+
+.step-item + .step-item::before {
+  content: '';
+  position: absolute;
+  top: 18rpx;
+  left: -50%;
+  width: 100%;
+  height: 4rpx;
+  background: #e0e0e0;
+  z-index: 0;
+}
+
+.step-done + .step-item::before,
+.step-item.step-active + .step-item::before {
+  background: #e0e0e0;
+}
+
+.step-item.step-done + .step-item::before {
+  background: #FFD700;
+}
+
+.step-circle {
+  width: 36rpx;
+  height: 36rpx;
+  border-radius: 50%;
+  background: #e0e0e0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  position: relative;
+  z-index: 1;
+}
+
+.step-active .step-circle {
+  background: #FFD700;
+}
+
+.step-done .step-circle {
+  background: #52c41a;
+}
+
+.step-num {
+  font-size: 20rpx;
+  color: #fff;
+  font-weight: bold;
+}
+
+.step-label {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 10rpx;
+}
+
+.step-active .step-label {
+  color: #B8860B;
+  font-weight: bold;
+}
+
+.step-done .step-label {
+  color: #52c41a;
+}
+
+/* ===== 步骤内容 ===== */
+.step-content {
+  padding: 30rpx;
+}
+
+.content-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 40rpx;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+
+.content-title {
+  font-size: 34rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  text-align: center;
+  margin-bottom: 10rpx;
+}
+
+.content-desc {
+  font-size: 26rpx;
+  color: #999;
+  display: block;
+  text-align: center;
+  margin-bottom: 40rpx;
+}
+
+/* ===== Step1: 目标选择 ===== */
+.target-selector {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 20rpx;
+}
+
+.target-label {
+  margin-right: 30rpx;
+}
+
+.target-label-text {
+  font-size: 28rpx;
+  color: #666;
+}
+
+.target-control {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+}
+
+.target-btn {
+  width: 60rpx;
+  height: 60rpx;
+  border-radius: 50%;
+  background: #FFF8E1;
+  border: 2rpx solid #FFD700;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  font-size: 36rpx;
+  color: #B8860B;
+  font-weight: bold;
+}
+
+.target-btn:active {
+  opacity: 0.7;
+}
+
+.target-value-wrap {
+  margin: 0 30rpx;
+  text-align: center;
+}
+
+.target-value {
+  font-size: 72rpx;
+  font-weight: bold;
+  color: #B8860B;
+  display: block;
+}
+
+.target-unit {
+  font-size: 28rpx;
+  color: #999;
+}
+
+.target-range {
+  text-align: center;
+  margin-bottom: 40rpx;
+}
+
+.range-text {
+  font-size: 24rpx;
+  color: #ccc;
+}
+
+.start-btn-wrap {
+  text-align: center;
+}
+
+.start-btn {
+  display: inline-block;
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+  padding: 20rpx 80rpx;
+  border-radius: 44rpx;
+  box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.4);
+}
+
+.start-btn:active {
+  opacity: 0.8;
+}
+
+.start-btn-text {
+  font-size: 30rpx;
+  color: #fff;
+  font-weight: bold;
+}
+
+/* ===== Step2: 题型选择 ===== */
+.type-grid {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  justify-content: space-between;
+}
+
+.type-card {
+  width: calc(50% - 10rpx);
+  padding: 24rpx;
+  margin-bottom: 20rpx;
+  background: #f9f9f9;
+  border-radius: 16rpx;
+  border: 2rpx solid transparent;
+  position: relative;
+  text-align: center;
+}
+
+.type-card:active {
+  border-color: #FFD700;
+}
+
+.type-recommended {
+  border-color: #FFD700;
+  background: #FFF8E1;
+}
+
+.type-icon {
+  font-size: 40rpx;
+  display: block;
+  margin-bottom: 8rpx;
+}
+
+.type-name {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+
+.type-desc {
+  font-size: 22rpx;
+  color: #999;
+  display: block;
+  margin-top: 4rpx;
+}
+
+.type-recommend-badge {
+  position: absolute;
+  top: -8rpx;
+  right: -8rpx;
+  background: #FF6B35;
+  border-radius: 12rpx;
+  padding: 2rpx 12rpx;
+}
+
+.badge-text {
+  font-size: 20rpx;
+  color: #fff;
+  font-weight: bold;
+}
+
+/* ===== Step3: 出题 ===== */
+.step3-header {
+  display: flex;
+  flex-direction: row;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 30rpx;
+}
+
+.selected-type-label {
+  font-size: 28rpx;
+  color: #B8860B;
+  font-weight: bold;
+}
+
+.step3-progress {
+  font-size: 26rpx;
+  color: #666;
+}
+
+.question-section, .answer-section {
+  margin-bottom: 30rpx;
+}
+
+.question-label, .answer-label {
+  font-size: 28rpx;
+  color: #333;
+  font-weight: 500;
+  display: block;
+  margin-bottom: 12rpx;
+}
+
+.question-input {
+  width: 100%;
+  min-height: 160rpx;
+  background: #f5f7fa;
+  border-radius: 12rpx;
+  padding: 20rpx;
+  font-size: 26rpx;
+  color: #333;
+  box-sizing: border-box;
+}
+
+.answer-input {
+  width: 100%;
+  height: 72rpx;
+  background: #f5f7fa;
+  border-radius: 12rpx;
+  padding: 0 20rpx;
+  font-size: 26rpx;
+  color: #333;
+  box-sizing: border-box;
+}
+
+.char-count {
+  font-size: 22rpx;
+  color: #ccc;
+  text-align: right;
+  display: block;
+  margin-top: 6rpx;
+}
+
+.submit-btn-wrap {
+  text-align: center;
+  margin-top: 20rpx;
+}
+
+.submit-btn {
+  display: inline-block;
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+  padding: 20rpx 80rpx;
+  border-radius: 44rpx;
+  box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.4);
+}
+
+.submit-btn.disabled {
+  opacity: 0.5;
+}
+
+.submit-btn-text {
+  font-size: 30rpx;
+  color: #fff;
+  font-weight: bold;
+}
+
+/* ===== 反馈 ===== */
+.feedback-wrap {
+  margin-top: 30rpx;
+}
+
+.feedback-card {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 24rpx;
+  border-radius: 16rpx;
+  margin-bottom: 20rpx;
+}
+
+.feedback-correct {
+  background: #f0fff4;
+  border: 2rpx solid #52c41a;
+}
+
+.feedback-wrong {
+  background: #fff5f5;
+  border: 2rpx solid #ff4d4f;
+}
+
+.feedback-icon {
+  font-size: 48rpx;
+  margin-right: 20rpx;
+}
+
+.feedback-info {
+  flex: 1;
+}
+
+.feedback-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+
+.feedback-subtitle {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 4rpx;
+  display: block;
+}
+
+.next-btn-wrap {
+  text-align: center;
+}
+
+.next-btn {
+  display: inline-block;
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+  padding: 16rpx 60rpx;
+  border-radius: 40rpx;
+}
+
+.next-btn:active {
+  opacity: 0.8;
+}
+
+.next-btn-text {
+  font-size: 28rpx;
+  color: #fff;
+  font-weight: bold;
+}
+
+/* ===== Step4: 庆祝 ===== */
+.celebration-card {
+  background: #fff;
+  border-radius: 24rpx;
+  padding: 60rpx 40rpx;
+  text-align: center;
+  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
+}
+
+.celebration-icon {
+  font-size: 120rpx;
+  display: block;
+  margin-bottom: 20rpx;
+}
+
+.celebration-title {
+  font-size: 40rpx;
+  font-weight: bold;
+  color: #B8860B;
+  display: block;
+  margin-bottom: 8rpx;
+}
+
+.celebration-subtitle {
+  font-size: 28rpx;
+  color: #999;
+  display: block;
+  margin-bottom: 40rpx;
+}
+
+.stats-grid {
+  display: flex;
+  flex-direction: row;
+  justify-content: center;
+  margin-bottom: 40rpx;
+}
+
+.stat-item {
+  text-align: center;
+  margin: 0 30rpx;
+}
+
+.stat-value {
+  font-size: 48rpx;
+  font-weight: bold;
+  color: #B8860B;
+  display: block;
+}
+
+.stat-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-top: 6rpx;
+  display: block;
+}
+
+.celebration-actions {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.action-btn {
+  width: 60%;
+  padding: 20rpx 0;
+  border-radius: 44rpx;
+  margin-bottom: 20rpx;
+  text-align: center;
+}
+
+.action-btn.primary {
+  background: linear-gradient(135deg, #FFD700, #FFA500);
+  box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.4);
+}
+
+.action-btn.secondary {
+  background: #fff;
+  border: 2rpx solid #ddd;
+}
+
+.action-btn-text {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #fff;
+}
+
+.secondary-text {
+  color: #666;
+}
+</style>

+ 178 - 0
cfc-frontend/pages/wisdom/training-hub.vue

@@ -0,0 +1,178 @@
+<template>
+  <view class="container">
+    <view class="header-bar">
+      <text class="back-btn" @click="goBack">← 返回</text>
+      <text class="header-title">认知训练中心</text>
+      <text class="header-placeholder"></text>
+    </view>
+
+    <view class="desc-section">
+      <text class="desc-text">选择训练游戏,提升你的认知能力</text>
+    </view>
+
+    <view class="game-card" @click="goGame('schulte')">
+      <view class="game-card-left">
+        <text class="game-icon">🎯</text>
+      </view>
+      <view class="game-card-center">
+        <text class="game-name">舒尔特方格</text>
+        <text class="game-desc">提升专注力和反应速度</text>
+      </view>
+      <text class="game-arrow">›</text>
+    </view>
+
+    <view class="game-card" @click="goGame('1a2b')">
+      <view class="game-card-left">
+        <text class="game-icon">🔢</text>
+      </view>
+      <view class="game-card-center">
+        <text class="game-name">猜数字</text>
+        <text class="game-desc">锻炼逻辑思维和推理能力</text>
+      </view>
+      <text class="game-arrow">›</text>
+    </view>
+
+    <view class="note-section">
+      <text class="note-text">更多训练游戏正在开发中,敬请期待</text>
+    </view>
+  </view>
+</template>
+
+<script>
+export default {
+  data: function() {
+    return {
+      childId: null
+    }
+  },
+  onLoad: function(options) {
+    if (options && options.childId) {
+      this.childId = options.childId
+    }
+  },
+  methods: {
+    goBack: function() {
+      uni.navigateBack()
+    },
+    goGame: function(game) {
+      var url = ''
+      if (game === 'schulte') {
+        url = '/pages/games/schulte'
+      } else if (game === '1a2b') {
+        url = '/pages/games/1a2b'
+      }
+      if (url) {
+        uni.navigateTo({ url: url })
+      }
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  min-height: 100vh;
+  background: linear-gradient(180deg, #FFF8E1 0%, #f5f7fa 100%);
+  padding-bottom: 60rpx;
+}
+
+.header-bar {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 80rpx 30rpx 30rpx;
+}
+
+.back-btn {
+  font-size: 30rpx;
+  color: #B8860B;
+  padding: 10rpx;
+}
+
+.header-title {
+  font-size: 36rpx;
+  font-weight: bold;
+  color: #B8860B;
+}
+
+.header-placeholder {
+  width: 60rpx;
+}
+
+.desc-section {
+  padding: 0 30rpx 30rpx;
+}
+
+.desc-text {
+  font-size: 26rpx;
+  color: #999;
+  text-align: center;
+  display: block;
+}
+
+.game-card {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  background: #fff;
+  margin: 0 30rpx 20rpx;
+  padding: 30rpx;
+  border-radius: 20rpx;
+  box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.12);
+  border-left: 8rpx solid #FFD700;
+}
+
+.game-card:active {
+  opacity: 0.8;
+}
+
+.game-card-left {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 20rpx;
+  background: #FFF8E1;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-right: 20rpx;
+}
+
+.game-icon {
+  font-size: 40rpx;
+}
+
+.game-card-center {
+  flex: 1;
+}
+
+.game-name {
+  font-size: 30rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 6rpx;
+}
+
+.game-desc {
+  font-size: 24rpx;
+  color: #999;
+  display: block;
+}
+
+.game-arrow {
+  font-size: 40rpx;
+  color: #ccc;
+  margin-left: 10rpx;
+}
+
+.note-section {
+  padding: 40rpx 30rpx;
+  text-align: center;
+}
+
+.note-text {
+  font-size: 24rpx;
+  color: #ccc;
+}
+</style>