Explorar el Código

chore: clean up deprecated components and pages

移除40个废弃文件:guideFamilyTask, ActionArticleRecommend,
EnergyWeekCard, FunTaskItem, HealthCheckinCard, HealthDimensionsSection,
HealthLikeButton, HealthTips, MediaRecorder, OctopusGraph, PearlDiagram,
PointsProgressRing, RepurchaseReminder, SkeletonBlock, TaskItem/TaskReminder,
UserQuickEntry, WisdomTips; assessment/body/membership/parent/register-child/
wisdom_temp 等老旧页面; teacherGuard router
liaoxg hace 3 meses
padre
commit
37d2d39a6a
Se han modificado 40 ficheros con 0 adiciones y 10637 borrados
  1. 0 45
      cfc-frontend/api/guideFamilyTask.js
  2. 0 222
      cfc-frontend/components/ActionArticleRecommend.vue
  3. 0 83
      cfc-frontend/components/EnergyWeekCard.vue
  4. 0 97
      cfc-frontend/components/FunTaskItem.vue
  5. 0 187
      cfc-frontend/components/HealthCheckinCard.vue
  6. 0 164
      cfc-frontend/components/HealthDimensionsSection.vue
  7. 0 64
      cfc-frontend/components/HealthLikeButton.vue
  8. 0 98
      cfc-frontend/components/HealthTips.vue
  9. 0 311
      cfc-frontend/components/MediaRecorder.vue
  10. 0 266
      cfc-frontend/components/OctopusGraph.vue
  11. 0 193
      cfc-frontend/components/PearlDiagram.vue
  12. 0 124
      cfc-frontend/components/PointsProgressRing.vue
  13. 0 120
      cfc-frontend/components/RepurchaseReminder.vue
  14. 0 193
      cfc-frontend/components/SkeletonBlock.vue
  15. 0 57
      cfc-frontend/components/TaskItem.vue
  16. 0 92
      cfc-frontend/components/TaskReminder.vue
  17. 0 94
      cfc-frontend/components/UserQuickEntry.vue
  18. 0 111
      cfc-frontend/components/WisdomTips.vue
  19. 0 361
      cfc-frontend/pages/assessment/child-info.vue
  20. 0 267
      cfc-frontend/pages/assessment/order.vue
  21. 0 712
      cfc-frontend/pages/assessment/record.vue
  22. 0 517
      cfc-frontend/pages/body/checkin.vue
  23. 0 221
      cfc-frontend/pages/body/exercise-record.vue
  24. 0 317
      cfc-frontend/pages/body/food-list.vue
  25. 0 189
      cfc-frontend/pages/body/growth-curve.vue
  26. 0 1009
      cfc-frontend/pages/body/health-report.vue
  27. 0 190
      cfc-frontend/pages/body/health-trend.vue
  28. 0 241
      cfc-frontend/pages/body/meal-record.vue
  29. 0 249
      cfc-frontend/pages/body/recipe-recommend.vue
  30. 0 242
      cfc-frontend/pages/body/sleep-record.vue
  31. 0 161
      cfc-frontend/pages/body/water-record.vue
  32. 0 694
      cfc-frontend/pages/membership/index.vue
  33. 0 278
      cfc-frontend/pages/parent/invite/index.vue
  34. 0 82
      cfc-frontend/pages/register-child/index.vue
  35. 0 410
      cfc-frontend/pages/wisdom/wisdom_temp/cognitive-report.vue
  36. 0 371
      cfc-frontend/pages/wisdom/wisdom_temp/index.vue
  37. 0 311
      cfc-frontend/pages/wisdom/wisdom_temp/member-wisdom-detail.vue
  38. 0 921
      cfc-frontend/pages/wisdom/wisdom_temp/self-quiz.vue
  39. 0 359
      cfc-frontend/pages/wisdom/wisdom_temp/training-hub.vue
  40. 0 14
      cfc-frontend/router/teacherGuard.js

+ 0 - 45
cfc-frontend/api/guideFamilyTask.js

@@ -1,45 +0,0 @@
-import request from '@/utils/request';
-
-/**
- * 获取关联家庭列表(教师)
- */
-export function fetchBoundFamilies() {
-  return request.get('/api/guide/families');
-}
-
-/**
- * 获取家庭任务概览
- */
-export function fetchFamilyOverview(familyId) {
-  return request.get(`/api/guide/families/${familyId}/overview`);
-}
-
-/**
- * 获取孩子任务分页列表
- */
-export function fetchChildTasks(familyId, childId, page = 1, size = 10, status) {
-  const params = { page, size };
-  if (status) params.status = status;
-  return request.get(`/api/guide/families/${familyId}/children/${childId}/tasks`, { params });
-}
-
-/**
- * 获取孩子待审核任务列表
- */
-export function fetchPendingReviewTasks(familyId, childId) {
-  return request.get(`/api/guide/families/${familyId}/children/${childId}/pending-review`);
-}
-
-/**
- * 审核单个任务
- */
-export function reviewTask(familyId, taskId, approved, comment) {
-  return request.post(`/api/guide/families/${familyId}/tasks/${taskId}/review`, { approved, comment });
-}
-
-/**
- * 批量审核任务
- */
-export function batchReviewTasks(familyId, taskIds, approved) {
-  return request.post(`/api/guide/families/${familyId}/tasks/batch-review?approved=${approved}`, taskIds);
-}

+ 0 - 222
cfc-frontend/components/ActionArticleRecommend.vue

@@ -1,222 +0,0 @@
-<template>
-  <view class="rec-section">
-    <view class="rec-header">
-      <view class="rec-header-left">
-        <text class="rec-icon">&#x1F4D6;</text>
-        <text class="rec-title">推荐阅读</text>
-      </view>
-      <text class="rec-more" @click="$emit('moreArticles')">更多 ›</text>
-    </view>
-
-    <!-- 无数据 -->
-    <view v-if="!displayArticles || displayArticles.length === 0" class="rec-empty">
-      <text class="rec-empty-text">{{ isLoggedIn ? '暂无阅读推荐' : '登录后查看更多文章' }}</text>
-    </view>
-
-    <!-- 卡片列表 -->
-    <view v-else class="rec-list">
-      <view
-        class="rec-card"
-        v-for="(article, idx) in displayArticles"
-        :key="idx"
-        @click="$emit('articleClick', article)"
-      >
-        <!-- 封面图(可选) -->
-        <view class="rec-card-cover" v-if="article.coverImage">
-          <image class="rec-cover-img" :src="article.coverImage" mode="aspectFill" />
-          <view class="rec-cover-dim" :style="{ background: article.categoryColor || gradient(idx) }">{{ article.category || '推荐文章' }}</view>
-        </view>
-
-        <!-- 文字区 -->
-        <view class="rec-card-body" :class="{ 'rec-card-body-no-cover': !article.coverImage }">
-          <view class="rec-category-row" v-if="!article.coverImage">
-            <text class="rec-category-tag" :style="{ background: article.categoryColor || gradient(idx) }">{{ article.category || '推荐文章' }}</text>
-          </view>
-          <text class="rec-card-title">{{ article.title }}</text>
-          <text class="rec-card-desc" v-if="article.summary">{{ article.summary }}</text>
-          <view class="rec-card-foot">
-            <text class="rec-card-date" v-if="article.date">{{ article.date }}</text>
-            <view class="rec-card-stats">
-              <text class="rec-stat">&#x1F441; {{ article.views || '0' }}</text>
-              <text class="rec-stat">&#x2764; {{ article.likes || '0' }}</text>
-            </view>
-          </view>
-        </view>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-var defaultGradients = [
-  'linear-gradient(135deg, #10B981, #34D399)',
-  'linear-gradient(135deg, #059669, #10B981)',
-  'linear-gradient(135deg, #047857, #059669)',
-  'linear-gradient(135deg, #34D399, #6EE7B7)',
-  'linear-gradient(135deg, #10B981, #A7F3D0)'
-]
-
-export default {
-  name: 'ActionArticleRecommend',
-  props: {
-    articles: { type: Array, default: function() { return [] } },
-    isLoggedIn: { type: Boolean, default: false }
-  },
-  computed: {
-    displayArticles: function() {
-      return this.articles && this.articles.length ? this.articles : []
-    }
-  },
-  methods: {
-    gradient: function(idx) {
-      return defaultGradients[idx % defaultGradients.length]
-    }
-  }
-}
-</script>
-
-<style scoped>
-.rec-section {
-  margin: 20rpx 20rpx 12rpx;
-}
-.rec-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 16rpx;
-  padding: 0 4rpx;
-}
-.rec-header-left {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-.rec-icon {
-  font-size: 34rpx;
-  margin-right: 10rpx;
-}
-.rec-title {
-  font-size: 32rpx;
-  font-weight: 700;
-  color: #065F46;
-  letter-spacing: 1rpx;
-}
-.rec-more {
-  font-size: 24rpx;
-  color: #10B981;
-  font-weight: 500;
-}
-
-/* 空状态 */
-.rec-empty {
-  display: flex;
-  justify-content: center;
-  padding: 60rpx 0;
-}
-.rec-empty-text {
-  font-size: 24rpx;
-  color: #9CA3AF;
-}
-
-/* 卡片列表 */
-.rec-list {
-  display: flex;
-  flex-direction: column;
-}
-.rec-card {
-  background: #FFFFFF;
-  border-radius: 24rpx;
-  overflow: hidden;
-  box-shadow: 0 4rpx 16rpx rgba(16, 185, 129, 0.08);
-  margin-bottom: 20rpx;
-}
-.rec-card:active {
-  opacity: 0.92;
-  transform: scale(0.985);
-}
-
-/* 封面图 */
-.rec-card-cover {
-  position: relative;
-  width: 100%;
-  height: 260rpx;
-  background: #f0f0f0;
-}
-.rec-cover-img {
-  width: 100%;
-  height: 100%;
-}
-.rec-cover-dim {
-  position: absolute;
-  bottom: 0;
-  left: 0;
-  right: 0;
-  padding: 10rpx 20rpx;
-  background: rgba(0,0,0,0.45);
-}
-.rec-cover-dim text {
-  font-size: 20rpx;
-  color: #fff;
-  font-weight: 500;
-}
-
-/* 文字内容区 */
-.rec-card-body {
-  padding: 24rpx 24rpx 20rpx;
-}
-.rec-card-body-no-cover {
-  padding-top: 28rpx;
-}
-.rec-category-row {
-  margin-bottom: 12rpx;
-}
-.rec-category-tag {
-  display: inline-block;
-  font-size: 20rpx;
-  color: #FFFFFF;
-  font-weight: 500;
-  padding: 4rpx 20rpx;
-  border-radius: 20rpx;
-}
-.rec-card-title {
-  font-size: 28rpx;
-  font-weight: 700;
-  color: #1F2937;
-  line-height: 1.45;
-  display: block;
-  margin-bottom: 8rpx;
-}
-.rec-card-desc {
-  font-size: 24rpx;
-  color: #6B7280;
-  line-height: 1.55;
-  display: block;
-  margin-bottom: 16rpx;
-  display: -webkit-box;
-  -webkit-box-orient: vertical;
-  -webkit-line-clamp: 2;
-  overflow: hidden;
-  text-overflow: ellipsis;
-}
-.rec-card-foot {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-}
-.rec-card-date {
-  font-size: 22rpx;
-  color: #9CA3AF;
-}
-.rec-card-stats {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-.rec-stat {
-  font-size: 22rpx;
-  color: #9CA3AF;
-  margin-left: 20rpx;
-}
-</style>

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

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

+ 0 - 97
cfc-frontend/components/FunTaskItem.vue

@@ -1,97 +0,0 @@
-<template>
-  <view class="fun-task-item" :class="{ completed: task.status === 'completed', urgent: isUrgent }">
-    <view class="task-icon">{{ getCategoryIcon() }}</view>
-    <view class="task-content">
-      <view class="task-title">{{ task.title }}</view>
-      <view class="task-meta">
-        <text class="points-badge">+{{ task.points }}分</text>
-        <text class="deadline">{{ getTimeLeft() }}</text>
-      </view>
-    </view>
-    <view class="task-action">
-      <button v-if="task.status === 'pending'" class="complete-btn" @click="$emit('complete')">
-        <text class="btn-icon">✨</text>
-        <text>完成</text>
-      </button>
-      <view v-else class="completed-badge">
-        <text class="check-icon">✅</text>
-        <text>已完成</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  name: 'FunTaskItem',
-  props: {
-    task: { type: Object, required: true }
-  },
-  computed: {
-    isUrgent() {
-      if (this.task.status === 'completed') return false
-      if (!this.task.deadline) return false
-      const deadline = new Date(this.task.deadline)
-      const now = new Date()
-      const hoursLeft = (deadline - now) / (1000 * 60 * 60)
-      return hoursLeft < 2
-    }
-  },
-  methods: {
-    getCategoryIcon() {
-      const icons = {
-        '学习类': '📚',
-        '生活类': '🏠',
-        '运动类': '⚽',
-        '其他': '⭐'
-      }
-      return icons[this.task.category] || '📝'
-    },
-    getTimeLeft() {
-      if (!this.task.deadline) return ''
-      const deadline = new Date(this.task.deadline)
-      const now = new Date()
-      const diff = deadline - now
-      if (diff < 0) return '已超时'
-      const hours = Math.floor(diff / (1000 * 60 * 60))
-      const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60))
-      if (hours > 24) {
-        const days = Math.floor(hours / 24)
-        return `${days}天后`
-      }
-      if (hours > 0) return `${hours}小时后`
-      return `${minutes}分钟后`
-    }
-  }
-}
-</script>
-
-<style scoped>
-.fun-task-item {
-  display: flex;
-  align-items: center;
-  background: var(--surface, #ffffff);
-  border-radius: var(--radius-md, 20rpx);
-  padding: 24rpx;
-  margin-bottom: 20rpx;
-  border: 2rpx solid var(--border, #FED7AA);
-  box-shadow: var(--shadow-sm);
-  transition: transform 0.15s ease;
-}
-.fun-task-item:active { transform: scale(0.98); }
-.fun-task-item.completed { opacity: 0.65; background: #FEF2F2; border-color: #FECACA; }
-.fun-task-item.urgent { border: 2rpx solid var(--color-error, #EF4444); animation: pulse-urgent 1.5s infinite; }
-@keyframes pulse-urgent { 0%, 100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.3); } 50% { box-shadow: 0 0 0 12rpx rgba(239,68,68,0); } }
-.task-icon { font-size: 44rpx; width: 72rpx; height: 72rpx; background: rgba(249,115,22,0.1); border-radius: 16rpx; display: flex; align-items: center; justify-content: center; margin-right: 20rpx; }
-.task-content { flex: 1; }
-.task-title { font-size: 28rpx; font-weight: 600; color: var(--text, #3D2E1E); margin-bottom: 8rpx; }
-.task-meta { display: flex; align-items: center; gap: 16rpx; }
-.points-badge { background: linear-gradient(135deg, #FEF3C7, #FDE68A); color: #92400E; font-size: 22rpx; padding: 4rpx 14rpx; border-radius: 20rpx; font-weight: 700; }
-.deadline { font-size: 22rpx; color: var(--muted, #94A3B8); }
-.task-action { margin-left: 16rpx; }
-.complete-btn { display: flex; align-items: center; background: linear-gradient(135deg, var(--color-primary, #F97316), #FB923C); color: #fff; border: none; border-radius: 30rpx; padding: 14rpx 28rpx; font-size: 24rpx; font-weight: 600; box-shadow: 0 4rpx 8rpx rgba(249,115,22,0.3); }
-.complete-btn:active { transform: scale(0.95); opacity: 0.9; }
-.btn-icon { margin-right: 6rpx; }
-.completed-badge { display: flex; align-items: center; background: rgba(34,197,94,0.1); color: var(--color-success, #22C55E); padding: 12rpx 20rpx; border-radius: 30rpx; font-size: 24rpx; font-weight: 600; }
-.check-icon { margin-right: 6rpx; }
-</style>

+ 0 - 187
cfc-frontend/components/HealthCheckinCard.vue

@@ -1,187 +0,0 @@
-<template>
-  <view class="health-checkin-card">
-    <view class="hcc-header">
-      <text class="hcc-title">🏃 健康打卡</text>
-      <text class="hcc-more" v-if="streak > 0">连续 {{ streak }} 天</text>
-    </view>
-    <view class="hcc-body">
-      <template v-if="todayCheckedIn">
-        <view class="hcc-done">
-          <text class="hcc-done-icon">✓</text>
-          <text class="hcc-done-text">今日已打卡</text>
-        </view>
-        <text class="hcc-energy">+{{ earnedEnergy }} 身能量</text>
-      </template>
-      <template v-else>
-        <view class="hcc-undo" @click="doCheckin">
-          <text class="hcc-btn">立即打卡</text>
-          <text class="hcc-btn-tip">完成健康打卡获得身能量</text>
-        </view>
-      </template>
-    </view>
-  </view>
-</template>
-
-<script>
-import api from '@/utils/api.js'
-export default {
-  props: {
-    childId: { type: Number, default: null }
-  },
-  data: function() {
-    return {
-      checkins: [],
-      loading: false
-    }
-  },
-  computed: {
-    todayCheckedIn: function() {
-      if (!this.checkins || this.checkins.length === 0) return false
-      var today = this.getDateString(new Date())
-      return this.checkins.some(function(c) {
-        if (!c.checkinDate) return false
-        var d = c.checkinDate
-        if (typeof d === 'string') {
-          return d.slice(0, 10) === today
-        }
-        return this.getDateString(new Date(d)).slice(0, 10) === today
-      }.bind(this))
-    },
-    streak: function() {
-      if (!this.checkins || this.checkins.length === 0) return 0
-      var sorted = this.checkins.slice().sort(function(a, b) {
-        if (!a.checkinDate || !b.checkinDate) return 0
-        var da = new Date(a.checkinDate)
-        var db = new Date(b.checkinDate)
-        return db - da
-      })
-      var streak = 0
-      var today = new Date()
-      today.setHours(0, 0, 0, 0)
-      var checkDate = new Date(today)
-      for (var i = 0; i < sorted.length; i++) {
-        var cDate = new Date(sorted[i].checkinDate)
-        cDate.setHours(0, 0, 0, 0)
-        var diff = Math.round((checkDate - cDate) / (1000 * 60 * 60 * 24))
-        if (diff === 0 || diff === 1) {
-          if (diff === 1) streak++
-          if (diff === 0 && streak === 0) streak = 1
-          checkDate = cDate
-          checkDate.setDate(checkDate.getDate() - 1)
-        } else {
-          break
-        }
-      }
-      return streak
-    },
-    earnedEnergy: function() {
-      if (!this.checkins || this.checkins.length === 0) return 5
-      var last = this.checkins[0]
-      return last && last.earnedEnergy ? last.earnedEnergy : 5
-    }
-  },
-  created: function() {
-    this.loadCheckins()
-  },
-  methods: {
-    getDateString: function(date) {
-      var y = date.getFullYear()
-      var m = ('' + (date.getMonth() + 1)).padStart(2, '0')
-      var d = ('' + date.getDate()).padStart(2, '0')
-      return y + '-' + m + '-' + d
-    },
-    loadCheckins: function() {
-      var self = this
-      if (!this.childId) return
-      var now = new Date()
-      var yearMonth = now.getFullYear() + '-' + ('' + (now.getMonth() + 1)).padStart(2, '0')
-      api.healthCheckinList({
-        childId: self.childId,
-        yearMonth: yearMonth
-      }).then(function(res) {
-        if (res.data) {
-          self.checkins = res.data
-        }
-      }).catch(function() {
-        self.checkins = []
-      })
-    },
-    doCheckin: function() {
-      var self = this
-      if (!this.childId) {
-        uni.showToast({ title: '请先选择孩子', icon: 'none' })
-        return
-      }
-      if (this.loading) return
-      this.loading = true
-      api.healthCheckinCreate({
-        childId: self.childId,
-        checkinDate: self.getDateString(new Date()),
-        behaviors: [],
-        mood: 'happy'
-      }).then(function(res) {
-        self.loading = false
-        uni.showToast({ title: '打卡成功!+' + (res.data && res.data.earnedEnergy || 5) + '身能量', icon: 'none' })
-        self.checkins.unshift(res.data)
-      }).catch(function() {
-        self.loading = false
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.health-checkin-card {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  margin: 20rpx 20rpx 0 20rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.hcc-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.hcc-title { font-size: 28rpx; font-weight: 700; color: #333; }
-.hcc-more { font-size: 22rpx; color: #F97316; }
-.hcc-body { display: flex; align-items: center; justify-content: center; }
-.hcc-done {
-  display: flex;
-  align-items: center;
-  gap: 12rpx;
-  padding: 20rpx 0;
-}
-.hcc-done-icon {
-  width: 56rpx;
-  height: 56rpx;
-  border-radius: 28rpx;
-  background: linear-gradient(135deg, #10B981, #34D399);
-  color: #fff;
-  font-size: 28rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.hcc-done-text { font-size: 30rpx; color: #10B981; font-weight: 600; }
-.hcc-energy { font-size: 22rpx; color: #999; margin-left: 16rpx; }
-.hcc-undo {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 12rpx 0;
-  width: 100%;
-}
-.hcc-btn {
-  background: linear-gradient(135deg, #FF8C42, #FFB074);
-  color: #fff;
-  font-size: 28rpx;
-  font-weight: 600;
-  padding: 16rpx 60rpx;
-  border-radius: 40rpx;
-}
-.hcc-btn:active { opacity: 0.8; }
-.hcc-btn-tip { font-size: 22rpx; color: #999; margin-top: 8rpx; }
-</style>

+ 0 - 164
cfc-frontend/components/HealthDimensionsSection.vue

@@ -1,164 +0,0 @@
-<template>
-  <view class="health-dimensions-section">
-    <view class="section-header">
-      <text class="section-title">七维健康全景</text>
-      <text class="section-more" @click="goHealthDimensions">查看详情 ›</text>
-    </view>
-
-    <!-- 加载态 -->
-    <view v-if="!healthData" class="loading-state">
-      <text class="loading-text">加载中...</text>
-    </view>
-
-    <!-- 维度列表(无数据时显示7项默认值,分数为0) -->
-    <view v-else class="dimension-grid">
-      <view
-        class="dimension-item"
-        v-for="(dim, idx) in displayDimensions"
-        :key="idx"
-        @click="goDimensionDetail(dim)">
-        <text class="dimension-icon">{{ getDimIcon(dim.dimension || dim.key) }}</text>
-        <text class="dimension-name">{{ dim.label }}</text>
-        <view class="dimension-score-bar">
-          <view
-            class="dimension-score-fill"
-            :style="'width:' + Math.min(100, (dim.score || 0)) + '%'"></view>
-        </view>
-        <text class="dimension-score">{{ dim.score || 0 }}分</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-var DIM_ICONS = {
-  growth: '\u{1F4AA}',
-  sleep: '\u{1F634}',
-  vision: '\u{1F441}',
-  immunity: '\u{1F6E1}',
-  nutrition: '\u{1F966}',
-  gut: '\u{1F9A9}',
-  exercise: '\u{1F3C3}',
-  body: '\u{1F3C3}',
-  mind: '\u{1F9E0}',
-  wisdom: '\u{1F4D6}',
-  action: '\u{1F331}',
-  wealth: '\u{1F4B0}',
-  social: '\u{1F91D}',
-  family: '\u{1F46A}'
-}
-
-var DEFAULT_DIMENSIONS = [
-  { dimension: 'growth', label: '成长发育', score: 0 },
-  { dimension: 'sleep', label: '睡眠', score: 0 },
-  { dimension: 'vision', label: '视力', score: 0 },
-  { dimension: 'immunity', label: '免疫力', score: 0 },
-  { dimension: 'nutrition', label: '营养', score: 0 },
-  { dimension: 'gut', label: '肠道', score: 0 },
-  { dimension: 'exercise', label: '运动', score: 0 }
-]
-
-export default {
-  props: {
-    healthData: { type: Object, default: null },
-    activeChildId: { type: [Number, String], default: null }
-  },
-  computed: {
-    displayDimensions: function() {
-      if (this.healthData && this.healthData.dimensions && this.healthData.dimensions.length > 0) {
-        return this.healthData.dimensions
-      }
-      return DEFAULT_DIMENSIONS
-    }
-  },
-  methods: {
-    getDimIcon: function(key) {
-      return DIM_ICONS[key] || '\u{2B50}'
-    },
-    goHealthDimensions: function() {
-      // FIX: 移除阻塞弹窗,始终允许导航
-      uni.navigateTo({ url: '/pages/body-detail/health-dimensions?childId=' + (this.activeChildId || '') })
-    },
-    goDimensionDetail: function(dim) {
-      var key = dim.dimension || dim.key
-      if (!key) return
-      uni.navigateTo({ url: '/pages/body-detail/dimension-detail?dimension=' + key + '&childId=' + (this.activeChildId || '') })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.health-dimensions-section {
-  margin: 20rpx 20rpx;
-  background: #FFFFFF;
-  border-radius: 24rpx;
-  padding: 24rpx 24rpx 32rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.06);
-}
-.section-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 24rpx;
-}
-.section-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-}
-.section-more {
-  font-size: 24rpx;
-  color: #FF8C42;
-}
-.loading-state, .empty-state {
-  display: flex;
-  justify-content: center;
-  padding: 40rpx 0;
-}
-.loading-text, .empty-tip {
-  font-size: 24rpx;
-  color: #999;
-}
-.dimension-grid {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 20rpx;
-}
-.dimension-item {
-  width: calc(33.33% - 14rpx);
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 16rpx 0;
-}
-.dimension-icon {
-  font-size: 40rpx;
-  margin-bottom: 8rpx;
-}
-.dimension-name {
-  font-size: 24rpx;
-  color: #333;
-  font-weight: 500;
-  margin-bottom: 10rpx;
-}
-.dimension-score-bar {
-  width: 100%;
-  height: 8rpx;
-  background: #F5F5F5;
-  border-radius: 4rpx;
-  overflow: hidden;
-  margin-bottom: 6rpx;
-}
-.dimension-score-fill {
-  height: 100%;
-  background: linear-gradient(90deg, #FF8C42, #FFB088);
-  border-radius: 4rpx;
-  transition: width 0.3s;
-}
-.dimension-score {
-  font-size: 22rpx;
-  color: #FF8C42;
-  font-weight: 500;
-}
-</style>

+ 0 - 64
cfc-frontend/components/HealthLikeButton.vue

@@ -1,64 +0,0 @@
-<template>
-  <view class="like-btn" :class="{ liked: isLikedVal }" @click="handleClick">
-    <text class="like-icon">{{ isLikedVal ? '❤️' : '🤍' }}</text>
-    <text class="like-count">{{ count }}</text>
-  </view>
-</template>
-
-<script>
-export default {
-  props: {
-    actionType: { type: String, default: '' },
-    actionId: { type: [Number, String], default: '' },
-    toUserId: { type: [Number, String], default: '' },
-    isLiked: { type: Boolean, default: false },
-    count: { type: Number, default: 0 }
-  },
-  data() {
-    return {
-      isLikedVal: this.isLiked
-    }
-  },
-  watch: {
-    isLiked: function(val) {
-      this.isLikedVal = val
-    }
-  },
-  methods: {
-    handleClick: function() {
-      if (this.isLikedVal) return
-      this.isLikedVal = true
-      this.$emit('like', {
-        actionType: this.actionType,
-        actionId: this.actionId,
-        toUserId: this.toUserId
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.like-btn {
-  display: inline-flex;
-  align-items: center;
-  gap: 6rpx;
-  padding: 8rpx 16rpx;
-  border-radius: 999rpx;
-  border: 1rpx solid #E2E8F0;
-  background: #FAFAFA;
-  transition: all 0.2s;
-}
-.like-btn.liked {
-  border-color: #FECDD3;
-  background: #FFF1F2;
-}
-.like-icon { font-size: 24rpx; }
-.like-count {
-  font-size: 22rpx;
-  color: #64748B;
-  min-width: 20rpx;
-  text-align: center;
-}
-.liked .like-count { color: #E11D48; }
-</style>

+ 0 - 98
cfc-frontend/components/HealthTips.vue

@@ -1,98 +0,0 @@
-<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)">
-        <text class="tip-icon">📋</text>
-        <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: 'body' },
-    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-extra/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 { font-size: 36rpx; margin-right: 16rpx; flex-shrink: 0; }
-.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>

+ 0 - 311
cfc-frontend/components/MediaRecorder.vue

@@ -1,311 +0,0 @@
-<template>
-	<view class="media-recorder">
-		<view class="toolbar">
-			<!-- 拍照按钮 -->
-			<button class="tool-btn" @click="chooseImage">
-				<text class="icon">📷</text>
-				<text>拍照</text>
-			</button>
-
-			<!-- 录像按钮 -->
-			<button class="tool-btn" @click="chooseVideo">
-				<text class="icon">📹</text>
-				<text>录像</text>
-			</button>
-
-			<!-- 录音按钮 -->
-			<button class="tool-btn" @click="toggleRecording">
-				<text class="icon">🎤</text>
-				<text>{{ isRecording ? '停止' : '录音' }}</text>
-			</button>
-
-			<!-- 文字按钮 -->
-			<button class="tool-btn" @click="showTextInput = !showTextInput">
-				<text class="icon">✏️</text>
-				<text>文字</text>
-			</button>
-		</view>
-
-		<!-- 文字输入框 -->
-		<view v-if="showTextInput" class="text-input-area">
-			<textarea
-				v-model="textContent"
-				placeholder="输入文字记录..."
-				class="text-input"
-				maxlength="500"
-			/>
-			<button class="btn-add-text" @click="addTextNote">添加</button>
-		</view>
-
-		<!-- 媒体预览列表 -->
-		<view class="media-list" v-if="mediaItems.length > 0">
-			<view class="media-item" v-for="(item, index) in mediaItems" :key="index">
-				<image v-if="item.type === 'image'" :src="item.url" class="media-preview" mode="aspectFill" />
-				<view v-else-if="item.type === 'video'" class="media-preview video-preview">
-					<text class="icon">📹</text>
-				</view>
-				<view v-else-if="item.type === 'audio'" class="media-preview audio-preview">
-					<text class="icon">🎤</text>
-					<text class="duration">{{ item.duration }}s</text>
-				</view>
-				<view v-else-if="item.type === 'text'" class="media-preview text-preview">
-					<text>{{ item.content }}</text>
-				</view>
-				<button class="btn-delete" @click="removeMedia(index)">×</button>
-			</view>
-		</view>
-	</view>
-</template>
-
-<script>
-import config from '@/config.js'
-export default {
-	name: 'MediaRecorder',
-	props: {
-		taskId: {
-			type: Number,
-			required: true
-		},
-		creatorId: {
-			type: Number,
-			required: true
-		}
-	},
-	data() {
-		return {
-			mediaItems: [],
-			textContent: '',
-			showTextInput: false,
-			isRecording: false,
-			recorderManager: null,
-			recordingStartTime: 0
-		}
-	},
-	created() {
-		// 初始化录音管理器
-		this.recorderManager = uni.getRecorderManager()
-		this.recorderManager.onStop((res) => {
-			const duration = (Date.now() - this.recordingStartTime) / 1000
-			this.uploadAudio(res.tempFilePath, duration)
-		})
-	},
-	methods: {
-		// 选择图片
-		chooseImage() {
-			uni.chooseImage({
-				count: 9,
-				sizeType: ['compressed'],
-				sourceType: ['album', 'camera'],
-				success: (res) => {
-					res.tempFilePaths.forEach(path => {
-						this.uploadFile(path, 'image')
-					})
-				}
-			})
-		},
-
-		// 选择视频
-		chooseVideo() {
-			uni.chooseVideo({
-				sourceType: ['album', 'camera'],
-				maxDuration: 60,
-				success: (res) => {
-					this.uploadFile(res.tempFilePath, 'video')
-				}
-			})
-		},
-
-		// 开始/停止录音
-		toggleRecording() {
-			if (this.isRecording) {
-				this.recorderManager.stop()
-				this.isRecording = false
-			} else {
-				this.recordingStartTime = Date.now()
-				this.recorderManager.start()
-				this.isRecording = true
-			}
-		},
-
-		// 添加文字记录
-		addTextNote() {
-			if (!this.textContent.trim()) {
-				uni.showToast({ title: '请输入内容', icon: 'none' })
-				return
-			}
-			this.mediaItems.push({
-				type: 'text',
-				content: this.textContent,
-				uploaded: false
-			})
-			this.textContent = ''
-			this.showTextInput = false
-			this.$emit('change', this.mediaItems)
-		},
-
-		// 上传文件
-		uploadFile(filePath, type) {
-			uni.showLoading({ title: '上传中...' })
-			uni.uploadFile({
-				url: config.api('/api/media/upload'),
-				filePath: filePath,
-				name: 'file',
-				formData: {
-					taskId: this.taskId,
-					creatorId: this.creatorId,
-					type: type
-				},
-				header: {
-					'Authorization': 'Bearer ' + uni.getStorageSync('token')
-				},
-				success: (res) => {
-					const data = JSON.parse(res.data)
-					if (data.code === 200) {
-						this.mediaItems.push({
-							id: data.data.id,
-							type: type,
-							url: data.data.url,
-							uploaded: true
-						})
-						this.$emit('change', this.mediaItems)
-					} else {
-						uni.showToast({ title: data.message || '上传失败', icon: 'none' })
-					}
-				},
-				fail: () => {
-					uni.showToast({ title: '上传失败', icon: 'none' })
-				},
-				complete: () => {
-					uni.hideLoading()
-				}
-			})
-		},
-
-		// 上传音频
-		uploadAudio(filePath, duration) {
-			this.uploadFile(filePath, 'audio')
-			this.mediaItems[this.mediaItems.length - 1].duration = Math.round(duration)
-		},
-
-		// 删除媒体
-		removeMedia(index) {
-			this.mediaItems.splice(index, 1)
-			this.$emit('change', this.mediaItems)
-		},
-
-		// 获取所有媒体数据
-		getMediaItems() {
-			return this.mediaItems
-		}
-	}
-}
-</script>
-
-<style lang="scss" scoped>
-.media-recorder {
-	margin: 20rpx 0;
-}
-
-.toolbar {
-	display: flex;
-	gap: 20rpx;
-	justify-content: space-around;
-	padding: 20rpx;
-	background: #fff;
-	border-radius: 12rpx;
-}
-
-.tool-btn {
-	display: flex;
-	flex-direction: column;
-	align-items: center;
-	padding: 20rpx;
-	background: #f8f8f8;
-	border-radius: 12rpx;
-	font-size: 24rpx;
-
-	.icon {
-		font-size: 48rpx;
-		margin-bottom: 10rpx;
-	}
-}
-
-.text-input-area {
-	margin-top: 20rpx;
-	background: #fff;
-	border-radius: 12rpx;
-	padding: 20rpx;
-}
-
-.text-input {
-	width: 100%;
-	min-height: 150rpx;
-	border: 1rpx solid #ddd;
-	border-radius: 8rpx;
-	padding: 20rpx;
-}
-
-.btn-add-text {
-	margin-top: 20rpx;
-	background: #F97316;
-	color: #fff;
-}
-
-.media-list {
-	display: flex;
-	flex-wrap: wrap;
-	gap: 20rpx;
-	margin-top: 20rpx;
-}
-
-.media-item {
-	position: relative;
-	width: 200rpx;
-	height: 200rpx;
-}
-
-.media-preview {
-	width: 100%;
-	height: 100%;
-	border-radius: 8rpx;
-	object-fit: cover;
-}
-
-.video-preview, .audio-preview, .text-preview {
-	display: flex;
-	flex-direction: column;
-	align-items: center;
-	justify-content: center;
-	background: #f8f8f8;
-
-	.icon {
-		font-size: 64rpx;
-	}
-
-	.duration {
-		font-size: 24rpx;
-		color: #666;
-		margin-top: 10rpx;
-	}
-}
-
-.text-preview {
-	padding: 20rpx;
-	font-size: 24rpx;
-	text-align: center;
-}
-
-.btn-delete {
-	position: absolute;
-	top: -10rpx;
-	right: -10rpx;
-	width: 40rpx;
-	height: 40rpx;
-	border-radius: 50%;
-	background: #ff4444;
-	color: #fff;
-	font-size: 32rpx;
-	line-height: 40rpx;
-	padding: 0;
-}
-</style>

+ 0 - 266
cfc-frontend/components/OctopusGraph.vue

@@ -1,266 +0,0 @@
-<template>
-  <view class="family-relationship-wrapper">
-    <!-- ===== 重要关系维护 ===== -->
-    <view class="contact-section" v-if="isLoggedIn">
-    <view class="contact-header">
-      <text class="contact-section-title">重要关系</text>
-      <view class="contact-header-right">
-        <text class="contact-more" @click="$emit('refresh')">刷新</text>
-        <text class="contact-import-btn" @click="$emit('import')">+ 导入</text>
-        <text class="contact-manage-btn" @click="$emit('manage')">⚙️</text>
-      </view>
-    </view>
-    <scroll-view class="contact-scroll" scroll-x enable-flex>
-      <view class="contact-scroll-inner">
-        <view class="contact-card-wrap" v-for="item in contactList" :key="item.id" @click="$emit('contactClick', item)">
-          <ContactCard :contact="item" />
-        </view>
-        <view class="contact-card-wrap contact-add-card" @click="$emit('import')">
-          <view class="add-card-inner">
-            <text class="add-card-icon">+</text>
-            <text class="add-card-label">导入通讯录</text>
-          </view>
-        </view>
-      </view>
-    </scroll-view>
-    <view class="contact-empty" v-if="contactList.length === 0">
-      <text class="contact-empty-text">暂无重要联系人,点击导入添加</text>
-    </view>
-  </view>
-
-  <!-- ===== 关系健康概览 ===== -->
-  <view class="rq-section" v-if="isLoggedIn && healthAlerts.length > 0">
-    <view class="rq-header">
-      <text class="rq-title">关系健康</text>
-      <text class="rq-more" @click="$emit('questionnaire')">去做问卷</text>
-    </view>
-    <!-- 健康预警列表 -->
-    <view class="rq-alerts" v-if="healthAlerts.length > 0">
-      <view class="rq-alert-card" v-for="alert in healthAlerts" :key="alert.memberId" @click="$emit('interactionLog', alert.memberId)">
-        <view class="rq-alert-icon">
-          <text class="rq-alert-emoji">⚠️</text>
-        </view>
-        <view class="rq-alert-info">
-          <text class="rq-alert-name">{{ alert.memberName }}</text>
-          <text class="rq-alert-desc">已{{ alert.daysSinceInteraction }}天无互动</text>
-        </view>
-        <view class="rq-alert-action">
-          <text class="rq-alert-btn">去互动</text>
-        </view>
-      </view>
-    </view>
-  <!-- 快捷操作 -->
-    <view class="rq-quick-actions">
-      <view class="rq-action-btn" @click="$emit('interactionLog')">
-        <text class="rq-action-icon">📝</text>
-        <text class="rq-action-label">记录互动</text>
-      </view>
-      <view class="rq-action-btn" @click="$emit('questionnaire')">
-        <text class="rq-action-icon">🤝</text>
-        <text class="rq-action-label">做关系问卷</text>
-      </view>
-    </view>
-  </view>
-  </view>
-</template>
-
-<script>
-export default {
-  props: {
-    contactList: { type: Array, default: function() { return [] } },
-    healthAlerts: { type: Array, default: function() { return [] } },
-    isLoggedIn: { type: Boolean, default: false }
-  }
-}
-</script>
-
-<style scoped>
-/* ===== 重要关系 ===== */
-.contact-section {
-  margin: 20rpx 20rpx 0;
-}
-.contact-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.contact-section-title {
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333;
-}
-.contact-header-right {
-  display: flex;
-  gap: 16rpx;
-  align-items: center;
-}
-.contact-more {
-  font-size: 24rpx;
-  color: #999;
-}
-.contact-import-btn {
-  font-size: 24rpx;
-  color: #F97316;
-  font-weight: 500;
-}
-.contact-manage-btn {
-  font-size: 30rpx;
-  color: #999;
-  padding: 4rpx;
-}
-.contact-scroll {
-  white-space: nowrap;
-  overflow: hidden;
-}
-.contact-scroll-inner {
-  display: flex;
-  flex-direction: row;
-  gap: 16rpx;
-  padding: 8rpx 0 16rpx;
-}
-.contact-card-wrap {
-  flex-shrink: 0;
-  width: 220rpx;
-}
-.contact-card-wrap:active {
-  opacity: 0.8;
-}
-.contact-add-card {
-  width: 160rpx;
-}
-.add-card-inner {
-  width: 160rpx;
-  height: 280rpx;
-  background: #fff;
-  border-radius: 16rpx;
-  border: 2rpx dashed #ddd;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  gap: 12rpx;
-}
-.add-card-icon {
-  font-size: 48rpx;
-  color: #ccc;
-}
-.add-card-label {
-  font-size: 22rpx;
-  color: #999;
-}
-.contact-empty {
-  padding: 40rpx 0;
-  text-align: center;
-}
-.contact-empty-text {
-  font-size: 26rpx;
-  color: #ccc;
-}
-
-/* ===== 关系健康概览 ===== */
-.rq-section {
-  margin: 20rpx 20rpx 0;
-}
-.rq-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.rq-title {
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333;
-}
-.rq-more {
-  font-size: 24rpx;
-  color: #F97316;
-  font-weight: 500;
-}
-/* 健康预警卡片 */
-.rq-alerts {
-  display: flex;
-  flex-direction: column;
-  gap: 12rpx;
-  margin-bottom: 16rpx;
-}
-.rq-alert-card {
-  background: #FFF7ED;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  display: flex;
-  align-items: center;
-  gap: 16rpx;
-  border: 1rpx solid #FED7AA;
-}
-.rq-alert-card:active {
-  opacity: 0.8;
-}
-.rq-alert-icon {
-  width: 64rpx;
-  height: 64rpx;
-  border-radius: 50%;
-  background: #fff;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-}
-.rq-alert-emoji {
-  font-size: 32rpx;
-}
-.rq-alert-info {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  gap: 4rpx;
-}
-.rq-alert-name {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #333;
-}
-.rq-alert-desc {
-  font-size: 24rpx;
-  color: #F97316;
-}
-.rq-alert-action {
-  flex-shrink: 0;
-}
-.rq-alert-btn {
-  font-size: 24rpx;
-  color: #fff;
-  background: #F97316;
-  padding: 8rpx 20rpx;
-  border-radius: 20rpx;
-}
-/* 快捷操作 */
-.rq-quick-actions {
-  display: flex;
-  gap: 16rpx;
-  margin-top: 8rpx;
-}
-.rq-action-btn {
-  flex: 1;
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  gap: 10rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.rq-action-btn:active {
-  opacity: 0.8;
-}
-.rq-action-icon {
-  font-size: 36rpx;
-}
-.rq-action-label {
-  font-size: 26rpx;
-  color: #333;
-  font-weight: 500;
-}
-</style>

+ 0 - 193
cfc-frontend/components/PearlDiagram.vue

@@ -1,193 +0,0 @@
-<template>
-  <view class="pearl-section">
-    <view class="pearl-header">
-      <text class="pearl-title">我的珍珠圈</text>
-      <text class="pearl-discover-btn" @click="$emit('discover')">发现新圈子</text>
-    </view>
-
-    <view v-if="circles && circles.length > 0" class="pearl-scroll-wrap">
-      <scroll-view class="pearl-scroll" scroll-x enable-flex>
-        <view class="pearl-scroll-inner">
-          <view
-            class="pearl-item"
-            v-for="item in circles"
-            :key="item.id"
-            @click="$emit('circleClick', item)">
-            <view class="pearl-icon-wrap">
-              <text class="pearl-icon">{{ typeIcon(item.type) }}</text>
-            </view>
-            <text class="pearl-name">{{ item.name }}</text>
-            <text class="pearl-count">{{ item.memberCount || 0 }}人</text>
-          </view>
-          <view class="pearl-add-item" @click="$emit('discover')">
-            <view class="pearl-add-icon-wrap">
-              <text class="pearl-add-icon">+</text>
-            </view>
-            <text class="pearl-add-label">发现更多</text>
-          </view>
-        </view>
-      </scroll-view>
-    </view>
-
-    <view v-else class="pearl-empty">
-      <text class="pearl-empty-text">暂无圈子</text>
-      <text class="pearl-empty-action" @click="$emit('discover')">去发现新圈子</text>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  name: 'PearlDiagram',
-  props: {
-    circles: {
-      type: Array,
-      default: function() { return [] }
-    },
-    isLoggedIn: {
-      type: Boolean,
-      default: false
-    }
-  },
-  methods: {
-    typeIcon: function(type) {
-      var map = {
-        activity: '\u{1F3AF}',
-        ability: '\u{1F9E0}',
-        health: '\u{1F4AA}',
-        product: '\u{1F6CD}',
-        provider: '\u{1F468}\u200D\u{1F3EB}',
-        topic: '\u{1F4AC}',
-        hobby: '\u{1F3A8}'
-      }
-      return map[type] || '\u{1F30D}'
-    }
-  }
-}
-</script>
-
-<style scoped>
-.pearl-section {
-  margin: 20rpx 20rpx 0;
-}
-.pearl-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.pearl-title {
-  font-size: 30rpx;
-  font-weight: 600;
-  color: #333;
-}
-.pearl-discover-btn {
-  font-size: 24rpx;
-  color: #F97316;
-  font-weight: 500;
-}
-.pearl-scroll {
-  white-space: nowrap;
-  overflow: hidden;
-}
-.pearl-scroll-inner {
-  display: flex;
-  flex-direction: row;
-  gap: 16rpx;
-  padding: 8rpx 0 16rpx;
-}
-.pearl-item {
-  flex-shrink: 0;
-  width: 160rpx;
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 20rpx 12rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.pearl-item:active {
-  opacity: 0.8;
-}
-.pearl-icon-wrap {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  background: #FFF7ED;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 10rpx;
-}
-.pearl-icon {
-  font-size: 36rpx;
-}
-.pearl-name {
-  font-size: 24rpx;
-  color: #333;
-  font-weight: 500;
-  text-align: center;
-  white-space: normal;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  display: -webkit-box;
-  -webkit-line-clamp: 2;
-  -webkit-box-orient: vertical;
-  max-width: 140rpx;
-}
-.pearl-count {
-  font-size: 20rpx;
-  color: #999;
-  margin-top: 4rpx;
-}
-.pearl-add-item {
-  flex-shrink: 0;
-  width: 120rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  padding: 20rpx 8rpx;
-}
-.pearl-add-item:active {
-  opacity: 0.7;
-}
-.pearl-add-icon-wrap {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  border: 2rpx dashed #ddd;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 10rpx;
-}
-.pearl-add-icon {
-  font-size: 36rpx;
-  color: #ccc;
-}
-.pearl-add-label {
-  font-size: 22rpx;
-  color: #999;
-}
-.pearl-empty {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 40rpx 0;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  gap: 12rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.pearl-empty-text {
-  font-size: 26rpx;
-  color: #ccc;
-}
-.pearl-empty-action {
-  font-size: 26rpx;
-  color: #F97316;
-  font-weight: 500;
-}
-</style>

+ 0 - 124
cfc-frontend/components/PointsProgressRing.vue

@@ -1,124 +0,0 @@
-<template>
-  <view class="points-ring-container">
-    <view class="ring-wrapper">
-      <svg class="progress-ring" viewBox="0 0 120 120">
-        <!-- Background ring -->
-        <circle
-          class="progress-ring__background"
-          cx="60"
-          cy="60"
-          r="54"
-          fill="none"
-          stroke="#f0f9ff"
-          stroke-width="10"
-        />
-        <!-- Progress ring -->
-        <circle
-          class="progress-ring__progress"
-          cx="60"
-          cy="60"
-          r="54"
-          fill="none"
-          :stroke="progressColor"
-          stroke-width="10"
-          stroke-linecap="round"
-          :stroke-dasharray="circumference"
-          :stroke-dashoffset="dashOffset"
-        />
-      </svg>
-      <view class="ring-content">
-        <text class="points-value">{{ points }}</text>
-        <text class="points-label">积分</text>
-      </view>
-    </view>
-    <view class="milestone-info" v-if="milestone">
-      <text class="milestone-text">🎯 距离下一个奖励还差 {{ milestone.remaining }} 分</text>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  name: 'PointsProgressRing',
-  props: {
-    points: { type: Number, default: 0 },
-    currentMilestone: { type: Number, default: 0 },
-    nextMilestone: { type: Number, default: 100 }
-  },
-  computed: {
-    circumference() {
-      return 2 * Math.PI * 54
-    },
-    progress() {
-      if (this.nextMilestone <= 0) return 0
-      return Math.min(this.currentMilestone / this.nextMilestone, 1)
-    },
-    dashOffset() {
-      return this.circumference * (1 - this.progress)
-    },
-    progressColor() {
-      if (this.progress >= 0.75) return '#22C55E' // green (success)
-      if (this.progress >= 0.5) return '#F97316'  // orange (primary)
-      return '#FB923C' // light orange
-    },
-    milestone() {
-      if (this.nextMilestone > this.points) {
-        return {
-          remaining: this.nextMilestone - this.points,
-          target: this.nextMilestone
-        }
-      }
-      return null
-    }
-  }
-}
-</script>
-
-<style scoped>
-.points-ring-container {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-.ring-wrapper {
-  position: relative;
-  width: 120px;
-  height: 120px;
-}
-.progress-ring {
-  width: 100%;
-  height: 100%;
-  transform: rotate(-90deg);
-}
-.progress-ring__progress {
-  transition: stroke-dashoffset 0.5s ease;
-}
-.ring-content {
-  position: absolute;
-  top: 50%;
-  left: 50%;
-  transform: translate(-50%, -50%);
-  text-align: center;
-}
-.points-value {
-  display: block;
-  font-size: 28px;
-  font-weight: 800;
-  color: var(--color-accent, #0EA5E9);
-}
-.points-label {
-  display: block;
-  font-size: 12px;
-  color: #94A3B8;
-}
-.milestone-info {
-  margin-top: 8px;
-  padding: 6px 12px;
-  background: linear-gradient(135deg, #FEF3C7, #FDE68A);
-  border-radius: 20px;
-}
-.milestone-text {
-  font-size: 12px;
-  color: #92400E;
-}
-</style>

+ 0 - 120
cfc-frontend/components/RepurchaseReminder.vue

@@ -1,120 +0,0 @@
-<template>
-  <view class="repurchase-container" v-if="reminders.length > 0">
-    <view class="rr-header">
-      <text class="rr-title">复购提醒</text>
-    </view>
-    <view
-      class="rr-item"
-      v-for="item in reminders"
-      :key="item.id"
-      @click="goProduct(item)">
-      <image class="rr-image" :src="item.coverImage" mode="aspectFill" />
-      <view class="rr-info">
-        <text class="rr-name">{{ item.productName }}</text>
-        <text class="rr-price" v-if="item.price">¥{{ (item.price / 100).toFixed(2) }}</text>
-        <text class="rr-hint">上次购买已过 {{ item.reminderDays }} 天,需要补货吗?</text>
-      </view>
-      <view class="rr-action">
-        <text class="rr-btn">去看看</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getRepurchaseReminders, clickRepurchaseReminder } from '../utils/api.js'
-
-export default {
-  data() {
-    return {
-      reminders: []
-    }
-  },
-  mounted() {
-    this.loadReminders()
-  },
-  methods: {
-    loadReminders: function() {
-      var self = this
-      getRepurchaseReminders().then(function(res) {
-        if (res.code === 200 && res.data) {
-          self.reminders = res.data
-        }
-      }).catch(function() {})
-    },
-    goProduct: function(item) {
-      clickRepurchaseReminder({ id: item.id })
-      uni.navigateTo({
-        url: '/pages/shop/detail?id=' + item.productId + '&from=repurchase'
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.repurchase-container {
-  background: #fff;
-  border-radius: 20rpx;
-  margin: 20rpx 30rpx;
-  padding: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.rr-header {
-  margin-bottom: 16rpx;
-}
-.rr-title {
-  font-size: 30rpx;
-  font-weight: 700;
-  color: #333;
-}
-.rr-item {
-  display: flex;
-  align-items: center;
-  padding: 16rpx 0;
-  border-top: 1rpx solid #f5f5f5;
-}
-.rr-image {
-  width: 120rpx;
-  height: 120rpx;
-  border-radius: 12rpx;
-  flex-shrink: 0;
-}
-.rr-info {
-  flex: 1;
-  margin-left: 16rpx;
-  min-width: 0;
-}
-.rr-name {
-  font-size: 26rpx;
-  font-weight: 600;
-  color: #333;
-  display: block;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  white-space: nowrap;
-}
-.rr-price {
-  font-size: 24rpx;
-  color: #F97316;
-  margin-top: 4rpx;
-  display: block;
-}
-.rr-hint {
-  font-size: 22rpx;
-  color: #999;
-  margin-top: 4rpx;
-  display: block;
-}
-.rr-action {
-  flex-shrink: 0;
-  margin-left: 12rpx;
-}
-.rr-btn {
-  background: #F97316;
-  color: #fff;
-  font-size: 22rpx;
-  padding: 8rpx 20rpx;
-  border-radius: 24rpx;
-}
-</style>

+ 0 - 193
cfc-frontend/components/SkeletonBlock.vue

@@ -1,193 +0,0 @@
-<template>
-  <view class="skeleton-wrapper">
-    <block v-for="(item, index) in computedItems" :key="index">
-      <!-- Banner: 通栏大矩块 -->
-      <view v-if="type === 'banner'" class="skeleton-banner" :style="bannerStyle"></view>
-      <!-- Card: 中等卡片 + 内部小矩块 -->
-      <view v-else-if="type === 'card'" class="skeleton-card" :style="cardStyle">
-        <view class="skeleton-card-inner"></view>
-        <view class="skeleton-card-line"></view>
-        <view class="skeleton-card-line skeleton-card-line--short"></view>
-      </view>
-      <!-- Row: 单行横条 -->
-      <view v-else-if="type === 'row'" class="skeleton-row" :style="rowStyle"></view>
-      <!-- Circle: 圆形 -->
-      <view v-else-if="type === 'circle'" class="skeleton-circle" :style="circleStyle"></view>
-      <!-- Text: 多行文字占位 -->
-      <view v-else class="skeleton-text-group">
-        <view
-          v-for="(row, ri) in textRows"
-          :key="ri"
-          class="skeleton-text-row"
-          :style="{ width: row.width }"
-        ></view>
-      </view>
-    </block>
-  </view>
-</template>
-
-<script>
-export default {
-  name: 'SkeletonBlock',
-  props: {
-    type: {
-      type: String,
-      default: 'row'
-    },
-    width: {
-      type: String,
-      default: ''
-    },
-    height: {
-      type: String,
-      default: ''
-    },
-    count: {
-      type: Number,
-      default: 1
-    }
-  },
-  computed: {
-    computedItems() {
-      var arr = []
-      for (var i = 0; i < this.count; i++) {
-        arr.push(i)
-      }
-      return arr
-    },
-    baseShimmer() {
-      // Reuse design system shimmer from uni.scss
-      return 'linear-gradient(90deg, #E2E8F0 25%, #EDF2F7 50%, #E2E8F0 75%)'
-    },
-    bannerStyle() {
-      var style = 'width: 100%; height: 200rpx; border-radius: 16rpx;'
-      if (this.width) {
-        style += 'width: ' + this.width + ';'
-      }
-      if (this.height) {
-        style += 'height: ' + this.height + ';'
-      }
-      return style
-    },
-    cardStyle() {
-      var style = 'width: 100%; height: 160rpx;'
-      if (this.width) {
-        style += 'width: ' + this.width + ';'
-      }
-      if (this.height) {
-        style += 'height: ' + this.height + ';'
-      }
-      return style
-    },
-    rowStyle() {
-      var style = 'width: 100%; height: 32rpx; margin-bottom: 12rpx;'
-      if (this.width) {
-        style += 'width: ' + this.width + ';'
-      }
-      if (this.height) {
-        style += 'height: ' + this.height + ';'
-      }
-      return style
-    },
-    circleStyle() {
-      var style = 'width: 80rpx; height: 80rpx; border-radius: 50%;'
-      if (this.width) {
-        style += 'width: ' + this.width + ';'
-      }
-      if (this.height) {
-        style += 'height: ' + this.height + ';'
-      }
-      return style
-    },
-    textRows() {
-      return [
-        { width: '100%' },
-        { width: '100%' },
-        { width: '88%' },
-        { width: '60%' }
-      ]
-    }
-  }
-}
-</script>
-
-<style scoped>
-.skeleton-wrapper {
-  display: flex;
-  flex-direction: column;
-}
-
-/* ===== Shimmer 基础 ===== */
-.skeleton-banner,
-.skeleton-card,
-.skeleton-row,
-.skeleton-circle,
-.skeleton-card-inner,
-.skeleton-card-line,
-.skeleton-text-row {
-  background: linear-gradient(90deg, #E2E8F0 25%, #F5F9FC 50%, #E2E8F0 75%);
-  background-size: 200% 100%;
-  animation: skeleton-shimmer 1.5s ease-in-out infinite;
-  will-change: transform;
-}
-
-@keyframes skeleton-shimmer {
-  0% {
-    background-position: -200% 0;
-  }
-  100% {
-    background-position: 200% 0;
-  }
-}
-
-/* ===== Banner ===== */
-.skeleton-banner {
-  margin-bottom: 20rpx;
-}
-
-/* ===== Card ===== */
-.skeleton-card {
-  background: none !important;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  margin-bottom: 20rpx;
-  display: flex;
-  flex-direction: column;
-  justify-content: space-between;
-  border: 1rpx solid #E2E8F0;
-}
-.skeleton-card-inner {
-  width: 60%;
-  height: 48rpx;
-  border-radius: 8rpx;
-}
-.skeleton-card-line {
-  width: 100%;
-  height: 20rpx;
-  border-radius: 6rpx;
-  margin-top: 10rpx;
-}
-.skeleton-card-line--short {
-  width: 40%;
-}
-
-/* ===== Row ===== */
-.skeleton-row {
-  border-radius: 8rpx;
-}
-
-/* ===== Circle ===== */
-.skeleton-circle {
-}
-
-/* ===== Text ===== */
-.skeleton-text-group {
-  display: flex;
-  flex-direction: column;
-  gap: 12rpx;
-}
-.skeleton-text-row {
-  height: 24rpx;
-  border-radius: 6rpx;
-}
-</style>

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

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

+ 0 - 92
cfc-frontend/components/TaskReminder.vue

@@ -1,92 +0,0 @@
-<template>
-  <view class="reminder-card" v-if="hasReminders">
-    <view class="reminder-header">
-      <text class="reminder-title">{{ title }}</text>
-      <text class="reminder-count">{{ count }}项</text>
-    </view>
-    <view class="reminder-list">
-      <view class="reminder-item" v-for="task in tasks" :key="task.id" @click="goToTask(task)">
-        <text class="task-name">{{ task.title }}</text>
-        <text class="task-deadline">截止: {{ formatTime(task.deadline) }}</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  props: {
-    title: { type: String, default: '待提醒任务' },
-    tasks: { type: Array, default: () => [] }
-  },
-  computed: {
-    hasReminders() {
-      return this.tasks && this.tasks.length > 0
-    },
-    count() {
-      return this.tasks ? this.tasks.length : 0
-    }
-  },
-  methods: {
-    formatTime(dateStr) {
-      if (!dateStr) return ''
-      const d = new Date(dateStr)
-      const h = d.getHours().toString().padStart(2, '0')
-      const m = d.getMinutes().toString().padStart(2, '0')
-      return h + ':' + m
-    },
-    goToTask(task) {
-      uni.navigateTo({ url: '/pages/tasks/tasks?highlight=' + task.id })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.reminder-card {
-  background: linear-gradient(135deg, #fef3c7, #fde68a);
-  border-radius: 16rpx;
-  padding: 20rpx 24rpx;
-  margin-bottom: 16rpx;
-}
-.reminder-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 12rpx;
-}
-.reminder-title {
-  font-size: 26rpx;
-  font-weight: bold;
-  color: #92400e;
-}
-.reminder-count {
-  font-size: 22rpx;
-  color: #92400e;
-  background: rgba(255,255,255,0.5);
-  padding: 4rpx 16rpx;
-  border-radius: 20rpx;
-}
-.reminder-list {
-  display: flex;
-  flex-direction: column;
-  gap: 8rpx;
-}
-.reminder-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  background: rgba(255,255,255,0.6);
-  border-radius: 10rpx;
-  padding: 12rpx 16rpx;
-}
-.task-name {
-  font-size: 24rpx;
-  color: #333;
-  flex: 1;
-}
-.task-deadline {
-  font-size: 20rpx;
-  color: #b45309;
-}
-</style>

+ 0 - 94
cfc-frontend/components/UserQuickEntry.vue

@@ -1,94 +0,0 @@
-<template>
-  <view class="user-quick-entry">
-    <view class="user-info">
-      <text class="user-info-text">
-        👤 当前用户:{{ userName }}({{ roleName }})
-      </text>
-      <view class="child-switcher" v-if="children && children.length > 0" @click="showChildPicker">
-        <text class="child-switcher-text">
-          切换孩子:{{ currentChildName || '选择' }} ▼
-        </text>
-      </view>
-    </view>
-    <view class="quick-actions">
-      <view class="quick-btn" @click="$emit('scrollTo', 'tasks')">
-        <text class="quick-btn-icon">📋</text>
-        <text class="quick-btn-label">今日任务</text>
-      </view>
-      <view class="quick-btn" @click="$emit('scrollTo', 'activities')">
-        <text class="quick-btn-icon">🔥</text>
-        <text class="quick-btn-label">相关活动</text>
-      </view>
-      <view class="quick-btn" @click="$emit('scrollTo', 'products')">
-        <text class="quick-btn-icon">🛍️</text>
-        <text class="quick-btn-label">推荐商品</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-export default {
-  props: {
-    userName: { type: String, default: '' },
-    roleName: { type: String, default: '' },
-    currentChildName: { type: String, default: '' },
-    children: { type: Array, default: function() { return [] } }
-  },
-  methods: {
-    showChildPicker: function() {
-      var self = this
-      var items = this.children.map(function(c) { return c.childName })
-      uni.showActionSheet({
-        itemList: items,
-        success: function(res) {
-          var child = self.children[res.tapIndex]
-          if (child) {
-            // FIX: 通过 Vuex store mutation 切换孩子,确保状态一致性
-            self.$store.commit('switchToChild', child.childId)
-            uni.setStorageSync('currentChildId', child.childId)
-            self.$emit('childChanged', child)
-          }
-        }
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.user-quick-entry {
-  margin: 0 30rpx 20rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 20rpx 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
-}
-.user-info {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-.user-info-text { font-size: 26rpx; color: #666; }
-.child-switcher-text { font-size: 24rpx; color: #F97316; }
-.quick-actions {
-  display: flex;
-  flex-direction: row;
-}
-.quick-btn {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 16rpx 0;
-  background: #FFF7ED;
-  border-radius: 16rpx;
-  margin-right: 16rpx;
-}
-.quick-btn:last-child { margin-right: 0; }
-.quick-btn:active { opacity: 0.7; }
-.quick-btn-icon { font-size: 36rpx; margin-bottom: 6rpx; }
-.quick-btn-label { font-size: 22rpx; color: #666; }
-</style>

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

@@ -1,111 +0,0 @@
-<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-extra/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>

+ 0 - 361
cfc-frontend/pages/assessment/child-info.vue

@@ -1,361 +0,0 @@
-<template>
-  <view class="container">
-    <view class="header">
-      <text class="title">孩子信息</text>
-      <text class="subtitle">请填写孩子的详细信息,用于测评档案</text>
-    </view>
-
-    <!-- 基本信息 -->
-    <view class="section">
-      <view class="section-title">基本信息</view>
-      <view class="form-group">
-        <view class="form-label">孩子姓名</view>
-        <input class="form-input" v-model="form.childName" placeholder="请输入孩子姓名" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">出生日期</view>
-        <picker mode="date" :value="form.childBirthday" @change="onBirthdayChange">
-          <view class="picker-value">{{ form.childBirthday || '请选择出生日期' }}</view>
-        </picker>
-      </view>
-      <view class="form-group">
-        <view class="form-label">性别</view>
-        <radio-group @change="onGenderChange">
-          <label class="radio-item">
-            <radio value="男" :checked="form.childGender === '男'" />
-            <text>男</text>
-          </label>
-          <label class="radio-item">
-            <radio value="女" :checked="form.childGender === '女'" />
-            <text>女</text>
-          </label>
-        </radio-group>
-      </view>
-      <view class="form-group">
-        <view class="form-label">年龄</view>
-        <text class="form-value">{{ form.childAge || '自动计算' }}</text>
-      </view>
-    </view>
-
-    <!-- 学校信息 -->
-    <view class="section">
-      <view class="section-title">学校信息</view>
-      <view class="form-group">
-        <view class="form-label">学校名称</view>
-        <input class="form-input" v-model="form.school" placeholder="请输入学校名称" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">年级</view>
-        <input class="form-input" v-model="form.grade" placeholder="如:一年级" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">班级</view>
-        <input class="form-input" v-model="form.gradeClass" placeholder="如:3班" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">在校成绩</view>
-        <radio-group @change="onSchoolScoreChange">
-          <label class="radio-item">
-            <radio value="优秀" :checked="form.schoolScore === '优秀'" />
-            <text>优秀</text>
-          </label>
-          <label class="radio-item">
-            <radio value="良好" :checked="form.schoolScore === '良好'" />
-            <text>良好</text>
-          </label>
-          <label class="radio-item">
-            <radio value="一般" :checked="form.schoolScore === '一般'" />
-            <text>一般</text>
-          </label>
-          <label class="radio-item">
-            <radio value="较差" :checked="form.schoolScore === '较差'" />
-            <text>较差</text>
-          </label>
-        </radio-group>
-      </view>
-    </view>
-
-    <!-- 身体信息 -->
-    <view class="section">
-      <view class="section-title">身体信息</view>
-      <view class="form-row">
-        <view class="form-group half">
-          <view class="form-label">身高 (cm)</view>
-          <input class="form-input" type="number" v-model="form.height" placeholder="cm" />
-        </view>
-        <view class="form-group half">
-          <view class="form-label">体重 (kg)</view>
-          <input class="form-input" type="digit" v-model="form.weight" placeholder="kg" />
-        </view>
-      </view>
-    </view>
-
-    <!-- 家庭信息 -->
-    <view class="section">
-      <view class="section-title">家庭信息</view>
-      <view class="form-group">
-        <view class="form-label">家庭状况</view>
-        <input class="form-input" v-model="form.familyStatus" placeholder="如:双亲家庭/单亲家庭" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">父母最高学历</view>
-        <radio-group @change="onParentDegreeChange">
-          <label class="radio-item">
-            <radio value="高中及以下" :checked="form.parentDegree === '高中及以下'" />
-            <text>高中及以下</text>
-          </label>
-          <label class="radio-item">
-            <radio value="大专" :checked="form.parentDegree === '大专'" />
-            <text>大专</text>
-          </label>
-          <label class="radio-item">
-            <radio value="本科" :checked="form.parentDegree === '本科'" />
-            <text>本科</text>
-          </label>
-          <label class="radio-item">
-            <radio value="硕士" :checked="form.parentDegree === '硕士'" />
-            <text>硕士</text>
-          </label>
-          <label class="radio-item">
-            <radio value="博士" :checked="form.parentDegree === '博士'" />
-            <text>博士</text>
-          </label>
-        </radio-group>
-      </view>
-      <view class="form-group">
-        <view class="form-label">民族</view>
-        <input class="form-input" v-model="form.nation" placeholder="如:汉族" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">是否独生子女</view>
-        <radio-group @change="onSingleChildChange">
-          <label class="radio-item">
-            <radio value="是" :checked="form.singleChild === '是'" />
-            <text>是</text>
-          </label>
-          <label class="radio-item">
-            <radio value="否" :checked="form.singleChild === '否'" />
-            <text>否</text>
-          </label>
-        </radio-group>
-      </view>
-    </view>
-
-    <!-- 地址信息 -->
-    <view class="section">
-      <view class="section-title">地址信息</view>
-      <view class="form-group">
-        <view class="form-label">省份</view>
-        <input class="form-input" v-model="form.province" placeholder="请输入省份" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">城市</view>
-        <input class="form-input" v-model="form.city" placeholder="请输入城市" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">区县</view>
-        <input class="form-input" v-model="form.district" placeholder="请输入区县" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">详细地址</view>
-        <input class="form-input" v-model="form.address" placeholder="请输入详细地址" />
-      </view>
-    </view>
-
-    <!-- 紧急联系人 -->
-    <view class="section">
-      <view class="section-title">紧急联系人</view>
-      <view class="form-group">
-        <view class="form-label">姓名</view>
-        <input class="form-input" v-model="form.urgentName" placeholder="紧急联系人姓名" />
-      </view>
-      <view class="form-group">
-        <view class="form-label">电话</view>
-        <input class="form-input" type="number" v-model="form.urgentPhone" placeholder="紧急联系人电话" />
-      </view>
-    </view>
-
-    <button class="btn-submit" @click="submit">下一步</button>
-  </view>
-</template>
-
-<script>
-export default {
-  data() {
-    return {
-      flowData: {},
-      form: {
-        childName: '',
-        childBirthday: '',
-        childGender: '',
-        childAge: 0,
-        school: '',
-        grade: '',
-        gradeClass: '',
-        height: '',
-        weight: '',
-        familyStatus: '',
-        parentDegree: '',
-        nation: '',
-        province: '',
-        city: '',
-        district: '',
-        address: '',
-        singleChild: '',
-        schoolScore: '',
-        urgentName: '',
-        urgentPhone: ''
-      }
-    }
-  },
-  onLoad() {
-    this.flowData = uni.getStorageSync('assessmentFlowData') || {}
-  },
-  methods: {
-    onBirthdayChange(e) {
-      this.form.childBirthday = e.detail.value
-      this.calcAge(e.detail.value)
-    },
-    calcAge(birthday) {
-      if (!birthday) return
-      const birth = new Date(birthday)
-      const today = new Date()
-      let age = today.getFullYear() - birth.getFullYear()
-      const m = today.getMonth() - birth.getMonth()
-      if (m < 0 || (m === 0 && today.getDate() < birth.getDate())) {
-        age--
-      }
-      this.form.childAge = age > 0 ? age : 0
-    },
-    onGenderChange(e) { this.form.childGender = e.detail.value },
-    onSchoolScoreChange(e) { this.form.schoolScore = e.detail.value },
-    onParentDegreeChange(e) { this.form.parentDegree = e.detail.value },
-    onSingleChildChange(e) { this.form.singleChild = e.detail.value },
-
-    validate() {
-      const required = ['childName', 'childBirthday', 'childGender']
-      for (let i = 0; i < required.length; i++) {
-        if (!this.form[required[i]]) {
-          uni.showToast({ title: '请填写必填项', icon: 'none' })
-          return false
-        }
-      }
-      return true
-    },
-
-    submit() {
-      if (!this.validate()) return
-      uni.setStorageSync('assessmentChildInfo', this.form)
-      const f = this.flowData
-      uni.navigateTo({
-        url: '/pages/assessment/order?guideId=' + (f.guideId || '') +
-          '&guideName=' + encodeURIComponent(f.guideName || '') +
-          '&packageId=' + (f.packageId || '') +
-          '&packageName=' + encodeURIComponent(f.packageName || 'DAN专注力测评') +
-          '&childId=' + (f.childId || '') +
-          '&appointmentDate=' + (f.appointmentDate || '') +
-          '&appointmentTime=' + (f.appointmentTime || '') +
-          '&totalPrice=' + (f.totalPrice || '0') +
-          '&discountAmount=' + (f.discountAmount || '0') +
-          '&actualPrice=' + ((f.totalPrice || 0) - (f.discountAmount || 0))
-      })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  padding: 30rpx;
-  background: #F8F8F8;
-  min-height: 100vh;
-}
-.header {
-  text-align: center;
-  padding: 40rpx 0 30rpx;
-}
-.title {
-  font-size: 48rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-  margin-bottom: 16rpx;
-}
-.subtitle {
-  font-size: 26rpx;
-  color: #666;
-}
-.section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx;
-  margin-bottom: 24rpx;
-}
-.section-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 24rpx;
-  padding-bottom: 16rpx;
-  border-bottom: 1rpx solid #eee;
-}
-.form-group {
-  margin-bottom: 24rpx;
-}
-.form-group:last-child {
-  margin-bottom: 0;
-}
-.form-label {
-  font-size: 28rpx;
-  color: #333;
-  margin-bottom: 12rpx;
-  font-weight: 500;
-}
-.form-input {
-  height: 80rpx;
-  background: #F8F8F8;
-  border-radius: 10rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  color: #333;
-}
-.form-value {
-  font-size: 28rpx;
-  color: #999;
-  line-height: 80rpx;
-}
-.picker-value {
-  height: 80rpx;
-  background: #F8F8F8;
-  border-radius: 10rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  color: #333;
-  line-height: 80rpx;
-}
-.form-row {
-  display: flex;
-  gap: 20rpx;
-}
-.half {
-  flex: 1;
-}
-.radio-item {
-  display: inline-flex;
-  align-items: center;
-  margin-right: 40rpx;
-  font-size: 28rpx;
-  color: #333;
-}
-.btn-submit {
-  width: 100%;
-  height: 90rpx;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: #fff;
-  border-radius: 45rpx;
-  font-size: 32rpx;
-  font-weight: bold;
-  margin-top: 40rpx;
-  margin-bottom: 40rpx;
-}
-</style>

+ 0 - 267
cfc-frontend/pages/assessment/order.vue

@@ -1,267 +0,0 @@
-<template>
-  <view class="container">
-    <view class="header">
-      <text class="title">确认订单</text>
-      <text class="subtitle">请确认测评订单信息并完成支付</text>
-    </view>
-
-    <!-- 订单信息 -->
-    <view class="section">
-      <view class="section-title">订单详情</view>
-      <view class="info-row">
-        <text class="info-label">任务模板名称</text>
-        <text class="info-value">{{ packageName }}</text>
-      </view>
-      <view class="info-row">
-        <text class="info-label">成长规划师</text>
-        <text class="info-value">{{ guideName }}</text>
-      </view>
-      <view class="info-row">
-        <text class="info-label">预约日期</text>
-        <text class="info-value">{{ appointmentDate }}</text>
-      </view>
-      <view class="info-row">
-        <text class="info-label">预约时间</text>
-        <text class="info-value">{{ appointmentTime }}</text>
-      </view>
-    </view>
-
-    <!-- 费用信息 -->
-    <view class="section">
-      <view class="section-title">费用信息</view>
-      <view class="price-row">
-        <text class="price-label">测评费用</text>
-        <text class="price-value">{{ formatPriceWithSymbol(totalPrice) }}</text>
-      </view>
-      <view class="price-row" v-if="discountAmount > 0">
-        <text class="price-label">优惠金额</text>
-        <text class="price-value discount">-{{ formatPriceWithSymbol(discountAmount) }}</text>
-      </view>
-      <view class="price-row total">
-        <text class="price-label">实付金额</text>
-        <text class="price-value highlight">{{ formatPriceWithSymbol(actualPrice) }}</text>
-      </view>
-    </view>
-
-    <!-- 支付方式 -->
-    <view class="section">
-      <view class="section-title">支付方式</view>
-      <view class="pay-methods">
-        <view class="pay-method-item" :class="{ active: payMethod === 'wechat' }" @click="payMethod = 'wechat'">
-          <text class="pay-icon">💳</text>
-          <text class="pay-name">微信支付</text>
-          <view class="pay-radio" v-if="payMethod === 'wechat'">
-            <view class="radio-inner" />
-          </view>
-        </view>
-        <view class="pay-method-item" :class="{ active: payMethod === 'alipay' }" @click="payMethod = 'alipay'">
-          <text class="pay-icon">🔷</text>
-          <text class="pay-name">支付宝</text>
-          <view class="pay-radio" v-if="payMethod === 'alipay'">
-            <view class="radio-inner" />
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <button class="btn-submit" @click="handlePay">确认支付 {{ formatPriceWithSymbol(actualPrice) }}</button>
-  </view>
-</template>
-
-<script>
-import { createAssessmentAppointment, payAssessmentOrder } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      packageName: '',
-      guideName: '',
-      appointmentDate: '',
-      appointmentTime: '',
-      totalPrice: '0',
-      discountAmount: '0',
-      actualPrice: '0',
-      payMethod: 'wechat',
-      childId: null,
-      guideId: null,
-      packageId: null
-    }
-  },
-  onLoad(options) {
-    this.packageName = decodeURIComponent(options.packageName || 'DAN专注力测评')
-    this.guideName = decodeURIComponent(options.guideName || '')
-    this.appointmentDate = options.appointmentDate || ''
-    this.appointmentTime = options.appointmentTime || ''
-    this.totalPrice = options.totalPrice || '0'
-    this.discountAmount = options.discountAmount || '0'
-    this.actualPrice = options.actualPrice || this.totalPrice
-    this.childId = options.childId || null
-    this.guideId = options.guideId || null
-    this.packageId = options.packageId || null
-  },
-  methods: {
-    async handlePay() {
-      if (!this.guideId) {
-        uni.showToast({ title: '请选择成长规划师', icon: 'none' })
-        return
-      }
-      try {
-        uni.showLoading({ title: '创建订单中...' })
-        const res = await createAssessmentAppointment({
-          childId: this.childId,
-          guideId: this.guideId,
-          assessorId: this.guideId,
-          packageId: this.packageId,
-          appointmentDate: this.appointmentDate,
-          appointmentTime: this.appointmentTime,
-          notes: '',
-          totalPrice: this.totalPrice,
-          guideName: this.guideName,
-          packageName: this.packageName,
-          discountAmount: this.discountAmount
-        })
-        if (res.code !== 200) {
-          uni.hideLoading()
-          return
-        }
-        const orderNo = res.data.orderNo
-        uni.showLoading({ title: '支付中...' })
-        const payRes = await payAssessmentOrder(orderNo, this.payMethod)
-        uni.hideLoading()
-        if (payRes.code === 200) {
-          uni.removeStorageSync('assessmentFlowData')
-          uni.removeStorageSync('assessmentChildInfo')
-          uni.showModal({
-            title: '支付成功',
-            content: '您的测评订单已支付,请按时参加测评',
-            showCancel: false,
-            success: () => {
-              uni.redirectTo({ url: '/pages/assessment/results' })
-            }
-          })
-        } else {
-          uni.showToast({ title: payRes.message || '支付失败', icon: 'none' })
-        }
-      } catch (e) {
-        uni.hideLoading()
-        uni.showToast({ title: '操作失败,请重试', icon: 'none' })
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  padding: 30rpx;
-  background: #F8F8F8;
-  min-height: 100vh;
-}
-.header {
-  text-align: center;
-  padding: 40rpx 0 30rpx;
-}
-.title {
-  font-size: 48rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-  margin-bottom: 16rpx;
-}
-.subtitle {
-  font-size: 26rpx;
-  color: #666;
-}
-.section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx;
-  margin-bottom: 24rpx;
-}
-.section-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 24rpx;
-  padding-bottom: 16rpx;
-  border-bottom: 1rpx solid #eee;
-}
-.info-row {
-  display: flex;
-  justify-content: space-between;
-  padding: 16rpx 0;
-  font-size: 28rpx;
-}
-.info-label { color: #666; }
-.info-value { color: #333; font-weight: 500; }
-.price-row {
-  display: flex;
-  justify-content: space-between;
-  padding: 16rpx 0;
-  font-size: 28rpx;
-}
-.price-label { color: #666; }
-.price-value { color: #333; font-weight: bold; }
-.price-value.discount { color: #F97316; }
-.price-row.total {
-  margin-top: 16rpx;
-  padding-top: 24rpx;
-  border-top: 1rpx dashed #eee;
-}
-.price-value.highlight {
-  font-size: 36rpx;
-  color: #F97316;
-}
-.pay-methods {
-  display: flex;
-  flex-direction: column;
-  gap: 20rpx;
-}
-.pay-method-item {
-  display: flex;
-  align-items: center;
-  padding: 24rpx;
-  background: #F8F8F8;
-  border-radius: 12rpx;
-  border: 2rpx solid transparent;
-}
-.pay-method-item.active {
-  border-color: #667eea;
-  background: #F0F4FF;
-}
-.pay-icon {
-  font-size: 40rpx;
-  margin-right: 20rpx;
-}
-.pay-name {
-  font-size: 28rpx;
-  color: #333;
-  flex: 1;
-}
-.pay-radio {
-  width: 40rpx;
-  height: 40rpx;
-  border-radius: 50%;
-  border: 2rpx solid #667eea;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.radio-inner {
-  width: 20rpx;
-  height: 20rpx;
-  border-radius: 50%;
-  background: #667eea;
-}
-.btn-submit {
-  width: 100%;
-  height: 90rpx;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: #fff;
-  border-radius: 45rpx;
-  font-size: 32rpx;
-  font-weight: bold;
-  margin-top: 40rpx;
-  margin-bottom: 40rpx;
-}
-</style>

+ 0 - 712
cfc-frontend/pages/assessment/record.vue

@@ -1,712 +0,0 @@
-<template>
-<view class="container">
-  <view class="header">
-    <text class="title">录入测评结果</text>
-    <text class="subtitle">专业的DAN测评,记录孩子的专注力评估结果</text>
-  </view>
-
-  <!-- 孩子信息 -->
-  <view class="section">
-    <view class="section-title">孩子信息</view>
-    <view class="info-card">
-      <view class="info-row">
-        <text class="info-label">姓名</text>
-        <text class="info-value">{{ childName || '未填写' }}</text>
-      </view>
-      <view class="info-row">
-        <text class="info-label">年龄</text>
-        <text class="info-value">{{ age || '-' }}岁</text>
-      </view>
-      <view class="info-row">
-        <text class="info-label">学校</text>
-        <text class="info-value">{{ school || '-' }}</text>
-      </view>
-    </view>
-  </view>
-
-  <!-- 各项评分 -->
-  <view class="section">
-    <view class="section-title">各项评分</view>
-    <view class="score-group">
-      <view class="score-item">
-        <view class="score-header">
-          <text class="score-label">注意力</text>
-          <text class="score-value">{{ form.attentionScore }}</text>
-        </view>
-        <slider
-          :value="form.attentionScore"
-          :min="0"
-          :max="100"
-          :step="1"
-          activeColor="#667eea"
-          backgroundColor="#E8E8E8"
-          block-size="20"
-          @change="onAttentionChange"
-        />
-      </view>
-      <view class="score-item">
-        <view class="score-header">
-          <text class="score-label">专注力</text>
-          <text class="score-value">{{ form.focusScore }}</text>
-        </view>
-        <slider
-          :value="form.focusScore"
-          :min="0"
-          :max="100"
-          :step="1"
-          activeColor="#667eea"
-          backgroundColor="#E8E8E8"
-          block-size="20"
-          @change="onFocusChange"
-        />
-      </view>
-      <view class="score-item">
-        <view class="score-header">
-          <text class="score-label">记忆力</text>
-          <text class="score-value">{{ form.memoryScore }}</text>
-        </view>
-        <slider
-          :value="form.memoryScore"
-          :min="0"
-          :max="100"
-          :step="1"
-          activeColor="#667eea"
-          backgroundColor="#E8E8E8"
-          block-size="20"
-          @change="onMemoryChange"
-        />
-      </view>
-      <view class="score-item">
-        <view class="score-header">
-          <text class="score-label">逻辑力</text>
-          <text class="score-value">{{ form.logicScore }}</text>
-        </view>
-        <slider
-          :value="form.logicScore"
-          :min="0"
-          :max="100"
-          :step="1"
-          activeColor="#667eea"
-          backgroundColor="#E8E8E8"
-          block-size="20"
-          @change="onLogicChange"
-        />
-      </view>
-    </view>
-
-    <!-- 综合评分 -->
-    <view class="overall-score">
-      <text class="overall-label">综合评分</text>
-      <view class="overall-badge">
-        <text class="overall-number">{{ overallScore }}</text>
-      </view>
-    </view>
-  </view>
-
-  <!-- 测评信息 -->
-  <view class="section">
-    <view class="section-title">测评信息</view>
-    <view class="form-group">
-      <text class="form-label">测评日期</text>
-      <picker mode="date" :value="form.assessmentDate" @change="onAssessmentDateChange">
-        <view class="picker-value">
-          <text>{{ form.assessmentDate || '选择日期' }}</text>
-          <text class="picker-arrow">›</text>
-        </view>
-      </picker>
-    </view>
-    <view class="form-group">
-      <text class="form-label">测评时长(分钟)</text>
-      <input
-        class="form-input"
-        type="number"
-        v-model="form.durationMinutes"
-        placeholder="请输入测评时长"
-      />
-    </view>
-  </view>
-
-  <!-- 媒体资料 -->
-  <view class="section">
-    <view class="section-title">媒体资料</view>
-    <text class="form-hint">上传测评过程中的照片、视频或音频</text>
-    <view class="media-grid">
-      <view
-        class="media-item"
-        v-for="(img, index) in mediaList"
-        :key="index"
-      >
-        <image class="media-image" :src="img" mode="aspectFill" />
-        <view class="media-remove" @click="removeMedia(index)">✕</view>
-      </view>
-      <view class="media-add" @click="chooseMedia" v-if="mediaList.length < 9">
-        <text class="media-add-icon">+</text>
-        <text class="media-add-text">添加</text>
-      </view>
-    </view>
-  </view>
-
-  <!-- 分析报告 -->
-  <view class="section">
-    <view class="section-title">分析报告</view>
-    <textarea
-      class="form-textarea"
-      v-model="form.analysisReport"
-      placeholder="请输入详细的测评分析报告"
-      :maxlength="2000"
-    />
-    <text class="char-count">{{ analysisReportLen }}/2000</text>
-  </view>
-
-  <!-- 成长建议 -->
-  <view class="section">
-    <view class="section-title">成长建议</view>
-    <textarea
-      class="form-textarea"
-      v-model="form.growthSuggestions"
-      placeholder="请输入针对孩子的成长建议"
-      :maxlength="1000"
-    />
-    <text class="char-count">{{ growthSuggestionsLen }}/1000</text>
-  </view>
-
-  <!-- 下次复查 -->
-  <view class="section">
-    <view class="section-title">下次复查</view>
-    <picker mode="date" :value="form.nextReviewDate" @change="onNextReviewDateChange">
-      <view class="picker-value">
-        <text>{{ form.nextReviewDate || '选择复查日期' }}</text>
-        <text class="picker-arrow">›</text>
-      </view>
-    </picker>
-  </view>
-
-  <!-- 结果描述 -->
-  <view class="section">
-    <view class="section-title">结果描述</view>
-    <textarea
-      class="form-textarea form-textarea-short"
-      v-model="form.resultDescription"
-      placeholder="请简要描述测评结果"
-      :maxlength="500"
-    />
-    <text class="char-count">{{ resultDescriptionLen }}/500</text>
-  </view>
-
-  <!-- 操作按钮 -->
-  <view class="btn-group">
-    <button class="btn-draft" @click="saveDraft" :disabled="submitting">保存草稿</button>
-    <button class="btn-submit" @click="submit" :disabled="submitting">提交结果</button>
-  </view>
-</view>
-</template>
-
-<script>
-import { recordAssessmentResult } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      appointmentId: '',
-      childId: '',
-      childName: '',
-      age: '',
-      school: '',
-      mediaList: [],
-      submitting: false,
-      form: {
-        attentionScore: 50,
-        focusScore: 50,
-        memoryScore: 50,
-        logicScore: 50,
-        assessmentDate: '',
-        durationMinutes: '',
-        analysisReport: '',
-        growthSuggestions: '',
-        nextReviewDate: '',
-        resultDescription: ''
-      }
-    }
-  },
-  computed: {
-    overallScore() {
-      var total = this.form.attentionScore + this.form.focusScore + this.form.memoryScore + this.form.logicScore
-      return Math.round(total / 4)
-    },
-    analysisReportLen() {
-      return (this.form.analysisReport && this.form.analysisReport.length) || 0
-    },
-    growthSuggestionsLen() {
-      return (this.form.growthSuggestions && this.form.growthSuggestions.length) || 0
-    },
-    resultDescriptionLen() {
-      return (this.form.resultDescription && this.form.resultDescription.length) || 0
-    }
-  },
-  onLoad(options) {
-    if (options.appointmentId) {
-      this.appointmentId = options.appointmentId
-    }
-    if (options.childId) {
-      this.childId = options.childId
-    }
-    if (options.childName) {
-      this.childName = decodeURIComponent(options.childName)
-    }
-    if (options.age) {
-      this.age = options.age
-    }
-    if (options.school) {
-      this.school = decodeURIComponent(options.school)
-    }
-    // 默认测评日期为今天
-    var now = new Date()
-    var year = now.getFullYear()
-    var month = String(now.getMonth() + 1).padStart(2, '0')
-    var day = String(now.getDate()).padStart(2, '0')
-    this.form.assessmentDate = year + '-' + month + '-' + day
-  },
-  methods: {
-    onAttentionChange(e) {
-      this.form.attentionScore = e.detail.value
-    },
-
-    onFocusChange(e) {
-      this.form.focusScore = e.detail.value
-    },
-
-    onMemoryChange(e) {
-      this.form.memoryScore = e.detail.value
-    },
-
-    onLogicChange(e) {
-      this.form.logicScore = e.detail.value
-    },
-
-    onAssessmentDateChange(e) {
-      this.form.assessmentDate = e.detail.value
-    },
-
-    onNextReviewDateChange(e) {
-      this.form.nextReviewDate = e.detail.value
-    },
-
-    chooseMedia() {
-      var that = this
-      uni.chooseImage({
-        count: 9 - that.mediaList.length,
-        sizeType: ['compressed'],
-        sourceType: ['album', 'camera'],
-        success: function(res) {
-          that.mediaList = that.mediaList.concat(res.tempFilePaths)
-        }
-      })
-    },
-
-    removeMedia(index) {
-      this.mediaList.splice(index, 1)
-    },
-
-    validateForm() {
-      if (!this.form.assessmentDate) {
-        uni.showToast({ title: '请选择测评日期', icon: 'none' })
-        return false
-      }
-      if (!this.form.durationMinutes || this.form.durationMinutes <= 0) {
-        uni.showToast({ title: '请输入有效的测评时长', icon: 'none' })
-        return false
-      }
-      if (!this.form.analysisReport || !this.form.analysisReport.trim()) {
-        uni.showToast({ title: '请填写分析报告', icon: 'none' })
-        return false
-      }
-      return true
-    },
-
-    buildRequestData(status) {
-      var teacherId = uni.getStorageSync('userId')
-      return {
-        childId: this.childId,
-        teacherId: teacherId,
-        appointmentId: this.appointmentId,
-        attentionScore: this.form.attentionScore,
-        focusScore: this.form.focusScore,
-        memoryScore: this.form.memoryScore,
-        logicScore: this.form.logicScore,
-        overallScore: this.overallScore,
-        assessmentDate: this.form.assessmentDate,
-        durationMinutes: Number(this.form.durationMinutes),
-        analysisReport: this.form.analysisReport,
-        growthSuggestions: this.form.growthSuggestions,
-        nextReviewDate: this.form.nextReviewDate,
-        resultDescription: this.form.resultDescription,
-        mediaUrls: this.mediaList.join(','),
-        status: status
-      }
-    },
-
-    async saveDraft() {
-      if (!this.childId) {
-        uni.showToast({ title: '缺少孩子信息', icon: 'none' })
-        return
-      }
-
-      try {
-        uni.showLoading({ title: '保存中...' })
-        this.submitting = true
-
-        var data = this.buildRequestData('draft')
-        var res = await recordAssessmentResult(data)
-
-        uni.hideLoading()
-        this.submitting = false
-
-        if (res.code === 200) {
-          uni.showToast({ title: '草稿已保存', icon: 'success' })
-        } else {
-          uni.showToast({ title: res.message || '保存失败', icon: 'none' })
-        }
-      } catch (e) {
-        uni.hideLoading()
-        this.submitting = false
-        uni.showToast({ title: '保存失败', icon: 'none' })
-      }
-    },
-
-    async submit() {
-      if (!this.validateForm()) {
-        return
-      }
-
-      try {
-        uni.showLoading({ title: '提交中...' })
-        this.submitting = true
-
-        var data = this.buildRequestData('completed')
-        var res = await recordAssessmentResult(data)
-
-        uni.hideLoading()
-        this.submitting = false
-
-        if (res.code === 200) {
-          uni.showModal({
-            title: '提交成功',
-            content: '测评结果已提交,家长将收到评估报告',
-            showCancel: false,
-            success: function() {
-              uni.navigateBack()
-            }
-          })
-        } else {
-          uni.showToast({ title: res.message || '提交失败', icon: 'none' })
-        }
-      } catch (e) {
-        uni.hideLoading()
-        this.submitting = false
-        uni.showToast({ title: '提交失败', icon: 'none' })
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  padding: 30rpx;
-  background: #F8F8F8;
-  min-height: 100vh;
-}
-
-.header {
-  text-align: center;
-  padding: 60rpx 0 40rpx;
-}
-
-.title {
-  font-size: 48rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-  margin-bottom: 20rpx;
-}
-
-.subtitle {
-  font-size: 28rpx;
-  color: #666;
-}
-
-.section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx;
-  margin-bottom: 30rpx;
-}
-
-.section-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 20rpx;
-}
-
-/* 孩子信息 */
-.info-card {
-  background: #F8F8F8;
-  border-radius: 10rpx;
-  padding: 20rpx;
-}
-
-.info-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 15rpx;
-}
-
-.info-row:last-child {
-  margin-bottom: 0;
-}
-
-.info-label {
-  font-size: 28rpx;
-  color: #999;
-}
-
-.info-value {
-  font-size: 28rpx;
-  color: #333;
-  font-weight: bold;
-}
-
-/* 评分 */
-.score-group {
-  margin-bottom: 20rpx;
-}
-
-.score-item {
-  margin-bottom: 20rpx;
-}
-
-.score-item:last-child {
-  margin-bottom: 0;
-}
-
-.score-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 10rpx;
-}
-
-.score-label {
-  font-size: 28rpx;
-  color: #333;
-  font-weight: bold;
-}
-
-.score-value {
-  font-size: 32rpx;
-  color: #667eea;
-  font-weight: bold;
-}
-
-.overall-score {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  border-radius: 10rpx;
-  padding: 24rpx 30rpx;
-  margin-top: 20rpx;
-}
-
-.overall-label {
-  font-size: 30rpx;
-  color: #fff;
-  font-weight: bold;
-}
-
-.overall-badge {
-  background: rgba(255, 255, 255, 0.2);
-  border-radius: 50%;
-  width: 80rpx;
-  height: 80rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.overall-number {
-  font-size: 36rpx;
-  color: #fff;
-  font-weight: bold;
-}
-
-/* 表单 */
-.form-group {
-  margin-bottom: 20rpx;
-}
-
-.form-group:last-child {
-  margin-bottom: 0;
-}
-
-.form-label {
-  font-size: 28rpx;
-  color: #333;
-  font-weight: bold;
-  display: block;
-  margin-bottom: 10rpx;
-}
-
-.form-hint {
-  font-size: 24rpx;
-  color: #999;
-  display: block;
-  margin-bottom: 15rpx;
-}
-
-.form-input {
-  height: 80rpx;
-  background: #F8F8F8;
-  border-radius: 10rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  color: #333;
-}
-
-.picker-value {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  height: 80rpx;
-  background: #F8F8F8;
-  border-radius: 10rpx;
-  padding: 0 20rpx;
-  font-size: 28rpx;
-  color: #333;
-}
-
-.picker-arrow {
-  font-size: 40rpx;
-  color: #999;
-}
-
-.form-textarea {
-  width: 100%;
-  height: 240rpx;
-  background: #F8F8F8;
-  border-radius: 10rpx;
-  padding: 20rpx;
-  font-size: 28rpx;
-  color: #333;
-  box-sizing: border-box;
-}
-
-.form-textarea-short {
-  height: 160rpx;
-}
-
-.char-count {
-  display: block;
-  text-align: right;
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 10rpx;
-}
-
-/* 媒体 */
-.media-grid {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 15rpx;
-}
-
-.media-item {
-  position: relative;
-  width: 200rpx;
-  height: 200rpx;
-}
-
-.media-image {
-  width: 200rpx;
-  height: 200rpx;
-  border-radius: 10rpx;
-  object-fit: cover;
-}
-
-.media-remove {
-  position: absolute;
-  top: -10rpx;
-  right: -10rpx;
-  width: 40rpx;
-  height: 40rpx;
-  background: #F97316;
-  color: #fff;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  font-size: 24rpx;
-}
-
-.media-add {
-  width: 200rpx;
-  height: 200rpx;
-  background: #F8F8F8;
-  border: 2rpx dashed #D0D0D0;
-  border-radius: 10rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-}
-
-.media-add-icon {
-  font-size: 60rpx;
-  color: #999;
-  line-height: 1;
-}
-
-.media-add-text {
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 10rpx;
-}
-
-/* 按钮 */
-.btn-group {
-  display: flex;
-  gap: 20rpx;
-  margin-top: 40rpx;
-  padding-bottom: 60rpx;
-}
-
-.btn-draft {
-  flex: 1;
-  height: 90rpx;
-  background: #fff;
-  color: #667eea;
-  border: 2rpx solid #667eea;
-  border-radius: 45rpx;
-  font-size: 30rpx;
-  font-weight: bold;
-  line-height: 90rpx;
-}
-
-.btn-submit {
-  flex: 1;
-  height: 90rpx;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  color: #fff;
-  border-radius: 45rpx;
-  font-size: 30rpx;
-  font-weight: bold;
-  line-height: 90rpx;
-}
-
-.btn-draft[disabled],
-.btn-submit[disabled] {
-  opacity: 0.6;
-}
-</style>

+ 0 - 517
cfc-frontend/pages/body/checkin.vue

@@ -1,517 +0,0 @@
-<template>
-  <view class="checkin-container">
-    <!-- 顶部导航 -->
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">健康打卡</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <!-- 能量奖励提示 -->
-    <view class="energy-tip">
-      <text class="tip-icon">⚡</text>
-      <text class="tip-text">每次打卡可获得 5 能量值</text>
-    </view>
-
-    <!-- 打卡表单 -->
-    <view class="form-section">
-      <view class="form-title">今日打卡</view>
-
-      <!-- 日期选择 -->
-      <view class="form-item">
-        <text class="form-label">打卡日期</text>
-        <view class="date-display" @click="pickDate">
-          <text>{{ checkinDate }}</text>
-          <text class="date-arrow">›</text>
-        </view>
-      </view>
-
-      <!-- 行为选择 -->
-      <view class="form-item">
-        <text class="form-label">健康行为</text>
-        <view class="behaviors-grid">
-          <view
-            class="behavior-chip"
-            v-for="bh in behaviorOptions"
-            :key="bh.value"
-            :class="{ 'behavior-selected': selectedBehaviors.indexOf(bh.value) !== -1 }"
-            @click="toggleBehavior(bh.value)"
-          >
-            <text>{{ bh.label }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 心情选择 -->
-      <view class="form-item">
-        <text class="form-label">今日心情</text>
-        <view class="mood-row">
-          <view
-            class="mood-item"
-            v-for="m in moodOptions"
-            :key="m.value"
-            :class="{ 'mood-selected': selectedMood === m.value }"
-            @click="selectMood(m.value)"
-          >
-            <text class="mood-emoji">{{ m.emoji }}</text>
-            <text class="mood-label">{{ m.label }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 备注 -->
-      <view class="form-item">
-        <text class="form-label">备注(选填)</text>
-        <textarea
-          class="note-input"
-          v-model="note"
-          placeholder="记录今天的健康小故事..."
-          maxlength="200"
-        ></textarea>
-      </view>
-
-      <!-- 提交按钮 -->
-      <view class="submit-btn" @click="doCheckin">
-        <text>提交打卡</text>
-      </view>
-    </view>
-
-    <!-- 打卡记录 -->
-    <view class="history-section">
-      <view class="section-title">打卡记录</view>
-      <view class="history-list">
-        <view class="history-item" v-for="item in checkinList" :key="item.id">
-          <view class="history-left">
-            <text class="history-date">{{ formatDate(item.checkinDate) }}</text>
-            <view class="history-behaviors">
-              <text class="bh-tag" v-for="bh in parseBehaviors(item.behaviors)" :key="bh">{{ bh }}</text>
-            </view>
-          </view>
-          <view class="history-right">
-            <text class="history-mood">{{ getMoodEmoji(item.mood) }}</text>
-            <text class="history-energy">+{{ item.earnedEnergy || 5 }}⚡</text>
-          </view>
-        </view>
-        <view class="history-empty" v-if="checkinList.length === 0 && !loadingHistory">
-          <text>还没有打卡记录,快来打卡吧!</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-
-const healthRequest = function(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': token ? 'Bearer ' + token : ''
-      },
-      success: function(res) {
-        if (res.data.code === 200) {
-          resolve(res.data)
-        } else {
-          reject(res.data)
-        }
-      },
-      fail: function(err) {
-        reject(err)
-      }
-    })
-  })
-}
-
-export default {
-  data() {
-    return {
-      childId: null,
-      checkinDate: '',
-      selectedBehaviors: [],
-      selectedMood: '',
-      note: '',
-      checkinList: [],
-      loadingHistory: false,
-      behaviorOptions: [
-        { label: '运动30分钟', value: '运动30分钟' },
-        { label: '喝足8杯水', value: '喝足8杯水' },
-        { label: '按时睡觉', value: '按时睡觉' },
-        { label: '均衡饮食', value: '均衡饮食' },
-        { label: '户外活动', value: '户外活动2小时' },
-        { label: '视力保护', value: '视力保护' },
-        { label: '情绪愉快', value: '情绪愉快' },
-        { label: '无零食', value: '无零食' }
-      ],
-      moodOptions: [
-        { label: '开心', value: 'happy', emoji: '😊' },
-        { label: '平静', value: 'normal', emoji: '😐' },
-        { label: '低落', value: 'sad', emoji: '😔' },
-        { label: '烦躁', value: 'angry', emoji: '😤' },
-        { label: '不适', value: 'sick', emoji: '🤒' }
-      ]
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-    this.checkinDate = this.formatDate(new Date())
-    this.loadHistory()
-  },
-  methods: {
-    pickDate: function() {
-      var self = this
-      var now = new Date()
-      uni.chooseDate({
-        success: function(res) {
-          self.checkinDate = res.date
-        }
-      })
-    },
-    toggleBehavior: function(value) {
-      var idx = this.selectedBehaviors.indexOf(value)
-      if (idx === -1) {
-        this.selectedBehaviors.push(value)
-      } else {
-        this.selectedBehaviors.splice(idx, 1)
-      }
-    },
-    selectMood: function(value) {
-      this.selectedMood = value
-    },
-    doCheckin: function() {
-      if (this.selectedBehaviors.length === 0) {
-        uni.showToast({ title: '请至少选择一项健康行为', icon: 'none' })
-        return
-      }
-      if (!this.childId) {
-        uni.showToast({ title: '孩子ID无效', icon: 'none' })
-        return
-      }
-      var self = this
-      uni.showLoading({ title: '提交中...' })
-      healthRequest('/api/health/checkin/create', {
-        childId: this.childId,
-        checkinDate: this.checkinDate,
-        behaviors: this.selectedBehaviors,
-        note: this.note,
-        mood: this.selectedMood
-      }).then(function(res) {
-        uni.hideLoading()
-        uni.showModal({
-          title: '打卡成功',
-          content: '恭喜获得 5 能量值!',
-          showCancel: false
-        })
-        // Reset form
-        self.selectedBehaviors = []
-        self.selectedMood = ''
-        self.note = ''
-        self.checkinDate = self.formatDate(new Date())
-        // Reload history
-        self.loadHistory()
-      }).catch(function(e) {
-        uni.hideLoading()
-        if (e && e.message && e.message.indexOf('404') !== -1) {
-          uni.showToast({ title: '健康打卡功能即将上线', icon: 'none' })
-        } else {
-          uni.showToast({ title: '打卡失败,请重试', icon: 'none' })
-        }
-      })
-    },
-    loadHistory: function() {
-      if (!this.childId) return
-      var self = this
-      this.loadingHistory = true
-      healthRequest('/api/health/checkin/list', {
-        childId: this.childId
-      }).then(function(res) {
-        self.loadingHistory = false
-        if (res.data) {
-          self.checkinList = res.data
-        }
-      }).catch(function(e) {
-        self.loadingHistory = false
-        console.log('获取打卡记录失败', e)
-      })
-    },
-    parseBehaviors: function(behaviorsStr) {
-      if (!behaviorsStr) return []
-      try {
-        if (typeof behaviorsStr === 'string') {
-          return JSON.parse(behaviorsStr)
-        }
-        return behaviorsStr
-      } catch (e) {
-        if (behaviorsStr.indexOf && behaviorsStr.indexOf('[') === 0) {
-          try { return JSON.parse(behaviorsStr) } catch (e2) { return [behaviorsStr] }
-        }
-        return [behaviorsStr]
-      }
-    },
-    getMoodEmoji: function(mood) {
-      var map = { happy: '😊', normal: '😐', sad: '😔', angry: '😤', sick: '🤒' }
-      return map[mood] || '😐'
-    },
-    formatDate: function(date) {
-      if (!date) return ''
-      if (typeof date === 'string') {
-        try { date = new Date(date) } catch (e) { return date }
-      }
-      var y = date.getFullYear()
-      var m = ('' + (date.getMonth() + 1)).padStart(2, '0')
-      var d = ('' + date.getDate()).padStart(2, '0')
-      return y + '-' + m + '-' + d
-    },
-    goBack: function() {
-      uni.navigateBack()
-    }
-  }
-}
-</script>
-
-<style scoped>
-.checkin-container {
-  min-height: 100vh;
-  background: #f5f7fa;
-  padding-bottom: 60rpx;
-}
-
-/* ===== 导航栏 ===== */
-.nav-bar {
-  position: sticky;
-  top: 0;
-  background: #fff;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  padding: 20rpx 30rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
-  z-index: 100;
-}
-.nav-back {
-  font-size: 32rpx;
-  color: #5B9BD5;
-}
-.nav-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-}
-.nav-placeholder {
-  width: 80rpx;
-}
-
-/* ===== 能量提示 ===== */
-.energy-tip {
-  margin: 20rpx 30rpx;
-  background: linear-gradient(135deg, #FFF8E1, #FFECB3);
-  border-radius: 16rpx;
-  padding: 16rpx 24rpx;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-.tip-icon {
-  font-size: 32rpx;
-  margin-right: 12rpx;
-}
-.tip-text {
-  font-size: 26rpx;
-  color: #E65100;
-}
-
-/* ===== 表单 ===== */
-.form-section {
-  margin: 20rpx 30rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.form-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 24rpx;
-}
-.form-item {
-  margin-bottom: 28rpx;
-}
-.form-label {
-  font-size: 26rpx;
-  color: #666;
-  display: block;
-  margin-bottom: 12rpx;
-}
-
-/* 日期 */
-.date-display {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  background: #f5f7fa;
-  border-radius: 12rpx;
-  padding: 16rpx 20rpx;
-  font-size: 28rpx;
-  color: #333;
-}
-.date-arrow {
-  font-size: 32rpx;
-  color: #ccc;
-}
-
-/* 行为选择 */
-.behaviors-grid {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-}
-.behavior-chip {
-  background: #f0f0f0;
-  color: #666;
-  font-size: 24rpx;
-  padding: 12rpx 20rpx;
-  border-radius: 24rpx;
-  margin-right: 16rpx;
-  margin-bottom: 12rpx;
-}
-.behavior-selected {
-  background: #E8F5E9;
-  color: #2E7D32;
-}
-
-/* 心情 */
-.mood-row {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-}
-.mood-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 12rpx 16rpx;
-  border-radius: 12rpx;
-}
-.mood-selected {
-  background: #FFF8E1;
-}
-.mood-emoji {
-  font-size: 40rpx;
-}
-.mood-label {
-  font-size: 20rpx;
-  color: #666;
-  margin-top: 4rpx;
-}
-
-/* 备注 */
-.note-input {
-  width: 100%;
-  box-sizing: border-box;
-  background: #f5f7fa;
-  border-radius: 12rpx;
-  padding: 16rpx;
-  font-size: 28rpx;
-  color: #333;
-  min-height: 120rpx;
-}
-
-/* 提交按钮 */
-.submit-btn {
-  background: linear-gradient(135deg, #4CAF50, #66BB6A);
-  color: #fff;
-  font-size: 32rpx;
-  font-weight: bold;
-  text-align: center;
-  padding: 24rpx;
-  border-radius: 40rpx;
-  margin-top: 16rpx;
-}
-.submit-btn:active {
-  opacity: 0.8;
-}
-
-/* ===== 历史记录 ===== */
-.history-section {
-  margin: 30rpx 30rpx 0;
-}
-.section-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 16rpx;
-}
-.history-list {
-  display: flex;
-  flex-direction: column;
-}
-.history-item {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  margin-bottom: 12rpx;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
-}
-.history-left {
-  flex: 1;
-}
-.history-date {
-  font-size: 26rpx;
-  color: #333;
-  font-weight: 500;
-  display: block;
-  margin-bottom: 8rpx;
-}
-.history-behaviors {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-}
-.bh-tag {
-  background: #E3F2FD;
-  color: #1565C0;
-  font-size: 20rpx;
-  padding: 4rpx 12rpx;
-  border-radius: 12rpx;
-  margin-right: 8rpx;
-  margin-bottom: 4rpx;
-}
-.history-right {
-  display: flex;
-  flex-direction: column;
-  align-items: flex-end;
-}
-.history-mood {
-  font-size: 32rpx;
-}
-.history-energy {
-  font-size: 22rpx;
-  color: #4CAF50;
-  margin-top: 4rpx;
-}
-.history-empty {
-  text-align: center;
-  padding: 40rpx;
-  color: #999;
-  font-size: 26rpx;
-}
-
-/* ===== 底部 ===== */
-.bottom-spacer {
-  height: 60rpx;
-}
-</style>

+ 0 - 221
cfc-frontend/pages/body/exercise-record.vue

@@ -1,221 +0,0 @@
-<template>
-  <view class="exercise-container">
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">运动记录</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <!-- 成员选择 -->
-    <view class="member-selector-card">
-      <text class="member-label">记录人</text>
-      <picker mode="selector" :range="memberOptions" :value="selectedMemberIndex" @change="onMemberChange">
-        <view class="picker-display">{{ memberOptions[selectedMemberIndex] }}</view>
-      </picker>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">记录运动</view>
-      <view class="exercise-types">
-        <view class="ex-type-item" v-for="et in exerciseTypes" :key="et.value"
-          :class="{ 'ex-type-active': selectedType === et.value }"
-          @click="selectedType = et.value">
-          <text class="ex-type-icon">{{ et.icon }}</text>
-          <text class="ex-type-label">{{ et.label }}</text>
-        </view>
-      </view>
-
-      <view class="input-row">
-        <text class="input-label">时长(分钟)</text>
-        <input class="input-field" v-model="duration" type="number" placeholder="输入运动时长" />
-      </view>
-      <view class="input-row">
-        <text class="input-label">强度</text>
-        <view class="intensity-options">
-          <text class="intensity-item" v-for="v in ['低','中','高']" :key="v"
-            :class="{ 'intensity-active': intensity === v }" @click="intensity = v">{{ v }}</text>
-        </view>
-      </view>
-      <view class="input-row">
-        <text class="input-label">备注</text>
-        <input class="input-field" v-model="remark" placeholder="可选" />
-      </view>
-
-      <view class="submit-btn" @click="submitExercise">保存记录</view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">今日运动摘要</view>
-      <view class="today-summary">
-        <text class="summary-stat">总时长:<text class="stat-value">{{ todayTotalMinutes }} 分钟</text></text>
-        <text class="summary-stat">消耗:<text class="stat-value">{{ todayCalories }} kcal</text></text>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">历史记录</view>
-      <view class="history-list">
-        <view class="history-item" v-for="item in exerciseHistory" :key="item.id">
-          <text class="history-icon">{{ getTypeIcon(item.exerciseType) }}</text>
-          <view class="history-info">
-            <text class="history-name">{{ item.exerciseType }}</text>
-            <text class="history-meta">{{ item.durationMinutes }}分钟 · {{ item.intensity || '中' }}强度</text>
-          </view>
-          <text class="history-time">{{ item.createdAt ? item.createdAt.substring(5,16) : '' }}</text>
-        </view>
-        <view class="history-empty" v-if="exerciseHistory.length === 0"><text>暂无记录</text></view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-import { getChildren } from '@/utils/api.js'
-
-export default {
-  data() {
-    return {
-      childId: null,
-      selectedMemberIndex: 0,
-      memberOptions: ['我自己'],
-      memberIds: [],
-      selectedType: 'running',
-      duration: '',
-      intensity: '中',
-      remark: '',
-      exerciseHistory: [],
-      exerciseTypes: [
-        { icon: '🏃', label: '跑步', value: 'running' },
-        { icon: '🚴', label: '骑行', value: 'cycling' },
-        { icon: '🏊', label: '游泳', value: 'swimming' },
-        { icon: '🤸', label: '跳绳', value: 'jumping_rope' },
-        { icon: '⚽', label: '球类', value: 'ball_games' },
-        { icon: '🧘', label: '拉伸', value: 'stretching' }
-      ]
-    }
-  },
-  computed: {
-    todayTotalMinutes: function() { return 0 },
-    todayCalories: function() { return 0 }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-    this.loadChildren()
-    this.loadHistory()
-  },
-  methods: {
-    loadChildren: function() {
-      var self = this
-      getChildren().then(function(res) {
-        if (res.code === 200 && res.data && res.data.length > 0) {
-          self.memberIds = res.data.map(function(c) { return c.childId })
-          self.memberOptions = ['我自己'].concat(res.data.map(function(c) { return c.name || c.nickname || '孩子' }))
-        }
-      }).catch(function() {})
-    },
-    onMemberChange: function(e) {
-      this.selectedMemberIndex = parseInt(e.detail.value)
-      if (this.selectedMemberIndex === 0) {
-        this.childId = null
-      } else {
-        this.childId = this.memberIds[this.selectedMemberIndex - 1]
-      }
-      this.loadHistory()
-    },
-    submitExercise: function() {
-      if (!this.duration || parseInt(this.duration) <= 0) {
-        uni.showToast({ title: '请输入有效时长', icon: 'none' })
-        return
-      }
-      var data = {
-        exerciseType: this.selectedType,
-        durationMinutes: parseInt(this.duration),
-        intensity: this.intensity,
-        remark: this.remark
-      }
-      if (this.childId) {
-        data.childId = this.childId
-        data.memberId = this.childId
-      }
-      uni.showLoading({ title: '保存中...' })
-      healthRequest('/api/health/exercise/create', data).then(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '保存成功', icon: 'success' })
-        this.duration = ''
-        this.remark = ''
-        this.loadHistory()
-      }.bind(this)).catch(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '保存失败', icon: 'none' })
-      })
-    },
-    loadHistory: function() {
-      var self = this
-      var params = {}
-      if (this.childId) params.childId = this.childId
-      healthRequest('/api/health/exercise/list', params).then(function(res) {
-        if (res.data) self.exerciseHistory = res.data
-      }).catch(function() { self.exerciseHistory = [] })
-    },
-    getTypeIcon: function(type) {
-      var map = { running: '🏃', cycling: '🚴', swimming: '🏊', jumping_rope: '🤸', ball_games: '⚽', stretching: '🧘' }
-      return map[type] || '🏃'
-    },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-
-function healthRequest(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' },
-      success: function(res) { if (res.data.code === 200) resolve(res.data); else reject(res.data) },
-      fail: function(err) { reject(err) }
-    })
-  })
-}
-</script>
-
-<style scoped>
-.exercise-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
-.nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.member-selector-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 20rpx 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
-.member-label { font-size: 28rpx; color: #666; }
-.picker-display { font-size: 28rpx; color: #333; font-weight: 500; }
-.section-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; }
-.exercise-types { display: flex; flex-direction: row; flex-wrap: wrap; margin-bottom: 20rpx; }
-.ex-type-item { display: flex; flex-direction: column; align-items: center; padding: 16rpx 24rpx; border-radius: 16rpx; background: #f0f0f0; margin: 0 12rpx 12rpx 0; min-width: 100rpx; }
-.ex-type-active { background: #E8F5E9; border: 2rpx solid #4CAF50; }
-.ex-type-icon { font-size: 36rpx; }
-.ex-type-label { font-size: 22rpx; color: #666; margin-top: 6rpx; }
-.input-row { display: flex; flex-direction: row; align-items: center; margin-bottom: 16rpx; }
-.input-label { font-size: 26rpx; color: #666; width: 140rpx; flex-shrink: 0; }
-.input-field { flex: 1; background: #f5f7fa; border-radius: 12rpx; padding: 14rpx 16rpx; font-size: 28rpx; }
-.intensity-options { display: flex; flex-direction: row; }
-.intensity-item { padding: 8rpx 24rpx; border-radius: 12rpx; background: #f0f0f0; font-size: 26rpx; color: #666; margin-right: 12rpx; }
-.intensity-active { background: #FFF3E0; color: #E65100; }
-.submit-btn { background: linear-gradient(135deg, #4CAF50, #66BB6A); color: #fff; font-size: 32rpx; font-weight: bold; text-align: center; padding: 22rpx; border-radius: 40rpx; margin-top: 16rpx; }
-.today-summary { display: flex; flex-direction: row; justify-content: space-around; }
-.summary-stat { font-size: 26rpx; color: #666; }
-.stat-value { font-size: 32rpx; color: #4CAF50; font-weight: bold; }
-.history-list { display: flex; flex-direction: column; }
-.history-item { display: flex; flex-direction: row; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.history-icon { font-size: 32rpx; margin-right: 16rpx; }
-.history-info { flex: 1; }
-.history-name { font-size: 28rpx; color: #333; }
-.history-meta { font-size: 22rpx; color: #999; }
-.history-time { font-size: 22rpx; color: #999; }
-.history-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
-.bottom-spacer { height: 60rpx; }
-</style>

+ 0 - 317
cfc-frontend/pages/body/food-list.vue

@@ -1,317 +0,0 @@
-<template>
-  <view class="food-list-container">
-    <!-- 顶部导航 -->
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">食材推荐</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <!-- Tab切换 -->
-    <view class="tab-bar">
-      <text class="tab-item" :class="{'active': activeTab === 'total'}" @click="switchTab('total')">全部食材</text>
-      <text class="tab-item" :class="{'active': activeTab === 'byMember'}" @click="switchTab('byMember')">按成员</text>
-    </view>
-
-    <!-- 全部食材模式 -->
-    <view v-if="activeTab === 'total'" class="food-section">
-      <view class="food-card" v-for="food in totalFoods" :key="food.id">
-        <view class="food-header">
-          <text class="food-name">{{ food.name }}</text>
-          <view class="food-score" :class="'score-' + getScoreLevel(food.score)">
-            <text>{{ food.score || '--' }}分</text>
-          </view>
-          <view class="rec-score" :class="'rec-' + getRecScoreLevel(food.recommendIndex)" v-if="food.recommendIndex != null">
-            <text>推荐 {{ food.recommendIndex }}</text>
-          </view>
-        </view>
-        <text class="food-category" v-if="food.category">{{ food.category }}</text>
-        <view class="food-nutrition" v-if="hasNutrition(food)">
-          <text class="nutri-item" v-if="food.energyKj">能量: {{ food.energyKj }}kJ</text>
-          <text class="nutri-item" v-if="food.protein">蛋白质: {{ food.protein }}g</text>
-          <text class="nutri-item" v-if="food.fat">脂肪: {{ food.fat }}g</text>
-          <text class="nutri-item" v-if="food.carbs">碳水: {{ food.carbs }}g</text>
-          <text class="nutri-item" v-if="food.fiber">膳食纤维: {{ food.fiber }}g</text>
-          <text class="nutri-item" v-if="food.starch">淀粉: {{ food.starch }}g</text>
-          <text class="nutri-item" v-if="food.cholesterol">胆固醇: {{ food.cholesterol }}mg</text>
-        </view>
-      </view>
-      <view class="empty-state" v-if="totalFoods.length === 0 && !loading">
-        <text>暂无食材推荐</text>
-        <text class="empty-hint">上传菌群检测报告后可获得个性化食材推荐</text>
-      </view>
-    </view>
-
-    <!-- 按成员模式 -->
-    <view v-if="activeTab === 'byMember'" class="food-section">
-      <view v-for="(foods, childId) in byMemberFoods" :key="childId" class="member-group">
-        <view class="member-header">
-          <text class="member-name">{{ getMemberName(childId) }}</text>
-          <text class="member-count">{{ foods.length }}项</text>
-        </view>
-        <view class="food-card" v-for="food in foods" :key="food.id">
-          <view class="food-header">
-            <text class="food-name">{{ food.name }}</text>
-          <view class="food-score" :class="'score-' + getScoreLevel(food.score)">
-            <text>{{ food.score || '--' }}分</text>
-          </view>
-          <view class="rec-score" :class="'rec-' + getRecScoreLevel(food.recommendIndex)" v-if="food.recommendIndex != null">
-            <text>推荐 {{ food.recommendIndex }}</text>
-          </view>
-        </view>
-        <text class="food-category" v-if="food.category">{{ food.category }}</text>
-        <view class="food-nutrition" v-if="hasNutrition(food)">
-          <text class="nutri-item" v-if="food.energyKj">能量: {{ food.energyKj }}kJ</text>
-          <text class="nutri-item" v-if="food.protein">蛋白质: {{ food.protein }}g</text>
-          <text class="nutri-item" v-if="food.fat">脂肪: {{ food.fat }}g</text>
-          <text class="nutri-item" v-if="food.carbs">碳水: {{ food.carbs }}g</text>
-          <text class="nutri-item" v-if="food.fiber">膳食纤维: {{ food.fiber }}g</text>
-          <text class="nutri-item" v-if="food.starch">淀粉: {{ food.starch }}g</text>
-          <text class="nutri-item" v-if="food.cholesterol">胆固醇: {{ food.cholesterol }}mg</text>
-        </view>
-      </view>
-    </view>
-      <view class="empty-state" v-if="Object.keys(byMemberFoods).length === 0 && !loading">
-        <text>暂无食材推荐</text>
-        <text class="empty-hint">上传菌群检测报告后可获得个性化食材推荐</text>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-
-const healthRequest = function(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': token ? 'Bearer ' + token : ''
-      },
-      success: function(res) {
-        if (res.data.code === 200) {
-          resolve(res.data)
-        } else {
-          reject(res.data)
-        }
-      },
-      fail: function(err) {
-        reject(err)
-      }
-    })
-  })
-}
-
-export default {
-  data: function() {
-    return {
-      familyId: null,
-      activeTab: 'total',
-      totalFoods: [],
-      byMemberFoods: {},
-      memberNames: {},
-      loading: false
-    }
-  },
-  onLoad: function(options) {
-    var familyId = options.familyId
-    this.familyId = familyId ? parseInt(familyId) : null
-    this.loadData()
-  },
-  methods: {
-    loadData: function() {
-      var self = this
-      if (!this.familyId) {
-        uni.showToast({ title: '缺少familyId', icon: 'none' })
-        return
-      }
-      uni.showLoading({ title: '加载中...' })
-      healthRequest('/api/health/foods/by-family', { familyId: this.familyId }).then(function(res) {
-        uni.hideLoading()
-        if (res.data) {
-          self.totalFoods = res.data.total || []
-          self.byMemberFoods = res.data.byMember || {}
-          self.memberNames = res.data.memberNames || {}
-        }
-      }).catch(function(e) {
-        uni.hideLoading()
-        console.log('获取食材推荐失败', e)
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      })
-    },
-    switchTab: function(tab) {
-      this.activeTab = tab
-    },
-    getScoreLevel: function(score) {
-      if (score == null) return 'unknown'
-      if (score >= 80) return 'high'
-      if (score >= 60) return 'mid'
-      return 'low'
-    },
-    getRecScoreLevel: function(score) {
-      if (score == null) return 'unknown'
-      if (score >= 80) return 'high'
-      if (score >= 60) return 'mid'
-      return 'low'
-    },
-    hasNutrition: function(food) {
-      return food.energyKj || food.protein || food.fat || food.carbs || food.fiber || food.starch || food.cholesterol
-    },
-    getMemberName: function(childId) {
-      return this.memberNames[childId] || '成员' + childId
-    },
-    goBack: function() {
-      uni.navigateBack()
-    }
-  }
-}
-</script>
-
-<style scoped>
-.food-list-container {
-  min-height: 100vh;
-  background: #f5f7fa;
-}
-.nav-bar {
-  position: sticky;
-  top: 0;
-  background: #fff;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  padding: 20rpx 30rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
-  z-index: 100;
-}
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.tab-bar {
-  display: flex;
-  flex-direction: row;
-  background: #fff;
-  padding: 0 30rpx;
-  border-bottom: 2rpx solid #eee;
-}
-.tab-item {
-  padding: 20rpx 0;
-  margin-right: 40rpx;
-  font-size: 28rpx;
-  color: #666;
-  border-bottom: 4rpx solid transparent;
-}
-.tab-item.active {
-  color: #F97316;
-  border-bottom-color: #F97316;
-  font-weight: bold;
-}
-.food-section {
-  padding: 20rpx 30rpx;
-}
-.member-group {
-  margin-bottom: 30rpx;
-}
-.member-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 16rpx;
-}
-.member-name {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-}
-.member-count {
-  font-size: 22rpx;
-  color: #999;
-}
-.food-card {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  margin-bottom: 12rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
-}
-.food-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: flex-start;
-  flex-wrap: wrap;
-  margin-bottom: 8rpx;
-}
-.food-name {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-  margin-right: 12rpx;
-}
-.food-score {
-  font-size: 24rpx;
-  padding: 4rpx 16rpx;
-  border-radius: 16rpx;
-  font-weight: 600;
-}
-.score-high { background: #DCFCE7; color: #16A34A; }
-.score-mid { background: #FEF3C7; color: #D97706; }
-.score-low { background: #FEE2E2; color: #DC2626; }
-.score-unknown { background: #F3F4F6; color: #6B7280; }
-.rec-score {
-  font-size: 22rpx;
-  padding: 4rpx 12rpx;
-  border-radius: 16rpx;
-  font-weight: 600;
-  margin-left: 8rpx;
-}
-.rec-high { background: #DBEAFE; color: #2563EB; }
-.rec-mid { background: #E0E7FF; color: #4F46E5; }
-.rec-low { background: #F3E8FF; color: #7C3AED; }
-.rec-unknown { background: #F3F4F6; color: #6B7280; }
-.food-category {
-  font-size: 22rpx;
-  color: #6B7280;
-  background: #F3F4F6;
-  padding: 2rpx 12rpx;
-  border-radius: 12rpx;
-  display: inline-block;
-  margin-bottom: 8rpx;
-}
-.food-nutrition {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  gap: 20rpx;
-  margin-top: 8rpx;
-}
-.nutri-item {
-  font-size: 22rpx;
-  color: #374151;
-}
-.empty-state {
-  text-align: center;
-  padding: 100rpx 40rpx;
-}
-.empty-state text:first-child {
-  font-size: 28rpx;
-  color: #999;
-  display: block;
-  margin-bottom: 12rpx;
-}
-.empty-hint {
-  font-size: 24rpx;
-  color: #bbb;
-}
-.bottom-spacer {
-  height: 40rpx;
-}
-</style>

+ 0 - 189
cfc-frontend/pages/body/growth-curve.vue

@@ -1,189 +0,0 @@
-<template>
-  <view class="growth-container">
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">生长曲线</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">当前数据</view>
-      <view class="current-stats" v-if="latestRecord">
-        <view class="stat-box">
-          <text class="stat-val">{{ latestRecord.heightCm || '--' }}</text>
-          <text class="stat-unit">cm</text>
-          <text class="stat-label">身高</text>
-        </view>
-        <view class="stat-box">
-          <text class="stat-val">{{ latestRecord.weightKg || '--' }}</text>
-          <text class="stat-unit">kg</text>
-          <text class="stat-label">体重</text>
-        </view>
-        <view class="stat-box">
-          <text class="stat-val">{{ latestRecord.bmi || '--' }}</text>
-          <text class="stat-unit">BMI</text>
-          <text class="stat-label">指数</text>
-        </view>
-      </view>
-      <view class="current-stats" v-else>
-        <view class="stat-box"><text class="stat-val">--</text><text class="stat-label">暂无数据</text></view>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">身高趋势</view>
-      <view class="chart-placeholder">
-        <view class="chart-svg">
-          <view class="chart-line" v-for="(pt, idx) in heightPoints" :key="idx"
-            :style="{ left: pt.x + '%', bottom: pt.y + '%' }">
-            <view class="chart-dot"></view>
-          </view>
-          <view class="chart-axis-y">
-            <text class="axis-label" v-for="(l, i) in ['150cm','130cm','110cm','90cm']" :key="i"
-              :style="{ bottom: (i * 25) + '%' }">{{ l }}</text>
-          </view>
-          <view class="chart-no-data" v-if="heightPoints.length < 2">
-            <text>记录更多数据后将显示生长趋势曲线</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">体重趋势</view>
-      <view class="chart-placeholder">
-        <view class="chart-svg">
-          <view class="chart-line" v-for="(pt, idx) in weightPoints" :key="idx"
-            :style="{ left: pt.x + '%', bottom: pt.y + '%' }">
-            <view class="chart-dot"></view>
-          </view>
-          <view class="chart-axis-y">
-            <text class="axis-label" v-for="(l, i) in ['50kg','40kg','30kg','20kg']" :key="i"
-              :style="{ bottom: (i * 25) + '%' }">{{ l }}</text>
-          </view>
-          <view class="chart-no-data" v-if="weightPoints.length < 2">
-            <text>记录更多数据后将显示体重趋势曲线</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">生长记录列表</view>
-      <view class="record-list">
-        <view class="record-item" v-for="item in growthRecords" :key="item.id">
-          <view class="record-date">{{ item.recordDate ? item.recordDate.substring(0,10) : '' }}</view>
-          <view class="record-data">
-            <text class="data-item" v-if="item.heightCm">{{ item.heightCm }}cm</text>
-            <text class="data-item" v-if="item.weightKg">{{ item.weightKg }}kg</text>
-            <text class="data-item" v-if="item.bmi">BMI {{ item.bmi }}</text>
-          </view>
-        </view>
-        <view class="record-empty" v-if="growthRecords.length === 0"><text>暂无生长记录</text></view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-
-export default {
-  data() {
-    return {
-      childId: null,
-      growthRecords: [],
-      latestRecord: null
-    }
-  },
-  computed: {
-    heightPoints: function() {
-      return this.calcPoints('heightCm')
-    },
-    weightPoints: function() {
-      return this.calcPoints('weightKg')
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-    if (this.childId) this.loadRecords()
-  },
-  methods: {
-    calcPoints: function(field) {
-      var records = this.growthRecords.slice().reverse()
-      var values = records.map(function(r) { return parseFloat(r[field]) }).filter(function(v) { return v && v > 0 })
-      if (values.length < 2) return []
-      var min = Math.min.apply(null, values)
-      var max = Math.max.apply(null, values)
-      var range = max - min || 1
-      var spacing = 80 / (values.length - 1)
-      return values.map(function(v, i) {
-        return { x: 10 + i * spacing, y: 10 + ((v - min) / range) * 70 }
-      })
-    },
-    loadRecords: function() {
-      var self = this
-      healthRequest('/api/health/growth/list', { memberId: this.childId }).then(function(res) {
-        if (res.data) {
-          self.growthRecords = res.data
-          if (res.data.length > 0) self.latestRecord = res.data[0]
-        }
-      }).catch(function() {
-        // Use mock data for demo
-        self.growthRecords = [
-          { id: 1, recordDate: '2026-01-15', heightCm: 110.5, weightKg: 18.2, bmi: 14.9 },
-          { id: 2, recordDate: '2026-02-15', heightCm: 111.2, weightKg: 18.5, bmi: 14.9 },
-          { id: 3, recordDate: '2026-03-15', heightCm: 112.0, weightKg: 19.0, bmi: 15.1 }
-        ]
-        self.latestRecord = self.growthRecords[self.growthRecords.length - 1]
-      })
-    },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-
-function healthRequest(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' },
-      success: function(res) { if (res.data.code === 200) resolve(res.data); else reject(res.data) },
-      fail: function(err) { reject(err) }
-    })
-  })
-}
-</script>
-
-<style scoped>
-.growth-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
-.nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.section-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; }
-.current-stats { display: flex; flex-direction: row; justify-content: space-around; }
-.stat-box { display: flex; flex-direction: column; align-items: center; }
-.stat-val { font-size: 44rpx; font-weight: bold; color: #FF8C42; }
-.stat-unit { font-size: 22rpx; color: #999; }
-.stat-label { font-size: 24rpx; color: #666; margin-top: 4rpx; }
-.chart-placeholder { background: #FFFBEB; border-radius: 16rpx; padding: 20rpx; min-height: 300rpx; position: relative; }
-.chart-svg { width: 100%; height: 300rpx; position: relative; }
-.chart-line { position: absolute; width: 20rpx; height: 20rpx; }
-.chart-dot { width: 16rpx; height: 16rpx; background: #FF8C42; border-radius: 50%; }
-.chart-axis-y { position: absolute; left: 0; top: 0; bottom: 30rpx; width: 80rpx; }
-.axis-label { position: absolute; font-size: 18rpx; color: #999; }
-.chart-no-data { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 24rpx; color: #ccc; text-align: center; }
-.record-list { display: flex; flex-direction: column; }
-.record-item { display: flex; flex-direction: row; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.record-date { font-size: 24rpx; color: #999; width: 160rpx; flex-shrink: 0; }
-.record-data { display: flex; flex-direction: row; flex-wrap: wrap; flex: 1; }
-.data-item { font-size: 26rpx; color: #333; margin-right: 16rpx; }
-.record-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
-.bottom-spacer { height: 60rpx; }
-</style>

+ 0 - 1009
cfc-frontend/pages/body/health-report.vue

@@ -1,1009 +0,0 @@
-<template>
-  <view class="report-container">
-    <!-- 顶部导航 -->
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">健康报告详情</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <!-- 报告头部 -->
-    <view class="report-header" v-if="report">
-      <view class="report-type-tag">{{ reportTypeLabel }}</view>
-      <view class="report-scores">
-        <view class="score-item">
-          <view class="score-circle-lg">
-            <text class="score-num">{{ report.overallScore || '--' }}</text>
-            <text class="score-unit">分</text>
-          </view>
-          <text class="score-label">综合评分</text>
-        </view>
-        <view class="score-item" v-if="report.gutHealthScore">
-          <view class="score-circle-lg score-circle-green">
-            <text class="score-num">{{ report.gutHealthScore }}</text>
-            <text class="score-unit">分</text>
-          </view>
-          <text class="score-label">肠道健康</text>
-        </view>
-      </view>
-      <view class="report-info-row" v-if="report.gutAge || report.gutType">
-        <text class="info-chip" v-if="report.gutAge">肠道年龄:{{ report.gutAge }}</text>
-        <text class="info-chip" v-if="report.gutType">肠道分型:{{ report.gutType }}</text>
-      </view>
-      <text class="report-date">报告日期:{{ formatDate(report.reportDate) }}</text>
-    </view>
-
-    <!-- ===== 评分可视化面板 (仅菌群报告) ===== -->
-    <view class="score-dashboard" v-if="report && report.reportType === 'gut_flora'">
-      <view class="main-scores">
-        <view class="main-score-item" v-for="item in mainScores" :key="item.key">
-          <view class="score-ring-wrap">
-            <svg width="140" height="140" viewBox="0 0 120 120">
-              <circle cx="60" cy="60" r="50" fill="none" stroke="#EEEEEE" stroke-width="8"/>
-              <circle cx="60" cy="60" r="50" fill="none" v-bind:stroke="item.color" stroke-width="8" v-bind:stroke-dasharray="item.circumference" v-bind:stroke-dashoffset="item.offset" stroke-linecap="round" transform="rotate(-90 60 60)"/>
-              <text x="60" y="52" text-anchor="middle" font-size="32" font-weight="bold" fill="#333">{{ item.score }}</text>
-              <text x="60" y="72" text-anchor="middle" font-size="14" fill="#999">分</text>
-            </svg>
-          </view>
-          <text class="main-score-label">{{ item.label }}</text>
-        </view>
-      </view>
-      <view class="flora-score-row" v-if="hasFloraScores">
-        <view class="flora-score-item" v-for="item in floraScores" :key="item.key">
-          <view class="flora-score-circle" v-bind:style="{ background: item.color }">
-            <text class="flora-score-num">{{ item.score }}</text>
-          </view>
-          <text class="flora-score-label">{{ item.label }}</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 指标列表 -->
-    <view class="section">
-      <view class="section-title">
-        <text>指标明细</text>
-        <text class="section-sub">共 {{ indicators.length }} 项</text>
-      </view>
-
-      <!-- 按分类分组 -->
-      <view class="category-group" v-for="group in groupedIndicators" :key="group.name">
-        <view class="category-header">
-          <text class="category-name">{{ group.name }}</text>
-          <text class="category-count">{{ group.items.length }}项</text>
-        </view>
-        <view class="indicator-card" v-for="ind in group.items" :key="ind._idx">
-          <view class="ind-header">
-            <text class="ind-name">{{ ind.indicatorName }}</text>
-            <view class="ind-status" :class="ind._cls">
-              <text>{{ ind._label }}</text>
-            </view>
-          </view>
-          <view class="ind-body">
-            <view class="ind-value-wrap">
-              <text class="ind-value">{{ ind.indicatorValue || '--' }}</text>
-              <text class="ind-unit">{{ ind.unit || '' }}</text>
-            </view>
-            <text class="ind-ref">参考值:{{ ind.refRange || '暂无' }}</text>
-          </view>
-        </view>
-      </view>
-
-      <view class="empty-indicators" v-if="indicators.length === 0 && !loading">
-        <text>暂无指标数据</text>
-      </view>
-    </view>
-
-    <!-- ===== 营养缺乏分析 ===== -->
-    <view class="section" v-if="deficiencyRecords && deficiencyRecords.length > 0">
-      <view class="section-title">
-        <text>🔬 营养缺乏分析</text>
-        <text class="section-sub">{{ deficiencyRecords.length }}项</text>
-      </view>
-      <view class="deficiency-list">
-        <view class="deficiency-card" v-for="dr in deficiencyRecords" :key="dr.nutrient">
-          <view class="deficiency-header">
-            <text class="deficiency-nutrient">{{ dr.nutrient }}</text>
-            <view class="deficiency-type-tag" :class="'dtype-' + dr.deficiencyType">
-              <text>{{ dr.deficiencyTypeLabel }}</text>
-            </view>
-          </view>
-          <view class="deficiency-severity-row">
-            <view class="severity-bar">
-              <view class="severity-fill" :style="{ width: (dr.severity * 100) + '%', background: dr.severityColor }"></view>
-            </view>
-            <text class="severity-val">{{ dr.severity }}</text>
-          </view>
-          <view class="deficiency-meta" v-if="dr.indicatorName">
-            <text class="meta-label">关联指标:</text>
-            <text class="meta-value">{{ dr.indicatorName }} ({{ dr.indicatorValue }})</text>
-          </view>
-          <view class="deficiency-foods" v-if="dr.foodSuggestions">
-            <text class="foods-label">推荐食物:</text>
-            <text class="foods-value">{{ dr.foodSuggestions }}</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- ===== 食材推荐 ===== -->
-    <view class="section" v-if="foods && foods.length > 0">
-      <view class="section-title">
-        <text>🥗 食材推荐</text>
-        <text class="section-sub">共 {{ foods.length }} 项</text>
-      </view>
-      <view class="food-list">
-        <view class="food-card" v-for="food in foods" :key="food.id">
-          <view class="food-header">
-            <text class="food-name">{{ food.name }}</text>
-            <view class="food-score" :class="food.scoreClass">
-              <text>{{ food.score || '--' }}</text>
-            </view>
-          </view>
-          <view class="food-meta" v-if="food.category">
-            <text class="food-category">{{ food.category }}</text>
-          </view>
-          <view class="food-nutrition" v-if="food.energyKj || food.protein || food.fat || food.carbs">
-            <view class="nutrition-item" v-if="food.energyKj">
-              <text class="n-label">能量</text>
-              <text class="n-value">{{ food.energyKj }} kJ</text>
-            </view>
-            <view class="nutrition-item" v-if="food.protein">
-              <text class="n-label">蛋白质</text>
-              <text class="n-value">{{ food.protein }}g</text>
-            </view>
-            <view class="nutrition-item" v-if="food.fat">
-              <text class="n-label">脂肪</text>
-              <text class="n-value">{{ food.fat }}g</text>
-            </view>
-            <view class="nutrition-item" v-if="food.carbs">
-              <text class="n-label">碳水</text>
-              <text class="n-value">{{ food.carbs }}g</text>
-            </view>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- ===== 五维雷达图 (菌群报告) ===== -->
-    <view class="section" v-if="report && report.reportType === 'gut_flora' && radarDimensions.length > 0">
-      <view class="section-title">
-        <text>五维雷达图</text>
-      </view>
-      <view class="radar-wrap">
-        <canvas canvas-id="radarCanvas" id="radarCanvas" class="radar-canvas" @touchstart.prevent></canvas>
-      </view>
-    </view>
-
-    <!-- 底部占位 -->
-    <view class="bottom-spacer"></view>
-
-    <!-- 营养分析入口按钮 -->
-    <view class="nutrition-fab" @click="goNutritionChat" v-if="report">
-      <text class="fab-icon">🥗</text>
-      <text class="fab-text">精准营养分析</text>
-    </view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-import { getDeficiencyAnalysis } from '@/utils/api.js'
-
-const healthRequest = function(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': token ? 'Bearer ' + token : ''
-      },
-      success: function(res) {
-        if (res.data.code === 200) {
-          resolve(res.data)
-        } else {
-          reject(res.data)
-        }
-      },
-      fail: function(err) {
-        reject(err)
-      }
-    })
-  })
-}
-
-export default {
-  data() {
-    return {
-      childId: null,
-      reportId: null,
-      report: null,
-      reportDetail: null,
-      indicators: [],
-      deficiencyRecords: [],
-      foods: [],
-      loading: false
-    }
-  },
-  computed: {
-    reportTypeLabel: function() {
-      if (!this.report) return ''
-      return this.report.reportType === 'gut_flora' ? '肠道检测报告' : '体检报告'
-    },
-    // 预计算分组,每个指标附加 _cls (CSS类) 和 _label (显示标签)
-    groupedIndicators: function() {
-      var result = []
-      var groupMap = {}
-      var idx = 0
-      for (var i = 0; i < this.indicators.length; i++) {
-        var ind = this.indicators[i]
-        var cat = ind.category || '其他'
-        // Compute status css class
-        var cls = 'status-normal'
-        var label = '正常'
-        var status = ind.status || ''
-        var s = status.toLowerCase()
-        if (s.indexOf('偏高') !== -1 || s.indexOf('过高') !== -1 || s.indexOf('concern') !== -1) {
-          cls = 'status-warning'
-          label = '偏高'
-        } else if (s.indexOf('偏低') !== -1 || s.indexOf('过低') !== -1 || s.indexOf('注意') !== -1 || s.indexOf('warning') !== -1) {
-          cls = 'status-warn'
-          label = '偏低'
-        } else if (s.indexOf('正常') !== -1 || s.indexOf('normal') !== -1 || s.indexOf('低风险') !== -1) {
-          cls = 'status-normal'
-          label = '正常'
-        } else if (status) {
-          label = status
-        }
-        ind._cls = cls
-        ind._label = label
-        ind._idx = idx
-        idx++
-        // Group
-        if (!groupMap[cat]) {
-          groupMap[cat] = { name: cat, items: [] }
-          result.push(groupMap[cat])
-        }
-        groupMap[cat].items.push(ind)
-      }
-      return result
-    },
-    circumference: function() {
-      return Math.round(2 * Math.PI * 50)
-    },
-    mainScores: function() {
-      if (!this.report) return []
-      var items = []
-      var r = this.report
-      var pairs = [
-        {key:'overall', label:'综合评分', score: r.overallScore, color:'#5B9BD5'},
-        {key:'gut', label:'肠道健康', score: r.gutHealthScore, color:'#4CAF50'},
-        {key:'chronic', label:'慢病控制', score: r.chronicDiseaseScore, color:'#FF9800'},
-        {key:'nutrition', label:'营养均衡', score: r.nutritionScore, color:'#2196F3'}
-      ]
-      var circ = Math.round(2 * Math.PI * 50)
-      for (var i = 0; i < pairs.length; i++) {
-        var p = pairs[i]
-        var s = p.score || 0
-        items.push({key: p.key, label: p.label, score: s, color: p.color, circumference: circ, offset: Math.round(circ - (s / 100) * circ)})
-      }
-      return items
-    },
-    hasFloraScores: function() {
-      if (!this.report) return false
-      var r = this.report
-      return (r.balanceScore != null) || (r.diversityScore != null) || (r.beneficialScore != null) || (r.harmfulScore != null) || (r.coreGenusScore != null)
-    },
-    floraScores: function() {
-      if (!this.report) return []
-      var r = this.report
-      var items = []
-      var pairs = [
-        {key:'balance', label:'菌群平衡', score: r.balanceScore},
-        {key:'diversity', label:'菌群多样性', score: r.diversityScore},
-        {key:'beneficial', label:'有益菌', score: r.beneficialScore},
-        {key:'harmful', label:'有害菌', score: r.harmfulScore},
-        {key:'coreGenus', label:'核心菌属', score: r.coreGenusScore}
-      ]
-      for (var i = 0; i < pairs.length; i++) {
-        var p = pairs[i]
-        if (p.score != null) {
-          var s = p.score
-          var color = '#4CAF50'
-          if (s < 30) { color = '#F44336' }
-          else if (s < 60) { color = '#FFC107' }
-          items.push({key: p.key, label: p.label, score: s, color: color})
-        }
-      }
-      return items
-    },
-    radarDimensions: function() {
-      if (!this.report) return []
-      var r = this.report
-      var dims = []
-      if (r.overallScore != null) dims.push({ label: '综合', value: r.overallScore })
-      if (r.gutHealthScore != null) dims.push({ label: '肠道', value: r.gutHealthScore })
-      if (r.nutritionScore != null) dims.push({ label: '营养', value: r.nutritionScore })
-      if (r.chronicDiseaseScore != null) dims.push({ label: '慢病', value: r.chronicDiseaseScore })
-      if (r.gutBalanceScore != null) dims.push({ label: '菌群平衡', value: r.gutBalanceScore })
-      return dims
-    }
-  },
-  onLoad: function(options) {
-    var childId = options.childId
-    var reportId = options.reportId
-    this.childId = childId ? parseInt(childId) : null
-    this.reportId = reportId ? parseInt(reportId) : null
-    this.loadData()
-  },
-  onReady: function() {
-    if (this.radarDimensions.length >= 3) {
-      this.drawRadarChart()
-    }
-  },
-  methods: {
-    loadData: function() {
-      var self = this
-      uni.showLoading({ title: '加载中...' })
-      // Load report + indicators in parallel
-      var promises = []
-      if (this.reportId) {
-        promises.push(healthRequest('/api/health/report/latest', { childId: this.childId }))
-        promises.push(healthRequest('/api/health/indicator/list', { reportId: this.reportId }))
-      } else if (this.childId) {
-        promises.push(healthRequest('/api/health/report/latest', { childId: this.childId }))
-      }
-      Promise.all(promises.map(function(p) {
-        return p.catch(function(e) { return null })
-      })).then(function(results) {
-        uni.hideLoading()
-        if (results[0] && results[0].data) {
-          self.report = results[0].data
-          self.reportId = self.report.id
-          self.loadDeficiencyRecords()
-          // Load indicators after getting reportId
-          if (self.reportId) {
-            healthRequest('/api/health/indicator/list', { reportId: self.reportId }).then(function(res) {
-              if (res.data) {
-                self.indicators = res.data
-              }
-            }).catch(function(e) {
-              console.log('获取指标失败', e)
-            })
-            healthRequest('/api/health/report/detail', { reportId: self.reportId }).then(function(res) {
-              if (res.data) {
-                self.reportDetail = res.data
-              }
-            }).catch(function(e) {
-              console.log('获取报告详情失败', e)
-            })
-            healthRequest('/api/health/foods', { reportId: self.reportId }).then(function(res) {
-              if (res.data) {
-                self.foods = res.data.map(function(f) {
-                  f.scoreClass = self.getFoodScoreClass(f.score)
-                  return f
-                })
-              }
-            }).catch(function(e) {
-              console.log('获取食材推荐失败', e)
-            })
-          }
-        }
-      }).catch(function(e) {
-        uni.hideLoading()
-        uni.showToast({ title: '加载失败', icon: 'none' })
-      })
-    },
-    goBack: function() {
-      uni.navigateBack()
-    },
-    goNutritionChat: function() {
-      if (!this.reportId) return
-      uni.navigateTo({
-        url: '/pages/ai/chat?mode=nutrition&reportId=' + this.reportId + '&firstMessage=' + encodeURIComponent('请根据这份菌群检测报告,分析我孩子的营养状况并提出建议')
-      })
-    },
-    loadDeficiencyRecords: function() {
-      if (!this.childId) return
-      var self = this
-      getDeficiencyAnalysis(this.childId).then(function(res) {
-          if (res.code === 200 && res.data) {
-          self.deficiencyRecords = res.data.map(function(dr) {
-            dr.deficiencyTypeLabel = self.getDeficiencyTypeLabel(dr.deficiencyType)
-            dr.severityColor = self.getSeverityColor(dr.severity)
-            return dr
-          })
-        }
-      }).catch(function(e) {
-        console.log('获取缺乏分析失败', e)
-      })
-    },
-    getDeficiencyTypeLabel: function(type) {
-      if (type === 'deficiency') return '缺乏'
-      if (type === 'excess') return '偏高'
-      if (type === 'borderline') return '边界值'
-      return '未分类'
-    },
-    getSeverityColor: function(severity) {
-      if (severity >= 0.7) return '#EF4444'
-      if (severity >= 0.3) return '#F59E0B'
-      return '#10B981'
-    },
-    drawRadarChart: function() {
-      var dims = this.radarDimensions
-      if (!dims || dims.length < 3) return
-      var ctx = uni.createCanvasContext('radarCanvas', this)
-      var cw = 300
-      var ch = 300
-      var cx = cw / 2
-      var cy = ch / 2
-      var maxR = 110
-      var n = dims.length
-      var angleStep = (2 * Math.PI) / n
-
-      for (var level = 1; level <= 5; level++) {
-        var r = maxR * (level / 5)
-        ctx.setStrokeStyle('#E5E7EB')
-        ctx.setLineWidth(1)
-        ctx.beginPath()
-        for (var j = 0; j < n; j++) {
-          var a = j * angleStep - Math.PI / 2
-          var x = cx + r * Math.cos(a)
-          var y = cy + r * Math.sin(a)
-          if (j === 0) ctx.moveTo(x, y)
-          else ctx.lineTo(x, y)
-        }
-        ctx.closePath()
-        ctx.stroke()
-      }
-
-      for (var k = 0; k < n; k++) {
-        var a2 = k * angleStep - Math.PI / 2
-        ctx.beginPath()
-        ctx.moveTo(cx, cy)
-        ctx.lineTo(cx + maxR * Math.cos(a2), cy + maxR * Math.sin(a2))
-        ctx.setStrokeStyle('#E5E7EB')
-        ctx.setLineWidth(1)
-        ctx.stroke()
-      }
-
-      ctx.beginPath()
-      for (var m = 0; m < n; m++) {
-        var angle = m * angleStep - Math.PI / 2
-        var val = dims[m].value || 0
-        var rr = maxR * (val / 100)
-        var px = cx + rr * Math.cos(angle)
-        var py = cy + rr * Math.sin(angle)
-        if (m === 0) ctx.moveTo(px, py)
-        else ctx.lineTo(px, py)
-      }
-      ctx.closePath()
-      ctx.setFillStyle('rgba(249, 115, 22, 0.2)')
-      ctx.fill()
-      ctx.setStrokeStyle('#F97316')
-      ctx.setLineWidth(2)
-      ctx.stroke()
-
-      for (var q = 0; q < n; q++) {
-        var angle3 = q * angleStep - Math.PI / 2
-        var lx = cx + (maxR + 20) * Math.cos(angle3)
-        var ly = cy + (maxR + 20) * Math.sin(angle3)
-        ctx.setFontSize(11)
-        ctx.setFillStyle('#6B7280')
-        ctx.setTextAlign('center')
-        ctx.setTextBaseline('middle')
-        ctx.fillText(dims[q].label + ' ' + (dims[q].value || '--'), lx, ly)
-      }
-
-      ctx.draw()
-    },
-    getFoodScoreClass: function(score) {
-      if (score == null) return 'score-unknown'
-      if (score >= 80) return 'score-high'
-      if (score >= 60) return 'score-mid'
-      return 'score-low'
-    },
-    formatDate: function(dateStr) {
-      if (!dateStr) return '--'
-      try {
-        var d = new Date(dateStr)
-        var y = d.getFullYear()
-        var m = ('' + (d.getMonth() + 1)).padStart(2, '0')
-        var day = ('' + d.getDate()).padStart(2, '0')
-        return y + '-' + m + '-' + day
-      } catch (e) {
-        return dateStr
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.report-container {
-  min-height: 100vh;
-  background: #f5f7fa;
-}
-
-/* ===== 导航栏 ===== */
-.nav-bar {
-  position: sticky;
-  top: 0;
-  background: #fff;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  padding: 20rpx 30rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
-  z-index: 100;
-}
-.nav-back {
-  font-size: 32rpx;
-  color: #5B9BD5;
-}
-.nav-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-}
-.nav-placeholder {
-  width: 80rpx;
-}
-
-/* ===== 报告头部 ===== */
-.report-header {
-  background: linear-gradient(135deg, #E3F2FD, #BBDEFB);
-  margin: 20rpx 30rpx;
-  border-radius: 20rpx;
-  padding: 30rpx 24rpx;
-}
-.report-type-tag {
-  display: inline-block;
-  background: #1565C0;
-  color: #fff;
-  font-size: 22rpx;
-  padding: 6rpx 20rpx;
-  border-radius: 20rpx;
-  margin-bottom: 20rpx;
-}
-.report-scores {
-  display: flex;
-  flex-direction: row;
-  margin-bottom: 20rpx;
-}
-.score-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin-right: 60rpx;
-}
-.score-circle-lg {
-  width: 140rpx;
-  height: 140rpx;
-  border-radius: 70rpx;
-  background: #fff;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 8rpx;
-}
-.score-circle-green {
-  background: #E8F5E9;
-}
-.score-num {
-  font-size: 48rpx;
-  font-weight: bold;
-  color: #1565C0;
-}
-.score-circle-green .score-num {
-  color: #2E7D32;
-}
-.score-unit {
-  font-size: 22rpx;
-  color: #64B5F6;
-}
-.score-circle-green .score-unit {
-  color: #81C784;
-}
-.score-label {
-  font-size: 24rpx;
-  color: #666;
-}
-.report-info-row {
-  display: flex;
-  flex-direction: row;
-  margin-bottom: 16rpx;
-}
-.info-chip {
-  background: #fff;
-  color: #333;
-  font-size: 24rpx;
-  padding: 8rpx 20rpx;
-  border-radius: 20rpx;
-  margin-right: 16rpx;
-}
-.report-date {
-  font-size: 22rpx;
-  color: #888;
-}
-
-/* ===== 指标明细 ===== */
-.section {
-  margin: 20rpx 30rpx;
-}
-.section-title {
-  display: flex;
-  flex-direction: row;
-  align-items: baseline;
-  margin-bottom: 20rpx;
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-}
-.section-sub {
-  font-size: 22rpx;
-  color: #999;
-  font-weight: normal;
-  margin-left: 12rpx;
-}
-.category-group {
-  margin-bottom: 30rpx;
-}
-.category-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 12rpx;
-}
-.category-name {
-  font-size: 26rpx;
-  font-weight: bold;
-  color: #333;
-}
-.category-count {
-  font-size: 22rpx;
-  color: #999;
-}
-.indicator-card {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  margin-bottom: 12rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
-}
-.ind-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 12rpx;
-}
-.ind-name {
-  font-size: 28rpx;
-  color: #333;
-  font-weight: 500;
-}
-.ind-status {
-  font-size: 22rpx;
-  padding: 4rpx 16rpx;
-  border-radius: 16rpx;
-}
-.status-normal {
-  background: #E8F5E9;
-  color: #2E7D32;
-}
-.status-warning {
-  background: #FFF3E0;
-  color: #E65100;
-}
-.status-warn {
-  background: #F3E5F5;
-  color: #7B1FA2;
-}
-.ind-body {
-  display: flex;
-  flex-direction: row;
-  align-items: baseline;
-  justify-content: space-between;
-}
-.ind-value-wrap {
-  display: flex;
-  flex-direction: row;
-  align-items: baseline;
-}
-.ind-value {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #1565C0;
-}
-.ind-unit {
-  font-size: 24rpx;
-  color: #999;
-  margin-left: 8rpx;
-}
-.ind-ref {
-  font-size: 22rpx;
-  color: #bbb;
-}
-.empty-indicators {
-  text-align: center;
-  padding: 60rpx;
-  color: #999;
-  font-size: 28rpx;
-}
-
-/* ===== 底部 ===== */
-.bottom-spacer {
-  height: 130rpx;
-}
-
-/* ===== 营养分析入口 FAB ===== */
-.nutrition-fab {
-  position: fixed;
-  bottom: 40rpx;
-  left: 50%;
-  transform: translateX(-50%);
-  background: linear-gradient(135deg, #43A047, #66BB6A);
-  color: #fff;
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding: 20rpx 40rpx;
-  border-radius: 50rpx;
-  box-shadow: 0 8rpx 24rpx rgba(67, 160, 71, 0.4);
-  z-index: 200;
-}
-
-.fab-icon {
-  font-size: 36rpx;
-  margin-right: 12rpx;
-}
-
-.fab-text {
-  font-size: 30rpx;
-  font-weight: 600;
-}
-
-.score-dashboard {
-  margin: 20rpx 30rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.main-scores {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  justify-content: space-around;
-}
-.main-score-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin-bottom: 20rpx;
-  width: 45%;
-}
-.score-ring-wrap {
-  margin-bottom: 8rpx;
-}
-.main-score-label {
-  font-size: 24rpx;
-  color: #666;
-  font-weight: 500;
-}
-.flora-score-row {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-around;
-  margin-top: 16rpx;
-  padding-top: 20rpx;
-  border-top: 2rpx solid #F0F0F0;
-}
-.flora-score-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-.flora-score-circle {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 40rpx;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 8rpx;
-}
-.flora-score-num {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #fff;
-}
-.flora-score-label {
-  font-size: 20rpx;
-  color: #888;
-}
-
-/* ===== 营养缺乏分析 ===== */
-.deficiency-list {
-  display: flex;
-  flex-direction: column;
-}
-.deficiency-card {
-  background: #FAFAFA;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  margin-bottom: 12rpx;
-}
-.deficiency-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 12rpx;
-}
-.deficiency-nutrient {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-}
-.deficiency-type-tag {
-  font-size: 22rpx;
-  padding: 4rpx 16rpx;
-  border-radius: 16rpx;
-  background: #FFF3E0;
-  color: #E65100;
-}
-.dtype-deficiency {
-  background: #FEE2E2;
-  color: #DC2626;
-}
-.dtype-excess {
-  background: #FFF3E0;
-  color: #E65100;
-}
-.dtype-borderline {
-  background: #FEF3C7;
-  color: #D97706;
-}
-.dtype-unclassified {
-  background: #F3F4F6;
-  color: #6B7280;
-}
-.deficiency-severity-row {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  margin-bottom: 8rpx;
-}
-.severity-bar {
-  flex: 1;
-  height: 12rpx;
-  background: #F3F4F6;
-  border-radius: 6rpx;
-  overflow: hidden;
-  margin-right: 16rpx;
-}
-.severity-fill {
-  height: 100%;
-  border-radius: 6rpx;
-}
-.severity-val {
-  font-size: 24rpx;
-  color: #9CA3AF;
-  width: 60rpx;
-  text-align: right;
-}
-.deficiency-meta {
-  font-size: 22rpx;
-  color: #6B7280;
-  margin-bottom: 6rpx;
-}
-.deficiency-foods {
-  font-size: 22rpx;
-  color: #374151;
-  margin-top: 8rpx;
-}
-.foods-label {
-  color: #9CA3AF;
-}
-.foods-value {
-  color: #2E7D32;
-}
-
-/* ===== 雷达图 ===== */
-.radar-wrap {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-}
-.radar-canvas {
-  width: 300px;
-  height: 300px;
-  margin: 0 auto;
-}
-
-/* ===== 食材推荐 ===== */
-.food-list {
-  display: flex;
-  flex-direction: column;
-}
-.food-card {
-  background: #fff;
-  border-radius: 16rpx;
-  padding: 20rpx;
-  margin-bottom: 12rpx;
-  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
-}
-.food-header {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 8rpx;
-}
-.food-name {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-}
-.food-score {
-  font-size: 24rpx;
-  padding: 4rpx 16rpx;
-  border-radius: 16rpx;
-  font-weight: 600;
-}
-.score-high {
-  background: #DCFCE7;
-  color: #16A34A;
-}
-.score-mid {
-  background: #FEF3C7;
-  color: #D97706;
-}
-.score-low {
-  background: #FEE2E2;
-  color: #DC2626;
-}
-.score-unknown {
-  background: #F3F4F6;
-  color: #6B7280;
-}
-.food-meta {
-  margin-bottom: 8rpx;
-}
-.food-category {
-  font-size: 22rpx;
-  color: #6B7280;
-  background: #F3F4F6;
-  padding: 2rpx 12rpx;
-  border-radius: 12rpx;
-}
-.food-nutrition {
-  display: flex;
-  flex-direction: row;
-  flex-wrap: wrap;
-  gap: 16rpx;
-  margin-top: 8rpx;
-}
-.nutrition-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  min-width: 100rpx;
-}
-.n-label {
-  font-size: 20rpx;
-  color: #9CA3AF;
-}
-.n-value {
-  font-size: 24rpx;
-  color: #374151;
-  font-weight: 500;
-}
-</style>

+ 0 - 190
cfc-frontend/pages/body/health-trend.vue

@@ -1,190 +0,0 @@
-<template>
-  <view class="trend-container">
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">健康趋势</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <view class="date-tabs">
-      <text class="date-tab" :class="{ 'date-tab-active': activeTab === '7d' }" @click="switchDateRange('7d')">近7天</text>
-      <text class="date-tab" :class="{ 'date-tab-active': activeTab === '30d' }" @click="switchDateRange('30d')">近30天</text>
-    </view>
-
-    <!-- 平均分概览 -->
-    <view class="summary-section" v-if="averages">
-      <view class="summary-title">平均评分</view>
-      <view class="summary-grid">
-        <view class="summary-item" v-for="(val, key) in averages" :key="key">
-          <text class="summary-value" :style="{ color: scoreColor(key) }">{{ val }}</text>
-          <text class="summary-label">{{ scoreLabel(key) }}</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 趋势图 -->
-    <view class="chart-section">
-      <view class="chart-category" v-for="cat in chartCategories" :key="cat.key">
-        <view class="chart-header">
-          <text class="chart-label">{{ cat.label }}</text>
-          <text class="chart-avg" :style="{ color: cat.color }">{{ currentAvg(cat.key) }}分</text>
-        </view>
-        <view class="chart-bars">
-          <view class="chart-bar-col" v-for="(day, idx) in trendData" :key="idx">
-            <view class="bar-wrapper">
-              <view class="bar-fill" :style="{ height: barHeight(day[cat.key]), background: cat.color }"></view>
-            </view>
-            <text class="bar-label">{{ day.date.substring(day.date.length - 2) }}</text>
-          </view>
-        </view>
-      </view>
-    </view>
-
-    <!-- 加载状态 -->
-    <view class="loading-state" v-if="loading">
-      <text class="loading-text">加载中...</text>
-    </view>
-
-    <!-- 空状态 -->
-    <view class="empty-state" v-if="!loading && trendData.length === 0">
-      <text class="empty-icon">📊</text>
-      <text class="empty-text">暂无趋势数据</text>
-      <text class="empty-desc">开始每日健康打卡后,这里将展示变化趋势</text>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-
-export default {
-  data() {
-    return {
-      childId: null,
-      activeTab: '7d',
-      loading: false,
-      trendData: [],
-      averages: null,
-      chartCategories: [
-        { key: 'diet', label: '饮食', color: '#FF8C42' },
-        { key: 'exercise', label: '运动', color: '#FFA726' },
-        { key: 'sleep', label: '作息', color: '#FF7043' },
-        { key: 'water', label: '饮水', color: '#FFB74D' },
-        { key: 'overall', label: '综合', color: '#E65100' }
-      ]
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-    this.loadData()
-  },
-  methods: {
-    loadData: function() {
-      this.loading = true
-      var self = this
-      var days = this.activeTab === '30d' ? 30 : 7
-      var token = uni.getStorageSync('token')
-      uni.request({
-        url: config.api('/api/daily/checkin/list'),
-        method: 'POST',
-        data: { childId: this.childId, days: days },
-        header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' },
-        success: function(res) {
-          self.loading = false
-          if (res.data && res.data.code === 200 && res.data.data) {
-            self.trendData = res.data.data.scores || []
-            self.averages = res.data.data.averages || null
-          } else {
-            self.useMockData()
-          }
-        },
-        fail: function() {
-          self.loading = false
-          self.useMockData()
-        }
-      })
-    },
-    useMockData: function() {
-      var days = this.activeTab === '30d' ? 30 : 7
-      var mock = []
-      var startDay = 25
-      for (var i = 0; i < days; i++) {
-        var dayNum = (startDay + i) % 31
-        if (dayNum === 0) dayNum = 1
-        mock.push({
-          date: '06-' + (dayNum < 10 ? '0' + dayNum : dayNum),
-          diet: Math.floor(Math.random() * 30) + 60,
-          exercise: Math.floor(Math.random() * 30) + 55,
-          sleep: Math.floor(Math.random() * 25) + 65,
-          water: Math.floor(Math.random() * 30) + 55,
-          overall: Math.floor(Math.random() * 20) + 70
-        })
-      }
-      this.trendData = mock
-      this.averages = {
-        diet: Math.round(mock.reduce(function(s, d) { return s + d.diet }, 0) / mock.length),
-        exercise: Math.round(mock.reduce(function(s, d) { return s + d.exercise }, 0) / mock.length),
-        sleep: Math.round(mock.reduce(function(s, d) { return s + d.sleep }, 0) / mock.length),
-        water: Math.round(mock.reduce(function(s, d) { return s + d.water }, 0) / mock.length),
-        overall: Math.round(mock.reduce(function(s, d) { return s + d.overall }, 0) / mock.length)
-      }
-    },
-    switchDateRange: function(range) {
-      this.activeTab = range
-      this.loadData()
-    },
-    barHeight: function(score) {
-      return Math.max(4, (score || 0) * 0.6) + 'px'
-    },
-    currentAvg: function(key) {
-      if (this.averages && this.averages[key] !== undefined) return this.averages[key]
-      return '--'
-    },
-    scoreLabel: function(key) {
-      var map = { diet: '饮食', exercise: '运动', sleep: '作息', water: '饮水', overall: '综合' }
-      return map[key] || key
-    },
-    scoreColor: function(key) {
-      var map = { diet: '#FF8C42', exercise: '#FFA726', sleep: '#FF7043', water: '#FFB74D', overall: '#E65100' }
-      return map[key] || '#FF8C42'
-    },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-</script>
-
-<style scoped>
-.trend-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
-.nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.date-tabs { display: flex; flex-direction: row; margin: 16rpx 30rpx; background: #fff; border-radius: 40rpx; padding: 4rpx; }
-.date-tab { flex: 1; text-align: center; padding: 16rpx; font-size: 26rpx; color: #666; border-radius: 36rpx; }
-.date-tab-active { background: #FF8C42; color: #fff; font-weight: bold; }
-.summary-section { margin: 16rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.summary-title { font-size: 28rpx; font-weight: bold; color: #333; margin-bottom: 16rpx; }
-.summary-grid { display: flex; flex-direction: row; flex-wrap: wrap; }
-.summary-item { display: flex; flex-direction: column; align-items: center; width: 20%; margin-bottom: 8rpx; }
-.summary-value { font-size: 36rpx; font-weight: bold; }
-.summary-label { font-size: 22rpx; color: #999; margin-top: 4rpx; }
-.chart-section { margin: 16rpx 30rpx; }
-.chart-category { background: #fff; border-radius: 20rpx; padding: 20rpx; margin-bottom: 12rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.chart-header { display: flex; flex-direction: row; align-items: center; justify-content: space-between; margin-bottom: 16rpx; }
-.chart-label { font-size: 28rpx; font-weight: 600; color: #333; }
-.chart-avg { font-size: 26rpx; font-weight: bold; }
-.chart-bars { display: flex; flex-direction: row; align-items: flex-end; height: 80rpx; }
-.chart-bar-col { display: flex; flex-direction: column; align-items: center; flex: 1; }
-.bar-wrapper { width: 12rpx; height: 60rpx; background: #f0f0f0; border-radius: 6rpx; position: relative; display: flex; flex-direction: column; justify-content: flex-end; }
-.bar-fill { width: 100%; border-radius: 6rpx; transition: height 0.3s; min-height: 4rpx; }
-.bar-label { font-size: 18rpx; color: #999; margin-top: 6rpx; }
-.loading-state { display: flex; justify-content: center; padding: 60rpx; }
-.loading-text { font-size: 28rpx; color: #999; }
-.empty-state { display: flex; flex-direction: column; align-items: center; padding: 80rpx 30rpx; }
-.empty-icon { font-size: 80rpx; margin-bottom: 16rpx; }
-.empty-text { font-size: 28rpx; color: #666; margin-bottom: 8rpx; }
-.empty-desc { font-size: 24rpx; color: #999; text-align: center; }
-.bottom-spacer { height: 60rpx; }
-</style>

+ 0 - 241
cfc-frontend/pages/body/meal-record.vue

@@ -1,241 +0,0 @@
-<template>
-  <view class="meal-container">
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">饮食记录</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <!-- 成员选择 -->
-    <view class="member-selector-card">
-      <text class="member-label">记录人</text>
-      <picker mode="selector" :range="memberOptions" :value="selectedMemberIndex" @change="onMemberChange">
-        <view class="picker-display">{{ memberOptions[selectedMemberIndex] }}</view>
-      </picker>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">今日饮食</view>
-      <view class="meal-types">
-        <view class="meal-type-item" v-for="type in mealTypes" :key="type.value"
-          :class="{ 'meal-type-active': activeMealType === type.value }"
-          @click="activeMealType = type.value">
-          <text>{{ type.label }}</text>
-        </view>
-      </view>
-
-      <view class="food-input-area">
-        <view class="input-row">
-          <text class="input-label">食物名称</text>
-          <input class="input-field" v-model="foodName" placeholder="输入食物名称" />
-        </view>
-        <view class="input-row">
-          <text class="input-label">份量</text>
-          <input class="input-field" v-model="foodAmount" placeholder="如:1碗、200g" />
-        </view>
-        <view class="input-row" v-if="foodName">
-          <text class="input-label">预估热量</text>
-          <input class="input-field" v-model="calories" type="number" placeholder="可选" />
-        </view>
-        <view class="add-food-btn" @click="addFood">添加食物</view>
-      </view>
-
-      <view class="food-list">
-        <view class="food-item" v-for="(food, idx) in todayFoods" :key="idx">
-          <view class="food-info">
-            <text class="food-name">{{ food.foodName }}</text>
-            <text class="food-amount">{{ food.foodAmount }}</text>
-          </view>
-          <text class="food-cal" v-if="food.calories">{{ food.calories }} kcal</text>
-          <text class="food-del" @click="todayFoods.splice(idx, 1)">✕</text>
-        </view>
-        <view class="food-empty" v-if="todayFoods.length === 0">
-          <text>暂未添加食物</text>
-        </view>
-      </view>
-
-      <view class="submit-btn" @click="submitMeal">
-        <text>保存记录</text>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">历史记录</view>
-      <view class="history-list">
-        <view class="history-item" v-for="item in mealHistory" :key="item.id">
-          <text class="history-date">{{ item.eatenAt ? item.eatenAt.substring(0,10) : '' }}</text>
-          <text class="history-type">{{ item.mealType }}</text>
-          <text class="history-food">{{ item.foodName }} {{ item.foodAmount }}</text>
-        </view>
-        <view class="history-empty" v-if="mealHistory.length === 0">
-          <text>暂无饮食记录</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-import { getChildren } from '@/utils/api.js'
-
-export default {
-  data() {
-    return {
-      childId: null,
-      selectedMemberIndex: 0,
-      memberOptions: ['我自己'],
-      memberIds: [],
-      activeMealType: 'breakfast',
-      foodName: '',
-      foodAmount: '',
-      calories: '',
-      todayFoods: [],
-      mealHistory: [],
-      mealTypes: [
-        { label: '早餐', value: 'breakfast' },
-        { label: '午餐', value: 'lunch' },
-        { label: '晚餐', value: 'dinner' },
-        { label: '加餐', value: 'snack' }
-      ]
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-    this.loadChildren()
-    if (this.childId) this.loadHistory()
-  },
-  methods: {
-    loadChildren: function() {
-      var self = this
-      getChildren().then(function(res) {
-        if (res.code === 200 && res.data && res.data.length > 0) {
-          self.memberIds = res.data.map(function(c) { return c.childId })
-          self.memberOptions = ['我自己'].concat(res.data.map(function(c) { return c.name || c.nickname || '孩子' }))
-        }
-      }).catch(function() {})
-    },
-    onMemberChange: function(e) {
-      this.selectedMemberIndex = parseInt(e.detail.value)
-      if (this.selectedMemberIndex === 0) {
-        this.childId = null
-      } else {
-        this.childId = this.memberIds[this.selectedMemberIndex - 1]
-      }
-      if (this.childId) this.loadHistory()
-    },
-    addFood: function() {
-      if (!this.foodName.trim()) {
-        uni.showToast({ title: '请输入食物名称', icon: 'none' })
-        return
-      }
-      this.todayFoods.push({
-        mealType: this.activeMealType,
-        foodName: this.foodName.trim(),
-        foodAmount: this.foodAmount.trim() || '1份',
-        calories: this.calories ? parseInt(this.calories) : null
-      })
-      this.foodName = ''
-      this.foodAmount = ''
-      this.calories = ''
-    },
-    submitMeal: function() {
-      if (this.todayFoods.length === 0) {
-        uni.showToast({ title: '请至少添加一个食物', icon: 'none' })
-        return
-      }
-      uni.showLoading({ title: '保存中...' })
-      var self = this
-      var promises = this.todayFoods.map(function(food) {
-        var data = {
-          mealType: food.mealType,
-          foodName: food.foodName,
-          foodAmount: food.foodAmount,
-          calories: food.calories
-        }
-        if (self.childId) {
-          data.childId = self.childId
-          data.memberId = self.childId
-        }
-        return healthRequest('/api/health/meal/create', data)
-      })
-      Promise.all(promises).then(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '保存成功', icon: 'success' })
-        this.todayFoods = []
-        this.loadHistory()
-      }.bind(this)).catch(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '保存失败,请在后续版本中使用', icon: 'none' })
-      })
-    },
-    loadHistory: function() {
-      var self = this
-      var params = {}
-      if (this.childId) params.childId = this.childId
-      healthRequest('/api/health/meal/list', params).then(function(res) {
-        if (res.data) self.mealHistory = res.data
-      }).catch(function() { self.mealHistory = [] })
-    },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-
-function healthRequest(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: {
-        'Content-Type': 'application/json',
-        'Authorization': token ? 'Bearer ' + token : ''
-      },
-      success: function(res) {
-        if (res.data.code === 200) resolve(res.data)
-        else reject(res.data)
-      },
-      fail: function(err) { reject(err) }
-    })
-  })
-}
-</script>
-
-<style scoped>
-.meal-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
-.nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.member-selector-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 20rpx 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
-.member-label { font-size: 28rpx; color: #666; }
-.picker-display { font-size: 28rpx; color: #333; font-weight: 500; }
-.section-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; }
-.meal-types { display: flex; flex-direction: row; margin-bottom: 20rpx; }
-.meal-type-item { padding: 10rpx 24rpx; border-radius: 20rpx; font-size: 26rpx; color: #666; background: #f0f0f0; margin-right: 16rpx; }
-.meal-type-active { background: #FFF3E0; color: #E65100; font-weight: 500; }
-.input-row { display: flex; flex-direction: row; align-items: center; margin-bottom: 16rpx; }
-.input-label { font-size: 26rpx; color: #666; width: 140rpx; flex-shrink: 0; }
-.input-field { flex: 1; background: #f5f7fa; border-radius: 12rpx; padding: 14rpx 16rpx; font-size: 28rpx; }
-.add-food-btn { background: #E3F2FD; color: #1565C0; font-size: 26rpx; text-align: center; padding: 14rpx; border-radius: 12rpx; margin-top: 8rpx; }
-.food-list { margin-top: 16rpx; }
-.food-item { display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 12rpx 0; border-bottom: 1rpx solid #f0f0f0; }
-.food-info { flex: 1; }
-.food-name { font-size: 28rpx; color: #333; }
-.food-amount { font-size: 24rpx; color: #999; margin-left: 12rpx; }
-.food-cal { font-size: 24rpx; color: #E65100; margin-right: 16rpx; }
-.food-del { font-size: 28rpx; color: #ccc; }
-.food-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
-.submit-btn { background: linear-gradient(135deg, #4CAF50, #66BB6A); color: #fff; font-size: 32rpx; font-weight: bold; text-align: center; padding: 22rpx; border-radius: 40rpx; margin-top: 16rpx; }
-.history-list { display: flex; flex-direction: column; }
-.history-item { display: flex; flex-direction: row; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.history-date { font-size: 24rpx; color: #999; width: 160rpx; }
-.history-type { font-size: 24rpx; color: #1565C0; background: #E3F2FD; border-radius: 8rpx; padding: 2rpx 12rpx; margin-right: 12rpx; }
-.history-food { font-size: 26rpx; color: #333; flex: 1; }
-.history-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
-.bottom-spacer { height: 60rpx; }
-</style>

+ 0 - 249
cfc-frontend/pages/body/recipe-recommend.vue

@@ -1,249 +0,0 @@
-<template>
-  <view class="recipe-container">
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">食谱推荐</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <view class="meal-tabs">
-      <text class="tab-item" v-for="mt in mealTypes" :key="mt.value"
-        :class="{ 'tab-active': selectedMeal === mt.value }"
-        @click="selectMeal(mt.value)">
-        <text class="tab-icon">{{ mt.icon }}</text>
-        <text class="tab-label">{{ mt.label }}</text>
-      </text>
-    </view>
-
-    <view class="section-card">
-      <view class="card-header">
-        <text class="card-title">🥗 AI 智能食谱</text>
-        <text class="card-subtitle">基于健康数据个性化推荐</text>
-      </view>
-      <view class="recommend-btn" :class="{ 'recommend-btn-loading': loading }" @click="doRecommend">
-        <text class="recommend-btn-text">{{ loading ? 'AI思考中...' : '生成今日食谱' }}</text>
-        <text class="recommend-btn-icon">✨</text>
-      </view>
-    </view>
-
-    <view class="section-card" v-if="recipeResult">
-      <view class="card-header">
-        <text class="card-title">📋 推荐食谱</text>
-      </view>
-      <view class="daily-meals">
-        <view class="meal-block" v-for="(meal, idx) in parsedMeals" :key="idx">
-          <view class="meal-block-header">
-            <text class="meal-block-icon">{{ mealTypeIcon(meal.mealType) }}</text>
-            <text class="meal-block-title">{{ meal.mealType || meal.recipeName }}</text>
-          </view>
-          <text class="meal-reason" v-if="meal.reason">{{ meal.reason }}</text>
-          <view class="meal-ingredients" v-if="meal.ingredients">
-            <text class="ingredient-label">食材:</text>
-            <text class="ingredient-text">{{ meal.ingredients }}</text>
-          </view>
-          <text class="meal-tips" v-if="meal.tips">💡 {{ meal.tips }}</text>
-        </view>
-      </view>
-      <view class="nutrition-tip" v-if="parsedNutritionTip">
-        <text class="tip-label">营养建议</text>
-        <text class="tip-text">{{ parsedNutritionTip }}</text>
-      </view>
-    </view>
-
-    <view class="section-card" v-if="recipeResult">
-      <view class="card-header">
-        <text class="card-title">🔄 食材替换建议</text>
-        <text class="card-subtitle">一键替换推荐食材</text>
-      </view>
-      <view class="replace-options">
-        <view class="replace-item" v-for="(food, idx) in replaceOptions" :key="idx" @click="replaceFood(food.id)">
-          <text class="replace-name">{{ food.name }}</text>
-          <text class="replace-score" v-if="food.score">{{ food.score }}分</text>
-          <text class="replace-icon">↻</text>
-        </view>
-        <view class="replace-empty" v-if="!replaceOptions || replaceOptions.length === 0">
-          <text>暂无替换建议</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="section-card" v-if="recipeResult">
-      <view class="card-header">
-        <text class="card-title">📊 营养摄取概要</text>
-      </view>
-      <view class="nutrition-summary">
-        <view class="nutri-stat" v-for="(val, key) in nutritionSummary" :key="key">
-          <text class="nutri-value">{{ val }}</text>
-          <text class="nutri-label">{{ key }}</text>
-        </view>
-        <view class="nutri-empty" v-if="!nutritionSummary || Object.keys(nutritionSummary).length === 0">
-          <text>暂无数据</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-
-export default {
-  data() {
-    return {
-      childId: null,
-      selectedMeal: 'daily',
-      loading: false,
-      recipeResult: null,
-      replaceOptions: [],
-      nutritionSummary: null,
-      mealTypes: [
-        { label: '每日', value: 'daily', icon: '📅' },
-        { label: '早餐', value: 'breakfast', icon: '🌅' },
-        { label: '午餐', value: 'lunch', icon: '☀️' },
-        { label: '晚餐', value: 'dinner', icon: '🌙' },
-        { label: '加餐', value: 'snack', icon: '🍪' }
-      ]
-    }
-  },
-  computed: {
-    parsedMeals: function() {
-      if (!this.recipeResult || !this.recipeResult.answer) return []
-      try {
-        var parsed = JSON.parse(this.recipeResult.answer)
-        if (parsed.dailyMeals) return parsed.dailyMeals
-        if (parsed.meals) return parsed.meals
-        return [{ recipeName: 'AI推荐', reason: this.recipeResult.answer.substring(0, 100) + '...' }]
-      } catch (e) {
-        return [{ recipeName: 'AI推荐方案', reason: this.recipeResult.answer }]
-      }
-    },
-    parsedNutritionTip: function() {
-      if (!this.recipeResult || !this.recipeResult.answer) return ''
-      try {
-        var parsed = JSON.parse(this.recipeResult.answer)
-        return parsed.nutritionTip || parsed.tips || ''
-      } catch (e) {
-        return ''
-      }
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-  },
-  methods: {
-    selectMeal: function(value) {
-      this.selectedMeal = value
-    },
-    doRecommend: function() {
-      this.loading = true
-      var self = this
-      healthRequest('/api/meal/recommend', {
-        mealType: this.selectedMeal !== 'daily' ? this.selectedMeal : null,
-        childId: this.childId
-      }).then(function(res) {
-        self.loading = false
-        if (res.data) {
-          self.recipeResult = res.data
-          self.replaceOptions = res.data.replaceOptions || []
-          self.nutritionSummary = res.data.nutritionSummary || null
-        }
-      }).catch(function(e) {
-        self.loading = false
-        var mockData = {
-          answer: JSON.stringify({
-            dailyMeals: [
-              { mealType: '早餐', recipeName: '全麦三明治+牛奶', reason: '搭配优质碳水+蛋白质', ingredients: '全麦面包2片、鸡蛋1个、生菜2片、牛奶200ml', tips: '全麦面包提供膳食纤维,牛奶补充钙质' },
-              { mealType: '午餐', recipeName: '清蒸鱼+杂粮饭', reason: '优质蛋白+复合碳水', ingredients: '鲈鱼150g、西兰花100g、杂粮米50g', tips: '鱼类富含DHA,有助于大脑发育' },
-              { mealType: '晚餐', recipeName: '番茄鸡肉意面', reason: '均衡营养易消化', ingredients: '鸡胸肉100g、番茄1个、意面80g', tips: '晚餐适量减少碳水量' }
-            ],
-            nutritionTip: '建议每日摄入:蛋白质50-60g,碳水150-200g,蔬果500g以上'
-          }),
-          conversationId: '',
-          replaceOptions: [{ id: 1, name: '鸡胸肉', score: 85 }, { id: 2, name: '三文鱼', score: 90 }, { id: 3, name: '豆腐', score: 75 }],
-          nutritionSummary: { 蛋白质: '55g', 碳水: '180g', 脂肪: '40g', 膳食纤维: '15g', 热量: '1450kcal' }
-        }
-        self.recipeResult = mockData
-        self.replaceOptions = mockData.replaceOptions
-        self.nutritionSummary = mockData.nutritionSummary
-      })
-    },
-    replaceFood: function(foodId) {
-      var self = this
-      healthRequest('/api/meal/replace', { foodId: foodId, childId: this.childId }).then(function(res) {
-        if (res.data) {
-          uni.showToast({ title: '已替换:' + (res.data.name || ''), icon: 'success' })
-        }
-      }).catch(function() {
-        uni.showToast({ title: '替换失败', icon: 'none' })
-      })
-    },
-    mealTypeIcon: function(type) {
-      var map = { '早餐': '🌅', '午餐': '☀️', '晚餐': '🌙', '加餐': '🍪', 'breakfast': '🌅', 'lunch': '☀️', 'dinner': '🌙', 'snack': '🍪' }
-      return map[type] || '🍽️'
-    },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-
-function healthRequest(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' },
-      success: function(res) { if (res.data.code === 200) resolve(res.data); else reject(res.data) },
-      fail: function(err) { reject(err) }
-    })
-  })
-}
-</script>
-
-<style scoped>
-.recipe-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
-.nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.meal-tabs { display: flex; flex-direction: row; margin: 16rpx 30rpx; }
-.tab-item { display: flex; flex-direction: column; align-items: center; padding: 12rpx 20rpx; border-radius: 16rpx; background: #fff; margin-right: 12rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); }
-.tab-active { background: #FFF3E0; border: 2rpx solid #FF8C42; }
-.tab-icon { font-size: 28rpx; }
-.tab-label { font-size: 22rpx; color: #666; margin-top: 4rpx; }
-.section-card { margin: 16rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.card-header { margin-bottom: 16rpx; }
-.card-title { font-size: 30rpx; font-weight: bold; color: #333; }
-.card-subtitle { font-size: 24rpx; color: #999; margin-top: 4rpx; }
-.recommend-btn { display: flex; flex-direction: row; align-items: center; justify-content: center; background: linear-gradient(135deg, #FF8C42, #FFA726); color: #fff; padding: 24rpx; border-radius: 40rpx; }
-.recommend-btn-loading { opacity: 0.7; }
-.recommend-btn-text { font-size: 32rpx; font-weight: bold; margin-right: 12rpx; }
-.recommend-btn-icon { font-size: 36rpx; }
-.daily-meals { display: flex; flex-direction: column; }
-.meal-block { background: #FFFBEB; border-radius: 16rpx; padding: 16rpx; margin-bottom: 12rpx; }
-.meal-block-header { display: flex; flex-direction: row; align-items: center; margin-bottom: 8rpx; }
-.meal-block-icon { font-size: 32rpx; margin-right: 8rpx; }
-.meal-block-title { font-size: 28rpx; font-weight: 600; color: #333; }
-.meal-reason { font-size: 24rpx; color: #666; margin-bottom: 8rpx; }
-.meal-ingredients { display: flex; flex-direction: row; flex-wrap: wrap; margin-bottom: 6rpx; }
-.ingredient-label { font-size: 24rpx; color: #999; }
-.ingredient-text { font-size: 24rpx; color: #333; flex: 1; }
-.meal-tips { font-size: 24rpx; color: #E65100; }
-.nutrition-tip { background: #E8F5E9; border-radius: 12rpx; padding: 16rpx; margin-top: 12rpx; }
-.tip-label { font-size: 24rpx; font-weight: bold; color: #2E7D32; display: block; margin-bottom: 4rpx; }
-.tip-text { font-size: 24rpx; color: #333; }
-.replace-options { display: flex; flex-direction: column; }
-.replace-item { display: flex; flex-direction: row; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.replace-name { flex: 1; font-size: 28rpx; color: #333; }
-.replace-score { font-size: 24rpx; color: #4CAF50; margin-right: 16rpx; }
-.replace-icon { font-size: 28rpx; color: #5B9BD5; }
-.replace-empty { text-align: center; padding: 20rpx; color: #999; }
-.nutrition-summary { display: flex; flex-direction: row; flex-wrap: wrap; }
-.nutri-stat { display: flex; flex-direction: column; align-items: center; min-width: 25%; margin-bottom: 12rpx; }
-.nutri-value { font-size: 32rpx; font-weight: bold; color: #FF8C42; }
-.nutri-label { font-size: 22rpx; color: #666; }
-.nutri-empty { text-align: center; padding: 20rpx; color: #999; }
-.bottom-spacer { height: 60rpx; }
-</style>

+ 0 - 242
cfc-frontend/pages/body/sleep-record.vue

@@ -1,242 +0,0 @@
-<template>
-  <view class="sleep-container">
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">作息记录</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <!-- 成员选择 -->
-    <view class="member-selector-card">
-      <text class="member-label">记录人</text>
-      <picker mode="selector" :range="memberOptions" :value="selectedMemberIndex" @change="onMemberChange">
-        <view class="picker-display">{{ memberOptions[selectedMemberIndex] }}</view>
-      </picker>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">记录睡眠</view>
-      <view class="input-row">
-        <text class="input-label">入睡时间</text>
-        <picker mode="time" :value="sleepTime" @change="onSleepTimeChange">
-          <view class="picker-display">{{ sleepTime || '选择时间' }}</view>
-        </picker>
-      </view>
-      <view class="input-row">
-        <text class="input-label">起床时间</text>
-        <picker mode="time" :value="wakeTime" @change="onWakeTimeChange">
-          <view class="picker-display">{{ wakeTime || '选择时间' }}</view>
-        </picker>
-      </view>
-      <view class="input-row">
-        <text class="input-label">睡眠质量</text>
-        <view class="quality-options">
-          <text class="quality-item" v-for="q in qualityOptions" :key="q.value"
-            :class="{ 'quality-active': quality === q.value }" @click="quality = q.value">
-            <text class="quality-emoji">{{ q.emoji }}</text>
-            <text class="quality-label">{{ q.label }}</text>
-          </text>
-        </view>
-      </view>
-      <view class="input-row">
-        <text class="input-label">备注</text>
-        <input class="input-field" v-model="remark" placeholder="可选" />
-      </view>
-      <view class="submit-btn" @click="submitSleep">保存记录</view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">本周睡眠概况</view>
-      <view class="sleep-stats">
-        <view class="stat-item">
-          <text class="stat-value">{{ avgDuration || '--' }}</text>
-          <text class="stat-label">平均时长(h)</text>
-        </view>
-        <view class="stat-item">
-          <text class="stat-value">{{ avgQuality || '--' }}</text>
-          <text class="stat-label">平均评分</text>
-        </view>
-        <view class="stat-item">
-          <text class="stat-value">{{ totalRecords || 0 }}</text>
-          <text class="stat-label">记录天数</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">历史记录</view>
-      <view class="history-list">
-        <view class="history-item" v-for="item in sleepHistory" :key="item.id">
-          <view class="history-info">
-            <text class="history-date">{{ item.createdAt ? item.createdAt.substring(0,10) : '' }}</text>
-            <text class="history-detail">{{ item.sleepTime ? item.sleepTime.substring(11,16) : '--' }} → {{ item.wakeTime ? item.wakeTime.substring(11,16) : '--' }}</text>
-          </view>
-          <view class="history-quality">
-            <text>{{ getQualityEmoji(item.qualityScore) }}</text>
-            <text class="quality-num" v-if="item.durationMinutes">{{ Math.round(item.durationMinutes/60) }}h</text>
-          </view>
-        </view>
-        <view class="history-empty" v-if="sleepHistory.length === 0"><text>暂无记录</text></view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-import { getChildren } from '@/utils/api.js'
-
-export default {
-  data() {
-    return {
-      childId: null,
-      selectedMemberIndex: 0,
-      memberOptions: ['我自己'],
-      memberIds: [],
-      sleepTime: '',
-      wakeTime: '',
-      quality: '',
-      remark: '',
-      sleepHistory: [],
-      avgDuration: null,
-      avgQuality: null,
-      totalRecords: 0,
-      qualityOptions: [
-        { label: '很好', value: 'excellent', emoji: '😴' },
-        { label: '还行', value: 'good', emoji: '🙂' },
-        { label: '一般', value: 'fair', emoji: '😐' },
-        { label: '很差', value: 'poor', emoji: '😫' }
-      ]
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-    this.loadChildren()
-    if (this.childId) this.loadHistory()
-  },
-  methods: {
-    loadChildren: function() {
-      var self = this
-      getChildren().then(function(res) {
-        if (res.code === 200 && res.data && res.data.length > 0) {
-          self.memberIds = res.data.map(function(c) { return c.childId })
-          self.memberOptions = ['我自己'].concat(res.data.map(function(c) { return c.name || c.nickname || '孩子' }))
-        }
-      }).catch(function() {})
-    },
-    onMemberChange: function(e) {
-      this.selectedMemberIndex = parseInt(e.detail.value)
-      if (this.selectedMemberIndex === 0) {
-        this.childId = null
-      } else {
-        this.childId = this.memberIds[this.selectedMemberIndex - 1]
-      }
-      if (this.childId) this.loadHistory()
-    },
-    onSleepTimeChange: function(e) { this.sleepTime = e.detail.value },
-    onWakeTimeChange: function(e) { this.wakeTime = e.detail.value },
-    getQualityEmoji: function(score) {
-      if (!score) return '🙂'
-      if (score >= 8) return '😴'
-      if (score >= 6) return '🙂'
-      if (score >= 4) return '😐'
-      return '😫'
-    },
-    submitSleep: function() {
-      if (!this.sleepTime || !this.wakeTime) {
-        uni.showToast({ title: '请选择睡眠时间', icon: 'none' })
-        return
-      }
-      var data = {
-        sleepTime: this.sleepTime,
-        wakeTime: this.wakeTime,
-        qualityScore: this.quality ? { excellent: 9, good: 7, fair: 5, poor: 2 }[this.quality] : null,
-        remark: this.remark
-      }
-      if (this.childId) {
-        data.childId = this.childId
-        data.memberId = this.childId
-      }
-      uni.showLoading({ title: '保存中...' })
-      healthRequest('/api/health/sleep/create', data).then(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '保存成功', icon: 'success' })
-        this.sleepTime = ''
-        this.wakeTime = ''
-        this.quality = ''
-        this.remark = ''
-        this.loadHistory()
-      }.bind(this)).catch(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '保存失败', icon: 'none' })
-      })
-    },
-    loadHistory: function() {
-      var self = this
-      var params = {}
-      if (this.childId) params.childId = this.childId
-      healthRequest('/api/health/sleep/list', params).then(function(res) {
-        if (res.data) {
-          self.sleepHistory = res.data.list || res.data
-          self.avgDuration = res.data.avgDuration || null
-          self.avgQuality = res.data.avgQuality || null
-          self.totalRecords = res.data.totalRecords || self.sleepHistory.length
-        }
-      }).catch(function() { self.sleepHistory = [] })
-    },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-
-function healthRequest(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' },
-      success: function(res) { if (res.data.code === 200) resolve(res.data); else reject(res.data) },
-      fail: function(err) { reject(err) }
-    })
-  })
-}
-</script>
-
-<style scoped>
-.sleep-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
-.nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.member-selector-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 20rpx 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); display: flex; flex-direction: row; align-items: center; justify-content: space-between; }
-.member-label { font-size: 28rpx; color: #666; }
-.section-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; }
-.input-row { display: flex; flex-direction: row; align-items: center; margin-bottom: 16rpx; }
-.input-label { font-size: 26rpx; color: #666; width: 140rpx; flex-shrink: 0; }
-.input-field { flex: 1; background: #f5f7fa; border-radius: 12rpx; padding: 14rpx 16rpx; font-size: 28rpx; }
-.picker-display { background: #f5f7fa; border-radius: 12rpx; padding: 14rpx 16rpx; font-size: 28rpx; color: #333; min-width: 200rpx; }
-.quality-options { display: flex; flex-direction: row; }
-.quality-item { display: flex; flex-direction: column; align-items: center; padding: 12rpx 20rpx; border-radius: 12rpx; background: #f0f0f0; margin-right: 12rpx; }
-.quality-active { background: #E3F2FD; border: 2rpx solid #5B9BD5; }
-.quality-emoji { font-size: 32rpx; }
-.quality-label { font-size: 20rpx; color: #666; margin-top: 4rpx; }
-.submit-btn { background: linear-gradient(135deg, #5B9BD5, #7EB8E0); color: #fff; font-size: 32rpx; font-weight: bold; text-align: center; padding: 22rpx; border-radius: 40rpx; margin-top: 16rpx; }
-.sleep-stats { display: flex; flex-direction: row; justify-content: space-around; }
-.stat-item { display: flex; flex-direction: column; align-items: center; }
-.stat-value { font-size: 40rpx; font-weight: bold; color: #5B9BD5; }
-.stat-label { font-size: 22rpx; color: #999; }
-.history-list { display: flex; flex-direction: column; }
-.history-item { display: flex; flex-direction: row; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.history-info { flex: 1; }
-.history-date { font-size: 24rpx; color: #999; display: block; }
-.history-detail { font-size: 28rpx; color: #333; }
-.history-quality { display: flex; flex-direction: column; align-items: center; }
-.history-quality text { font-size: 28rpx; }
-.quality-num { font-size: 22rpx; color: #999; }
-.history-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
-.bottom-spacer { height: 60rpx; }
-</style>

+ 0 - 161
cfc-frontend/pages/body/water-record.vue

@@ -1,161 +0,0 @@
-<template>
-  <view class="water-container">
-    <view class="nav-bar">
-      <text class="nav-back" @click="goBack">‹ 返回</text>
-      <text class="nav-title">饮水记录</text>
-      <view class="nav-placeholder"></view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">今日饮水</view>
-      <view class="water-progress">
-        <view class="water-circle">
-          <text class="water-amount">{{ todayTotal }}ml</text>
-          <text class="water-target">/ {{ dailyTarget }}ml</text>
-        </view>
-        <view class="progress-bar-bg">
-          <view class="progress-bar-fill" :style="'width:' + Math.min(100, todayTotal/dailyTarget*100) + '%'"></view>
-        </view>
-      </view>
-      <view class="quick-add-btns">
-        <text class="quick-btn" @click="addWater(100)">+100ml</text>
-        <text class="quick-btn" @click="addWater(200)">+200ml</text>
-        <text class="quick-btn" @click="addWater(300)">+300ml</text>
-        <text class="quick-btn" @click="addWater(500)">+500ml</text>
-      </view>
-      <view class="input-row">
-        <text class="input-label">自定义</text>
-        <input class="input-field" v-model="customAmount" type="number" placeholder="输入ml" />
-        <text class="input-unit" @click="addWater(parseInt(customAmount))">添加</text>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">今日饮水记录</view>
-      <view class="record-list">
-        <view class="record-item" v-for="(r, idx) in todayRecords" :key="idx">
-          <text class="record-icon">💧</text>
-          <text class="record-amount">{{ r.amountMl }}ml</text>
-          <text class="record-time">{{ r.createdAt ? r.createdAt.substring(11,16) : '' }}</text>
-        </view>
-        <view class="record-empty" v-if="todayRecords.length === 0"><text>今天还没喝水,快去喝水吧!</text></view>
-      </view>
-    </view>
-
-    <view class="section-card">
-      <view class="section-title">本周饮水趋势</view>
-      <view class="week-chart">
-        <view class="day-bar" v-for="(day, idx) in weekData" :key="idx">
-          <view class="bar-fill" :style="'height:' + Math.min(100, day.amount/dailyTarget*100) + '%'"></view>
-          <text class="bar-label">{{ day.label }}</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import config from '@/config.js'
-
-export default {
-  data() {
-    return {
-      childId: null,
-      customAmount: '',
-      todayRecords: [],
-      weekData: [
-        { label: '周一', amount: 0 }, { label: '周二', amount: 0 }, { label: '周三', amount: 0 },
-        { label: '周四', amount: 0 }, { label: '周五', amount: 0 }, { label: '周六', amount: 0 }, { label: '周日', amount: 0 }
-      ],
-      dailyTarget: 1500
-    }
-  },
-  computed: {
-    todayTotal: function() {
-      return this.todayRecords.reduce(function(sum, r) { return sum + (r.amountMl || 0) }, 0)
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId ? parseInt(options.childId) : null
-    if (this.childId) this.loadRecords()
-  },
-  methods: {
-    addWater: function(ml) {
-      if (!ml || ml <= 0) return
-      uni.showLoading({ title: '记录中...' })
-      healthRequest('/api/health/water/create', {
-        childId: this.childId,
-        memberId: this.childId,
-        amountMl: ml
-      }).then(function() {
-        uni.hideLoading()
-        this.customAmount = ''
-        this.loadRecords()
-      }.bind(this)).catch(function() {
-        uni.hideLoading()
-        uni.showToast({ title: '记录失败', icon: 'none' })
-      })
-    },
-    loadRecords: function() {
-      var self = this
-      healthRequest('/api/health/water/list', { childId: this.childId }).then(function(res) {
-        if (res.data) {
-          self.todayRecords = res.data.today || res.data.list || res.data
-          if (res.data.week) self.weekData = res.data.week
-          if (res.data.dailyTarget) self.dailyTarget = res.data.dailyTarget
-        }
-      }).catch(function() {})
-    },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-
-function healthRequest(url, data) {
-  var token = uni.getStorageSync('token')
-  return new Promise(function(resolve, reject) {
-    uni.request({
-      url: config.API_BASE_URL + url,
-      method: 'POST',
-      data: data,
-      header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' },
-      success: function(res) { if (res.data.code === 200) resolve(res.data); else reject(res.data) },
-      fail: function(err) { reject(err) }
-    })
-  })
-}
-</script>
-
-<style scoped>
-.water-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
-.nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
-.nav-back { font-size: 32rpx; color: #5B9BD5; }
-.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
-.nav-placeholder { width: 80rpx; }
-.section-card { margin: 20rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
-.section-title { font-size: 30rpx; font-weight: bold; color: #333; margin-bottom: 20rpx; }
-.water-progress { display: flex; flex-direction: column; align-items: center; margin-bottom: 20rpx; }
-.water-circle { display: flex; flex-direction: column; align-items: center; margin-bottom: 16rpx; }
-.water-amount { font-size: 56rpx; font-weight: bold; color: #2196F3; }
-.water-target { font-size: 24rpx; color: #999; }
-.progress-bar-bg { width: 100%; height: 20rpx; background: #E3F2FD; border-radius: 10rpx; overflow: hidden; }
-.progress-bar-fill { height: 100%; background: linear-gradient(90deg, #42A5F5, #2196F3); border-radius: 10rpx; transition: width 0.3s; }
-.quick-add-btns { display: flex; flex-direction: row; justify-content: space-around; margin-bottom: 16rpx; }
-.quick-btn { background: #E3F2FD; color: #1565C0; font-size: 26rpx; padding: 12rpx 24rpx; border-radius: 20rpx; }
-.input-row { display: flex; flex-direction: row; align-items: center; }
-.input-label { font-size: 26rpx; color: #666; width: 100rpx; }
-.input-field { flex: 1; background: #f5f7fa; border-radius: 12rpx; padding: 14rpx 16rpx; font-size: 28rpx; }
-.input-unit { font-size: 26rpx; color: #2196F3; padding: 14rpx 20rpx; }
-.record-list { display: flex; flex-direction: column; }
-.record-item { display: flex; flex-direction: row; align-items: center; padding: 10rpx 0; border-bottom: 1rpx solid #f5f5f5; }
-.record-icon { font-size: 28rpx; margin-right: 12rpx; }
-.record-amount { font-size: 28rpx; color: #333; flex: 1; }
-.record-time { font-size: 22rpx; color: #999; }
-.record-empty { text-align: center; padding: 30rpx; color: #999; font-size: 26rpx; }
-.week-chart { display: flex; flex-direction: row; align-items: flex-end; justify-content: space-around; height: 200rpx; }
-.day-bar { display: flex; flex-direction: column; align-items: center; width: 60rpx; }
-.bar-fill { width: 40rpx; background: linear-gradient(180deg, #42A5F5, #1565C0); border-radius: 8rpx 8rpx 0 0; min-height: 4rpx; }
-.bar-label { font-size: 20rpx; color: #999; margin-top: 8rpx; }
-.bottom-spacer { height: 60rpx; }
-</style>

+ 0 - 694
cfc-frontend/pages/membership/index.vue

@@ -1,694 +0,0 @@
-<template>
-  <scroll-view class="container" scroll-y>
-    <!-- 加载状态 -->
-    <view class="loading-wrap" v-if="loading">
-      <text class="loading-text">加载中...</text>
-    </view>
-
-    <!-- 错误状态 -->
-    <view class="error-wrap" v-if="error">
-      <text class="error-text">{{ error }}</text>
-      <button class="btn-retry" @click="loadData">重新加载</button>
-    </view>
-
-    <template v-if="!loading && !error">
-      <!-- 当前会员状态卡片 -->
-      <view class="membership-card">
-        <view class="card-header">
-          <view class="card-header-left">
-            <text class="card-title">我的会员</text>
-            <text class="card-subtitle">Membership</text>
-          </view>
-          <view class="card-level-badge" :class="isFamilyMember ? 'badge-family' : 'badge-free'">
-            <text class="badge-icon">{{ isFamilyMember ? '●' : '○' }}</text>
-            <text class="badge-text">{{ currentLevelName }}</text>
-          </view>
-        </view>
-        <view class="card-body" v-if="membership && membership.levelCode && membership.levelCode !== 'FREE'">
-          <view class="expiry-row">
-            <text class="expiry-label">到期时间</text>
-            <text class="expiry-value">{{ formatDate(membership.endDate) }}</text>
-          </view>
-          <view class="days-remaining" v-if="membership.isActive">
-            <text class="days-num">{{ membership.daysRemaining || 0 }}</text>
-            <text class="days-unit">天剩余</text>
-          </view>
-          <view class="expired-msg" v-else>
-            <text class="expired-text">会员已过期,请续费</text>
-          </view>
-        </view>
-        <view class="card-body" v-else>
-          <text class="free-hint">当前为免费版,升级享受更多服务</text>
-        </view>
-        <button class="btn-upgrade" @click="goToUpgrade">
-          {{ isFamilyMember ? '续费升级' : '立即升级' }}
-        </button>
-      </view>
-
-      <!-- 会员特权亮点 -->
-      <view class="highlights-section">
-        <text class="section-title">会员特权</text>
-        <view class="highlights-grid">
-          <view class="highlight-item" v-for="(item, idx) in highlights" :key="idx">
-            <view class="highlight-icon" :style="'background:' + item.bg">
-              <text class="highlight-emoji">{{ item.icon }}</text>
-            </view>
-            <text class="highlight-name">{{ item.name }}</text>
-            <text class="highlight-desc">{{ item.desc }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 开通CTA -->
-      <view class="cta-section" v-if="!membership || !membership.isActive">
-        <text class="cta-title">开通家庭会员,享受全部特权</text>
-        <text class="cta-price">{{ familyPriceText }}/年</text>
-        <button class="cta-btn" @click="goToUpgrade">立即开通</button>
-      </view>
-
-      <!-- 等级对比/权益说明 -->
-      <view class="compare-section">
-        <text class="section-title">等级对比</text>
-        <view class="compare-table">
-          <view class="compare-row header-row">
-            <text class="compare-cell cell-feature">权益项</text>
-            <text class="compare-cell cell-free">免费</text>
-            <text class="compare-cell cell-family active">家庭会员</text>
-            <text class="compare-cell cell-premium active">高级会员</text>
-          </view>
-          <view class="compare-row" v-for="(item, idx) in compareData" :key="idx">
-            <text class="compare-cell cell-feature">{{ item.name }}</text>
-            <text class="compare-cell cell-free">{{ item.free }}</text>
-            <text class="compare-cell cell-family active">{{ item.family }}</text>
-            <text class="compare-cell cell-premium active">{{ item.premium }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 会员等级列表 -->
-      <view class="levels-section">
-        <text class="section-title">会员等级</text>
-        <view class="levels-list">
-          <view class="level-card" v-for="level in levels" :key="level.levelCode" :class="{'level-card-active': level.levelCode === currentLevelCode}">
-            <view class="level-header">
-              <view class="level-name-row">
-                <text class="level-name">{{ level.levelName }}</text>
-                <view class="level-current-tag" v-if="level.levelCode === currentLevelCode">
-                  <text class="tag-text">当前</text>
-                </view>
-              </view>
-              <text class="level-price" v-if="level.priceMonthly > 0">{{ formatPrice(level.priceMonthly) }}/月</text>
-              <text class="level-price free" v-else>免费</text>
-            </view>
-            <view class="level-features">
-              <text class="feature-tag" v-for="(feature, idx) in parseFeatures(level.features)" :key="idx">
-                {{ feature }}
-              </text>
-            </view>
-            <button class="btn-level-action" v-if="level.levelCode !== currentLevelCode && level.levelCode !== 'FREE'" @click="handlePurchase(level)">
-              {{ level.levelCode === 'FAMILY' ? '开通' : '升级' }}
-            </button>
-          </view>
-        </view>
-      </view>
-
-      <!-- 升级记录 -->
-      <view class="records-section" v-if="records && records.length > 0">
-        <text class="section-title">升级记录</text>
-        <view class="record-item" v-for="record in records" :key="record.id">
-          <view class="record-left">
-            <text class="record-level">{{ record.levelName }}</text>
-            <text class="record-time">{{ formatDate(record.createdAt) }}</text>
-          </view>
-          <text class="record-amount">{{ formatPrice(record.amount) }}</text>
-        </view>
-      </view>
-    </template>
-  </scroll-view>
-</template>
-
-<script>
-import { getMembershipLevels, getMyMembership, getCurrentLevel, getUpgradeRecords } from '../../utils/api.js'
-
-export default {
-  data() {
-    return {
-      loading: true,
-      error: '',
-      levels: [],
-      membership: null,
-      currentLevel: {},
-      currentLevelCode: '',
-      currentLevelName: '免费用户',
-      records: [],
-      highlights: [
-        { icon: '🛒', name: '折扣购物', desc: '商品会员专享价', bg: '#FEF3C7' },
-        { icon: '👨‍👩‍👧‍👦', name: '家庭管理', desc: '多成员统一管理', bg: '#DBEAFE' },
-        { icon: '💰', name: '推荐佣金', desc: '分享赚取佣金', bg: '#D1FAE5' },
-        { icon: '🎪', name: '专属活动', desc: '会员优先参与', bg: '#FCE7F3' }
-      ],
-      compareData: [
-        { name: '商品折扣', free: '普通价格', family: '9折', premium: '8折' },
-        { name: '家庭管理', free: '基础功能', family: '全部功能', premium: '全部功能' },
-        { name: '推荐佣金', free: '不可参与', family: '最高15%', premium: '最高20%' },
-        { name: '专属活动', free: '部分参与', family: '全部参与', premium: '优先参与' },
-        { name: '成长报告', free: '月度', family: '每周+深度', premium: '每日+深度+专家解读' },
-        { name: '任务配额', free: '每日5个', family: '不限量', premium: '不限量+优先审核' }
-      ]
-    }
-  },
-  computed: {
-    isFamilyMember() {
-      return this.membership && this.membership.levelCode && this.membership.levelCode !== 'FREE' && this.membership.isActive
-    },
-    familyPriceText() {
-      var familyLevel = this.levels.find(function(l) { return l.levelCode === 'FAMILY' })
-      if (familyLevel && familyLevel.priceYearly > 0) {
-        return this.formatPrice(familyLevel.priceYearly)
-      }
-      return '¥365.00'
-    }
-  },
-  onShow() {
-    this.loadData()
-  },
-  methods: {
-    async loadData() {
-      this.loading = true
-      this.error = ''
-      var hasError = false
-
-      try {
-        var levelsRes = await getMembershipLevels()
-        this.levels = levelsRes.data || []
-      } catch (e) {
-        console.error('获取会员等级失败', e)
-        hasError = true
-      }
-
-      try {
-        var membershipRes = await getMyMembership()
-        this.membership = membershipRes.data
-        if (this.membership && this.membership.levelCode && this.membership.levelCode !== 'FREE') {
-          this.currentLevelCode = this.membership.levelCode
-          this.currentLevelName = this.membership.levelName || '家庭会员'
-        } else {
-          this.currentLevelCode = 'FREE'
-          this.currentLevelName = '免费用户'
-        }
-      } catch (e) {
-        console.error('获取会员信息失败', e)
-        this.currentLevelCode = 'FREE'
-        this.currentLevelName = '免费用户'
-        hasError = true
-      }
-
-      try {
-        var currentRes = await getCurrentLevel()
-        if (currentRes.data) {
-          this.currentLevel = currentRes.data
-          if (!this.currentLevelCode || this.currentLevelCode === 'FREE') {
-            this.currentLevelCode = currentRes.data.levelCode || 'FREE'
-            this.currentLevelName = currentRes.data.levelName || '免费用户'
-          }
-        }
-      } catch (e) {
-        console.error('获取当前等级失败', e)
-      }
-
-      try {
-        var recordsRes = await getUpgradeRecords()
-        if (recordsRes.data && recordsRes.data.records) {
-          this.records = recordsRes.data.records
-        } else if (Array.isArray(recordsRes.data)) {
-          this.records = recordsRes.data
-        }
-      } catch (e) {
-        console.error('获取升级记录失败', e)
-      }
-
-      if (hasError && this.levels.length === 0 && !this.membership) {
-        this.error = '加载失败,请重试'
-      }
-      this.loading = false
-    },
-    formatDate(date) {
-      if (!date) return ''
-      var d = new Date(date)
-      return d.getFullYear() + '-' + String(d.getMonth() + 1).padStart(2, '0') + '-' + String(d.getDate()).padStart(2, '0')
-    },
-    formatPrice(cents) {
-      if (!cents && cents !== 0) return '¥0.00'
-      return '¥' + (cents / 100).toFixed(2)
-    },
-    parseFeatures(features) {
-      if (!features) return []
-      if (Array.isArray(features)) return features
-      try {
-        return JSON.parse(features)
-      } catch (e) {
-        return []
-      }
-    },
-    goToUpgrade() {
-      uni.navigateTo({ url: '/pages/membership/upgrade' })
-    },
-    handlePurchase(level) {
-      uni.showToast({ title: '即将上线', icon: 'none' })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  padding: 30rpx;
-  min-height: 100vh;
-  background: #FFF7ED;
-}
-
-/* ===== 加载/错误状态 ===== */
-.loading-wrap {
-  display: flex;
-  justify-content: center;
-  align-items: center;
-  padding: 200rpx 0;
-}
-.loading-text {
-  font-size: 28rpx;
-  color: #94A3B8;
-}
-.error-wrap {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 200rpx 60rpx;
-}
-.error-text {
-  font-size: 28rpx;
-  color: #EF4444;
-  margin-bottom: 30rpx;
-}
-.btn-retry {
-  background: #F97316;
-  color: #fff;
-  font-size: 28rpx;
-  border-radius: 40rpx;
-  padding: 16rpx 48rpx;
-  border: none;
-}
-.btn-retry::after {
-  border: none;
-}
-
-/* ===== 会员状态卡片 ===== */
-.membership-card {
-  background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
-  border-radius: 20rpx;
-  padding: 40rpx;
-  margin-bottom: 30rpx;
-}
-.card-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.card-header-left {
-  display: flex;
-  flex-direction: column;
-}
-.card-title {
-  font-size: 32rpx;
-  color: #fff;
-  font-weight: bold;
-}
-.card-subtitle {
-  font-size: 20rpx;
-  color: rgba(255,255,255,0.6);
-  margin-top: 4rpx;
-}
-.card-level-badge {
-  display: inline-flex;
-  align-items: center;
-  padding: 10rpx 24rpx;
-  border-radius: 9999rpx;
-  font-size: 24rpx;
-}
-.badge-free {
-  background: rgba(255,255,255,0.2);
-  color: #fff;
-}
-.badge-family {
-  background: rgba(255,255,255,0.3);
-  color: #fff;
-}
-.badge-icon {
-  font-size: 20rpx;
-  margin-right: 8rpx;
-}
-.badge-text {
-  font-weight: 600;
-}
-.card-body {
-  margin-top: 30rpx;
-}
-.expiry-row {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.expiry-label {
-  font-size: 26rpx;
-  color: rgba(255,255,255,0.8);
-}
-.expiry-value {
-  font-size: 28rpx;
-  color: #fff;
-  font-weight: 500;
-}
-.days-remaining {
-  display: flex;
-  align-items: baseline;
-  justify-content: center;
-  margin-top: 20rpx;
-}
-.days-num {
-  font-size: 56rpx;
-  font-weight: bold;
-  color: #FEF3C7;
-}
-.days-unit {
-  font-size: 26rpx;
-  color: rgba(255,255,255,0.7);
-  margin-left: 8rpx;
-}
-.expired-msg {
-  text-align: center;
-  margin-top: 16rpx;
-}
-.expired-text {
-  font-size: 26rpx;
-  color: #FEF3C7;
-}
-.free-hint {
-  font-size: 26rpx;
-  color: rgba(255,255,255,0.85);
-  text-align: center;
-  display: block;
-}
-.btn-upgrade {
-  margin-top: 30rpx;
-  background: #FFD700;
-  color: #333;
-  border-radius: 40rpx;
-  font-size: 28rpx;
-  font-weight: 600;
-  border: none;
-}
-.btn-upgrade::after {
-  border: none;
-}
-.btn-upgrade:active {
-  opacity: 0.9;
-}
-
-/* ===== 会员特权亮点 ===== */
-.highlights-section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx;
-  margin-bottom: 30rpx;
-  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
-}
-.section-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #1E293B;
-  display: block;
-  margin-bottom: 24rpx;
-}
-.highlights-grid {
-  display: flex;
-  flex-wrap: wrap;
-}
-.highlight-item {
-  width: 50%;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 24rpx 16rpx;
-  box-sizing: border-box;
-}
-.highlight-icon {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 12rpx;
-}
-.highlight-emoji {
-  font-size: 36rpx;
-}
-.highlight-name {
-  font-size: 28rpx;
-  font-weight: 600;
-  color: #1E293B;
-  margin-bottom: 4rpx;
-}
-.highlight-desc {
-  font-size: 22rpx;
-  color: #94A3B8;
-}
-
-/* ===== CTA开通 ===== */
-.cta-section {
-  background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
-  border-radius: 20rpx;
-  padding: 40rpx;
-  text-align: center;
-  margin-bottom: 30rpx;
-}
-.cta-title {
-  font-size: 30rpx;
-  color: #fff;
-  font-weight: 600;
-  display: block;
-}
-.cta-price {
-  font-size: 44rpx;
-  color: #FEF3C7;
-  font-weight: bold;
-  margin: 16rpx 0 24rpx;
-  display: block;
-}
-.cta-btn {
-  width: 280rpx;
-  height: 72rpx;
-  line-height: 72rpx;
-  background: #fff;
-  color: #F97316;
-  font-size: 28rpx;
-  font-weight: 600;
-  border-radius: 36rpx;
-  border: none;
-}
-.cta-btn::after {
-  border: none;
-}
-.cta-btn:active {
-  opacity: 0.9;
-}
-
-/* ===== 等级对比表 ===== */
-.compare-section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx;
-  margin-bottom: 30rpx;
-  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
-}
-.compare-table {
-  border-radius: 16rpx;
-  overflow: hidden;
-  border: 1rpx solid #FED7AA;
-}
-.compare-row {
-  display: flex;
-  border-bottom: 1rpx solid #FED7AA;
-}
-.compare-row:last-child {
-  border-bottom: none;
-}
-.header-row {
-  background: #FFF7ED;
-}
-.compare-cell {
-  flex: 1;
-  padding: 20rpx 16rpx;
-  font-size: 26rpx;
-  text-align: center;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-.cell-feature {
-  flex: 1.5;
-  color: #1E293B;
-  font-weight: 500;
-  justify-content: flex-start;
-}
-.cell-free {
-  color: #94A3B8;
-}
-.cell-family.active {
-  color: #F97316;
-  font-weight: 600;
-}
-.cell-premium.active {
-  color: #8B5CF6;
-  font-weight: 600;
-}
-.header-row .compare-cell {
-  font-size: 24rpx;
-  font-weight: 600;
-  color: #64748B;
-}
-.header-row .cell-family.active {
-  color: #F97316;
-}
-.header-row .cell-premium.active {
-  color: #8B5CF6;
-}
-
-/* ===== 会员等级列表 ===== */
-.levels-section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx;
-  margin-bottom: 30rpx;
-  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
-}
-.levels-list {
-  display: flex;
-  flex-direction: column;
-}
-.level-card {
-  border: 1rpx solid #FED7AA;
-  border-radius: 16rpx;
-  padding: 24rpx 32rpx;
-  margin-bottom: 20rpx;
-}
-.level-card:last-child {
-  margin-bottom: 0;
-}
-.level-card-active {
-  border-color: #F97316;
-  background: #FFF7ED;
-}
-.level-header {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-}
-.level-name-row {
-  display: flex;
-  align-items: center;
-}
-.level-name {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #1E293B;
-}
-.level-current-tag {
-  margin-left: 12rpx;
-  background: #F97316;
-  border-radius: 9999rpx;
-  padding: 4rpx 16rpx;
-}
-.tag-text {
-  font-size: 20rpx;
-  color: #fff;
-}
-.level-price {
-  font-size: 26rpx;
-  color: #F97316;
-  font-weight: 600;
-}
-.level-price.free {
-  color: #10B981;
-}
-.level-features {
-  display: flex;
-  flex-wrap: wrap;
-  margin-top: 16rpx;
-}
-.feature-tag {
-  font-size: 22rpx;
-  padding: 6rpx 16rpx;
-  background: #FFF7ED;
-  border-radius: 20rpx;
-  color: #64748B;
-  margin-right: 10rpx;
-  margin-bottom: 8rpx;
-}
-.level-card-active .feature-tag {
-  background: #FEF3C7;
-  color: #92400E;
-}
-.btn-level-action {
-  margin-top: 16rpx;
-  background: linear-gradient(135deg, #F97316 0%, #EA580C 100%);
-  color: #fff;
-  font-size: 26rpx;
-  font-weight: 600;
-  border-radius: 40rpx;
-  padding: 12rpx 0;
-  border: none;
-}
-.btn-level-action::after {
-  border: none;
-}
-.btn-level-action:active {
-  opacity: 0.9;
-}
-
-/* ===== 升级记录 ===== */
-.records-section {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx;
-  margin-bottom: 30rpx;
-  box-shadow: 0 2rpx 12rpx rgba(249,115,22,0.08);
-}
-.record-item {
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
-  padding: 20rpx 0;
-  border-bottom: 1rpx solid #FED7AA;
-}
-.record-item:last-child {
-  border-bottom: none;
-}
-.record-left {
-  flex: 1;
-}
-.record-level {
-  font-size: 28rpx;
-  color: #1E293B;
-  display: block;
-}
-.record-time {
-  font-size: 22rpx;
-  color: #94A3B8;
-  margin-top: 4rpx;
-  display: block;
-}
-.record-amount {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #F97316;
-}
-</style>

+ 0 - 278
cfc-frontend/pages/parent/invite/index.vue

@@ -1,278 +0,0 @@
-<template>
-  <view class="container">
-    <view class="header">
-      <text class="title">邀请家人加入</text>
-      <text class="desc">分享给好友,好友注册后自动成为家庭成员</text>
-    </view>
-
-    <!-- 邀请码展示 -->
-    <view class="invite-card" v-if="inviteCode">
-      <view class="invite-code">{{ inviteCode }}</view>
-      <text class="invite-tip">好友也可输入邀请码加入</text>
-    </view>
-
-    <!-- 分享按钮区域 -->
-    <view class="share-section">
-      <button class="btn-share-large" open-type="share">
-        <text class="share-icon">📤</text>
-        <text class="share-text">发送给好友</text>
-      </button>
-      <text class="share-hint">点击上方按钮,分享到微信好友</text>
-    </view>
-
-    <!-- 其他方式 -->
-    <view class="other-methods">
-      <view class="method-title">其他方式</view>
-      <view class="method-item" @click="copyCode">
-        <text class="method-icon">📋</text>
-        <text class="method-text">复制邀请码</text>
-      </view>
-      <view class="method-item" @click="generatePoster">
-        <text class="method-icon">🖼️</text>
-        <text class="method-text">生成邀请海报</text>
-      </view>
-    </view>
-
-    <!-- 成长规划师邀请说明 -->
-    <view class="guide-tip" v-if="role === 'teacher'">
-      <text class="tip-title">成长规划师权限说明</text>
-      <text class="tip-text">通过邀请码绑定家庭后,您将获得:</text>
-      <text class="tip-item">• 查看家庭成员任务完成情况</text>
-      <text class="tip-item">• 审核任务执行</text>
-      <text class="tip-item">• 制定任务</text>
-    </view>
-
-    <!-- 成长规划师绑定家庭 -->
-    <view class="section" v-if="role === 'teacher'">
-      <view class="section-title">绑定已有家庭</view>
-      <input 
-        class="input" 
-        v-model="familyCode" 
-        placeholder="输入家庭邀请码" 
-      />
-      <button class="btn-primary" @click="bindFamily">绑定家庭</button>
-    </view>
-
-    <!-- 海报弹窗 -->
-    <view class="poster-mask" v-if="showPoster" @click="showPoster = false">
-      <view class="poster-content" @click.stop>
-        <canvas canvas-id="posterCanvas" class="poster-canvas"></canvas>
-        <button class="btn-save" @click="savePoster">保存到相册</button>
-        <text class="poster-hint">保存图片后发送给好友</text>
-      </view>
-    </view>
-  </view>
-</template>
-
-<script>
-import { getInviteCode, generateFamilyInviteCode, bindGuideFamily, generateQrCodeFromText } from '@/utils/api.js'
-
-export default {
-  data() {
-    return {
-      inviteCode: '',
-      familyCode: '',
-      role: 'parent',
-      showPoster: false,
-      familyName: ''
-    }
-  },
-  onLoad() {
-    this.role = uni.getStorageSync('role') || 'parent'
-    this.loadInviteCode()
-    this.loadFamilyName()
-  },
-  onShareAppMessage() {
-    var code = this.inviteCode || ''
-    var path = '/pages/invite/join'
-    if (code) {
-      path = path + '?inviteCode=' + code
-    }
-    return {
-      title: '我在浠艾福发现了好用的家庭成长工具,快来加入!',
-      path: path,
-      imageUrl: ''
-    }
-  },
-  methods: {
-    async loadInviteCode() {
-      try {
-        const res = await getInviteCode()
-        if (res.data) {
-          this.inviteCode = res.data
-        } else {
-          const genRes = await generateFamilyInviteCode()
-          this.inviteCode = genRes.data
-        }
-      } catch (e) {
-        console.error('获取邀请码失败', e)
-      }
-    },
-    async loadFamilyName() {
-      try {
-        const userInfo = uni.getStorageSync('userInfo')
-        if (userInfo && userInfo.nickname) {
-          this.familyName = userInfo.nickname + '的家庭'
-        } else {
-          this.familyName = '我的家庭'
-        }
-      } catch (e) {
-        this.familyName = '我的家庭'
-      }
-    },
-    copyCode() {
-      uni.setClipboardData({
-        data: this.inviteCode,
-        success: () => {
-          uni.showToast({ title: '已复制邀请码', icon: 'success' })
-        }
-      })
-    },
-    async generatePoster() {
-      this.showPoster = true
-
-      // 先获取二维码
-      var qrTempPath = ''
-      if (this.inviteCode) {
-        var invitePath = '/pages/invite/join?inviteCode=' + this.inviteCode
-        try {
-          var res = await generateQrCodeFromText(invitePath)
-          if (res.code === 200 && res.data) {
-            var rawBase64 = res.data.qrCodeBase64
-            if (rawBase64.indexOf('base64,') >= 0) {
-              rawBase64 = rawBase64.split('base64,')[1]
-            }
-            var fsm = uni.getFileSystemManager()
-            var path = wx.env.USER_DATA_PATH + '/qr_poster_' + Date.now() + '.png'
-            fsm.writeFileSync(path, rawBase64, 'base64')
-            qrTempPath = path
-          }
-        } catch (e) {
-          console.error('生成二维码失败', e)
-        }
-      }
-
-      this.$nextTick(function() {
-        this.drawPoster(qrTempPath)
-      }.bind(this))
-    },
-    drawPoster(qrTempPath) {
-      const ctx = uni.createCanvasContext('posterCanvas')
-      const width = 300
-      const height = 450
-      var self = this
-      
-      // 背景
-      ctx.setFillStyle('#667eea')
-      ctx.fillRect(0, 0, width, height)
-      
-      // 标题
-      ctx.setFillStyle('#ffffff')
-      ctx.setFontSize(20)
-      ctx.setTextAlign('center')
-      ctx.fillText('邀请加入家庭', width / 2, 40)
-      
-      // 家庭名
-      ctx.setFontSize(14)
-      ctx.fillText(self.familyName, width / 2, 70)
-      
-      // 邀请码
-      ctx.setFillStyle('#ffffff')
-      ctx.setFontSize(32)
-      ctx.setTextAlign('center')
-      ctx.fillText(self.inviteCode, width / 2, 130)
-      
-      // 提示
-      ctx.setFontSize(12)
-      ctx.setFillStyle('rgba(255,255,255,0.8)')
-      ctx.setTextAlign('center')
-      ctx.fillText('输入邀请码加入家庭', width / 2, 170)
-      
-      // 二维码区域
-      if (qrTempPath) {
-        ctx.drawImage(qrTempPath, 75, 210, 150, 150)
-      } else {
-        ctx.setFillStyle('#ffffff')
-        ctx.fillRect(75, 210, 150, 150)
-        ctx.setFillStyle('#667eea')
-        ctx.setFontSize(12)
-        ctx.setTextAlign('center')
-        ctx.fillText('扫码注册', width / 2, 300)
-      }
-      
-      ctx.draw()
-    },
-    savePoster() {
-      uni.canvasToTempFilePath({
-        canvasId: 'posterCanvas',
-        success: (res) => {
-          uni.saveImageToPhotosAlbum({
-            filePath: res.tempFilePath,
-            success: () => {
-              uni.showToast({ title: '已保存到相册', icon: 'success' })
-              this.showPoster = false
-            },
-            fail: () => {
-              uni.showToast({ title: '保存失败', icon: 'none' })
-            }
-          })
-        },
-        fail: () => {
-          uni.showToast({ title: '生成失败', icon: 'none' })
-        }
-      })
-    },
-    async bindFamily() {
-      if (!this.familyCode) {
-        uni.showToast({ title: '请输入家庭邀请码', icon: 'none' })
-        return
-      }
-      try {
-        await bindGuideFamily(this.familyCode)
-        uni.showToast({ title: '绑定成功', icon: 'success' })
-      } catch (e) {
-        uni.showToast({ title: e.message || '绑定失败', icon: 'none' })
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container { padding: 30rpx; min-height: 100vh; background: #f5f5f5; }
-.header { text-align: center; padding: 40rpx 0; }
-.title { font-size: 40rpx; font-weight: bold; display: block; margin-bottom: 16rpx; color: #333; }
-.desc { font-size: 28rpx; color: #666; }
-.invite-card { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); border-radius: 20rpx; padding: 50rpx; text-align: center; margin: 30rpx 0; }
-.invite-code { font-size: 56rpx; font-weight: bold; color: #fff; letter-spacing: 8rpx; margin-bottom: 16rpx; }
-.invite-tip { font-size: 24rpx; color: rgba(255,255,255,0.8); }
-
-.share-section { background: #fff; border-radius: 20rpx; padding: 40rpx; margin: 30rpx 0; text-align: center; }
-.btn-share-large { background: #07c160; color: #fff; border-radius: 60rpx; padding: 24rpx 60rpx; display: inline-flex; align-items: center; justify-content: center; }
-.btn-share-large::after { border: none; }
-.share-icon { font-size: 36rpx; margin-right: 16rpx; }
-.share-text { font-size: 32rpx; font-weight: bold; }
-.share-hint { display: block; margin-top: 20rpx; font-size: 24rpx; color: #999; }
-
-.other-methods { background: #fff; border-radius: 20rpx; padding: 30rpx; margin: 30rpx 0; }
-.method-title { font-size: 28rpx; color: #999; margin-bottom: 20rpx; }
-.method-item { display: flex; align-items: center; padding: 20rpx 0; border-bottom: 1rpx solid #f0f0f0; }
-.method-item:last-child { border-bottom: none; }
-.method-icon { font-size: 40rpx; margin-right: 20rpx; }
-.method-text { font-size: 28rpx; color: #333; }
-
-.guide-tip { background: #fff3e0; border-radius: 16rpx; padding: 30rpx; margin: 30rpx 0; }
-.tip-title { font-size: 28rpx; font-weight: bold; color: #ff9800; display: block; margin-bottom: 16rpx; }
-.tip-text { font-size: 24rpx; color: #666; display: block; margin-bottom: 12rpx; }
-.tip-item { font-size: 24rpx; color: #666; display: block; margin-left: 20rpx; line-height: 1.8; }
-
-.section { background: #fff; border-radius: 20rpx; padding: 30rpx; margin: 30rpx 0; }
-.section-title { font-size: 28rpx; font-weight: bold; margin-bottom: 20rpx; }
-.input { border: 1rpx solid #ddd; border-radius: 10rpx; padding: 20rpx; margin-bottom: 20rpx; font-size: 28rpx; }
-
-.poster-mask { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.8); display: flex; align-items: center; justify-content: center; z-index: 999; }
-.poster-content { background: #fff; border-radius: 20rpx; padding: 30rpx; text-align: center; }
-.poster-canvas { width: 300px; height: 450px; margin: 0 auto; }
-.btn-save { background: #07c160; color: #fff; border-radius: 40rpx; padding: 20rpx; margin: 20rpx 0; }
-.poster-hint { font-size: 24rpx; color: #999; display: block; }
-</style>

+ 0 - 82
cfc-frontend/pages/register-child/index.vue

@@ -1,82 +0,0 @@
-<template>
-  <view class="register-child-page">
-    <PlayfulCard elevation="1">
-      <template slot="header">
-        <view class="playful-card-header">
-          <text class="playful-title">为孩子创建账户</text>
-        </view>
-      </template>
-
-      <view v-if="step === 1" class="form-step">
-        <text>请输入孩子的姓名</text>
-        <input v-model="childName" placeholder="姓名" class="input" />
-        <text>出生日期</text>
-        <input v-model="birth" type="date" class="input" />
-      </view>
-
-      <view v-else-if="step === 2" class="form-step">
-        <text>选择头像</text>
-        <view class="avatar-grid">
-          <image v-for="n in 5" :key="n" :src="avatarPlaceholder" class="avatar" @click="selectAvatar(n)" />
-        </view>
-      </view>
-
-      <view v-else class="form-step">
-        <text>确认信息</text>
-        <text>Name: {{ childName }}</text>
-        <text>Birth: {{ birth }}</text>
-        <text>Avatar: {{ selectedAvatar || '默认' }}</text>
-      </view>
-
-      <template slot="actions">
-        <PlayfulButton v-if="step < 3" variant="primary" size="md" @click="nextStep">
-          下一步
-        </PlayfulButton>
-        <PlayfulButton v-else variant="primary" size="md" @click="finishRegistration">
-          完成
-        </PlayfulButton>
-      </template>
-    </PlayfulCard>
-  </view>
-</template>
-
-<script>
-import PlayfulCard from '@/components/PlayfulCard.vue';
-import PlayfulButton from '@/components/PlayfulButton.vue';
-
-export default {
-  name: 'RegisterChild',
-  components: { PlayfulCard, PlayfulButton },
-  data() {
-    return {
-      step: 1,
-      childName: '',
-      birth: '',
-      selectedAvatar: null,
-      avatarPlaceholder: '/static/avatar-placeholder.png'
-    }
-  },
-  methods: {
-    nextStep() {
-      if (this.step < 3) this.step++;
-      else this.finishRegistration();
-    },
-    selectAvatar(n) { this.selectedAvatar = n; },
-    finishRegistration() {
-      // Here you would typically POST the data to your backend
-      uni.showToast({ title: '注册完成!', icon: 'success' });
-      // Navigate to child dashboard or home
-      uni.redirectTo({ url: '/pages/index-home/index' });
-    }
-  }
-}
-</script>
-
-<style scoped>
-.register-child-page { padding: 16px; }
-.input { width: 100%; padding: 12px; border-radius: 12px; border: 1px solid var(--border); margin-bottom: 12px; font-size: 1rem; }
-.playful-card-header { font-family: var(--font-display); font-weight: 700; font-size: 1.2rem; }
-.playful-title { font-family: var(--font-display); }
-.avatar-grid { display: flex; gap: 10px; flex-wrap: wrap; }
-.avatar { width: 72px; height: 72px; border-radius: 50%; border: 2px solid #eee; cursor: pointer; }
-</style>

+ 0 - 410
cfc-frontend/pages/wisdom/wisdom_temp/cognitive-report.vue

@@ -1,410 +0,0 @@
-<template>
-  <view class="container">
-    <PageBanner theme="wisdom"
-      tagline="认知能力 · 全面评估"
-      quote="知人者智,自知者明" />
-
-    <view class="report-card" v-if="report">
-      <view class="report-header">
-        <text class="report-title">认知测评报告</text>
-        <text class="report-date">测评日期: {{ report.assessmentDate || '-' }}</text>
-      </view>
-
-      <!-- 综合认知评分 -->
-      <view class="overall-score-wrap">
-        <text class="overall-label">综合认知评分</text>
-        <view class="overall-row">
-          <text class="overall-value">{{ report.overallScore || 0 }}</text>
-          <view class="dan-badge">
-            <text class="dan-level">{{ report.danLevel || '-' }}</text>
-          </view>
-        </view>
-      </view>
-
-      <!-- 七维条形图 -->
-      <view class="dimension-list">
-        <view class="dimension-item" v-for="dim in dimensions" :key="dim.key">
-          <view class="dimension-header">
-            <view class="dimension-label-row">
-              <text class="dimension-icon">{{ dim.icon }}</text>
-              <text class="dimension-name">{{ dim.label }}</text>
-            </view>
-            <text class="dimension-score">{{ report[dim.key] || 0 }}</text>
-          </view>
-          <view class="dimension-bar">
-            <view class="dimension-bar-fill" :style="{ width: (report[dim.key] || 0) + '%' }"></view>
-          </view>
-          <text class="dimension-desc">{{ dim.desc }}</text>
-        </view>
-      </view>
-
-      <!-- 分析报告 -->
-      <view class="report-analysis" v-if="report.analysisReport">
-        <view class="analysis-header-row">
-          <text class="analysis-icon">&#x1F4DD;</text>
-          <text class="analysis-title">分析报告</text>
-        </view>
-        <text class="analysis-content">{{ report.analysisReport }}</text>
-      </view>
-
-      <!-- 成长建议 -->
-      <view class="report-suggestions" v-if="report.growthSuggestions">
-        <view class="analysis-header-row">
-          <text class="analysis-icon">&#x1F331;</text>
-          <text class="analysis-title">成长建议</text>
-        </view>
-        <text class="suggestion-content">{{ report.growthSuggestions }}</text>
-      </view>
-
-      <!-- 底部操作 -->
-      <view class="action-row">
-        <view class="action-btn secondary" @click="goAssessment">
-          <text class="action-btn-text">预约测评</text>
-        </view>
-        <view class="action-btn primary" @click="goTraining">
-          <text class="action-btn-text">认知训练</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 加载中 -->
-    <view class="loading-state" v-if="loading">
-      <text class="loading-text">加载中...</text>
-    </view>
-
-    <!-- 无数据 -->
-    <view class="empty-state" v-if="!report && !loading">
-      <text class="empty-icon">&#x1F9E0;</text>
-      <text class="empty-title">暂无认知测评数据</text>
-      <text class="empty-desc">完成 DAN 认知评估后,可在此查看详细的六维认知分析报告</text>
-      <view class="empty-btn" @click="goAssessment">预约测评</view>
-    </view>
-  </view>
-</template>
-
-<script>
-import PageBanner from '../../components/PageBanner.vue'
-import { getAssessmentLatestResult } from '../../utils/api.js'
-
-export default {
-  components: { PageBanner },
-  data: function() {
-    return {
-      childId: null,
-      report: null,
-      loading: true,
-      dimensions: [
-        { key: 'perceptionScore', label: '感知能力', icon: '\u{1F441}', desc: '感官信息处理能力' },
-        { key: 'focusScore', label: '专注力', icon: '\u{1F3AF}', desc: '持续集中和抗干扰能力' },
-        { 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: '信息处理和反应速度' }
-      ]
-    }
-  },
-  onLoad: function(options) {
-    if (options && options.childId) {
-      this.childId = options.childId
-    } else {
-      this.childId = uni.getStorageSync('currentChildId') || null
-    }
-    this.loadReport()
-  },
-  methods: {
-    loadReport: function() {
-      var self = this
-      var childId = self.childId
-      if (!childId) {
-        self.loading = false
-        return
-      }
-      self.loading = true
-      getAssessmentLatestResult(childId).then(function(res) {
-        if (res.code === 200 && res.data) {
-          self.report = res.data
-        }
-        self.loading = false
-      }).catch(function() {
-        self.loading = false
-      })
-    },
-    goAssessment: function() {
-      uni.navigateTo({ url: '/pages/assessment/apply' })
-    },
-    goTraining: function() {
-      uni.navigateTo({ url: '/pages/mind/training?childId=' + (this.childId || '') })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  min-height: 100vh;
-  background: #f5f7fa;
-  padding-bottom: 120rpx;
-}
-
-/* ===== 报告卡 ===== */
-.report-card {
-  margin: 20rpx 30rpx;
-  background: #fff;
-  border-radius: 24rpx;
-  padding: 40rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
-}
-
-.report-header {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 30rpx;
-}
-
-.report-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #333;
-}
-
-.report-date {
-  font-size: 24rpx;
-  color: #999;
-}
-
-/* ===== 综合评分 ===== */
-.overall-score-wrap {
-  text-align: center;
-  padding: 40rpx 0;
-  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-  border-radius: 20rpx;
-  margin-bottom: 40rpx;
-  color: #fff;
-}
-
-.overall-label {
-  font-size: 28rpx;
-  opacity: 0.9;
-  display: block;
-  margin-bottom: 16rpx;
-}
-
-.overall-row {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: center;
-}
-
-.overall-value {
-  font-size: 80rpx;
-  font-weight: bold;
-  margin-right: 20rpx;
-}
-
-.dan-badge {
-  background: rgba(255,255,255,0.25);
-  border-radius: 12rpx;
-  padding: 6rpx 20rpx;
-}
-
-.dan-level {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #FFD700;
-}
-
-/* ===== 七维条形图 ===== */
-.dimension-list {
-  margin-bottom: 30rpx;
-}
-
-.dimension-item {
-  margin-bottom: 28rpx;
-}
-
-.dimension-header {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  align-items: center;
-  margin-bottom: 10rpx;
-}
-
-.dimension-label-row {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-
-.dimension-icon {
-  font-size: 28rpx;
-  margin-right: 10rpx;
-}
-
-.dimension-name {
-  font-size: 28rpx;
-  color: #333;
-  font-weight: 500;
-}
-
-.dimension-score {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #667eea;
-}
-
-.dimension-bar {
-  height: 16rpx;
-  background: #f0f0f0;
-  border-radius: 8rpx;
-  overflow: hidden;
-  margin-bottom: 8rpx;
-}
-
-.dimension-bar-fill {
-  height: 100%;
-  background: linear-gradient(90deg, #667eea, #764ba2);
-  border-radius: 8rpx;
-  transition: width 0.5s;
-}
-
-.dimension-desc {
-  font-size: 22rpx;
-  color: #999;
-  line-height: 1.4;
-}
-
-/* ===== 分析/建议 ===== */
-.report-analysis, .report-suggestions {
-  margin-top: 30rpx;
-  padding: 28rpx;
-  border-radius: 16rpx;
-}
-
-.report-analysis {
-  background: #f9f9ff;
-}
-
-.report-suggestions {
-  background: #f0fdf4;
-}
-
-.analysis-header-row {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  margin-bottom: 16rpx;
-}
-
-.analysis-icon {
-  font-size: 30rpx;
-  margin-right: 10rpx;
-}
-
-.analysis-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-}
-
-.analysis-content, .suggestion-content {
-  font-size: 26rpx;
-  color: #666;
-  line-height: 1.7;
-  display: block;
-}
-
-/* ===== 底部操作 ===== */
-.action-row {
-  display: flex;
-  flex-direction: row;
-  justify-content: space-between;
-  margin-top: 40rpx;
-}
-
-.action-btn {
-  flex: 1;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 20rpx 0;
-  border-radius: 40rpx;
-}
-
-.action-btn.primary {
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  margin-left: 16rpx;
-  box-shadow: 0 4rpx 16rpx rgba(102,126,234,0.3);
-}
-
-.action-btn.secondary {
-  background: #fff;
-  border: 2rpx solid #ddd;
-  margin-right: 16rpx;
-}
-
-.action-btn-text {
-  font-size: 28rpx;
-  font-weight: 500;
-  color: #fff;
-}
-
-.action-btn.secondary .action-btn-text {
-  color: #666;
-}
-
-/* ===== 加载中 ===== */
-.loading-state {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 120rpx 0;
-}
-
-.loading-text {
-  font-size: 28rpx;
-  color: #999;
-}
-
-/* ===== 空状态 ===== */
-.empty-state {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  padding: 120rpx 40rpx 80rpx;
-}
-
-.empty-icon {
-  font-size: 100rpx;
-  margin-bottom: 20rpx;
-}
-
-.empty-title {
-  font-size: 32rpx;
-  color: #333;
-  font-weight: bold;
-  margin-bottom: 16rpx;
-}
-
-.empty-desc {
-  font-size: 26rpx;
-  color: #999;
-  text-align: center;
-  line-height: 1.6;
-  margin-bottom: 40rpx;
-}
-
-.empty-btn {
-  background: linear-gradient(135deg, #667eea, #764ba2);
-  color: #fff;
-  font-size: 28rpx;
-  font-weight: bold;
-  padding: 16rpx 64rpx;
-  border-radius: 40rpx;
-  box-shadow: 0 4rpx 16rpx rgba(102,126,234,0.3);
-}
-</style>

+ 0 - 371
cfc-frontend/pages/wisdom/wisdom_temp/index.vue

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

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

@@ -1,311 +0,0 @@
-<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,
-      sandboxData: null,
-      dualDimension: null,
-      dimensionTasks: [],
-      dimensionActivities: [],
-      dimensionProducts: [],
-      loading: true
-    }
-  },
-  computed: {
-    graphMembers: function() {
-      if (!this.sandboxData || !this.sandboxData.members) return []
-      return this.sandboxData.members.map(function(m) {
-        return {
-          id: m.memberId || m.id,
-          nickname: m.name || m.nickname || '成员',
-          memberType: m.memberType || 'child'
-        }
-      })
-    },
-    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 {}
-    }
-  },
-  onLoad: function(options) {
-    this.childId = options.childId || uni.getStorageSync('currentChildId') || null
-  },
-  onShow: function() {
-    var self = this
-    self.selfId = uni.getStorageSync('userId') || null
-    self.loadChildren()
-    self.loadSandboxData()
-    if (self.childId) {
-      self.loadEnergyData()
-      self.loadDimensionTasks()
-      self.loadDimensionActivities()
-      self.loadDimensionProducts()
-    }
-    self.loading = false
-  },
-  methods: {
-    loadChildren: function() {
-      var self = this
-      getChildren().then(function(res) {
-        if (res.code === 200 && res.data) {
-          for (var i = 0; i < res.data.length; i++) {
-            if (res.data[i].childId === self.childId) {
-              self.memberInfo = res.data[i]
-              break
-            }
-          }
-          if (!self.memberInfo && res.data.length > 0) {
-            self.memberInfo = res.data[0]
-            self.childId = res.data[0].childId
-          }
-        }
-      }).catch(function() {})
-    },
-    loadSandboxData: function() {
-      var self = this
-      getFamilyEnergySandbox().then(function(res) {
-        if (res && res.data) self.sandboxData = res.data
-      }).catch(function() {})
-    },
-    loadEnergyData: function() {
-      var self = this
-      getEnergyOverview(this.childId).then(function(res) {
-        if (res && res.data) {
-          var dims = res.data.dimensions
-          if (dims && dims.length > 0) {
-            for (var i = 0; i < dims.length; i++) {
-              if (dims[i].code === 'wisdom') {
-                self.dualDimension = dims[i]
-                break
-              }
-            }
-          }
-        }
-      }).catch(function() {})
-    },
-    loadDimensionTasks: function() {
-      var self = this
-      getTodayTasksByCategory(this.childId, 'wisdom').then(function(res) {
-        if (res && res.data) {
-          self.dimensionTasks = Array.isArray(res.data) ? res.data.slice(0, 5) : []
-        }
-      }).catch(function() { self.dimensionTasks = [] })
-    },
-    loadDimensionActivities: function() {
-      var self = this
-      getActivityList({ dimensionCode: 'wisdom', page: 1, size: 3 }).then(function(res) {
-        if (res && res.data) {
-          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
-          self.dimensionActivities = list.slice(0, 3)
-        }
-      }).catch(function() { self.dimensionActivities = [] })
-    },
-    loadDimensionProducts: function() {
-      var self = this
-      getProductsByDomain('wisdom', 1, 4).then(function(res) {
-        if (res && res.data) {
-          var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
-          self.dimensionProducts = list.slice(0, 4)
-        }
-      }).catch(function() { self.dimensionProducts = [] })
-    },
-    onTaskClick: function(task) {
-      uni.navigateTo({ url: '/pages/tasks/tasks' })
-    },
-    goActivityDetail: function(act) {
-      if (act && act.id) uni.navigateTo({ url: '/pages/discover/activity-detail/activity-detail?id=' + act.id })
-    },
-    goMoreActivities: function() {
-      uni.navigateTo({ url: '/pages/activity/index' })
-    },
-    goProductDetail: function(prod) {
-      if (prod && prod.id) uni.navigateTo({ url: '/pages/discover/product-detail/product-detail?id=' + prod.id })
-    },
-    goMoreProducts: function() {
-      uni.navigateTo({ url: '/pages/shop/index' })
-    },
-    goTasks: function() { uni.switchTab({ url: '/pages/tasks/tasks' }) },
-    goBack: function() { uni.navigateBack() }
-  }
-}
-</script>
-
-<style scoped>
-.detail-container {
-  min-height: 100vh;
-  background: #f5f7fa;
-  padding-bottom: 40rpx;
-}
-.nav-bar {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  padding: 20rpx 30rpx;
-  background: #fff;
-}
-.nav-back {
-  padding: 10rpx;
-}
-.nav-back-icon {
-  font-size: 36rpx;
-  color: #333;
-}
-.nav-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #333;
-  margin-left: 16rpx;
-}
-.member-card {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  margin: 20rpx 30rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 24rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
-}
-.member-avatar {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 40rpx;
-  background: linear-gradient(135deg, #FF6B35, #FBBF24);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 20rpx;
-}
-.wisdom-avatar {
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-}
-.avatar-text {
-  font-size: 32rpx;
-  color: #fff;
-  font-weight: bold;
-}
-.member-info {
-  flex: 1;
-  display: flex;
-  flex-direction: column;
-}
-.member-name {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
-}
-.member-role {
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 4rpx;
-}
-.member-score {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-}
-.score-value {
-  font-size: 40rpx;
-  font-weight: bold;
-  color: #FF6B35;
-}
-.wisdom-score {
-  color: #FFD700;
-}
-.score-label {
-  font-size: 22rpx;
-  color: #999;
-}
-.bottom-spacer {
-  height: 120rpx;
-}
-</style>

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

@@ -1,921 +0,0 @@
-<template>
-  <view class="container">
-    <view class="resume-prompt" v-if="showResume">
-      <text class="resume-text">检测到上次未完成的测评</text>
-      <view class="resume-btns">
-        <view class="btn btn--outline" @tap="resumeSession">继续答题</view>
-        <view class="btn btn--ghost" @tap="clearSession">重新开始</view>
-      </view>
-    </view>
-
-    <view class="step-wrap" v-if="!showResume">
-      <view class="step-row">
-        <view class="step-item" v-for="i in 4" :key="i" @tap="goStep(i)">
-          <view class="step-dot" :class="{
-            'step-dot--active': step === i,
-            'step-dot--done': step > i,
-            'step-dot--pending': step < i
-          }">
-            <text v-if="step > i" class="step-check">&#10003;</text>
-            <text v-else class="step-num">{{ i }}</text>
-          </view>
-          <text class="step-label" :class="{
-            'step-label--active': step >= i,
-            'step-label--muted': step < i
-          }">{{ stepLabels[i - 1] }}</text>
-        </view>
-      </view>
-    </view>
-
-    <view class="step-content" v-if="step === 1 && !showResume">
-      <view class="content-header">
-        <text class="content-title">选择测评目标</text>
-        <text class="content-sub">选择你想提升的能力维度</text>
-      </view>
-      <view class="goal-grid">
-        <view class="goal-card" v-for="g in goals" :key="g.id" :class="{ 'goal-card--selected': selectedGoal && selectedGoal.id === g.id }" @tap="selectGoal(g)">
-          <view class="goal-icon-wrap">
-            <text class="goal-icon">{{ g.icon }}</text>
-          </view>
-          <text class="goal-title">{{ g.title }}</text>
-          <text class="goal-desc">{{ g.desc }}</text>
-        </view>
-      </view>
-      <view class="action-bar">
-        <view class="btn btn--primary" :class="{ 'btn--disabled': !selectedGoal }" @tap="goStep2">下一步</view>
-      </view>
-    </view>
-
-    <view class="step-content" v-if="step === 2 && !showResume">
-      <view class="content-header">
-        <text class="content-title">选择题目类型</text>
-        <text class="content-sub">共5道题,选择适合的答题方式</text>
-      </view>
-      <view class="type-list">
-        <view class="type-card" v-for="t in questionTypes" :key="t.id" :class="{ 'type-card--selected': questionType === t.id }" @tap="selectType(t.id)">
-          <view class="type-icon-wrap">
-            <text class="type-icon">{{ t.icon }}</text>
-          </view>
-          <view class="type-info">
-            <text class="type-name">{{ t.name }}</text>
-            <text class="type-desc">{{ t.desc }}</text>
-          </view>
-          <view class="type-radio" :class="{ 'type-radio--checked': questionType === t.id }">
-            <view v-if="questionType === t.id" class="type-radio-dot"></view>
-          </view>
-        </view>
-      </view>
-      <view class="action-bar action-bar--double">
-        <view class="btn btn--outline" @tap="goStep1">上一步</view>
-        <view class="btn btn--primary" :class="{ 'btn--disabled': !questionType }" @tap="startQuiz">开始答题</view>
-      </view>
-    </view>
-
-    <view class="step-content" v-if="step === 3 && !showResume">
-      <view class="progress-header">
-        <text class="progress-label">第 {{ currentQ + 1 }}/{{ questions.length }} 题</text>
-        <view class="progress-bar">
-          <view class="progress-fill" :style="'width:' + progressPct + '%'"></view>
-        </view>
-      </view>
-      <view class="question-card" v-if="currentQuestion">
-        <view class="q-tag">{{ tagLabel }}</view>
-        <text class="q-text">{{ currentQuestion.question }}</text>
-        <view class="q-options" v-if="currentQuestion.isTF">
-          <view class="q-option q-option--tf" :class="{ 'q-option--selected': currentAnswer === '正确' }" @tap="currentAnswer = '正确'">
-            <text class="q-option-tf-text">正确</text>
-          </view>
-          <view class="q-option q-option--tf" :class="{ 'q-option--selected': currentAnswer === '错误' }" @tap="currentAnswer = '错误'">
-            <text class="q-option-tf-text">错误</text>
-          </view>
-        </view>
-        <view class="q-options" v-else>
-          <view class="q-option" v-for="(opt, oi) in currentQuestion.options" :key="oi" :class="{ 'q-option--selected': currentAnswer === opt }" @tap="currentAnswer = opt">
-            <view class="q-radio" :class="{ 'q-radio--checked': currentAnswer === opt }">
-              <view v-if="currentAnswer === opt" class="q-radio-dot"></view>
-            </view>
-            <text class="q-option-text">{{ opt }}</text>
-          </view>
-        </view>
-      </view>
-      <view class="action-bar action-bar--double">
-        <view class="btn btn--outline" :class="{ 'btn--disabled': currentQ === 0 }" @tap="prevQuestion">上一题</view>
-        <view class="btn btn--primary" :class="{ 'btn--disabled': !currentAnswer }" @tap="nextQuestion">{{ currentQ < questions.length - 1 ? '下一题' : '查看结果' }}</view>
-      </view>
-    </view>
-
-    <view class="step-content" v-if="step === 4 && !showResume">
-      <view class="result-header">
-        <view class="score-circle">
-          <text class="score-value">{{ score }}/5</text>
-        </view>
-        <text class="score-label">{{ resultMessage }}</text>
-      </view>
-      <view class="section-title">维度分析</view>
-      <view class="result-dims">
-        <view class="dim-bar-item" v-for="(dim, di) in dimBreakdown" :key="di">
-          <view class="dim-bar-header">
-            <text class="dim-bar-name">{{ dim.name }}</text>
-            <text class="dim-bar-count">{{ dim.correct }}/{{ dim.total }}</text>
-          </view>
-          <view class="dim-bar-track">
-            <view class="dim-bar-fill" :style="'width:' + dim.pct + '%'"></view>
-          </view>
-        </view>
-      </view>
-      <view class="action-bar">
-        <view class="btn btn--primary" @tap="resetQuiz">再测一次</view>
-        <view class="btn btn--outline" @tap="goHome">返回首页</view>
-      </view>
-    </view>
-
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-var QUESTION_BANK = [
-  { dimension: 'focus', question: '以下哪个因素最容易影响注意力集中?', options: ['噪音', '温度', '光照', '以上都是'], answer: '以上都是' },
-  { dimension: 'focus', question: '持续专注工作建议多久休息一次?', options: ['15分钟', '30分钟', '45分钟', '60分钟'], answer: '45分钟' },
-  { dimension: 'focus', question: '舒尔特方格的训练目标是提升什么?', options: ['记忆力', '专注力', '逻辑思维', '创造力'], answer: '专注力' },
-  { dimension: 'memory', question: '短期记忆通常能记住几个信息组块?', options: ['3-4个', '5-9个', '10-15个', '20个以上'], answer: '5-9个' },
-  { dimension: 'memory', question: '以下哪个方法有助于增强记忆?', options: ['死记硬背', '联想记忆', '反复抄写', '一次性学习'], answer: '联想记忆' },
-  { dimension: 'memory', question: '睡眠对记忆的影响是什么?', options: ['削弱记忆', '巩固记忆', '无影响', '干扰记忆'], answer: '巩固记忆' },
-  { dimension: 'logic', question: '如果A>B且B>C,那么?', options: ['A=C', 'A>C', 'A<C', '不确定'], answer: 'A>C' },
-  { dimension: 'logic', question: '1, 1, 2, 3, 5, 8, ? 下一个数是多少?', options: ['10', '11', '12', '13'], answer: '13' },
-  { dimension: 'logic', question: '以下哪个不是有效推理方式?', options: ['归纳推理', '演绎推理', '循环论证', '类比推理'], answer: '循环论证' },
-  { dimension: 'perception', question: '人眼能分辨约多少种颜色?', options: ['几百种', '几千种', '几万种', '数百万种'], answer: '数百万种' },
-  { dimension: 'perception', question: '以下哪种错觉与视觉感知有关?', options: ['缪勒-莱尔错觉', '德布罗意错觉', '薛定谔错觉', '迈克尔逊错觉'], answer: '缪勒-莱尔错觉' },
-  { dimension: 'perception', question: '感觉适应是指什么?', options: ['敏感度降低', '敏感度升高', '感知消失', '感知增强'], answer: '敏感度降低' },
-  { dimension: 'spatial', question: '正方体有多少个面?', options: ['4', '6', '8', '12'], answer: '6' },
-  { dimension: 'spatial', question: '以下哪个是立体几何的基本要素?', options: ['点线面', '正反合', '红黄蓝', '上下左右'], answer: '点线面' },
-  { dimension: 'spatial', question: '一个立方体展开图有几个正方形?', options: ['4', '6', '8', '12'], answer: '6' },
-  { dimension: 'speed', question: '反应时实验测量的是什么?', options: ['思考速度', '反应速度', '运动速度', '阅读速度'], answer: '反应速度' },
-  { dimension: 'speed', question: '以下哪个因素会影响加工速度?', options: ['年龄', '情绪', '疲劳', '以上都是'], answer: '以上都是' },
-  { dimension: 'speed', question: '信息加工速度最快的年龄段是?', options: ['儿童期', '青春期', '成年早期', '中老年期'], answer: '成年早期' }
-]
-
-export default {
-  data() {
-    return {
-      step: 1,
-      selectedGoal: null,
-      questionType: null,
-      questions: [],
-      currentQ: 0,
-      answers: [],
-      showResume: false,
-      savedSession: null,
-      goals: [
-        { id: 'focus', title: '专注力', desc: '提升注意力集中能力', icon: '集' },
-        { id: 'memory', title: '记忆力', desc: '增强信息记忆与回忆能力', icon: '忆' },
-        { id: 'logic', title: '逻辑思维', desc: '锻炼逻辑推理与判断能力', icon: '逻' },
-        { id: 'perception', title: '感知觉', desc: '提高感官信息处理能力', icon: '感' },
-        { id: 'spatial', title: '空间思维', desc: '培养空间想象与构建能力', icon: '间' },
-        { id: 'speed', title: '加工速度', desc: '加快信息处理与反应速度', icon: '速' }
-      ],
-      questionTypes: [
-        { id: 'choice', name: '选择题', desc: '四选一标准选择题', icon: '选' },
-        { id: 'truefalse', name: '判断题', desc: '判断陈述正误', icon: '判' },
-        { id: 'mix', name: '混合题型', desc: '选择题与判断题混合', icon: '混' }
-      ],
-      stepLabels: ['目标', '题型', '答题', '结果']
-    }
-  },
-  computed: {
-    currentQuestion: function() {
-      if (this.questions.length === 0) {
-        return null
-      }
-      return this.questions[this.currentQ]
-    },
-    currentAnswer: {
-      get: function() {
-        return this.answers[this.currentQ] || null
-      },
-      set: function(val) {
-        this.$set(this.answers, this.currentQ, val)
-      }
-    },
-    progressPct: function() {
-      if (this.questions.length === 0) {
-        return 0
-      }
-      return ((this.currentQ + 1) / this.questions.length) * 100
-    },
-    score: function() {
-      var correct = 0
-      for (var i = 0; i < this.questions.length; i++) {
-        var q = this.questions[i]
-        var a = this.answers[i]
-        if (q && a && q.answer === a) {
-          correct++
-        }
-      }
-      return correct
-    },
-    resultMessage: function() {
-      var s = this.score
-      if (s >= 5) return '太棒了!满分通过!'
-      if (s >= 4) return '非常优秀!继续保持!'
-      if (s >= 3) return '表现不错,还有提升空间!'
-      return '继续加油,多练习会更好!'
-    },
-    tagLabel: function() {
-      if (!this.currentQuestion) return ''
-      var dimMap = {
-        focus: '专注力',
-        memory: '记忆力',
-        logic: '逻辑思维',
-        perception: '感知觉',
-        spatial: '空间思维',
-        speed: '加工速度'
-      }
-      return dimMap[this.currentQuestion.dimension] || ''
-    },
-    dimBreakdown: function() {
-      var dimNames = {
-        focus: '专注力',
-        memory: '记忆力',
-        logic: '逻辑思维',
-        perception: '感知觉',
-        spatial: '空间思维',
-        speed: '加工速度'
-      }
-      var dims = {}
-      for (var i = 0; i < this.questions.length; i++) {
-        var q = this.questions[i]
-        if (!q) continue
-        if (!dims[q.dimension]) {
-          dims[q.dimension] = { total: 0, correct: 0 }
-        }
-        dims[q.dimension].total++
-        if (this.answers[i] && q.answer === this.answers[i]) {
-          dims[q.dimension].correct++
-        }
-      }
-      var result = []
-      var keys = Object.keys(dims)
-      for (var k = 0; k < keys.length; k++) {
-        var key = keys[k]
-        var item = dims[key]
-        result.push({
-          name: dimNames[key] || key,
-          total: item.total,
-          correct: item.correct,
-          pct: item.total > 0 ? Math.round((item.correct / item.total) * 100) : 0
-        })
-      }
-      return result
-    }
-  },
-  onLoad: function() {
-    this.checkSession()
-  },
-  onUnload: function() {
-    this.saveSession()
-  },
-  methods: {
-    checkSession: function() {
-      var saved = uni.getStorageSync('selfQuizSession')
-      if (saved) {
-        try {
-          var session = JSON.parse(saved)
-          if (session && session.step && session.step >= 2 && session.step <= 3) {
-            this.showResume = true
-            this.savedSession = session
-          } else {
-            uni.removeStorageSync('selfQuizSession')
-          }
-        } catch (e) {
-          uni.removeStorageSync('selfQuizSession')
-        }
-      }
-    },
-    saveSession: function() {
-      var session = {
-        step: this.step,
-        selectedGoal: this.selectedGoal,
-        questionType: this.questionType,
-        questions: this.questions,
-        currentQ: this.currentQ,
-        answers: this.answers
-      }
-      uni.setStorageSync('selfQuizSession', JSON.stringify(session))
-    },
-    resumeSession: function() {
-      if (!this.savedSession) return
-      var s = this.savedSession
-      this.step = s.step
-      this.selectedGoal = s.selectedGoal
-      this.questionType = s.questionType
-      this.questions = s.questions || []
-      this.currentQ = s.currentQ || 0
-      this.answers = s.answers || []
-      this.showResume = false
-      this.savedSession = null
-    },
-    clearSession: function() {
-      uni.removeStorageSync('selfQuizSession')
-      this.showResume = false
-      this.savedSession = null
-    },
-    selectGoal: function(g) {
-      this.selectedGoal = g
-    },
-    goStep2: function() {
-      if (this.selectedGoal) {
-        this.step = 2
-        this.saveSession()
-      }
-    },
-    goStep1: function() {
-      this.step = 1
-      this.saveSession()
-    },
-    selectType: function(id) {
-      this.questionType = id
-    },
-    startQuiz: function() {
-      if (!this.questionType) return
-      this.generateQuestions()
-      this.currentQ = 0
-      this.answers = []
-      this.step = 3
-      this.saveSession()
-    },
-    shuffle: function(arr) {
-      var a = arr.slice()
-      for (var i = a.length - 1; i > 0; i--) {
-        var j = Math.floor(Math.random() * (i + 1))
-        var tmp = a[i]
-        a[i] = a[j]
-        a[j] = tmp
-      }
-      return a
-    },
-    convertToTF: function(q) {
-      var opts = q.options
-      var idx = opts.indexOf(q.answer)
-      var wrongOpts = []
-      for (var w = 0; w < opts.length; w++) {
-        if (opts[w] !== q.answer) {
-          wrongOpts.push(opts[w])
-        }
-      }
-      var isTrue = Math.random() > 0.4
-      var claimIdx
-      if (isTrue) {
-        claimIdx = idx
-      } else {
-        var randomWrong = wrongOpts[Math.floor(Math.random() * wrongOpts.length)]
-        claimIdx = opts.indexOf(randomWrong)
-      }
-      return {
-        dimension: q.dimension,
-        question: q.question + ' 正确答案是"' + opts[claimIdx] + '"',
-        options: ['正确', '错误'],
-        answer: isTrue ? '正确' : '错误',
-        isTF: true
-      }
-    },
-    generateQuestions: function() {
-      var goalId = this.selectedGoal && this.selectedGoal.id
-      if (!goalId) return
-      var pool = []
-      for (var p = 0; p < QUESTION_BANK.length; p++) {
-        if (QUESTION_BANK[p].dimension === goalId) {
-          pool.push(QUESTION_BANK[p])
-        }
-      }
-      var shuffled = this.shuffle(pool)
-      var type = this.questionType
-      if (type === 'truefalse') {
-        var tfQuestions = []
-        var count = Math.min(5, shuffled.length)
-        for (var ti = 0; ti < count; ti++) {
-          tfQuestions.push(this.convertToTF(shuffled[ti]))
-        }
-        this.questions = tfQuestions
-      } else if (type === 'mix') {
-        var allDims = ['focus', 'memory', 'logic', 'perception', 'spatial', 'speed']
-        var otherDims = []
-        for (var od = 0; od < allDims.length; od++) {
-          if (allDims[od] !== goalId) {
-            otherDims.push(allDims[od])
-          }
-        }
-        otherDims = this.shuffle(otherDims)
-        var selectedDims = [goalId].concat(otherDims.slice(0, 4))
-        var mixedQuestions = []
-        for (var di = 0; di < selectedDims.length; di++) {
-          var dimPool = []
-          for (var dq = 0; dq < QUESTION_BANK.length; dq++) {
-            if (QUESTION_BANK[dq].dimension === selectedDims[di]) {
-              dimPool.push(QUESTION_BANK[dq])
-            }
-          }
-          if (dimPool.length > 0) {
-            var pick = this.shuffle(dimPool)
-            mixedQuestions.push(pick[0])
-          }
-        }
-        this.questions = this.shuffle(mixedQuestions).slice(0, 5)
-      } else {
-        this.questions = shuffled.slice(0, 5)
-      }
-    },
-    nextQuestion: function() {
-      if (!this.currentAnswer) return
-      if (this.currentQ < this.questions.length - 1) {
-        this.currentQ++
-        this.saveSession()
-      } else {
-        this.step = 4
-        this.saveSession()
-      }
-    },
-    prevQuestion: function() {
-      if (this.currentQ > 0) {
-        this.currentQ--
-      }
-    },
-    resetQuiz: function() {
-      uni.removeStorageSync('selfQuizSession')
-      this.step = 1
-      this.selectedGoal = null
-      this.questionType = null
-      this.questions = []
-      this.currentQ = 0
-      this.answers = []
-    },
-    goHome: function() {
-      uni.switchTab({ url: '/pages/wisdom/index' })
-    },
-    goStep: function(n) {
-      if (n === this.step) return
-      if (n < this.step) {
-        this.step = n
-        this.saveSession()
-      }
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  min-height: 100vh;
-  background: #f5f7fa;
-  padding: 30rpx;
-  padding-bottom: 120rpx;
-  box-sizing: border-box;
-}
-
-.resume-prompt {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 50rpx 30rpx;
-  text-align: center;
-  margin-top: 160rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
-}
-.resume-text {
-  font-size: 28rpx;
-  color: #333;
-  display: block;
-  margin-bottom: 30rpx;
-}
-.resume-btns {
-  display: flex;
-  justify-content: center;
-  gap: 20rpx;
-}
-
-.step-wrap {
-  margin-bottom: 10rpx;
-}
-.step-row {
-  display: flex;
-  justify-content: center;
-  align-items: flex-start;
-  padding: 20rpx 0;
-}
-.step-item {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin: 0 24rpx;
-}
-.step-dot {
-  width: 48rpx;
-  height: 48rpx;
-  border-radius: 50%;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  border: 3rpx solid #ddd;
-  background: #fff;
-  transition: all 0.3s;
-}
-.step-dot--active {
-  background: #FFD700;
-  border-color: #FFD700;
-}
-.step-dot--done {
-  border-color: #FFD700;
-  background: #FFD700;
-}
-.step-dot--pending {
-  border-color: #ddd;
-  background: #fff;
-}
-.step-check {
-  font-size: 22rpx;
-  color: #fff;
-  font-weight: bold;
-}
-.step-num {
-  font-size: 22rpx;
-  color: #999;
-}
-.step-dot--active .step-num {
-  color: #fff;
-}
-.step-label {
-  font-size: 20rpx;
-  margin-top: 8rpx;
-  color: #999;
-}
-.step-label--active {
-  color: #FFD700;
-  font-weight: bold;
-}
-.step-label--muted {
-  color: #ccc;
-}
-
-.step-content {
-  animation: fadeIn 0.3s ease;
-}
-@keyframes fadeIn {
-  from { opacity: 0; transform: translateY(20rpx); }
-  to { opacity: 1; transform: translateY(0); }
-}
-
-.content-header {
-  text-align: center;
-  margin-bottom: 30rpx;
-}
-.content-title {
-  font-size: 36rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-}
-.content-sub {
-  font-size: 24rpx;
-  color: #999;
-  margin-top: 10rpx;
-  display: block;
-}
-.section-title {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 20rpx;
-}
-
-.goal-grid {
-  display: flex;
-  flex-wrap: wrap;
-}
-.goal-card {
-  width: calc(50% - 12rpx);
-  margin: 6rpx;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 30rpx 16rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  border: 3rpx solid transparent;
-  transition: all 0.3s;
-  box-sizing: border-box;
-}
-.goal-card--selected {
-  border-color: #FFD700;
-  background: #FFF8E1;
-}
-.goal-icon-wrap {
-  width: 80rpx;
-  height: 80rpx;
-  border-radius: 50%;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 16rpx;
-}
-.goal-icon {
-  font-size: 32rpx;
-  color: #fff;
-  font-weight: bold;
-}
-.goal-title {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-  margin-bottom: 6rpx;
-}
-.goal-desc {
-  font-size: 22rpx;
-  color: #999;
-  text-align: center;
-  line-height: 1.4;
-}
-
-.type-list {
-  margin-top: 10rpx;
-}
-.type-card {
-  display: flex;
-  align-items: center;
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 28rpx 24rpx;
-  margin-bottom: 20rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
-  border: 3rpx solid transparent;
-  transition: all 0.3s;
-}
-.type-card--selected {
-  border-color: #FFD700;
-  background: #FFF8E1;
-}
-.type-icon-wrap {
-  width: 72rpx;
-  height: 72rpx;
-  border-radius: 50%;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-right: 20rpx;
-  flex-shrink: 0;
-}
-.type-icon {
-  font-size: 28rpx;
-  color: #fff;
-  font-weight: bold;
-}
-.type-info {
-  flex: 1;
-}
-.type-name {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #333;
-  display: block;
-}
-.type-desc {
-  font-size: 22rpx;
-  color: #999;
-  margin-top: 4rpx;
-  display: block;
-}
-.type-radio {
-  width: 36rpx;
-  height: 36rpx;
-  border-radius: 50%;
-  border: 3rpx solid #ddd;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  margin-left: 16rpx;
-}
-.type-radio--checked {
-  border-color: #FFD700;
-}
-.type-radio-dot {
-  width: 20rpx;
-  height: 20rpx;
-  border-radius: 50%;
-  background: #FFD700;
-}
-
-.progress-header {
-  margin-bottom: 30rpx;
-}
-.progress-label {
-  font-size: 26rpx;
-  color: #666;
-  display: block;
-  margin-bottom: 12rpx;
-}
-.progress-bar {
-  height: 8rpx;
-  background: #eee;
-  border-radius: 4rpx;
-  overflow: hidden;
-}
-.progress-fill {
-  height: 100%;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  border-radius: 4rpx;
-  transition: width 0.3s ease;
-}
-
-.question-card {
-  background: #fff;
-  border-radius: 20rpx;
-  padding: 36rpx 28rpx;
-  box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
-  margin-bottom: 30rpx;
-}
-.q-tag {
-  display: inline-block;
-  padding: 6rpx 20rpx;
-  background: #FFF8E1;
-  color: #B8860B;
-  font-size: 20rpx;
-  border-radius: 20rpx;
-  margin-bottom: 20rpx;
-}
-.q-text {
-  font-size: 30rpx;
-  color: #333;
-  line-height: 1.6;
-  display: block;
-  margin-bottom: 30rpx;
-}
-.q-options {
-  display: flex;
-  flex-direction: column;
-  gap: 16rpx;
-}
-.q-option {
-  display: flex;
-  align-items: center;
-  padding: 24rpx 20rpx;
-  border-radius: 16rpx;
-  border: 2rpx solid #eee;
-  transition: all 0.3s;
-}
-.q-option--selected {
-  border-color: #FFD700;
-  background: #FFF8E1;
-}
-.q-option--tf {
-  justify-content: center;
-  padding: 28rpx;
-}
-.q-option-tf-text {
-  font-size: 28rpx;
-  font-weight: bold;
-  color: #666;
-}
-.q-option--selected .q-option-tf-text {
-  color: #B8860B;
-}
-.q-radio {
-  width: 32rpx;
-  height: 32rpx;
-  border-radius: 50%;
-  border: 3rpx solid #ddd;
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  flex-shrink: 0;
-  margin-right: 16rpx;
-}
-.q-radio--checked {
-  border-color: #FFD700;
-}
-.q-radio-dot {
-  width: 18rpx;
-  height: 18rpx;
-  border-radius: 50%;
-  background: #FFD700;
-}
-.q-option-text {
-  font-size: 26rpx;
-  color: #333;
-  flex: 1;
-}
-
-.action-bar {
-  margin-top: 40rpx;
-  display: flex;
-  flex-direction: column;
-  gap: 16rpx;
-}
-.action-bar--double {
-  flex-direction: row;
-  gap: 20rpx;
-}
-.action-bar--double .btn {
-  flex: 1;
-}
-
-.btn {
-  padding: 24rpx 0;
-  border-radius: 16rpx;
-  text-align: center;
-  font-size: 28rpx;
-  font-weight: bold;
-  transition: all 0.3s;
-  box-sizing: border-box;
-}
-.btn--primary {
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  color: #fff;
-}
-.btn--primary:active {
-  opacity: 0.9;
-  transform: scale(0.98);
-}
-.btn--disabled {
-  opacity: 0.4;
-  pointer-events: none;
-}
-.btn--outline {
-  border: 3rpx solid #FFD700;
-  color: #B8860B;
-  background: #fff;
-}
-.btn--outline:active {
-  background: #FFF8E1;
-}
-.btn--ghost {
-  color: #999;
-  background: transparent;
-}
-
-.result-header {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  margin: 40rpx 0;
-}
-.score-circle {
-  width: 160rpx;
-  height: 160rpx;
-  border-radius: 50%;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  box-shadow: 0 8rpx 30rpx rgba(255,215,0,0.3);
-  margin-bottom: 20rpx;
-}
-.score-value {
-  font-size: 40rpx;
-  font-weight: bold;
-  color: #fff;
-}
-.score-label {
-  font-size: 28rpx;
-  color: #666;
-  display: block;
-}
-
-.result-dims {
-  margin: 20rpx 0 30rpx;
-}
-.dim-bar-item {
-  margin-bottom: 24rpx;
-}
-.dim-bar-header {
-  display: flex;
-  justify-content: space-between;
-  margin-bottom: 8rpx;
-}
-.dim-bar-name {
-  font-size: 24rpx;
-  color: #333;
-  font-weight: bold;
-}
-.dim-bar-count {
-  font-size: 22rpx;
-  color: #999;
-}
-.dim-bar-track {
-  height: 8rpx;
-  background: #eee;
-  border-radius: 4rpx;
-  overflow: hidden;
-}
-.dim-bar-fill {
-  height: 100%;
-  background: linear-gradient(135deg, #FFD700, #FFA500);
-  border-radius: 4rpx;
-  transition: width 0.6s ease;
-}
-
-.bottom-spacer {
-  height: 120rpx;
-}
-</style>

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

@@ -1,359 +0,0 @@
-<template>
-  <view class="container">
-    <PageBanner theme="wisdom"
-      tagline="智慧训练中心"
-      :quote="bannerQuote" />
-
-    <!-- 训练说明 -->
-    <view class="intro-card">
-      <text class="intro-icon">&#x2696;&#xFE0F;</text>
-      <text class="intro-title">智·训练中心</text>
-      <text class="intro-desc">通过科学设计的认知训练游戏,全面提升专注力、逻辑思维与反应速度。每日坚持训练,让智慧之金更加闪耀。</text>
-    </view>
-
-    <!-- 推荐训练 -->
-    <view class="section">
-      <view class="section-header">
-        <text class="section-title">推荐训练</text>
-        <text class="section-subtitle">选择一项开始挑战</text>
-      </view>
-      <view class="game-list">
-        <view class="game-card" v-for="game in games" :key="game.code" @click="goGame(game)">
-          <view class="game-icon-wrap">
-            <text class="game-icon">{{ game.icon }}</text>
-          </view>
-          <view class="game-info">
-            <text class="game-title">{{ game.name }}</text>
-            <text class="game-desc">{{ game.desc }}</text>
-            <view class="game-meta">
-              <view class="difficulty-row">
-                <text class="difficulty-label">难度:</text>
-                <view class="stars-row">
-                  <text class="star filled" v-for="(i, idx) in game.difficulty" :key="idx">&#x2605;</text>
-                  <text class="star empty" v-for="(i, idx) in (5 - game.difficulty)" :key="idx">&#x2605;</text>
-                </view>
-              </view>
-              <text class="game-badge" v-if="game.badge">{{ game.badge }}</text>
-            </view>
-          </view>
-          <button class="start-btn" hover-class="start-btn-hover">开始挑战</button>
-        </view>
-      </view>
-    </view>
-
-    <!-- 训练小贴士 -->
-    <view class="tip-section">
-      <view class="tip-card">
-        <view class="tip-left-bar"></view>
-        <view class="tip-body">
-          <text class="tip-title">&#x1F4A1; 训练建议</text>
-          <text class="tip-content">每次训练15-30分钟效果最佳;保持专注,减少干扰;循序渐进,从简单模式开始;定期复盘进步,保持训练动力。</text>
-        </view>
-      </view>
-    </view>
-
-    <!-- 底部占位 -->
-    <view class="bottom-spacer"></view>
-  </view>
-</template>
-
-<script>
-import PageBanner from '../../components/PageBanner.vue'
-
-var GAME_LIST = [
-  {
-    code: 'schulte',
-    name: '舒尔特方格',
-    icon: '\u{1F3AF}',
-    desc: '提升专注力与反应速度',
-    difficulty: 3,
-    badge: '入门推荐'
-  },
-  {
-    code: '1a2b',
-    name: '猜数字',
-    icon: '\u{1F522}',
-    desc: '锻炼逻辑思维与推理能力',
-    difficulty: 4,
-    badge: '进阶挑战'
-  }
-]
-
-var GAME_PAGES = {
-  schulte: '/pages/games/schulte',
-  '1a2b': '/pages/games/1a2b'
-}
-
-export default {
-  components: { PageBanner },
-  data: function() {
-    return {
-      games: GAME_LIST,
-      childId: null,
-      bannerQuote: '学而不思则罔,思而不学则殆'
-    }
-  },
-  onLoad: function(options) {
-    if (options && options.childId) {
-      this.childId = options.childId
-    } else {
-      var stored = uni.getStorageSync('currentChildId')
-      if (stored) {
-        this.childId = stored
-      }
-    }
-    uni.setNavigationBarTitle({
-      title: '智慧训练中心'
-    })
-  },
-  methods: {
-    goGame: function(game) {
-      var url = GAME_PAGES[game.code]
-      if (!url) {
-        uni.showToast({ title: '游戏暂未开放', icon: 'none' })
-        return
-      }
-      var childId = this.childId || ''
-      var queryParams = '?childId=' + childId + '&dimension=wisdom'
-      uni.navigateTo({ url: url + queryParams })
-    }
-  }
-}
-</script>
-
-<style scoped>
-.container {
-  min-height: 100vh;
-  background: #FFFBEB;
-  padding-bottom: 120rpx;
-}
-
-/* ===== 训练说明卡 ===== */
-.intro-card {
-  margin: 30rpx;
-  background: linear-gradient(135deg, #FFFBEB 0%, #FFF8E1 100%);
-  border-radius: 24rpx;
-  padding: 40rpx 32rpx;
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  border: 1rpx solid rgba(255, 215, 0, 0.2);
-  box-shadow: 0 4rpx 20rpx rgba(255, 215, 0, 0.1);
-}
-
-.intro-icon {
-  font-size: 56rpx;
-  margin-bottom: 16rpx;
-}
-
-.intro-title {
-  font-size: 34rpx;
-  font-weight: bold;
-  color: #B8860B;
-  margin-bottom: 16rpx;
-  letter-spacing: 2rpx;
-}
-
-.intro-desc {
-  font-size: 26rpx;
-  color: #8B7355;
-  text-align: center;
-  line-height: 1.7;
-}
-
-/* ===== 通用区段 ===== */
-.section {
-  margin: 20rpx 30rpx;
-}
-
-.section-header {
-  display: flex;
-  flex-direction: column;
-  margin-bottom: 24rpx;
-}
-
-.section-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #5D4037;
-}
-
-.section-subtitle {
-  font-size: 24rpx;
-  color: #A0896E;
-  margin-top: 6rpx;
-}
-
-/* ===== 游戏卡片 ===== */
-.game-list {
-  display: flex;
-  flex-direction: column;
-}
-
-.game-card {
-  display: flex;
-  flex-direction: column;
-  background: #FFFFFF;
-  border-radius: 24rpx;
-  padding: 32rpx;
-  margin-bottom: 24rpx;
-  border: 1rpx solid rgba(255, 215, 0, 0.15);
-  box-shadow: 0 4rpx 16rpx rgba(255, 215, 0, 0.08);
-}
-
-.game-card:active {
-  opacity: 0.85;
-  transform: scale(0.98);
-}
-
-.game-icon-wrap {
-  width: 100rpx;
-  height: 100rpx;
-  border-radius: 50rpx;
-  background: linear-gradient(135deg, #FFD700 0%, #FFA000 100%);
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  margin-bottom: 20rpx;
-  box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
-}
-
-.game-icon {
-  font-size: 48rpx;
-}
-
-.game-info {
-  display: flex;
-  flex-direction: column;
-  margin-bottom: 24rpx;
-}
-
-.game-title {
-  font-size: 32rpx;
-  font-weight: bold;
-  color: #5D4037;
-  margin-bottom: 8rpx;
-}
-
-.game-desc {
-  font-size: 26rpx;
-  color: #8B7355;
-  margin-bottom: 16rpx;
-  line-height: 1.5;
-}
-
-.game-meta {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-  justify-content: space-between;
-}
-
-.difficulty-row {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-
-.difficulty-label {
-  font-size: 22rpx;
-  color: #A0896E;
-  margin-right: 8rpx;
-}
-
-.stars-row {
-  display: flex;
-  flex-direction: row;
-  align-items: center;
-}
-
-.star {
-  font-size: 24rpx;
-  margin-right: 4rpx;
-}
-
-.star.filled {
-  color: #FFD700;
-}
-
-.star.empty {
-  color: #E0D5C0;
-}
-
-.game-badge {
-  font-size: 20rpx;
-  color: #B8860B;
-  background: #FFFBEB;
-  border-radius: 20rpx;
-  padding: 4rpx 16rpx;
-  border: 1rpx solid rgba(255, 215, 0, 0.3);
-  line-height: 1.6;
-}
-
-.start-btn {
-  width: 100%;
-  height: 80rpx;
-  line-height: 80rpx;
-  text-align: center;
-  background: linear-gradient(135deg, #FFD700 0%, #FFA000 100%);
-  color: #5D4037;
-  font-size: 30rpx;
-  font-weight: bold;
-  border-radius: 40rpx;
-  border: none;
-  padding: 0;
-  letter-spacing: 4rpx;
-  box-shadow: 0 4rpx 12rpx rgba(255, 215, 0, 0.3);
-}
-
-.start-btn-hover {
-  opacity: 0.8;
-  transform: scale(0.96);
-}
-
-/* ===== 训练小贴士 ===== */
-.tip-section {
-  margin: 20rpx 30rpx 0;
-}
-
-.tip-card {
-  display: flex;
-  flex-direction: row;
-  background: #FFFFFF;
-  border-radius: 20rpx;
-  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
-  overflow: hidden;
-  border: 1rpx solid rgba(255, 215, 0, 0.1);
-}
-
-.tip-left-bar {
-  width: 6rpx;
-  flex-shrink: 0;
-  background: linear-gradient(180deg, #FFD700 0%, #FFA000 100%);
-}
-
-.tip-body {
-  flex: 1;
-  padding: 28rpx 24rpx;
-  display: flex;
-  flex-direction: column;
-}
-
-.tip-title {
-  font-size: 26rpx;
-  font-weight: bold;
-  color: #B8860B;
-  margin-bottom: 12rpx;
-}
-
-.tip-content {
-  font-size: 24rpx;
-  color: #8B7355;
-  line-height: 1.7;
-}
-
-/* ===== 底部 ===== */
-.bottom-spacer {
-  height: 120rpx;
-}
-</style>

+ 0 - 14
cfc-frontend/router/teacherGuard.js

@@ -1,14 +0,0 @@
-// Teacher role route guard for Vue Router
-// Import this guard in your router entry (e.g., src/router/index.js) and call `router.beforeEach(teacherGuard)`
-
-export default function teacherGuard(to, from, next) {
-  // Assume user role is stored in localStorage after login
-  const role = localStorage.getItem('role');
-  // List of routes that require teacher role (prefix /guide)
-  const teacherOnly = to.path.startsWith('/guide');
-  if (teacherOnly && role !== 'teacher') {
-    // Redirect to a safe page, e.g., home or login
-    return next({ path: '/', replace: true });
-  }
-  return next();
-}