Browse Source

feat(frontend): TabBar 重构 - 4Tabs(首页/健康/成长/我的) + 中间五维启动器 + 自定义TabBar

- pages.json: 主包5→4页, tabBar 4项(custom:true), 分包调整
- 五维 index 页移入分包: mind→mind-detail, body→body-detail, wisdom→wisdom-detail, wealth分包
- 新建 custom-tab-bar: 4Tab + 中间⭐按钮 + 五维半屏浮层, 规划师5Tab不变
- 新建 pages/health/index.vue 健康Tab页(报告/测评/打卡概览)
- 增强 pages/growth/index.vue 为方案执行中心(待办/功能入口)
- 新建 utils/nav.js 统一导航工具
- 替换 switchTab 引用为 nav.goPage/navigateTo
- 活动推荐移至成长页, 健康页新增科普文章推荐
- profile 显示完整菜单(showServices=true)
Xiaogang Liao 1 month ago
parent
commit
db6c2d3c

+ 205 - 26
cfc-frontend/custom-tab-bar/index.vue

@@ -1,6 +1,6 @@
 <template>
 <template>
-  <view class="tab-bar-wrap" v-if="isLoggedIn">
-    <!-- 普通用户TabBar -->
+  <view class="tab-bar-wrap">
+    <!-- 普通用户 TabBar:4 Tab + 中间五维按钮 -->
     <view v-if="currentRole !== 'teacher'" class="tab-bar">
     <view v-if="currentRole !== 'teacher'" class="tab-bar">
       <view
       <view
         v-for="(item, index) in normalTabs"
         v-for="(item, index) in normalTabs"
@@ -16,9 +16,16 @@
         ></image>
         ></image>
         <text class="tab-text">{{ item.text }}</text>
         <text class="tab-text">{{ item.text }}</text>
       </view>
       </view>
+
+      <!-- 中间五维启动器按钮 -->
+      <view class="center-btn" @click="openDimPanel">
+        <view class="center-btn-inner">
+          <text class="center-btn-icon">⭐</text>
+        </view>
+      </view>
     </view>
     </view>
 
 
-    <!-- 规划师TabBar -->
+    <!-- 规划师 TabBar:5 Tab(不变) -->
     <view v-else class="tab-bar">
     <view v-else class="tab-bar">
       <view
       <view
         v-for="(item, index) in teacherTabs"
         v-for="(item, index) in teacherTabs"
@@ -35,6 +42,29 @@
         <text class="tab-text">{{ item.text }}</text>
         <text class="tab-text">{{ item.text }}</text>
       </view>
       </view>
     </view>
     </view>
+
+    <!-- 五维半屏浮层 -->
+    <view class="dim-overlay" v-if="showDimPanel" @click="closeDimPanel">
+      <view class="dim-panel" @click.stop>
+        <view class="dim-header">
+          <text class="dim-title">五维能量</text>
+          <text class="dim-close" @click="closeDimPanel">✕</text>
+        </view>
+        <view class="dim-grid">
+          <view
+            v-for="(dim, idx) in dimensions"
+            :key="idx"
+            class="dim-card"
+            :style="{ backgroundColor: dim.bgColor }"
+            @click="goDimension(dim)"
+          >
+            <text class="dim-icon">{{ dim.icon }}</text>
+            <text class="dim-name">{{ dim.name }}</text>
+            <text class="dim-desc">{{ dim.desc }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
   </view>
   </view>
 </template>
 </template>
 
 
@@ -43,12 +73,12 @@ export default {
   data() {
   data() {
     return {
     return {
       currentIndex: 0,
       currentIndex: 0,
+      showDimPanel: false,
       normalTabs: [
       normalTabs: [
-        { text: '行', pagePath: '/pages/index-home/index', icon: '/static/tab-action.png', selectedIcon: '/static/tab-action-active.png' },
-        { text: '心', pagePath: '/pages/mind/index', icon: '/static/tab-mind.png', selectedIcon: '/static/tab-mind-active.png' },
-        { text: '身', pagePath: '/pages/body/index', icon: '/static/tab-body.png', selectedIcon: '/static/tab-body-active.png' },
-        { text: '智', pagePath: '/pages/wisdom/index', icon: '/static/tab-wisdom.png', selectedIcon: '/static/tab-wisdom-active.png' },
-        { text: '富', pagePath: '/pages/wealth/index', icon: '/static/tab-wealth.png', selectedIcon: '/static/tab-wealth-active.png' }
+        { text: '首页', pagePath: '/pages/index-home/index', icon: '/static/tab-home.png', selectedIcon: '/static/tab-home-active.png' },
+        { text: '健康', pagePath: '/pages/health/index', icon: '/static/tab-health.png', selectedIcon: '/static/tab-health-active.png' },
+        { text: '成长', pagePath: '/pages/growth/index', icon: '/static/tab-growth.png', selectedIcon: '/static/tab-growth-active.png' },
+        { text: '我的', pagePath: '/pages/profile/profile', icon: '/static/tab-profile.png', selectedIcon: '/static/tab-profile-active.png' }
       ],
       ],
       teacherTabs: [
       teacherTabs: [
         { text: '首页', pagePath: '/pages/teacher/index', icon: '/static/tab-action.png', selectedIcon: '/static/tab-action-active.png' },
         { text: '首页', pagePath: '/pages/teacher/index', icon: '/static/tab-action.png', selectedIcon: '/static/tab-action-active.png' },
@@ -56,13 +86,17 @@ export default {
         { text: '团队', pagePath: '/pages/teacher/team', icon: '/static/tab-team.png', selectedIcon: '/static/tab-team-active.png' },
         { text: '团队', pagePath: '/pages/teacher/team', icon: '/static/tab-team.png', selectedIcon: '/static/tab-team-active.png' },
         { text: '成长档案', pagePath: '/pages/growth/index', icon: '/static/tab-growth.png', selectedIcon: '/static/tab-growth-active.png' },
         { text: '成长档案', pagePath: '/pages/growth/index', icon: '/static/tab-growth.png', selectedIcon: '/static/tab-growth-active.png' },
         { text: '我的', pagePath: '/pages/teacher/teacher-profile', icon: '/static/tab-profile.png', selectedIcon: '/static/tab-profile-active.png' }
         { text: '我的', pagePath: '/pages/teacher/teacher-profile', icon: '/static/tab-profile.png', selectedIcon: '/static/tab-profile-active.png' }
+      ],
+      dimensions: [
+        { code: 'action', name: '行', desc: '知行合一', icon: '🌿', color: '#10B981', bgColor: 'rgba(16,185,129,0.15)' },
+        { code: 'mind', name: '心', desc: '大爱传承', icon: '🔥', color: '#FF6B9D', bgColor: 'rgba(255,107,157,0.15)' },
+        { code: 'body', name: '身', desc: '主动健康', icon: '🌏', color: '#FF8C42', bgColor: 'rgba(255,140,66,0.15)' },
+        { code: 'wisdom', name: '智', desc: '赋能未来', icon: '⚡', color: '#6366F1', bgColor: 'rgba(99,102,241,0.15)' },
+        { code: 'wealth', name: '富', desc: '利他创富', icon: '💧', color: '#F59E0B', bgColor: 'rgba(245,158,11,0.15)' }
       ]
       ]
     }
     }
   },
   },
   computed: {
   computed: {
-    isLoggedIn() {
-      return !!uni.getStorageSync('token')
-    },
     currentRole() {
     currentRole() {
       return uni.getStorageSync('currentRole') || 'parent'
       return uni.getStorageSync('currentRole') || 'parent'
     }
     }
@@ -75,30 +109,49 @@ export default {
   },
   },
   methods: {
   methods: {
     updateSelected() {
     updateSelected() {
-      const pages = getCurrentPages()
+      var pages = getCurrentPages()
       if (pages.length) {
       if (pages.length) {
-        const currentPage = pages[pages.length - 1]
-        const currentPath = '/' + currentPage.route
-
-        if (this.currentRole === 'teacher') {
-          const idx = this.teacherTabs.findIndex(t => t.pagePath === currentPath)
-          this.currentIndex = idx >= 0 ? idx : 0
-        } else {
-          const idx = this.normalTabs.findIndex(t => t.pagePath === currentPath)
-          this.currentIndex = idx >= 0 ? idx : 0
-        }
+        var currentPage = pages[pages.length - 1]
+        var currentPath = '/' + currentPage.route
+
+        var tabs = this.currentRole === 'teacher' ? this.teacherTabs : this.normalTabs
+        var idx = tabs.findIndex(function(t) { return t.pagePath === currentPath })
+        this.currentIndex = idx >= 0 ? idx : 0
       }
       }
     },
     },
     switchTab(item, index) {
     switchTab(item, index) {
       this.currentIndex = index
       this.currentIndex = index
-      // 原生TabBar页使用switchTab,非Tab页使用redirectTo(比reLaunch轻量)
-      // 注意:switchTab 只能跳转 pages.json 中 tabBar.list 注册的页面,否则会触发路由系统错误
-      const nativeTabPages = ['/pages/index-home/index', '/pages/mind/index', '/pages/body/index', '/pages/wisdom/index', '/pages/wealth/index']
-      if (nativeTabPages.includes(item.pagePath)) {
+      // TabBar 注册页用 switchTab,非 TabBar 页用 redirectTo
+      var tabBarPages = ['/pages/index-home/index', '/pages/health/index', '/pages/growth/index', '/pages/profile/profile']
+      if (tabBarPages.indexOf(item.pagePath) !== -1) {
         uni.switchTab({ url: item.pagePath })
         uni.switchTab({ url: item.pagePath })
       } else {
       } else {
         uni.redirectTo({ url: item.pagePath })
         uni.redirectTo({ url: item.pagePath })
       }
       }
+    },
+    openDimPanel() {
+      this.showDimPanel = true
+    },
+    closeDimPanel() {
+      this.showDimPanel = false
+    },
+    goDimension(dim) {
+      this.closeDimPanel()
+      var dimMap = {
+        action: '/pages/index-home/index',
+        mind: '/pages/mind-detail/index',
+        body: '/pages/body-detail/index',
+        wisdom: '/pages/wisdom-detail/index',
+        wealth: '/pages/wealth/index'
+      }
+      var url = dimMap[dim.code]
+      if (!url) return
+      var tabBarPages = ['/pages/index-home/index', '/pages/health/index', '/pages/growth/index', '/pages/profile/profile']
+      if (tabBarPages.indexOf(url) !== -1) {
+        uni.switchTab({ url: url })
+      } else {
+        uni.navigateTo({ url: url })
+      }
     }
     }
   }
   }
 }
 }
@@ -119,6 +172,7 @@ export default {
   background: #fff;
   background: #fff;
   padding-bottom: env(safe-area-inset-bottom);
   padding-bottom: env(safe-area-inset-bottom);
   border-top: 1rpx solid #eee;
   border-top: 1rpx solid #eee;
+  position: relative;
 }
 }
 
 
 .tab-item {
 .tab-item {
@@ -127,6 +181,16 @@ export default {
   flex-direction: column;
   flex-direction: column;
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
+  position: relative;
+  z-index: 1;
+}
+
+.tab-item:first-child {
+  margin-right: 40rpx;
+}
+
+.tab-item:nth-child(4) {
+  margin-left: 40rpx;
 }
 }
 
 
 .tab-icon {
 .tab-icon {
@@ -143,4 +207,119 @@ export default {
 .tab-item.active .tab-text {
 .tab-item.active .tab-text {
   color: #F97316;
   color: #F97316;
 }
 }
+
+/* 中间五维启动器按钮 */
+.center-btn {
+  position: absolute;
+  left: 50%;
+  top: -20rpx;
+  transform: translateX(-50%);
+  width: 100rpx;
+  height: 100rpx;
+  z-index: 2;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.center-btn-inner {
+  width: 88rpx;
+  height: 88rpx;
+  border-radius: 50%;
+  background: linear-gradient(135deg, #F97316, #FB923C);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  box-shadow: 0 4rpx 16rpx rgba(249,115,22,0.4);
+}
+
+.center-btn-icon {
+  font-size: 40rpx;
+  line-height: 1;
+}
+
+/* 五维半屏浮层 */
+.dim-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: rgba(0,0,0,0.5);
+  z-index: 1000;
+  display: flex;
+  align-items: flex-end;
+  animation: fadeIn 0.2s ease;
+}
+
+.dim-panel {
+  width: 100%;
+  background: #fff;
+  border-radius: 32rpx 32rpx 0 0;
+  padding: 40rpx 32rpx;
+  padding-bottom: calc(100rpx + env(safe-area-inset-bottom) + 32rpx);
+  animation: slideUp 0.3s ease;
+}
+
+.dim-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  margin-bottom: 32rpx;
+}
+
+.dim-title {
+  font-size: 32rpx;
+  font-weight: 600;
+  color: #333;
+}
+
+.dim-close {
+  font-size: 32rpx;
+  color: #999;
+  padding: 8rpx;
+}
+
+.dim-grid {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 20rpx;
+}
+
+.dim-card {
+  width: calc(33.33% - 14rpx);
+  border-radius: 20rpx;
+  padding: 24rpx 16rpx;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  box-sizing: border-box;
+}
+
+.dim-icon {
+  font-size: 48rpx;
+  margin-bottom: 8rpx;
+}
+
+.dim-name {
+  font-size: 28rpx;
+  font-weight: 600;
+  color: #333;
+  margin-bottom: 4rpx;
+}
+
+.dim-desc {
+  font-size: 20rpx;
+  color: #999;
+}
+
+@keyframes fadeIn {
+  from { opacity: 0; }
+  to { opacity: 1; }
+}
+
+@keyframes slideUp {
+  from { transform: translateY(100%); }
+  to { transform: translateY(0); }
+}
 </style>
 </style>

+ 51 - 45
cfc-frontend/pages.json

@@ -7,27 +7,21 @@
       }
       }
     },
     },
     {
     {
-      "path": "pages/mind/index",
+      "path": "pages/health/index",
       "style": {
       "style": {
-        "navigationBarTitleText": ""
+        "navigationBarTitleText": "健康"
       }
       }
     },
     },
     {
     {
-      "path": "pages/body/index",
+      "path": "pages/growth/index",
       "style": {
       "style": {
-        "navigationBarTitleText": ""
+        "navigationBarTitleText": "成长档案"
       }
       }
     },
     },
     {
     {
-      "path": "pages/wisdom/index",
+      "path": "pages/profile/profile",
       "style": {
       "style": {
-        "navigationBarTitleText": "智"
-      }
-    },
-    {
-      "path": "pages/wealth/index",
-      "style": {
-        "navigationBarTitleText": "富"
+        "navigationBarTitleText": "我的"
       }
       }
     }
     }
   ],
   ],
@@ -386,12 +380,6 @@
     {
     {
       "root": "pages/growth",
       "root": "pages/growth",
       "pages": [
       "pages": [
-        {
-          "path": "index",
-          "style": {
-            "navigationBarTitleText": "成长档案"
-          }
-        },
         {
         {
           "path": "create",
           "path": "create",
           "style": {
           "style": {
@@ -863,6 +851,12 @@
     {
     {
       "root": "pages/body-detail",
       "root": "pages/body-detail",
       "pages": [
       "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "身"
+          }
+        },
         {
         {
           "path": "health-report",
           "path": "health-report",
           "style": {
           "style": {
@@ -905,6 +899,12 @@
     {
     {
       "root": "pages/mind-detail",
       "root": "pages/mind-detail",
       "pages": [
       "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "心"
+          }
+        },
         {
         {
           "path": "articles",
           "path": "articles",
           "style": {
           "style": {
@@ -977,6 +977,12 @@
     {
     {
       "root": "pages/wisdom-detail",
       "root": "pages/wisdom-detail",
       "pages": [
       "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "智"
+          }
+        },
         {
         {
           "path": "cognitive-report",
           "path": "cognitive-report",
           "style": {
           "style": {
@@ -1004,6 +1010,17 @@
         }
         }
       ]
       ]
     },
     },
+    {
+      "root": "pages/wealth",
+      "pages": [
+        {
+          "path": "index",
+          "style": {
+            "navigationBarTitleText": "富"
+          }
+        }
+      ]
+    },
     {
     {
       "root": "pages/action-detail",
       "root": "pages/action-detail",
       "pages": [
       "pages": [
@@ -1373,12 +1390,6 @@
     {
     {
       "root": "pages/profile",
       "root": "pages/profile",
       "pages": [
       "pages": [
-        {
-          "path": "profile",
-          "style": {
-            "navigationBarTitleText": "我的"
-          }
-        },
         {
         {
           "path": "index",
           "path": "index",
           "style": {
           "style": {
@@ -1459,36 +1470,31 @@
     "selectedColor": "#F97316",
     "selectedColor": "#F97316",
     "borderStyle": "black",
     "borderStyle": "black",
     "backgroundColor": "#FFFFFF",
     "backgroundColor": "#FFFFFF",
+    "custom": true,
     "list": [
     "list": [
       {
       {
         "pagePath": "pages/index-home/index",
         "pagePath": "pages/index-home/index",
-        "text": "行",
-        "iconPath": "static/tab-action.png",
-        "selectedIconPath": "static/tab-action-active.png"
-      },
-      {
-        "pagePath": "pages/mind/index",
-        "text": "心",
-        "iconPath": "/static/tab-mind.png",
-        "selectedIconPath": "/static/tab-mind-active.png"
+        "text": "首页",
+        "iconPath": "static/tab-home.png",
+        "selectedIconPath": "static/tab-home-active.png"
       },
       },
       {
       {
-        "pagePath": "pages/body/index",
-        "text": "",
-        "iconPath": "/static/tab-body.png",
-        "selectedIconPath": "/static/tab-body-active.png"
+        "pagePath": "pages/health/index",
+        "text": "健康",
+        "iconPath": "static/tab-health.png",
+        "selectedIconPath": "static/tab-health-active.png"
       },
       },
       {
       {
-        "pagePath": "pages/wisdom/index",
-        "text": "",
-        "iconPath": "static/tab-wisdom.png",
-        "selectedIconPath": "static/tab-wisdom-active.png"
+        "pagePath": "pages/growth/index",
+        "text": "成长",
+        "iconPath": "static/tab-growth.png",
+        "selectedIconPath": "static/tab-growth-active.png"
       },
       },
       {
       {
-        "pagePath": "pages/wealth/index",
-        "text": "",
-        "iconPath": "static/tab-wealth.png",
-        "selectedIconPath": "static/tab-wealth-active.png"
+        "pagePath": "pages/profile/profile",
+        "text": "我的",
+        "iconPath": "static/tab-profile.png",
+        "selectedIconPath": "static/tab-profile-active.png"
       }
       }
     ]
     ]
   },
   },

+ 0 - 0
cfc-frontend/pages/body/index.vue → cfc-frontend/pages/body-detail/index.vue


+ 388 - 94
cfc-frontend/pages/growth/index.vue

@@ -1,93 +1,186 @@
 <template>
 <template>
   <view class="container">
   <view class="container">
-    <view class="header">
-      <text class="title">成长档案</text>
-      <text class="subtitle">记录孩子的每一次成长</text>
+    <PageBanner theme="action" tagline="方案 · 执行 · 成长" quote="知行合一,每天进步一点点" />
+
+    <!-- 无家庭状态 -->
+    <view v-if="!hasFamily">
+      <FamilyEmptyState type="card" />
     </view>
     </view>
 
 
-    <view v-if="hasFamily">
-    <view class="children-list" v-if="!memberId">
-      <view class="child-card" v-for="child in children" :key="child.id" @click="viewChildRecords(child)">
-        <view class="child-info">
-          <text class="child-name">{{ child.nickname || child.name }}</text>
-          <text class="child-age" v-if="child.age">年龄:{{ child.age }}岁</text>
-        </view>
-        <view class="arrow">
-          <text class="arrow-icon">›</text>
+    <view v-else>
+      <!-- 家庭成员选择条 -->
+      <FamilyMemberStrip />
+
+      <!-- 今日待办卡片 -->
+      <view class="section">
+        <view class="todo-card">
+          <view class="todo-header">
+            <text class="todo-title">今日执行概览</text>
+          </view>
+          <view class="todo-grid">
+            <view class="todo-item">
+              <text class="todo-icon">✅</text>
+              <text class="todo-label">今日打卡</text>
+              <text class="todo-value" :class="todayCheckedIn ? 'text-success' : 'text-muted'">{{ todayCheckedIn ? '已完成' : '未完成' }}</text>
+            </view>
+            <view class="todo-item">
+              <text class="todo-icon">📋</text>
+              <text class="todo-label">待完成任务</text>
+              <text class="todo-value">{{ pendingTasks }} 项</text>
+            </view>
+            <view class="todo-item">
+              <text class="todo-icon">🔥</text>
+              <text class="todo-label">进行中挑战</text>
+              <text class="todo-value">{{ activeChallenges }} 个</text>
+            </view>
+            <view class="todo-item">
+              <text class="todo-icon">🍽️</text>
+              <text class="todo-label">今日饮食</text>
+              <text class="todo-value">{{ mealCount }} 餐</text>
+            </view>
+          </view>
         </view>
         </view>
       </view>
       </view>
-      <view class="empty-state" v-if="children.length === 0 && !loading">
-        <text class="empty-icon">📋</text>
-        <text class="empty-text">还没有添加孩子</text>
-        <text class="empty-hint">请先在"我的"页面添加孩子</text>
-      </view>
-    </view>
 
 
-    <view class="records-section" v-if="selectedChild">
-      <view class="section-header">
-        <text class="section-title">{{ selectedChildName }} 的成长档案</text>
+      <!-- 功能入口网格 -->
+      <view class="section">
+        <view class="grid-3col">
+          <view class="grid-item" @click="goPage('/pages/health/daily-checkin')">
+            <view class="grid-icon" style="background:rgba(16,185,129,0.15)"><text>✅</text></view>
+            <text class="grid-label">每日打卡</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/tasks/daily-tasks')">
+            <view class="grid-icon" style="background:rgba(99,102,241,0.15)"><text>📋</text></view>
+            <text class="grid-label">日常任务</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/challenge-manage')">
+            <view class="grid-icon" style="background:rgba(239,68,68,0.15)"><text>🔥</text></view>
+            <text class="grid-label">家庭挑战</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/diet/index')">
+            <view class="grid-icon" style="background:rgba(245,158,11,0.15)"><text>🍽️</text></view>
+            <text class="grid-label">饮食记录</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/exercise-index')">
+            <view class="grid-icon" style="background:rgba(34,197,94,0.15)"><text>🏃</text></view>
+            <text class="grid-label">运动记录</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/sleep-index')">
+            <view class="grid-icon" style="background:rgba(139,92,246,0.15)"><text>😴</text></view>
+            <text class="grid-label">作息记录</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/growth/detail')">
+            <view class="grid-icon" style="background:rgba(249,115,22,0.15)"><text>📈</text></view>
+            <text class="grid-label">成长档案</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/meditation-index')">
+            <view class="grid-icon" style="background:rgba(255,107,157,0.15)"><text>🧘</text></view>
+            <text class="grid-label">冥想练习</text>
+          </view>
+        </view>
       </view>
       </view>
 
 
-      <view class="filter-tabs">
-        <view class="filter-tab" :class="activeFilter === 'all' ? 'tab-active' : ''" @click="setFilter('all')">
-          <text class="tab-text" :class="activeFilter === 'all' ? 'tab-active-text' : ''">全部</text>
-        </view>
-        <view class="filter-tab" :class="activeFilter === 'manual' ? 'tab-active' : ''" @click="setFilter('manual')">
-          <text class="tab-text" :class="activeFilter === 'manual' ? 'tab-active-text' : ''">手动</text>
-        </view>
-        <view class="filter-tab" :class="activeFilter === 'assessment' ? 'tab-active' : ''" @click="setFilter('assessment')">
-          <text class="tab-text" :class="activeFilter === 'assessment' ? 'tab-active-text' : ''">测评</text>
-        </view>
-        <view class="filter-tab" :class="activeFilter === 'upload' ? 'tab-active' : ''" @click="setFilter('upload')">
-          <text class="tab-text" :class="activeFilter === 'upload' ? 'tab-active-text' : ''">上传</text>
+      <!-- 活动推荐(从首页移过来) -->
+      <DimensionActivities
+        dimensionCode="action"
+        :activities="dimensionActivities"
+        :isLoggedIn="true"
+        @activityClick="goActivityDetail"
+        @moreActivities="goMoreActivities" />
+
+      <!-- 成长档案(原有内容) -->
+      <view class="section">
+        <view class="section-header">
+          <text class="section-title">成长档案</text>
+          <text class="section-more" v-if="records.length > 0" @click="viewAllRecords">查看全部 ›</text>
         </view>
         </view>
-        <view class="filter-tab" :class="activeFilter === 'supplement' ? 'tab-active' : ''" @click="setFilter('supplement')">
-          <text class="tab-text" :class="activeFilter === 'supplement' ? 'tab-active-text' : ''">补充</text>
+
+        <!-- 孩子选择 -->
+        <view class="children-list" v-if="!selectedChild">
+          <view class="child-card" v-for="child in children" :key="child.id" @click="viewChildRecords(child)">
+            <view class="child-info">
+              <text class="child-name">{{ child.nickname || child.name }}</text>
+              <text class="child-age" v-if="child.age">年龄:{{ child.age }}岁</text>
+            </view>
+            <view class="arrow">
+              <text class="arrow-icon">›</text>
+            </view>
+          </view>
+          <view class="empty-state" v-if="children.length === 0 && !loading">
+            <text class="empty-icon">📋</text>
+            <text class="empty-text">还没有添加孩子</text>
+            <text class="empty-hint">请先在"我的"页面添加孩子</text>
+          </view>
         </view>
         </view>
-      </view>
 
 
-      <view class="record-card" v-for="record in filteredRecords" :key="record.id" @click="viewDetail(record)">
-        <view class="record-header">
-          <text class="record-title">{{ record.recordTitle || '成长档案' }}</text>
-          <view class="record-badges">
-            <text class="record-source-tag" v-if="record.sourceType">{{ sourceTypeLabel(record.sourceType) }}</text>
-            <text class="record-tag" :class="record.status === 'completed' ? 'tag-completed' : 'tag-pending'">
-              {{ record.status === 'completed' ? '已完成' : '待同步' }}
-            </text>
+        <!-- 档案记录 -->
+        <view class="records-section" v-if="selectedChild">
+          <view class="filter-tabs">
+            <view class="filter-tab" :class="activeFilter === 'all' ? 'tab-active' : ''" @click="setFilter('all')">
+              <text class="tab-text" :class="activeFilter === 'all' ? 'tab-active-text' : ''">全部</text>
+            </view>
+            <view class="filter-tab" :class="activeFilter === 'manual' ? 'tab-active' : ''" @click="setFilter('manual')">
+              <text class="tab-text" :class="activeFilter === 'manual' ? 'tab-active-text' : ''">手动</text>
+            </view>
+            <view class="filter-tab" :class="activeFilter === 'assessment' ? 'tab-active' : ''" @click="setFilter('assessment')">
+              <text class="tab-text" :class="activeFilter === 'assessment' ? 'tab-active-text' : ''">测评</text>
+            </view>
+            <view class="filter-tab" :class="activeFilter === 'upload' ? 'tab-active' : ''" @click="setFilter('upload')">
+              <text class="tab-text" :class="activeFilter === 'upload' ? 'tab-active-text' : ''">上传</text>
+            </view>
+            <view class="filter-tab" :class="activeFilter === 'supplement' ? 'tab-active' : ''" @click="setFilter('supplement')">
+              <text class="tab-text" :class="activeFilter === 'supplement' ? 'tab-active-text' : ''">补充</text>
+            </view>
+          </view>
+
+          <view class="record-card" v-for="record in filteredRecords" :key="record.id" @click="viewDetail(record)">
+            <view class="record-header">
+              <text class="record-title">{{ record.recordTitle || '成长档案' }}</text>
+              <view class="record-badges">
+                <text class="record-source-tag" v-if="record.sourceType">{{ sourceTypeLabel(record.sourceType) }}</text>
+                <text class="record-tag" :class="record.status === 'completed' ? 'tag-completed' : 'tag-pending'">
+                  {{ record.status === 'completed' ? '已完成' : '待同步' }}
+                </text>
+              </view>
+            </view>
+            <view class="record-scores" v-if="record.overallScore || record.reportDate">
+              <text class="score-item" v-if="record.overallScore">得分: {{ record.overallScore }}</text>
+              <text class="score-item" v-if="record.reportDate">{{ record.reportDate }}</text>
+              <text class="score-item" v-if="!record.reportDate && record.createdAt">{{ formatDate(record.createdAt) }}</text>
+            </view>
+          </view>
+          <view class="empty-state" v-if="filteredRecords.length === 0 && records.length > 0">
+            <text class="empty-text">该类型暂无记录</text>
+          </view>
+          <view class="empty-state" v-if="records.length === 0">
+            <text class="empty-text">暂无成长档案记录</text>
           </view>
           </view>
         </view>
         </view>
-        <view class="record-scores" v-if="record.overallScore || record.reportDate">
-          <text class="score-item" v-if="record.overallScore">得分: {{ record.overallScore }}</text>
-          <text class="score-item" v-if="record.reportDate">{{ record.reportDate }}</text>
-          <text class="score-item" v-if="!record.reportDate && record.createdAt">{{ formatDate(record.createdAt) }}</text>
+
+        <!-- 创建按钮 -->
+        <view class="footer-btn" v-if="selectedChild">
+          <button class="btn-create" @click="createRecord">创建成长档案</button>
+        </view>
+        <view class="footer-btn" v-if="!selectedChild && children.length > 0">
+          <button class="btn-invite-guide" open-type="share" @click="prepareGuideInvite">邀请成长规划师</button>
         </view>
         </view>
       </view>
       </view>
-      <view class="empty-state" v-if="filteredRecords.length === 0 && records.length > 0">
-        <text class="empty-text">该类型暂无记录</text>
-      </view>
-      <view class="empty-state" v-if="records.length === 0">
-        <text class="empty-text">暂无成长档案记录</text>
-      </view>
-    </view>
-
-    <view class="footer-btn" v-if="hasFamily && selectedChild">
-      <button class="btn-create" @click="createRecord">创建成长档案</button>
-    </view>
-    <view class="footer-btn" v-if="hasFamily && !selectedChild && children.length > 0">
-      <button class="btn-invite-guide" open-type="share" @click="prepareGuideInvite">邀请成长规划师</button>
-    </view>
-    </view>
-
-    <view v-else>
-      <FamilyEmptyState type="card" />
     </view>
     </view>
   </view>
   </view>
 </template>
 </template>
 
 
 <script>
 <script>
-import { getChildren, getChildRecords } from '../../utils/api.js'
+import PageBanner from '../../components/PageBanner.vue'
+import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import DimensionActivities from '../../components/DimensionActivities.vue'
+import { getChildren, getChildRecords, getActivityList } from '../../utils/api.js'
 
 
 export default {
 export default {
+  components: {
+    PageBanner,
+    FamilyMemberStrip,
+    DimensionActivities
+  },
   data() {
   data() {
     return {
     return {
       loading: false,
       loading: false,
@@ -97,7 +190,12 @@ export default {
       records: [],
       records: [],
       memberId: '',
       memberId: '',
       activeFilter: 'all',
       activeFilter: 'all',
-      shareData: null
+      shareData: null,
+      todayCheckedIn: false,
+      pendingTasks: 0,
+      activeChallenges: 0,
+      mealCount: 0,
+      dimensionActivities: []
     }
     }
   },
   },
   computed: {
   computed: {
@@ -123,15 +221,82 @@ export default {
   onLoad(options) {
   onLoad(options) {
     this.memberId = options.memberId || ''
     this.memberId = options.memberId || ''
     this.loadChildren()
     this.loadChildren()
+    this.loadTodoData()
+    this.loadDimensionActivities()
   },
   },
   methods: {
   methods: {
-    sourceTypeLabel(type) {
+    goPage: function(url) {
+      uni.navigateTo({ url: url })
+    },
+    loadDimensionActivities: function() {
+      var self = this
+      getActivityList({ dimensionCode: '', page: 1, size: 3 }).then(function(res) {
+        if (res && res.code === 200) {
+          var rawData = res.data && (Array.isArray(res.data) ? res.data : (res.data.data || res.data.records || []))
+          self.dimensionActivities = Array.isArray(rawData) ? rawData.slice(0, 3) : []
+        }
+      }).catch(function() { self.dimensionActivities = [] })
+    },
+    goActivityDetail: function(act) {
+      uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
+    },
+    goMoreActivities: function() {
+      uni.navigateTo({ url: '/pages/activity/index' })
+    },
+    viewAllRecords: function() {
+      // 滚动到档案区域或展开全部
+      if (this.selectedChild) {
+        uni.navigateTo({ url: '/pages/growth/detail?memberId=' + this.selectedChild.id })
+      }
+    },
+    sourceTypeLabel: function(type) {
       var map = { 'manual': '手动', 'assessment': '测评', 'upload': '上传', 'supplement': '补充' }
       var map = { 'manual': '手动', 'assessment': '测评', 'upload': '上传', 'supplement': '补充' }
       return map[type] || type
       return map[type] || type
     },
     },
-    setFilter(type) {
+    setFilter: function(type) {
       this.activeFilter = type
       this.activeFilter = type
     },
     },
+    loadTodoData: function() {
+      var self = this
+      try {
+        var api = require('../../utils/api.js')
+        // 检查打卡状态
+        if (api.getCheckinStats) {
+          api.getCheckinStats().then(function(res) {
+            if (res.code === 200 && res.data) {
+              self.todayCheckedIn = !!(res.data.todayCheckedIn || res.data.checkedIn || (res.data.today && res.data.today.checkedIn))
+            }
+          }).catch(function() {})
+        }
+        // 待完成任务数
+        if (api.getTodayTasks && self.children && self.children.length > 0) {
+          var childId = self.selectedChild ? self.selectedChild.id : self.children[0].id
+          api.getTodayTasks(childId).then(function(res) {
+            if (res.code === 200) {
+              self.pendingTasks = (res.data && res.data.length) || 0
+            }
+          }).catch(function() {})
+        }
+        // 进行中挑战
+        if (api.getChallengeList) {
+          api.getChallengeList({ status: 'active' }).then(function(res) {
+            if (res.code === 200) {
+              self.activeChallenges = (res.data && res.data.length) || 0
+            }
+          }).catch(function() {})
+        }
+        // 饮食记录数
+        if (api.getMealLogs) {
+          api.getMealLogs({ date: new Date().toISOString().slice(0, 10) }).then(function(res) {
+            if (res.code === 200) {
+              self.mealCount = (res.data && res.data.length) || 0
+            }
+          }).catch(function() {})
+        }
+      } catch (e) {
+        console.error('加载待办数据失败', e)
+      }
+    },
     async loadChildren() {
     async loadChildren() {
       this.loading = true
       this.loading = true
       try {
       try {
@@ -166,23 +331,23 @@ export default {
         this.loading = false
         this.loading = false
       }
       }
     },
     },
-    viewDetail(record) {
+    viewDetail: function(record) {
       uni.navigateTo({
       uni.navigateTo({
         url: '/pages/growth/detail?recordId=' + record.id
         url: '/pages/growth/detail?recordId=' + record.id
       })
       })
     },
     },
-    createRecord() {
+    createRecord: function() {
       uni.navigateTo({
       uni.navigateTo({
         url: '/pages/growth/create?memberId=' + this.selectedChild.id + '&childName=' + encodeURIComponent(this.selectedChildName)
         url: '/pages/growth/create?memberId=' + this.selectedChild.id + '&childName=' + encodeURIComponent(this.selectedChildName)
       })
       })
     },
     },
-    formatDate(date) {
+    formatDate: function(date) {
       if (!date) return ''
       if (!date) return ''
       return date.substring(0, 10)
       return date.substring(0, 10)
     },
     },
     async prepareGuideInvite() {
     async prepareGuideInvite() {
       try {
       try {
-        var ui = uni.getStorageSync('userInfo') || {};
+        var ui = uni.getStorageSync('userInfo') || {}
         if (!ui.familyId) {
         if (!ui.familyId) {
           uni.showToast({ title: '请先创建家庭', icon: 'none' })
           uni.showToast({ title: '请先创建家庭', icon: 'none' })
           return
           return
@@ -206,25 +371,130 @@ export default {
 
 
 <style scoped>
 <style scoped>
 .container {
 .container {
-  padding: 30rpx;
   background: #F8F8F8;
   background: #F8F8F8;
   min-height: 100vh;
   min-height: 100vh;
+  padding-bottom: 120rpx;
 }
 }
-.header {
-  text-align: center;
-  padding: 40rpx 0;
+
+.section {
+  margin: 20rpx 24rpx;
 }
 }
-.title {
-  font-size: 40rpx;
+
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 16rpx;
+}
+
+.section-title {
+  font-size: 30rpx;
   font-weight: bold;
   font-weight: bold;
   color: #333;
   color: #333;
-  display: block;
-  margin-bottom: 10rpx;
 }
 }
-.subtitle {
-  font-size: 26rpx;
+
+.section-more {
+  font-size: 24rpx;
+  color: #F97316;
+}
+
+/* 今日待办卡片 */
+.todo-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+
+.todo-header {
+  margin-bottom: 16rpx;
+}
+
+.todo-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.todo-grid {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.todo-item {
+  width: 50%;
+  display: flex;
+  align-items: center;
+  padding: 10rpx 0;
+  flex-wrap: wrap;
+}
+
+.todo-icon {
+  font-size: 28rpx;
+  margin-right: 8rpx;
+}
+
+.todo-label {
+  font-size: 24rpx;
   color: #999;
   color: #999;
+  margin-right: 8rpx;
+}
+
+.todo-value {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+}
+
+.text-success {
+  color: #10B981;
+}
+
+.text-muted {
+  color: #ccc;
+}
+
+/* 功能入口网格 3列 */
+.grid-3col {
+  display: flex;
+  flex-wrap: wrap;
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 8rpx 0;
+}
+
+.grid-item {
+  width: 33.33%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 20rpx 0;
+}
+
+.grid-icon {
+  width: 72rpx;
+  height: 72rpx;
+  border-radius: 18rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8rpx;
+}
+
+.grid-icon text {
+  font-size: 32rpx;
 }
 }
+
+.grid-item .grid-label {
+  font-size: 24rpx;
+  color: #666;
+}
+
+/* 孩子选择 */
+.children-list {
+  margin-top: 8rpx;
+}
+
 .child-card {
 .child-card {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
@@ -233,44 +503,45 @@ export default {
   padding: 30rpx;
   padding: 30rpx;
   margin-bottom: 20rpx;
   margin-bottom: 20rpx;
 }
 }
+
 .child-info {
 .child-info {
   flex: 1;
   flex: 1;
 }
 }
+
 .child-name {
 .child-name {
   font-size: 32rpx;
   font-size: 32rpx;
   font-weight: bold;
   font-weight: bold;
   color: #333;
   color: #333;
   display: block;
   display: block;
 }
 }
+
 .child-age {
 .child-age {
   font-size: 24rpx;
   font-size: 24rpx;
   color: #999;
   color: #999;
   margin-top: 6rpx;
   margin-top: 6rpx;
   display: block;
   display: block;
 }
 }
+
 .arrow {
 .arrow {
   width: 40rpx;
   width: 40rpx;
 }
 }
+
 .arrow-icon {
 .arrow-icon {
   font-size: 40rpx;
   font-size: 40rpx;
   color: #ccc;
   color: #ccc;
 }
 }
+
+/* 记录筛选 */
 .records-section {
 .records-section {
-  margin-top: 20rpx;
-}
-.section-header {
-  margin-bottom: 20rpx;
-}
-.section-title {
-  font-size: 30rpx;
-  font-weight: bold;
-  color: #333;
+  margin-top: 16rpx;
 }
 }
+
 .filter-tabs {
 .filter-tabs {
   display: flex;
   display: flex;
   gap: 16rpx;
   gap: 16rpx;
   margin-bottom: 20rpx;
   margin-bottom: 20rpx;
 }
 }
+
 .filter-tab {
 .filter-tab {
   flex: 1;
   flex: 1;
   text-align: center;
   text-align: center;
@@ -279,40 +550,49 @@ export default {
   background: #fff;
   background: #fff;
   border: 1rpx solid #E0E0E0;
   border: 1rpx solid #E0E0E0;
 }
 }
+
 .tab-active {
 .tab-active {
   background: #667eea;
   background: #667eea;
   border-color: #667eea;
   border-color: #667eea;
 }
 }
+
 .tab-text {
 .tab-text {
   font-size: 24rpx;
   font-size: 24rpx;
   color: #666;
   color: #666;
 }
 }
+
 .tab-active-text {
 .tab-active-text {
   color: #fff;
   color: #fff;
 }
 }
+
+/* 记录卡片 */
 .record-card {
 .record-card {
   background: #fff;
   background: #fff;
   border-radius: 16rpx;
   border-radius: 16rpx;
   padding: 24rpx;
   padding: 24rpx;
   margin-bottom: 16rpx;
   margin-bottom: 16rpx;
 }
 }
+
 .record-header {
 .record-header {
   display: flex;
   display: flex;
   justify-content: space-between;
   justify-content: space-between;
   align-items: center;
   align-items: center;
   margin-bottom: 12rpx;
   margin-bottom: 12rpx;
 }
 }
+
 .record-title {
 .record-title {
   font-size: 28rpx;
   font-size: 28rpx;
   color: #333;
   color: #333;
   font-weight: bold;
   font-weight: bold;
   flex: 1;
   flex: 1;
 }
 }
+
 .record-badges {
 .record-badges {
   display: flex;
   display: flex;
   gap: 8rpx;
   gap: 8rpx;
   flex-shrink: 0;
   flex-shrink: 0;
 }
 }
+
 .record-source-tag {
 .record-source-tag {
   font-size: 20rpx;
   font-size: 20rpx;
   padding: 4rpx 10rpx;
   padding: 4rpx 10rpx;
@@ -320,50 +600,63 @@ export default {
   background: #f0f0ff;
   background: #f0f0ff;
   color: #667eea;
   color: #667eea;
 }
 }
+
 .record-tag {
 .record-tag {
   font-size: 22rpx;
   font-size: 22rpx;
   padding: 4rpx 12rpx;
   padding: 4rpx 12rpx;
   border-radius: 20rpx;
   border-radius: 20rpx;
 }
 }
+
 .tag-completed {
 .tag-completed {
   background: #e8f5e9;
   background: #e8f5e9;
   color: #4caf50;
   color: #4caf50;
 }
 }
+
 .tag-pending {
 .tag-pending {
   background: #fff3e0;
   background: #fff3e0;
   color: #ff9800;
   color: #ff9800;
 }
 }
+
 .record-scores {
 .record-scores {
   display: flex;
   display: flex;
   gap: 20rpx;
   gap: 20rpx;
 }
 }
+
 .score-item {
 .score-item {
   font-size: 24rpx;
   font-size: 24rpx;
   color: #666;
   color: #666;
 }
 }
+
+/* 空状态 */
 .empty-state {
 .empty-state {
   text-align: center;
   text-align: center;
   padding: 60rpx 0;
   padding: 60rpx 0;
 }
 }
+
 .empty-icon {
 .empty-icon {
   font-size: 80rpx;
   font-size: 80rpx;
   display: block;
   display: block;
   margin-bottom: 20rpx;
   margin-bottom: 20rpx;
 }
 }
+
 .empty-text {
 .empty-text {
   font-size: 28rpx;
   font-size: 28rpx;
   color: #999;
   color: #999;
   display: block;
   display: block;
 }
 }
+
 .empty-hint {
 .empty-hint {
   font-size: 24rpx;
   font-size: 24rpx;
   color: #ccc;
   color: #ccc;
   margin-top: 10rpx;
   margin-top: 10rpx;
   display: block;
   display: block;
 }
 }
+
+/* 按钮 */
 .footer-btn {
 .footer-btn {
   padding: 30rpx 0;
   padding: 30rpx 0;
 }
 }
+
 .btn-create {
 .btn-create {
   width: 100%;
   width: 100%;
   height: 88rpx;
   height: 88rpx;
@@ -375,6 +668,7 @@ export default {
   font-weight: bold;
   font-weight: bold;
   text-align: center;
   text-align: center;
 }
 }
+
 .btn-invite-guide {
 .btn-invite-guide {
   width: 100%;
   width: 100%;
   height: 88rpx;
   height: 88rpx;
@@ -386,4 +680,4 @@ export default {
   font-weight: bold;
   font-weight: bold;
   text-align: center;
   text-align: center;
 }
 }
-</style>
+</style>

+ 400 - 0
cfc-frontend/pages/health/index.vue

@@ -0,0 +1,400 @@
+<template>
+  <view class="container">
+    <!-- 未登录态 -->
+    <view v-if="!isLoggedIn">
+      <PageBanner theme="body" tagline="科学管理全家健康" quote="身体健康是幸福的基础" />
+      <LoginGuideCard
+        title="登录后查看全家健康数据"
+        :features="[
+          '申请专业测评',
+          '查看健康报告和诊断结果',
+          '了解家人健康状态'
+        ]"
+      />
+    </view>
+
+    <!-- 登录态 -->
+    <view v-else>
+      <!-- 品牌头部 -->
+      <PageBanner theme="body" tagline="科学测评 · 精准诊断" quote="了解是改善的第一步" />
+
+      <!-- 家庭成员选择条 -->
+      <FamilyMemberStrip />
+
+      <!-- 健康概览卡片 -->
+      <view class="section">
+        <view class="overview-card">
+          <view class="overview-item">
+            <text class="overview-label">健康综合评分</text>
+            <text class="overview-value">{{ healthScore || '--' }}</text>
+            <text class="overview-unit">分</text>
+          </view>
+          <view class="overview-divider"></view>
+          <view class="overview-grid">
+            <view class="overview-grid-item">
+              <text class="grid-value">{{ pendingReports }}</text>
+              <text class="grid-label">待处理报告</text>
+            </view>
+            <view class="overview-grid-item">
+              <text class="grid-value">{{ lastAssessmentDate }}</text>
+              <text class="grid-label">上次测评</text>
+            </view>
+            <view class="overview-grid-item">
+              <text class="grid-value">{{ streakDays }}</text>
+              <text class="grid-label">连续打卡</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 功能入口网格 -->
+      <view class="section">
+        <view class="grid-3col">
+          <view class="grid-item" @click="goPage('/pages/assessment/purchase')">
+            <view class="grid-icon" style="background:rgba(16,185,129,0.15)"><text>📋</text></view>
+            <text class="grid-label">申请测评</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/report-list')">
+            <view class="grid-icon" style="background:rgba(99,102,241,0.15)"><text>📊</text></view>
+            <text class="grid-label">健康报告</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/report-survey')">
+            <view class="grid-icon" style="background:rgba(245,158,11,0.15)"><text>📝</text></view>
+            <text class="grid-label">健康调研</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/physical-exam-detail')">
+            <view class="grid-icon" style="background:rgba(239,68,68,0.15)"><text>🔬</text></view>
+            <text class="grid-label">体检报告</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/gut-index')">
+            <view class="grid-icon" style="background:rgba(139,92,246,0.15)"><text>🧬</text></view>
+            <text class="grid-label">肠道菌群</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/health/daily-checkin')">
+            <view class="grid-icon" style="background:rgba(249,115,22,0.15)"><text>🌡️</text></view>
+            <text class="grid-label">体征记录</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/assessment/purchase')">
+            <view class="grid-icon" style="background:rgba(16,185,129,0.15)"><text>📐</text></view>
+            <text class="grid-label">五维测评</text>
+          </view>
+          <view class="grid-item" @click="goPage('/pages/mind-detail/emotion-report')">
+            <view class="grid-icon" style="background:rgba(255,107,157,0.15)"><text>🧠</text></view>
+            <text class="grid-label">心智评估</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 最近报告 -->
+      <view class="section" v-if="reports.length > 0">
+        <view class="section-header">
+          <text class="section-title">最近报告</text>
+          <text class="section-more" @click="goPage('/pages/health/report-list')">查看全部 ›</text>
+        </view>
+        <view
+          class="report-card"
+          v-for="(report, idx) in reports.slice(0, 3)"
+          :key="idx"
+          @click="goReportDetail(report)"
+        >
+          <text class="report-icon">📄</text>
+          <view class="report-info">
+            <text class="report-name">{{ report.title || '健康报告' }}</text>
+            <text class="report-status">{{ report.status === 'resolved' ? '已解析' : '待确认' }}</text>
+          </view>
+          <text class="report-arrow">›</text>
+        </view>
+      </view>
+
+      <!-- 诊断建议 -->
+      <view class="section" v-if="diagnosisSuggestion">
+        <view class="diagnosis-card">
+          <text class="diagnosis-title">💡 诊断建议</text>
+          <text class="diagnosis-text">{{ diagnosisSuggestion }}</text>
+        </view>
+      </view>
+
+      <!-- 健康科普推荐 -->
+      <ArticleBookshelf
+        :articles="healthArticles"
+        :isLoggedIn="true"
+        @articleClick="goArticleDetail"
+        @moreArticles="goMoreArticles" />
+    </view>
+  </view>
+</template>
+
+<script>
+import PageBanner from '../../components/PageBanner.vue'
+import LoginGuideCard from '../../components/LoginGuideCard.vue'
+import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
+import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
+
+export default {
+  components: {
+    PageBanner,
+    LoginGuideCard,
+    FamilyMemberStrip,
+    ArticleBookshelf
+  },
+  data() {
+    return {
+      isLoggedIn: false,
+      healthScore: null,
+      pendingReports: 0,
+      lastAssessmentDate: '--',
+      streakDays: 0,
+      reports: [],
+      diagnosisSuggestion: '',
+      healthArticles: []
+    }
+  },
+  onShow() {
+    this.isLoggedIn = !!uni.getStorageSync('token')
+    if (this.isLoggedIn) {
+      this.loadHealthData()
+    }
+  },
+  methods: {
+    goPage: function(url) {
+      uni.navigateTo({ url: url })
+    },
+    goReportDetail: function(report) {
+      if (report.id) {
+        uni.navigateTo({ url: '/pages/health/report-list?reportId=' + report.id })
+      } else {
+        uni.navigateTo({ url: '/pages/health/report-list' })
+      }
+    },
+    loadHealthData: function() {
+      var self = this
+      try {
+        var api = require('../../utils/api.js')
+        // 加载健康报告列表
+        api.getReportList().then(function(res) {
+          if (res.code === 200 && res.data) {
+            var list = Array.isArray(res.data) ? res.data : (res.data.list || [])
+            self.reports = list
+            self.pendingReports = list.filter(function(r) { return r.status !== 'resolved' }).length
+            if (list.length > 0) {
+              self.lastAssessmentDate = list[0].reportDate || '--'
+            }
+          }
+        }).catch(function() {})
+        // 加载健康评分
+        api.getHealthScoreInfo().then(function(res) {
+          if (res.code === 200 && res.data) {
+            self.healthScore = res.data.score || res.data.healthScore || null
+          }
+        }).catch(function() {})
+        // 加载打卡数据
+        api.getHealthStreak().then(function(res) {
+          if (res.code === 200 && res.data) {
+            self.streakDays = res.data.streakDays || res.data.days || res.data.currentStreak || 0
+          }
+        }).catch(function() {})
+        // 加载健康科普文章
+        api.getFeaturedArticles({ size: 5, dimensionCode: 'body' }).then(function(res) {
+          if (res.code === 200 && res.data) {
+            var list = Array.isArray(res.data) ? res.data : (res.data.records || [])
+            self.healthArticles = list.slice(0, 5) || []
+          }
+        }).catch(function() {})
+      } catch (e) {
+        console.error('加载健康数据失败', e)
+      }
+    },
+    goArticleDetail: function(article) {
+      var id = article.id || article
+      uni.navigateTo({ url: '/pages/article-center/article-detail?id=' + id })
+    },
+    goMoreArticles: function() {
+      uni.navigateTo({ url: '/pages/article-center/index?dimension=body' })
+    }
+  }
+}
+</script>
+
+<style scoped>
+.container {
+  background: #F8F8F8;
+  min-height: 100vh;
+  padding-bottom: 120rpx;
+}
+
+.section {
+  margin: 20rpx 24rpx;
+}
+
+.section-header {
+  display: flex;
+  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: #F97316;
+}
+
+/* 健康概览卡片 */
+.overview-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 28rpx;
+  box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
+}
+
+.overview-item {
+  display: flex;
+  align-items: baseline;
+  justify-content: center;
+  margin-bottom: 20rpx;
+}
+
+.overview-label {
+  font-size: 24rpx;
+  color: #999;
+  margin-right: 12rpx;
+}
+
+.overview-value {
+  font-size: 56rpx;
+  font-weight: bold;
+  color: #F97316;
+}
+
+.overview-unit {
+  font-size: 24rpx;
+  color: #999;
+  margin-left: 4rpx;
+}
+
+.overview-divider {
+  height: 1rpx;
+  background: #f0f0f0;
+  margin: 16rpx 0;
+}
+
+.overview-grid {
+  display: flex;
+  justify-content: space-around;
+}
+
+.overview-grid-item {
+  text-align: center;
+  flex: 1;
+}
+
+.grid-value {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+}
+
+.grid-label {
+  font-size: 22rpx;
+  color: #999;
+  margin-top: 4rpx;
+  display: block;
+}
+
+/* 功能入口网格 3列 */
+.grid-3col {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+.grid-item {
+  width: 33.33%;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 24rpx 0;
+}
+
+.grid-icon {
+  width: 80rpx;
+  height: 80rpx;
+  border-radius: 20rpx;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  margin-bottom: 8rpx;
+}
+
+.grid-icon text {
+  font-size: 36rpx;
+}
+
+.grid-item .grid-label {
+  font-size: 24rpx;
+  color: #666;
+}
+
+/* 最近报告 */
+.report-card {
+  display: flex;
+  align-items: center;
+  background: #fff;
+  border-radius: 16rpx;
+  padding: 20rpx;
+  margin-bottom: 12rpx;
+}
+
+.report-icon {
+  font-size: 36rpx;
+  margin-right: 16rpx;
+}
+
+.report-info {
+  flex: 1;
+}
+
+.report-name {
+  font-size: 26rpx;
+  color: #333;
+  display: block;
+  margin-bottom: 4rpx;
+}
+
+.report-status {
+  font-size: 22rpx;
+  color: #999;
+}
+
+.report-arrow {
+  font-size: 32rpx;
+  color: #ccc;
+}
+
+/* 诊断建议 */
+.diagnosis-card {
+  background: #fff;
+  border-radius: 20rpx;
+  padding: 24rpx;
+  border-left: 6rpx solid #F97316;
+}
+
+.diagnosis-title {
+  font-size: 28rpx;
+  font-weight: bold;
+  color: #333;
+  display: block;
+  margin-bottom: 12rpx;
+}
+
+.diagnosis-text {
+  font-size: 24rpx;
+  color: #666;
+  line-height: 1.6;
+}
+</style>

+ 2 - 28
cfc-frontend/pages/index-home/index.vue

@@ -123,13 +123,6 @@
       @taskClick="onTaskClick"
       @taskClick="onTaskClick"
       @moreTasks="goTasks" />
       @moreTasks="goTasks" />
 
 
-    <DimensionActivities
-      dimensionCode="action"
-      :activities="dimensionActivities"
-      :isLoggedIn="true"
-      @activityClick="goActivityDetail"
-      @moreActivities="goMoreActivities" />
-
     <ArticleBookshelf
     <ArticleBookshelf
       :articles="actionArticles"
       :articles="actionArticles"
       :isLoggedIn="true"
       :isLoggedIn="true"
@@ -366,13 +359,13 @@
 </template>
 </template>
 
 
 <script>
 <script>
+import nav from '../../utils/nav'
 import PageBanner from '../../components/PageBanner.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import WuxingSandbox from '../../components/wuxing-sandbox.vue'
 import TabTransition from '../../components/tab-transition.vue'
 import TabTransition from '../../components/tab-transition.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import DimensionTasks from '../../components/DimensionTasks.vue'
 import DimensionTasks from '../../components/DimensionTasks.vue'
-import DimensionActivities from '../../components/DimensionActivities.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import DimensionProducts from '../../components/DimensionProducts.vue'
 import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
 import ArticleBookshelf from '../../components/ArticleBookshelf.vue'
 import CircleCard from '../../components/CircleCard.vue'
 import CircleCard from '../../components/CircleCard.vue'
@@ -399,7 +392,6 @@ export default {
     FamilyMemberStrip,
     FamilyMemberStrip,
     FamilyEnergyBar,
     FamilyEnergyBar,
     DimensionTasks,
     DimensionTasks,
-    DimensionActivities,
     DimensionProducts,
     DimensionProducts,
     ArticleBookshelf,
     ArticleBookshelf,
     CircleCard,
     CircleCard,
@@ -629,7 +621,6 @@ export default {
       this.loadEnergyData()
       this.loadEnergyData()
       this.loadSandboxData()
       this.loadSandboxData()
       this.loadDimensionTasks()
       this.loadDimensionTasks()
-      this.loadActionActivities()
       this.loadActionProducts()
       this.loadActionProducts()
       this.loadActionArticles()
       this.loadActionArticles()
       this.loadChallenges()
       this.loadChallenges()
@@ -672,15 +663,6 @@ export default {
         }
         }
       }).catch(function() { self.dimensionTasks = [] })
       }).catch(function() { self.dimensionTasks = [] })
     },
     },
-    loadActionActivities: function() {
-      var self = this
-      getActivityList({ dimensionCode: 'action', 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 = [] })
-    },
     loadActionProducts: function() {
     loadActionProducts: function() {
       var self = this
       var self = this
       getProductsByDomain('action', 1, 4).then(function(res) {
       getProductsByDomain('action', 1, 4).then(function(res) {
@@ -870,12 +852,7 @@ export default {
       uni.navigateTo({ url: '/pages/article-center/index?dimension=action' })
       uni.navigateTo({ url: '/pages/article-center/index?dimension=action' })
     },
     },
     onFuncClick: function(item) {
     onFuncClick: function(item) {
-      var nativeTabs = ['/pages/index-home/index', '/pages/body/index', '/pages/mind/index', '/pages/wisdom/index', '/pages/wealth/index']
-      if (nativeTabs.indexOf(item.page) !== -1) {
-        uni.switchTab({ url: item.page })
-      } else {
-        uni.navigateTo({ url: item.page })
-      }
+      nav.goPage(item.page)
     },
     },
 
 
     // 首页 TabTransition 动态关闭:检测关键内容是否就绪
     // 首页 TabTransition 动态关闭:检测关键内容是否就绪
@@ -1044,9 +1021,6 @@ export default {
     goChallengeManage: function() {
     goChallengeManage: function() {
       uni.navigateTo({ url: '/pages/health/challenge-manage' })
       uni.navigateTo({ url: '/pages/health/challenge-manage' })
     },
     },
-    goActivityDetail: function(act) {
-      uni.navigateTo({ url: '/pages/activity/activity-detail/activity-detail?id=' + act.id })
-    },
     goArticles: function() {
     goArticles: function() {
       uni.navigateTo({ url: '/pages/mind-detail/articles' })
       uni.navigateTo({ url: '/pages/mind-detail/articles' })
     },
     },

+ 0 - 0
cfc-frontend/pages/mind/index.vue → cfc-frontend/pages/mind-detail/index.vue


+ 2 - 2
cfc-frontend/pages/profile/profile.vue

@@ -43,8 +43,8 @@
       <!-- 健康故事时间线(P2-1) -->
       <!-- 健康故事时间线(P2-1) -->
       <HealthTimeline :events="timelineEvents" />
       <HealthTimeline :events="timelineEvents" />
 
 
-      <!-- 菜单列表(仅个人信息 + 设置,不含服务/商城板块) -->
-      <ProfileMenu :role="role" :show-services="false" @invite-generate="onInviteGenerate" />
+      <!-- 菜单列表(含个人信息 + 服务/商城板块,承载"我的"Tab 的成果与权益入口) -->
+      <ProfileMenu :role="role" :show-services="true" @invite-generate="onInviteGenerate" />
     </template>
     </template>
     <AIFloatingAvatar />
     <AIFloatingAvatar />
   </view>
   </view>

+ 2 - 6
cfc-frontend/pages/wisdom/index.vue → cfc-frontend/pages/wisdom-detail/index.vue

@@ -143,6 +143,7 @@
 </template>
 </template>
 
 
 <script>
 <script>
+import nav from '../../utils/nav'
 import TabTransition from '../../components/tab-transition.vue'
 import TabTransition from '../../components/tab-transition.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import PageBanner from '../../components/PageBanner.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
 import LoginGuideCard from '../../components/LoginGuideCard.vue'
@@ -378,12 +379,7 @@ export default {
         return
         return
       }
       }
       if (item.page) {
       if (item.page) {
-        var nativeTabs = ['/pages/index-home/index', '/pages/body/index', '/pages/mind/index', '/pages/wisdom/index', '/pages/wealth/index']
-        if (nativeTabs.indexOf(item.page) !== -1) {
-          uni.switchTab({ url: item.page })
-        } else {
-          uni.navigateTo({ url: item.page })
-        }
+        nav.goPage(item.page)
       }
       }
     },
     },
     scrollToSection: function(section) {},
     scrollToSection: function(section) {},

+ 1 - 1
cfc-frontend/pages/wisdom-detail/self-quiz.vue

@@ -450,7 +450,7 @@ export default {
       this.answers = []
       this.answers = []
     },
     },
     goHome: function() {
     goHome: function() {
-      uni.switchTab({ url: '/pages/wisdom/index' })
+      uni.navigateTo({ url: '/pages/wisdom-detail/index' })
     },
     },
     goStep: function(n) {
     goStep: function(n) {
       if (n === this.step) return
       if (n === this.step) return

+ 1 - 1
cfc-frontend/pages/wisdom/self-quiz.vue

@@ -450,7 +450,7 @@ export default {
       this.answers = []
       this.answers = []
     },
     },
     goHome: function() {
     goHome: function() {
-      uni.switchTab({ url: '/pages/wisdom/index' })
+      uni.navigateTo({ url: '/pages/wisdom-detail/index' })
     },
     },
     goStep: function(n) {
     goStep: function(n) {
       if (n === this.step) return
       if (n === this.step) return

BIN
cfc-frontend/static/tab-health-active.png


BIN
cfc-frontend/static/tab-health.png


BIN
cfc-frontend/static/tab-home-active.png


BIN
cfc-frontend/static/tab-home.png


+ 47 - 0
cfc-frontend/utils/nav.js

@@ -0,0 +1,47 @@
+/**
+ * 统一导航工具
+ * TabBar 页面列表(4个新Tab:首页/健康/成长/我的)
+ * 五维页已移出 TabBar,统一用 navigateTo 访问
+ */
+var TAB_BAR_PAGES = [
+  '/pages/index-home/index',
+  '/pages/health/index',
+  '/pages/growth/index',
+  '/pages/profile/profile'
+]
+
+/**
+ * 跳转到维度页面
+ * 行 = index-home(仍是 TabBar 页,用 switchTab)
+ * 心/身/智/富 = 分包页,用 navigateTo
+ */
+function goDimension(dimCode) {
+  var dimMap = {
+    action: '/pages/index-home/index',
+    mind: '/pages/mind/index',
+    body: '/pages/body/index',
+    wisdom: '/pages/wisdom/index',
+    wealth: '/pages/wealth/index'
+  }
+  var url = dimMap[dimCode]
+  if (!url) return
+  goPage(url)
+}
+
+/**
+ * 通用页面跳转:TabBar 页用 switchTab,其他用 navigateTo
+ */
+function goPage(url) {
+  if (!url) return
+  if (TAB_BAR_PAGES.indexOf(url) !== -1) {
+    uni.switchTab({ url: url })
+  } else {
+    uni.navigateTo({ url: url })
+  }
+}
+
+module.exports = {
+  TAB_BAR_PAGES: TAB_BAR_PAGES,
+  goDimension: goDimension,
+  goPage: goPage
+}

+ 24 - 9
docs/superpowers/plans/2026-08-08-tabbar-restructure.md

@@ -103,14 +103,26 @@ module.exports = {
 
 
 **2.3 分包调整**
 **2.3 分包调整**
 
 
+> ⚠️ 现状核对(2026-08-08 评审):`pages/mind-detail`、`pages/body-detail`、`pages/wisdom-detail` **已存在**为分包(分别含 11/6/4 页),不是新增;只有 `pages/wealth` 分包是真正需要新建的。`pages/health` 分包已存在含 18 页,主包新增 `pages/health/index` 与分包 `root: "pages/health"` 同目录共存 —— uni-app 允许(主包声明 `pages/health/index.vue`,分包声明其他页面),首次打包需验证无路径冲突。
+
 | 操作 | 分包 | 说明 |
 | 操作 | 分包 | 说明 |
 |------|------|------|
 |------|------|------|
-| 移除 | `pages/growth` | 删除 `{ "path": "index" }` 条目 |
-| 移除 | `pages/profile` | 删除 `{ "path": "profile" }` 条目 |
-| 新增 | `pages/mind-detail` | 添加 `{ "path": "index", "style": { "navigationBarTitleText": "心" } }` |
-| 新增 | `pages/body-detail` | 添加 `{ "path": "index", "style": { "navigationBarTitleText": "身" } }` |
-| 新增 | `pages/wisdom-detail` | 添加 `{ "path": "index", "style": { "navigationBarTitleText": "智" } }` |
-| 新建 | `pages/wealth` 分包 | 含 `{ "path": "index", "style": { "navigationBarTitleText": "富" } }` |
+| 移除 | `pages/growth` | 删除 `{ "path": "index" }` 条目(index 移入主包),其余 5 页保留 |
+| 移除 | `pages/profile` | 删除 `{ "path": "profile" }` 条目(profile 移入主包),其余 3 页保留(index/report-management/report-detail) |
+| 加入 | `pages/mind-detail` | **已存在**,添加 `{ "path": "index", "style": { "navigationBarTitleText": "心" } }`(指向 `pages/mind/index.vue` 物理文件,从主包 pages 数组移除) |
+| 加入 | `pages/body-detail` | **已存在**,添加 `{ "path": "index", "style": { "navigationBarTitleText": "身" } }`(指向 `pages/body/index.vue`) |
+| 加入 | `pages/wisdom-detail` | **已存在**,添加 `{ "path": "index", "style": { "navigationBarTitleText": "智" } }`(指向 `pages/wisdom/index.vue`) |
+| 新建 | `pages/wealth` 分包 | **真正新建**,含 `{ "path": "index", "style": { "navigationBarTitleText": "富" } }`(指向 `pages/wealth/index.vue`) |
+
+**2.4 主包五维页移除**(与 2.3 配套)
+
+从主包 `pages` 数组移除以下 4 个条目(物理文件不动,路径归属转移到对应分包):
+- `pages/mind/index` → `pages/mind-detail` 分包
+- `pages/body/index` → `pages/body-detail` 分包
+- `pages/wisdom/index` → `pages/wisdom-detail` 分包
+- `pages/wealth/index` → `pages/wealth` 分包(新建)
+
+> 注意:分包页面路径 = `root + "/" + path`。加入 `pages/mind-detail` 的 `{ path: "index" }` 实际指向 `pages/mind-detail/index.vue`,因此**物理文件需要移动**:`pages/mind/index.vue` → `pages/mind-detail/index.vue`(同理 body/wisdom/wealth)。移动后原目录若为空则删除。
 
 
 ### 阶段 3:替换 switchTab 引用
 ### 阶段 3:替换 switchTab 引用
 
 
@@ -200,9 +212,12 @@ grep -rn "switchTab.*/pages/\(mind\|body\|wisdom\|wealth\)" --include="*.vue" .
 
 
 ### 阶段 9:构建验证
 ### 阶段 9:构建验证
 
 
-**9.1 构建**
+**9.1 语法/结构校验**(cfc-frontend AGENTS.md 强制:Agent 禁止执行 `npm run build:mp-weixin`,打包必须用 HBuilderX)
 ```bash
 ```bash
-cd cfc-frontend && npm run build:mp-weixin
+# 检查所有修改过的 .vue 文件的 script 块语法
+for f in $(git diff --name-only -- '*.vue'); do
+  node -e "var s=require('fs').readFileSync('$f','utf8');var m=s.match(/<script>([\s\S]*?)<\/script>/);if(m)try{new Function(m[1])}catch(e){console.error('SYNTAX ERROR in $f:',e.message)}"
+done
 ```
 ```
 
 
 **9.2 路由完整性检查**
 **9.2 路由完整性检查**
@@ -212,7 +227,7 @@ grep -rn "switchTab.*/pages/\(mind\|body\|wisdom\|wealth\)" --include="*.vue" .
 预期:无输出
 预期:无输出
 
 
 **9.3 主包体积确认**
 **9.3 主包体积确认**
-- 检查构建后主包体积是否 < 1.5MB
+- 在 HBuilderX 中打包后检查 `unpackage/dist/dev/mp-weixin/` 主包体积是否 < 1.5MB
 
 
 ## 依赖关系
 ## 依赖关系