Răsfoiți Sursa

feat(frontend): 健康报告确认页指标按类别分组折叠, 支持全部展开/折叠与需处理计数

liaoxg 1 lună în urmă
părinte
comite
9a5adb5cba
1 a modificat fișierele cu 157 adăugiri și 16 ștergeri
  1. 157 16
      cfc-frontend/pages/health/report-confirm.vue

+ 157 - 16
cfc-frontend/pages/health/report-confirm.vue

@@ -147,23 +147,46 @@
 
       <!-- 健康指标(舌诊时隐藏,使用上方舌象指标卡片) -->
       <view class="card" v-if="payload.type !== 'tongue' && payload.indicators && payload.indicators.length">
-        <view class="card-title">健康指标 ({{ payload.indicators.length }})</view>
-        <view class="indicator-item" v-for="(ind, idx) in payload.indicators" :key="idx">
-          <view class="ind-header">
-            <input class="ind-name" v-model="payload.indicators[idx].name" />
-            <input class="ind-value" v-model="payload.indicators[idx].value" />
+        <view class="card-title">
+          <text>健康指标 ({{ payload.indicators.length }})</text>
+          <view class="card-title-actions">
+            <text class="expand-all-btn" @click="toggleAllGroups(true)">全部展开</text>
+            <text class="expand-all-btn" @click="toggleAllGroups(false)">全部折叠</text>
           </view>
-          <view class="ind-detail" v-if="payload.indicators[idx].unit || payload.indicators[idx].referenceRange">
-            <text class="ind-ref" v-if="payload.indicators[idx].referenceRange">参考: {{ payload.indicators[idx].referenceRange }}</text>
-            <text class="ind-unit" v-if="payload.indicators[idx].unit">{{ payload.indicators[idx].unit }}</text>
-          </view>
-          <view class="ind-symptoms" v-if="payload.indicators[idx].symptoms">
-            <text class="ind-symptom-label">症状:</text>
-            <input class="ind-symptom-input" v-model="payload.indicators[idx].symptoms" />
+        </view>
+
+        <!-- 按指标类型分组折叠 -->
+        <view class="indicator-group" v-for="(group, gIdx) in indicatorGroups" :key="gIdx">
+          <view class="indicator-group-header" @click="toggleGroup(gIdx)">
+            <view class="indicator-group-header-left">
+              <text class="indicator-group-arrow" :class="{ 'arrow-open': groupExpandList[gIdx] }">›</text>
+              <text class="indicator-group-name">{{ group.category }}</text>
+            </view>
+            <view class="indicator-group-header-right">
+              <text class="indicator-group-need" v-if="group.needCount > 0">{{ group.needCount }} 项需处理</text>
+              <text class="indicator-group-count">{{ group.items.length }} 项</text>
+            </view>
           </view>
-          <view class="ind-status-row">
-            <view class="status-tag" :class="'status-' + (payload.indicators[idx].status || 'normal')">
-              {{ statusText(payload.indicators[idx].status) }}
+
+          <view class="indicator-group-body" v-if="groupExpandList[gIdx]">
+            <view class="indicator-item" v-for="(ind, idx) in group.items" :key="idx">
+              <view class="ind-header">
+                <input class="ind-name" v-model="ind.indicatorName" />
+                <input class="ind-value" v-model="ind.indicatorValue" />
+              </view>
+              <view class="ind-detail" v-if="ind.unit || ind.refRange">
+                <text class="ind-ref" v-if="ind.refRange">参考: {{ ind.refRange }}</text>
+                <text class="ind-unit" v-if="ind.unit">{{ ind.unit }}</text>
+              </view>
+              <view class="ind-symptoms" v-if="ind.symptoms">
+                <text class="ind-symptom-label">症状:</text>
+                <input class="ind-symptom-input" v-model="ind.symptoms" />
+              </view>
+              <view class="ind-status-row">
+                <view class="status-tag" :class="'status-' + (ind.status || 'normal')">
+                  {{ statusText(ind.status) }}
+                </view>
+              </view>
             </view>
           </view>
         </view>
@@ -256,7 +279,9 @@ export default {
         age: ''
       },
       creatingMember: false,
-      createdMemberId: null
+      createdMemberId: null,
+      // 指标分组折叠状态(按分组下标,默认全部折叠)
+      groupExpandList: []
     }
   },
   computed: {
@@ -286,6 +311,27 @@ export default {
       if (this.matchLabel === 'profile_match') return '根据档案信息推测匹配,请手动确认'
       if (this.matchLabel === 'no_match') return '无法匹配到现有家庭成员,可新建成员'
       return ''
+    },
+    // 指标按类型(category)分组,保持解析顺序,统计需处理(非正常)数量
+    indicatorGroups() {
+      var groups = []
+      var orderMap = {}
+      var list = (this.payload && this.payload.indicators) || []
+      for (var i = 0; i < list.length; i++) {
+        var ind = list[i]
+        var cat = ind.category || '其他指标'
+        if (!orderMap[cat]) {
+          orderMap[cat] = groups.length
+          groups.push({ category: cat, items: [], needCount: 0 })
+        }
+        var group = groups[orderMap[cat]]
+        group.items.push(ind)
+        var st = ind.status || 'normal'
+        if (st === 'high' || st === 'low' || st === 'abnormal') {
+          group.needCount++
+        }
+      }
+      return groups
     }
   },
   onLoad: function(options) {
@@ -418,6 +464,16 @@ export default {
         self.creatingMember = false
       })
     },
+    toggleGroup(gIdx) {
+      this.$set(this.groupExpandList, gIdx, !this.groupExpandList[gIdx])
+    },
+    toggleAllGroups(expand) {
+      var arr = []
+      for (var i = 0; i < this.indicatorGroups.length; i++) {
+        arr.push(expand)
+      }
+      this.groupExpandList = arr
+    },
     statusText(status) {
       if (status === 'high') return '偏高'
       if (status === 'low') return '偏低'
@@ -566,6 +622,91 @@ export default {
   margin-bottom: 20rpx;
   padding-bottom: 12rpx;
   border-bottom: 2rpx solid #F0F0F0;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
+.card-title-actions {
+  display: flex;
+  flex-direction: row;
+  gap: 16rpx;
+}
+.expand-all-btn {
+  font-size: 22rpx;
+  color: #5B9BD5;
+  padding: 4rpx 12rpx;
+  border: 2rpx solid #5B9BD5;
+  border-radius: 20rpx;
+}
+.expand-all-btn:active { opacity: 0.7; }
+
+/* ===== 指标分组折叠 ===== */
+.indicator-group {
+  border: 2rpx solid #E8EDF2;
+  border-radius: 12rpx;
+  margin-bottom: 12rpx;
+  overflow: hidden;
+}
+.indicator-group-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx 16rpx;
+  background: #F1F5F9;
+}
+.indicator-group-header:active { opacity: 0.8; }
+.indicator-group-header-left {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  flex: 1;
+  min-width: 0;
+}
+.indicator-group-arrow {
+  font-size: 30rpx;
+  color: #94A3B8;
+  margin-right: 12rpx;
+  transform: rotate(0deg);
+  transition: transform 0.2s ease;
+  flex-shrink: 0;
+}
+.indicator-group-arrow.arrow-open {
+  transform: rotate(90deg);
+}
+.indicator-group-name {
+  font-size: 26rpx;
+  color: #333;
+  font-weight: 600;
+  flex: 1;
+  min-width: 0;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}
+.indicator-group-header-right {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  gap: 12rpx;
+  flex-shrink: 0;
+  margin-left: 12rpx;
+}
+.indicator-group-need {
+  font-size: 20rpx;
+  color: #E65100;
+  background: #FFF3E0;
+  padding: 4rpx 12rpx;
+  border-radius: 20rpx;
+  font-weight: 500;
+}
+.indicator-group-count {
+  font-size: 22rpx;
+  color: #94A3B8;
+}
+.indicator-group-body {
+  padding: 16rpx;
 }
 
 .field-row {