Просмотр исходного кода

feat: 肠道菌群报告/健康报告列表/首页优化

- 肠道菌群报告页面 (gut-flora-detail.vue):
  - 报告日期仅显示 yyyy-MM-dd,去掉时间部分
  - 指标名称前添加 '?' 知识库按钮,点击弹出说明
  - 补全缺失数据:shannonIndex 徽章、healthRating 评级行、指标单位 unit、症状 symptoms
  - 修复报告解读框死代码:后端返回 reportInterpretation.gutAgeAssessment

- 健康报告列表 (report-list.vue):
  - 滑动删除功能修复:移除组件层 @click 冲突,仅用 #right slot 实现左滑出现删除
  - 点击卡片正常跳转详情

- 首页 (index-home/index.vue):
  - 新增 onShareAppMessage/onShareTimeline 分享配置

- 后端 HealthReportService:
  - getReportDetail 新增 reportInterpretation 字段,基于 gutAge/gutType 生成评估文案
liaoxg 2 месяцев назад
Родитель
Сommit
72b1036290

+ 15 - 0
cfc-backend/src/main/java/com/etotem/cfc/service/HealthReportService.java

@@ -543,6 +543,21 @@ public class HealthReportService {
             // 通用展示块(blocks 为空时前端回退旧字段)
             detail.put("blocks", reportBlockService.getBlocks(report.getReportType(), reportId));
 
+            // 报告解读(前端 gut-flora-detail 读取 reportInterpretation.gutAgeAssessment)
+            Map<String, Object> interp = new HashMap<>();
+            StringBuilder sb = new StringBuilder();
+            if (report.getGutAge() != null && !report.getGutAge().isEmpty()) {
+                sb.append("您的肠道年龄为 ").append(report.getGutAge()).append(" 岁");
+            }
+            if (report.getGutType() != null && !report.getGutType().isEmpty()) {
+                if (sb.length() > 0) sb.append(",");
+                sb.append("肠道类型为 ").append(report.getGutType());
+            }
+            if (sb.length() > 0) {
+                interp.put("gutAgeAssessment", sb.toString());
+                detail.put("reportInterpretation", interp);
+            }
+
             // 编辑回填: payload_json 快照 → 嵌套 payload 结构
             if (report.getPayloadJson() != null && !report.getPayloadJson().isEmpty()) {
                 try {

+ 57 - 23
cfc-frontend/pages/health/gut-flora-detail.vue

@@ -10,12 +10,16 @@
           <text class="meta-item" v-if="report.age">{{ report.age }}岁</text>
         </view>
         <view class="report-meta">
-          <text class="meta-item" v-if="report.reportDate">日期: {{ report.reportDate }}</text>
+          <text class="meta-item" v-if="report.reportDate">日期: {{ reportDateText }}</text>
           <text class="meta-item" v-if="report.reportNumber">编号: {{ report.reportNumber }}</text>
         </view>
-        <view class="badge-row" v-if="report.gutAge || report.gutType">
+        <view class="badge-row" v-if="report.gutAge || report.gutType || report.shannonIndex">
           <text class="badge" v-if="report.gutAge">肠龄: {{ report.gutAge }}</text>
           <text class="badge" v-if="report.gutType">肠型: {{ report.gutType }}</text>
+          <text class="badge" v-if="report.shannonIndex">多样性指数: {{ report.shannonIndex }}</text>
+        </view>
+        <view class="rating-line" v-if="report.healthRating">
+          <text class="rating-text">综合评级: {{ report.healthRating }}</text>
         </view>
         <!-- 报告解读 -->
         <view class="interpretation-box" v-if="reportInterpretation && reportInterpretation.gutAgeAssessment">
@@ -133,16 +137,18 @@
           <text class="accordion-arrow">{{ accordionOpen.gut ? '收起' : '展开' }}</text>
         </view>
         <view class="accordion-body" v-if="accordionOpen.gut">
-          <view class="indicator-card" v-for="(item, idx) in gutIndicators" :key="idx">
-            <view class="indicator-row">
-              <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
-                {{ item.indicatorName }}
-                <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
-                <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
-                <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
-              </text>
-              <view class="indicator-value" v-if="!isEditing">
+            <view class="indicator-card" v-for="(item, idx) in gutIndicators" :key="idx">
+              <view class="indicator-row">
+<text class="ind-help-btn" @tap.stop="showKnowledge(indicatorType(item), item.indicatorName)">?</text>
+                <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
+                  {{ item.indicatorName }}
+                  <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
+                  <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
+                  <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
+                </text>
+                <view class="indicator-value" v-if="!isEditing">
                 <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
+                <text class="indicator-unit" v-if="item.unit">{{ item.unit }}</text>
               </view>
               <view class="indicator-edit" v-if="isEditing">
                 <input class="edit-input" type="text" v-model="item.indicatorValue" />
@@ -152,6 +158,7 @@
               </view>
             </view>
             <text class="indicator-range" v-if="item.refRange">参考: {{ item.refRange }}</text>
+            <text class="indicator-symptoms" v-if="item.symptoms">症状: {{ item.symptoms }}</text>
           </view>
           <view class="empty-hint" v-if="gutIndicators.length === 0">暂无数据</view>
         </view>
@@ -167,16 +174,18 @@
         <view class="accordion-body" v-if="accordionOpen.nutri">
           <view class="sub-group" v-for="(group, gIdx) in nutritionGroups" :key="gIdx">
             <text class="sub-group-title">{{ group.category }}</text>
-            <view class="indicator-card" v-for="(item, idx) in group.items" :key="idx">
-              <view class="indicator-row">
-                <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
-                  {{ item.indicatorName }}
-                  <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
-                  <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
-                  <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
-                </text>
-                <view class="indicator-value" v-if="!isEditing">
+              <view class="indicator-card" v-for="(item, idx) in group.items" :key="idx">
+                <view class="indicator-row">
+<text class="ind-help-btn" @tap.stop="showKnowledge(indicatorType(item), item.indicatorName)">?</text>
+                  <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
+                    {{ item.indicatorName }}
+                    <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
+                    <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
+                    <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
+                  </text>
+                  <view class="indicator-value" v-if="!isEditing">
                   <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
+                  <text class="indicator-unit" v-if="item.unit">{{ item.unit }}</text>
                 </view>
                 <view class="indicator-edit" v-if="isEditing">
                   <input class="edit-input" type="text" v-model="item.indicatorValue" />
@@ -185,7 +194,8 @@
                   </picker>
                 </view>
               </view>
-              <text class="indicator-range" v-if="item.refRange">参考: {{ item.refRange }}</text>
+<text class="indicator-range" v-if="item.refRange">参考: {{ item.refRange }}</text>
+            <text class="indicator-symptoms" v-if="item.symptoms">症状: {{ item.symptoms }}</text>
             </view>
           </view>
           <view class="empty-hint" v-if="nutritionGroups.length === 0">暂无数据</view>
@@ -233,6 +243,7 @@
 import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis, confirmReportPreview, discardReportDraft, parseReportDraft, getHealthFoods, getFoodCautionList } from '../../utils/api.js'
 import HealthKnowledgePopup from '../../components/health-knowledge-popup.vue'
 import FeedbackPopup from '../../components/feedback-popup.vue'
+import { parseDate } from '../../utils/format.js'
 
 var INDICATOR_TYPE_MAP = {
   '维生素': 'vitamin',
@@ -317,6 +328,17 @@ export default {
   },
   methods: {
     goBack: function() { uni.navigateBack() },
+    /** 报告日期仅显示 yyyy-MM-dd(去掉时间部分) */
+    reportDateText: function() {
+      var d = this.report.reportDate
+      if (!d) return ''
+      var date = parseDate(d)
+      if (!date) return String(d).substring(0, 10)
+      var y = date.getFullYear()
+      var m = ('' + (date.getMonth() + 1)).padStart(2, '0')
+      var day = ('' + date.getDate()).padStart(2, '0')
+      return y + '-' + m + '-' + day
+    },
     /** 中文状态 → CSS安全英文 */
     _cssForStatus: function(s) {
       var map = { '偏高': 'high', '过多': 'excess', '偏低': 'low', '缺乏': 'deficient', '不足': 'insufficient', '异常': 'abnormal', '注意': 'warning' }
@@ -407,8 +429,9 @@ export default {
         report.personName = s.personName || ''
         report.reportNumber = s.reportNumber || ''
         report.gutAge = s.gutAge || ''
-        report.gutType = s.gutType || ''
-        report.overallScore = s.overallScore
+report.gutType = s.gutType || ''
+      report.shannonIndex = s.shannonIndex || ''
+      report.overallScore = s.overallScore
         report.gutHealthScore = s.gutHealthScore
         report.chronicDiseaseScore = s.chronicDiseaseScore
         report.nutritionScore = s.nutritionScore
@@ -732,6 +755,17 @@ export default {
 
 .indicator-range { font-size: 22rpx; color: #bbb; display: block; margin-top: 4rpx; }
 
+/* 指标知识库 "?" 按钮(与通用报告页一致) */
+.ind-help-btn { display: inline-flex; align-items: center; justify-content: center; width: 36rpx; height: 36rpx; border-radius: 50%; background: #E3F2FD; color: #1976D2; font-size: 22rpx; font-weight: 600; margin-left: 8rpx; flex-shrink: 0; }
+
+/* 综合评级行 */
+.rating-line { margin: 12rpx 0 0; }
+.rating-text { font-size: 24rpx; color: #666; display: block; }
+
+/* 指标单位与症状 */
+.indicator-unit { font-size: 22rpx; color: #999; margin-left: 6rpx; }
+.indicator-symptoms { font-size: 22rpx; color: #E65100; display: block; margin-top: 4rpx; }
+
 .indicator-edit { display: flex; gap: 8rpx; align-items: center; }
 .edit-input { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 6rpx 10rpx; font-size: 26rpx; width: 100rpx; }
 .edit-picker { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 6rpx 10rpx; font-size: 24rpx; color: #333; }

+ 36 - 30
cfc-frontend/pages/health/report-list.vue

@@ -27,26 +27,25 @@
 
     <!-- 报告列表 -->
     <view class="report-list" v-else-if="reports.length > 0">
-      <view
-        class="report-card"
-        v-for="item in reports"
-        :key="item.id"
-        @click="goToDetail(item.id, item.reportType)"
-      >
-        <view class="report-card-left">
-          <view class="report-type-badge" :class="'badge-' + (item.reportType === 'gut_flora' ? 'gut' : item.reportType === 'tongue' ? 'tongue' : 'exam')">
-            <text>{{ item.reportType === 'gut_flora' ? '肠道检测' : item.reportType === 'tongue' ? '舌诊' : '体检报告' }}</text>
+      <swipe-action v-for="item in reports" :key="item.id">
+        <view class="report-card" @click="goToDetail(item.id, item.reportType)">
+          <view class="report-card-left">
+            <view class="report-type-badge" :class="'badge-' + (item.reportType === 'gut_flora' ? 'gut' : item.reportType === 'tongue' ? 'tongue' : 'exam')">
+              <text>{{ item.reportType === 'gut_flora' ? '肠道检测' : item.reportType === 'tongue' ? '舌诊' : '体检报告' }}</text>
+            </view>
+            <text class="report-person" v-if="item.personName">{{ item.personName }}</text>
+            <text class="report-date">报告日期:{{ formatDate(item.reportDate) }}</text>
+            <text class="report-upload-date">上传日期:{{ formatDate(item.createdAt) }}</text>
+          </view>
+          <view class="report-card-right">
+            <text class="report-score">{{ item.overallScore || '--' }}<text class="score-unit">分</text></text>
+            <text class="report-arrow">›</text>
           </view>
-          <text class="report-person" v-if="item.personName">{{ item.personName }}</text>
-          <text class="report-date">报告日期:{{ formatDate(item.reportDate) }}</text>
-          <text class="report-upload-date">上传日期:{{ formatDate(item.createdAt) }}</text>
-        </view>
-        <view class="report-card-right">
-          <text class="report-score">{{ item.overallScore || '--' }}<text class="score-unit">分</text></text>
-          <view class="report-del" @click.stop="confirmDelete(item)"><text class="report-del-text">删除</text></view>
-          <text class="report-arrow">›</text>
         </view>
-      </view>
+        <template #right>
+          <button class="swipe-delete" @click.stop="confirmDelete(item)">删除</button>
+        </template>
+      </swipe-action>
     </view>
 
     <!-- 空状态 -->
@@ -297,18 +296,6 @@ export default {
   align-items: center;
   gap: 12rpx;
 }
-.report-del {
-  padding: 8rpx 20rpx;
-  border: 1rpx solid #F56C6C;
-  border-radius: 24rpx;
-}
-.report-del:active {
-  background: #FEF0F0;
-}
-.report-del-text {
-  font-size: 22rpx;
-  color: #F56C6C;
-}
 .report-score {
   font-size: 36rpx;
   font-weight: bold;
@@ -359,6 +346,25 @@ export default {
   opacity: 0.8;
 }
 
+/* ===== 滑动删除 ===== */
+.swipe-delete {
+  height: 100%;
+  width: 160rpx;
+  background: #F56C6C;
+  color: #fff;
+  font-size: 28rpx;
+  border: none;
+  border-radius: 0;
+}
+
+/* swipe-action 组件内部样式覆盖 */
+/deep/ .uni-swipe-action__content {
+  border-radius: 16rpx;
+}
+/deep/ .uni-swipe-action__button {
+  border-radius: 0 16rpx 16rpx 0;
+}
+
 /* ===== 底部 ===== */
 .bottom-spacer {
   height: 60rpx;

+ 17 - 4
cfc-frontend/pages/index-home/index.vue

@@ -1203,12 +1203,25 @@ export default {
         })
       }, 500)
     },
-    getImageUrl: function(path) {
+getImageUrl: function(path) {
       return config.API_BASE_URL + path
     },
-
-    
-
+    /** 转发给朋友/群 */
+    onShareAppMessage: function() {
+      return {
+        title: '浠艾福 · 家庭五维幸福成长',
+        path: '/pages/index-home/index',
+        imageUrl: '/static/share-cover.png'
+      }
+    },
+    /** 分享到朋友圈 */
+    onShareTimeline: function() {
+      return {
+        title: '浠艾福 · 家庭五维幸福成长',
+        query: '',
+        imageUrl: '/static/share-cover.png'
+      }
+    }
   }
 }
 </script>