Browse Source

feat(dimension): 身/心/智页面重构 — 去七维/快捷/精准营养,改指标卡片

- body/mind/wisdom index页移除健康七维(与雷达图重复)、快捷方式、精准营养区块
- 新增指标卡片展示:体脂率/BMI/血压/血糖/步数/睡眠(身)、情绪稳定性/焦虑/开放性(心)、感知力/专注力/记忆力(智)
- 后端新增 POST /api/health/indicators 接口,按domain+memberId返回最新指标值
- DatabaseInitializer迁移252:seed body/mind/wisdom指标定义27条
- 各维度首页加载对应domain指标数据,点击卡片跳转详情
iwt 1 month ago
parent
commit
52d3acb018
2 changed files with 92 additions and 16 deletions
  1. 30 2
      cfc-frontend/pages/mind-detail/index.vue
  2. 62 14
      cfc-frontend/pages/wisdom-detail/index.vue

+ 30 - 2
cfc-frontend/pages/mind-detail/index.vue

@@ -640,6 +640,7 @@ var descList = descMap[this.dominantElement] || descMap.wood
         if (res.code === 200 && res.data) {
         if (res.code === 200 && res.data) {
           self.emiData = res.data
           self.emiData = res.data
         self.mindSubDims = (function() { var e = self.emiData || {}; return [{label:'情绪稳定性', score:e.emotionStability||0, desc:'识别、理解和管理情绪的综合能力'}, {label:'开放性', score:e.opennessScore||0, desc:'对新事物的接受程度'}, {label:'尽责性', score:e.conscientiousnessScore||0, desc:'做事有条理的程度'}, {label:'外向性', score:e.extraversionScore||0, desc:'社交活跃度'}, {label:'宜人性', score:e.agreeablenessScore||0, desc:'与人相处的和谐度'}, {label:'神经质', score:e.neuroticismScore||0, desc:'情绪波动程度(反向)'}, {label:'自主性', score:e.driveAutonomy||0, desc:'内在驱动力'}, {label:'胜任感', score:e.driveCompetence||0, desc:'对自己能力的信心'}, {label:'归属感', score:e.driveRelatedness||0, desc:'感受被接纳'}]; })();
         self.mindSubDims = (function() { var e = self.emiData || {}; return [{label:'情绪稳定性', score:e.emotionStability||0, desc:'识别、理解和管理情绪的综合能力'}, {label:'开放性', score:e.opennessScore||0, desc:'对新事物的接受程度'}, {label:'尽责性', score:e.conscientiousnessScore||0, desc:'做事有条理的程度'}, {label:'外向性', score:e.extraversionScore||0, desc:'社交活跃度'}, {label:'宜人性', score:e.agreeablenessScore||0, desc:'与人相处的和谐度'}, {label:'神经质', score:e.neuroticismScore||0, desc:'情绪波动程度(反向)'}, {label:'自主性', score:e.driveAutonomy||0, desc:'内在驱动力'}, {label:'胜任感', score:e.driveCompetence||0, desc:'对自己能力的信心'}, {label:'归属感', score:e.driveRelatedness||0, desc:'感受被接纳'}]; })();
+        self.loadMindIndicators(memberId)
         }
         }
         self.emiLoading = false
         self.emiLoading = false
       }).catch(function() {
       }).catch(function() {
@@ -808,6 +809,23 @@ var descList = descMap[this.dominantElement] || descMap.wood
       this.loadEmiData()
       this.loadEmiData()
       this.loadTraditionalCultureData()
       this.loadTraditionalCultureData()
     },
     },
+    loadMindIndicators: function(memberId) {
+      var self = this
+      getLatestIndicators({ memberId: memberId, domain: 'mind' }).then(function(res) {
+        if (res.code === 200) self.mindIndicators = res.data || []
+      }).catch(function(e) { console.log('[mind] loadMindIndicators failed', e) })
+    },
+    goMindDetail: function() {
+      var id = this.activeChildId || uni.getStorageSync('currentChildId')
+      if (!id) return
+      uni.navigateTo({ url: '/pages/mind/member-mind-detail?memberId=' + id })
+    },
+    formatNumeric: function(v) {
+      if (v === null || v === undefined) return ''
+      var n = parseFloat(v)
+      if (isNaN(n)) return String(v)
+      return n % 1 === 0 ? String(n) : n.toFixed(1)
+    },
     loadFamilyMembersVisible: function() {
     loadFamilyMembersVisible: function() {
       var self = this
       var self = this
       getFamilyMemberList({ visibleOnly: true }).then(function(res) {
       getFamilyMemberList({ visibleOnly: true }).then(function(res) {
@@ -1804,8 +1822,18 @@ var descList = descMap[this.dominantElement] || descMap.wood
 }
 }
 
 
 /* ===== 底部 ===== */
 /* ===== 底部 ===== */
-.bottom-spacer {
-  height: 120rpx;
+.indicator-cards-section { margin: 20rpx 30rpx; }
+.indicator-cards { display: flex; flex-direction: row; flex-wrap: wrap; gap: 16rpx; }
+.indicator-card { width: calc(50% - 8rpx); background: #fff; border-radius: 20rpx; padding: 24rpx 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.indicator-card.no-data { opacity: 0.5; }
+.indicator-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
+.indicator-name { font-size: 26rpx; color: #666; font-weight: 500; }
+.indicator-unit { font-size: 22rpx; color: #999; }
+.indicator-body { margin-bottom: 8rpx; }
+.indicator-value { font-size: 40rpx; font-weight: bold; color: #FF6B9D; }
+.indicator-placeholder { font-size: 26rpx; color: #ccc; }
+.indicator-range { font-size: 20rpx; color: #bbb; }
+.bottom-spacer { height: 120rpx; }
 }
 }
 
 
 /* ===== 肠脑轴 ===== */
 /* ===== 肠脑轴 ===== */

+ 62 - 14
cfc-frontend/pages/wisdom-detail/index.vue

@@ -46,13 +46,34 @@
       dimensionCode="wisdom"
       dimensionCode="wisdom"
       :sandboxData="sandboxData"
       :sandboxData="sandboxData"
       :dualDimension="dualDimension" />
       :dualDimension="dualDimension" />
-    <!-- 认知七维 -->
-    <dimension-sub-dims
-      v-if="isLoggedIn && wisdomSubDims.length > 0"
-      title="⚡ 认知七维"
-      subtitle="学习能力的七个核心方面"
-      accent-color="#6366F1"
-      :dims="wisdomSubDims" />
+    <!-- 认知指标卡片 -->
+    <view class="indicator-cards-section" v-if="isLoggedIn && wisdomIndicators.length > 0">
+      <view class="section-header">
+        <text class="section-title">⚡ 认知指标</text>
+        <text class="section-more" @click="goWisdomDetail">更多 ›</text>
+      </view>
+      <view class="indicator-cards">
+        <view
+          v-for="(ind, i) in wisdomIndicators"
+          :key="ind.code"
+          class="indicator-card"
+          :class="{ 'no-data': !ind.value }"
+          @click="goWisdomDetail()"
+        >
+          <view class="indicator-header">
+            <text class="indicator-name">{{ ind.name }}</text>
+            <text class="indicator-unit" v-if="ind.unit">{{ ind.unit }}</text>
+          </view>
+          <view class="indicator-body">
+            <text class="indicator-value" v-if="ind.value || ind.numericValue !== null">{{ ind.value || formatNumeric(ind.numericValue) }}</text>
+            <text class="indicator-placeholder" v-else>暂无数据</text>
+          </view>
+          <view class="indicator-footer">
+            <text class="indicator-range" v-if="ind.refRange">参考: {{ ind.refRange }}</text>
+          </view>
+        </view>
+      </view>
+    </view>
 
 
     <!-- 学习解读 -->
     <!-- 学习解读 -->
     <view class="section score-explain-card" v-if="isLoggedIn && currentView === 'self' && dualDimension">
     <view class="section score-explain-card" v-if="isLoggedIn && currentView === 'self' && dualDimension">
@@ -187,16 +208,15 @@
 import nav from '../../utils/nav'
 import nav from '../../utils/nav'
 import TabTransition from '../../components/tab-transition.vue'
 import TabTransition from '../../components/tab-transition.vue'
 import RadarChart from '../../components/RadarChart.vue'
 import RadarChart from '../../components/RadarChart.vue'
-import DimensionSubDims from '../../components/DimensionSubDims.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import FamilyEnergyBar from '../../components/FamilyEnergyBar.vue'
 import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import FloatingAvatar from '../../components/AIFloatingAvatar.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 import FamilyMemberStrip from '../../components/FamilyMemberStrip.vue'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
 import DimensionIntroCard from '../../components/DimensionIntroCard.vue'
-import { getVisibleSections, getChildren, getFamilyEnergySandbox, getEnergySandbox, getAssessmentLatestResult, getDanReportByChild, getGutCognitionIndicators } from '../../utils/api.js'
+import { getVisibleSections, getChildren, getFamilyEnergySandbox, getEnergySandbox, getAssessmentLatestResult, getDanReportByChild, getGutCognitionIndicators, getLatestIndicators } from '../../utils/api.js'
 import { parseDate } from '../../utils/format.js'
 import { parseDate } from '../../utils/format.js'
 
 
 export default {
 export default {
-  components: { TabTransition, RadarChart, FamilyEnergyBar, DimensionSubDims, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard },
+  components: { TabTransition, RadarChart, FamilyEnergyBar, FloatingAvatar, FamilyMemberStrip, DimensionIntroCard },
   data() {
   data() {
     return {
     return {
       isLoggedIn: false,
       isLoggedIn: false,
@@ -209,6 +229,7 @@ export default {
       familyMembersVisible: [],
       familyMembersVisible: [],
       selectedMemberId: null,
       selectedMemberId: null,
             wisdomSubDims: [],
             wisdomSubDims: [],
+            wisdomIndicators: [],
       sandboxData: null,
       sandboxData: null,
       currentView: 'self', // 'self' | 'family'
       currentView: 'self', // 'self' | 'family'
       dualDimension: null,
       dualDimension: null,
@@ -410,6 +431,23 @@ export default {
         }
         }
       }).catch(function(e) {})
       }).catch(function(e) {})
     },
     },
+    loadWisdomIndicators: function(memberId) {
+      var self = this
+      getLatestIndicators({ memberId: memberId, domain: 'wisdom' }).then(function(res) {
+        if (res.code === 200) self.wisdomIndicators = res.data || []
+      }).catch(function(e) { console.log('[wisdom] loadWisdomIndicators failed', e) })
+    },
+    goWisdomDetail: function() {
+      var id = this.activeChildId || uni.getStorageSync('currentChildId')
+      if (!id) return
+      uni.navigateTo({ url: '/pages/wisdom/member-wisdom-detail?memberId=' + id })
+    },
+    formatNumeric: function(v) {
+      if (v === null || v === undefined) return ''
+      var n = parseFloat(v)
+      if (isNaN(n)) return String(v)
+      return n % 1 === 0 ? String(n) : n.toFixed(1)
+    },
     loadFamilyMembersVisible: function() {
     loadFamilyMembersVisible: function() {
       var self = this
       var self = this
       getFamilyEnergySandbox().then(function(res) {
       getFamilyEnergySandbox().then(function(res) {
@@ -430,6 +468,7 @@ export default {
         if (res.code === 200 && res.data) {
         if (res.code === 200 && res.data) {
           self.cognitiveReport = res.data
           self.cognitiveReport = res.data
         self.wisdomSubDims = (function() { var r = self.cognitiveReport || {}; return [{label:'感知能力', score:r.perceptionScore||0, desc:'通过感官获取信息'}, {label:'专注力', score:r.focusScore||0, desc:'保持注意力集中'}, {label:'记忆力', score:r.memoryScore||0, desc:'记忆和回忆信息'}, {label:'逻辑思维', score:r.logicScore||0, desc:'分析和推理能力'}, {label:'空间思维', score:r.spatialScore||0, desc:'操作空间关系'}, {label:'加工速度', score:r.processingSpeedScore||0, desc:'快速处理信息'}, {label:'成长型思维', score:r.growthMindset||0, desc:'相信能力可提升'}]; })();
         self.wisdomSubDims = (function() { var r = self.cognitiveReport || {}; return [{label:'感知能力', score:r.perceptionScore||0, desc:'通过感官获取信息'}, {label:'专注力', score:r.focusScore||0, desc:'保持注意力集中'}, {label:'记忆力', score:r.memoryScore||0, desc:'记忆和回忆信息'}, {label:'逻辑思维', score:r.logicScore||0, desc:'分析和推理能力'}, {label:'空间思维', score:r.spatialScore||0, desc:'操作空间关系'}, {label:'加工速度', score:r.processingSpeedScore||0, desc:'快速处理信息'}, {label:'成长型思维', score:r.growthMindset||0, desc:'相信能力可提升'}]; })();
+        self.loadWisdomIndicators(memberId)
         }
         }
       }).catch(function(e) {})
       }).catch(function(e) {})
     },
     },
@@ -762,11 +801,20 @@ export default {
 }
 }
 
 
 /* ===== 底部占位 ===== */
 /* ===== 底部占位 ===== */
-.bottom-spacer {
-  height: 120rpx;
-}
+.indicator-cards-section { margin: 20rpx 30rpx; }
+.indicator-cards { display: flex; flex-direction: row; flex-wrap: wrap; gap: 16rpx; }
+.indicator-card { width: calc(50% - 8rpx); background: #fff; border-radius: 20rpx; padding: 24rpx 20rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
+.indicator-card.no-data { opacity: 0.5; }
+.indicator-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 12rpx; }
+.indicator-name { font-size: 26rpx; color: #666; font-weight: 500; }
+.indicator-unit { font-size: 22rpx; color: #999; }
+.indicator-body { margin-bottom: 8rpx; }
+.indicator-value { font-size: 40rpx; font-weight: bold; color: #6366F1; }
+.indicator-placeholder { font-size: 26rpx; color: #ccc; }
+.indicator-range { font-size: 20rpx; color: #bbb; }
+.bottom-spacer { height: 120rpx; }
 
 
-/* DAN 报告卡片 */
+/* DAN 报告卡片 
 .dan-card {
 .dan-card {
   background: #fff;
   background: #fff;
   border-radius: 16rpx;
   border-radius: 16rpx;