Explorar el Código

feat(self-check): 结果页改 AI 解读 + 生成计划按钮 + 问问AI(P0-1/P0-2/P1-2)

iwt hace 2 meses
padre
commit
721be69407
Se han modificado 1 ficheros con 119 adiciones y 48 borrados
  1. 119 48
      cfc-frontend/pages/family/self-check-result.vue

+ 119 - 48
cfc-frontend/pages/family/self-check-result.vue

@@ -67,80 +67,56 @@
         </view>
       </view>
 
-      <!-- 五行相生寻源建议(P0-2) -->
+      <!-- AI 健康解读(P0-1) -->
       <view class="scr-card" v-if="result.advices && result.advices.length > 0">
         <view class="scr-card-title">
-          <text class="scr-card-title-text">五行相生 · 寻源建议</text>
-          <text class="scr-card-sub">低分维度 {{ result.advices.length }} 项</text>
+          <text class="scr-card-title-text">AI 健康解读</text>
+          <text class="scr-card-sub">基于你的五维自检结果</text>
         </view>
-        <view class="scr-advice-intro">低于6分的维度,先别硬补——从"生"它的上游借力,同时避开"克"它的压制源:</view>
-        <view class="scr-advice-item" v-for="adv in result.advices" :key="adv.dimension">
-          <!-- 低分维度 -->
+        <view v-for="adv in result.advices" :key="adv.dimension" class="scr-advice-item">
           <view class="scr-advice-dim-row">
             <view class="scr-advice-dim-badge" :style="{ background: adv.color }">
               <text class="scr-advice-dim-name">{{ adv.dimensionName }}</text>
-              <text class="scr-advice-dim-elem">{{ adv.element }}</text>
+              <text class="scr-advice-dim-elem">{{ adv.score }}分</text>
             </view>
-            <text class="scr-advice-dim-score">{{ adv.score }}分</text>
           </view>
-
-          <!-- 相生寻源 -->
-          <view class="scr-advice-flow">
-            <view class="scr-flow-node">
-              <view class="scr-flow-dot" :style="{ background: adv.upstreamColor }"></view>
-              <text class="scr-flow-label">上游 · {{ adv.upstreamName }}({{ adv.upstreamElement }})</text>
-            </view>
-            <text class="scr-flow-arrow">⟶</text>
-            <text class="scr-flow-action">{{ adv.upstreamReason }}</text>
-          </view>
-
-          <!-- 相克寻源 -->
-          <view class="scr-advice-flow">
-            <view class="scr-flow-node">
-              <view class="scr-flow-dot" :style="{ background: adv.restrainerColor }"></view>
-              <text class="scr-flow-label">压制 · {{ adv.restrainerName }}({{ adv.restrainerElement }})</text>
-            </view>
-            <text class="scr-flow-arrow">⛔</text>
-            <text class="scr-flow-action">{{ adv.restrainerReason }}</text>
-          </view>
-
-          <!-- 行动建议 -->
-          <view class="scr-advice-action">
-            <text class="scr-advice-action-label">本周行动</text>
-            <text class="scr-advice-action-text">{{ adv.action }}</text>
-          </view>
-
-          <!-- 一键加入今日微行动(微行动×五维联动) -->
-          <view class="scr-micro-row" v-if="joinedDims[adv.dimension]">
-            <text class="scr-micro-done">✅ 今日微行动已完成,能量已入账</text>
-          </view>
-          <view class="scr-micro-row" v-else>
-            <button class="scr-micro-btn" :loading="joiningDim === adv.dimension" :disabled="joiningDim !== ''" @click="joinMicroAction(adv)">
-              ⚡ 加入今日微行动
-            </button>
+          <text class="scr-advice-interpretation" v-if="adv.interpretation">{{ adv.interpretation }}</text>
+          <view class="scr-advice-actions" v-if="adv.microActions && adv.microActions.length > 0">
+            <text class="scr-advice-action-label">本周行动:</text>
+            <text v-for="(action, i) in adv.microActions" :key="'ma'+i" class="scr-advice-action">{{ i+1 }}. {{ action }}</text>
           </view>
         </view>
+        <view class="scr-family-insight" v-if="result.advices[0] && result.advices[0].familyInsight">
+          <text class="scr-family-insight-label">💡 家庭整体洞察</text>
+          <text class="scr-family-insight-text">{{ result.advices[0].familyInsight }}</text>
+        </view>
+        <view class="scr-fallback" v-if="result.advices[0] && result.advices[0].fallbackUsed">
+          <text>AI 分析暂时不可用,请稍后再试</text>
+        </view>
       </view>
-
       <!-- 全部健康提示 -->
-      <view class="scr-card" v-else>
+      <view class="scr-card" v-else-if="result.totalScore >= 35">
         <view class="scr-all-healthy">
           <text class="scr-all-healthy-icon">🌿</text>
           <text class="scr-all-healthy-text">五维状态均健康,请继续保持这份平衡!</text>
         </view>
       </view>
 
-      <!-- 历史与重测 -->
+      <!-- 历史与操作 -->
       <view class="scr-footer">
         <button class="scr-btn scr-btn-primary" @click="goHistory">历史记录</button>
         <button class="scr-btn scr-btn-outline" @click="retake">重新自检</button>
+        <button class="scr-btn scr-btn-secondary" v-if="hasLowScore" :loading="planGenerating" @click="generatePlan">
+          生成健康计划
+        </button>
+        <button class="scr-btn scr-btn-ghost" @click="askAI">问问 AI</button>
       </view>
     </template>
   </view>
 </template>
 
 <script>
-import { getSelfCheckLatest, getSelfCheckHistory } from '@/utils/api'
+import { getSelfCheckLatest, getSelfCheckHistory, generateSelfCheckPlan } from '@/utils/api'
 import { microActionToday, microActionComplete } from '@/utils/api'
 import { parseDate } from '@/utils/format.js'
 
@@ -164,7 +140,8 @@ export default {
       },
       // 今日微行动联动状态
       joiningDim: '',
-      joinedDims: {}
+      joinedDims: {},
+      planGenerating: false
     }
   },
   computed: {
@@ -176,6 +153,11 @@ export default {
     },
     overallLevelName: function() {
       return this.levelMap[this.overallLevel] || '留意'
+    },
+    hasLowScore: function() {
+      return this.result && this.result.advices && this.result.advices.some(function(a) {
+        return a.score != null && a.score <= 6
+      })
     }
   },
   onLoad: function(options) {
@@ -393,6 +375,33 @@ export default {
     },
     retake: function() {
       uni.navigateTo({ url: '/pages/family/self-check-entry' })
+    },
+    generatePlan: function() {
+      var self = this
+      if (this.planGenerating) return
+      this.planGenerating = true
+      generateSelfCheckPlan({ checkId: this.result && this.result.id }).then(function(res) {
+        self.planGenerating = false
+        if (res.code === 200 && res.data && res.data.planId) {
+          uni.showToast({ title: '计划已生成,可在健康计划页查看', icon: 'success' })
+          setTimeout(function() {
+            uni.navigateTo({ url: '/pages/health-main/index?planId=' + res.data.planId })
+          }, 1000)
+        } else {
+          uni.showToast({ title: (res && res.message) || '计划生成失败', icon: 'none' })
+        }
+      }).catch(function() {
+        self.planGenerating = false
+        uni.showToast({ title: '计划生成失败', icon: 'none' })
+      })
+    },
+    askAI: function() {
+      var selfCheckId = this.result && this.result.id
+      if (!selfCheckId) {
+        uni.showToast({ title: '暂无自检记录', icon: 'none' })
+        return
+      }
+      uni.navigateTo({ url: '/pages/ai/chat?selfCheckId=' + selfCheckId })
     }
   }
 }
@@ -756,4 +765,66 @@ export default {
   color: #F97316;
   border: 2rpx solid #F97316;
 }
+.scr-btn-secondary {
+  background: linear-gradient(135deg, #10B981, #34D399);
+  color: #fff;
+  font-weight: 600;
+}
+.scr-btn-ghost {
+  background: #fff;
+  color: #666;
+  border: 2rpx solid #D1D5DB;
+}
+.scr-advice-interpretation {
+  display: block;
+  font-size: 26rpx;
+  color: #555;
+  line-height: 1.6;
+  margin: 16rpx 0;
+}
+.scr-advice-actions {
+  display: flex;
+  flex-direction: column;
+  margin-top: 12rpx;
+}
+.scr-advice-action-label {
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #F97316;
+}
+.scr-advice-action {
+  font-size: 24rpx;
+  color: #555;
+  line-height: 1.5;
+}
+.scr-family-insight {
+  background: #F5FAFE;
+  border-radius: 16rpx;
+  padding: 20rpx 24rpx;
+  margin-top: 24rpx;
+}
+.scr-family-insight-label {
+  display: block;
+  font-size: 24rpx;
+  font-weight: 600;
+  color: #F97316;
+  margin-bottom: 8rpx;
+}
+.scr-family-insight-text {
+  display: block;
+  font-size: 26rpx;
+  color: #555;
+  line-height: 1.6;
+}
+.scr-fallback {
+  background: #FFF7ED;
+  border-radius: 12rpx;
+  padding: 16rpx 20rpx;
+  margin-top: 16rpx;
+  text-align: center;
+}
+.scr-fallback text {
+  font-size: 24rpx;
+  color: #999;
+}
 </style>