Przeglądaj źródła

feat: expand health survey with disease/family history, add physical exam detail page

asus 3 miesięcy temu
rodzic
commit
b311cf8fbf

+ 6 - 0
cfc-frontend/pages.json

@@ -934,6 +934,12 @@
             "navigationBarTitleText": "健康报告"
           }
         },
+        {
+          "path": "physical-exam-detail",
+          "style": {
+            "navigationBarTitleText": "体检报告详情"
+          }
+        },
         {
           "path": "nutrition-profile",
           "style": {

+ 326 - 0
cfc-frontend/pages/health/physical-exam-detail.vue

@@ -0,0 +1,326 @@
+<template>
+  <view class="detail-container">
+    <!-- 导航栏 -->
+    <view class="nav-bar">
+      <text class="nav-back" @click="goBack">‹ 返回</text>
+      <text class="nav-title">体检报告详情</text>
+      <view class="nav-placeholder"></view>
+    </view>
+
+    <!-- 加载状态 -->
+    <view class="loading-wrap" v-if="loading">
+      <text class="loading-text">加载中...</text>
+    </view>
+
+    <template v-else>
+      <!-- 报告摘要卡片 -->
+      <view class="summary-card">
+        <view class="summary-header">
+          <text class="summary-icon">🏥</text>
+          <text class="summary-title">体检报告</text>
+        </view>
+        <view class="summary-body">
+          <view class="summary-row">
+            <text class="summary-label">报告日期</text>
+            <text class="summary-value">{{ report.reportDate || '--' }}</text>
+          </view>
+          <view class="summary-row">
+            <text class="summary-label">综合评分</text>
+            <text class="summary-score" :class="scoreClass">{{ report.overallScore || '--' }}<text class="score-unit">分</text></text>
+          </view>
+          <view class="summary-row">
+            <text class="summary-label">报告编号</text>
+            <text class="summary-value">{{ report.reportNumber || '--' }}</text>
+          </view>
+          <view class="summary-row">
+            <text class="summary-label">备注</text>
+            <text class="summary-value">{{ report.remark || '无' }}</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- 指标分类列表 -->
+      <view class="indicator-section" v-for="(group, groupName) in groupedIndicators" :key="groupName">
+        <view class="group-header">
+          <text class="group-icon">{{ getCategoryIcon(groupName) }}</text>
+          <text class="group-title">{{ groupName }}</text>
+          <text class="group-count">{{ group.length }}项</text>
+        </view>
+
+        <view class="indicator-list">
+          <view class="indicator-item" v-for="ind in group" :key="ind.id || ind.indicatorName">
+            <view class="indicator-left">
+              <text class="indicator-name">{{ ind.indicatorName }}</text>
+              <text class="indicator-range" v-if="ind.refRange">参考值: {{ ind.refRange }}</text>
+            </view>
+            <view class="indicator-right">
+              <text class="indicator-value" :class="getStatusClass(ind)">{{ ind.indicatorValue }}</text>
+              <text class="indicator-unit" v-if="ind.unit">{{ ind.unit }}</text>
+              <text class="indicator-status" :class="getStatusClass(ind)">{{ ind.status || '--' }}</text>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- 空状态 -->
+      <view class="empty-wrap" v-if="!loading && indicatorGroups.length === 0">
+        <text class="empty-icon">📋</text>
+        <text class="empty-text">暂无指标数据</text>
+      </view>
+    </template>
+
+    <view class="bottom-spacer"></view>
+  </view>
+</template>
+
+<script>
+import { getReportDetail } from '../../utils/api.js'
+
+export default {
+  data() {
+    return {
+      reportId: null,
+      report: {},
+      indicators: [],
+      loading: false
+    }
+  },
+  computed: {
+    groupedIndicators: function() {
+      var groups = {}
+      var self = this
+      self.indicators.forEach(function(ind) {
+        var cat = ind.category || '其他指标'
+        if (!groups[cat]) {
+          groups[cat] = []
+        }
+        groups[cat].push(ind)
+      })
+      // 按分类排序,常用分类在前
+      var priority = ['血常规', '肝功能', '肾功能', '血脂', '血糖', '尿常规', '便常规',
+        '甲状腺功能', '肿瘤标志物', '维生素', '微量元素', '营养状况', '炎症指标',
+        '免疫指标', '其他指标']
+      var sorted = {}
+      priority.forEach(function(p) {
+        if (groups[p]) {
+          sorted[p] = groups[p]
+          delete groups[p]
+        }
+      })
+      // 剩余分类保持原序
+      Object.keys(groups).forEach(function(k) {
+        sorted[k] = groups[k]
+      })
+      return sorted
+    },
+    scoreClass: function() {
+      var s = parseInt(this.report.overallScore)
+      if (isNaN(s)) return ''
+      if (s >= 80) return 'score-good'
+      if (s >= 60) return 'score-warn'
+      return 'score-bad'
+    }
+  },
+  onLoad: function(options) {
+    this.reportId = options.reportId ? parseInt(options.reportId) : null
+    this.loadData()
+  },
+  methods: {
+    loadData: function() {
+      var self = this
+      if (!self.reportId) {
+        uni.showToast({ title: '参数错误', icon: 'none' })
+        return
+      }
+      self.loading = true
+      getReportDetail(self.reportId).then(function(res) {
+        if (res.code === 200 && res.data) {
+          var data = res.data
+          self.report = data.report || data
+          // 指标可能来自 indicators 字段或直接是 report 的一部分
+          if (data.indicators) {
+            self.indicators = data.indicators
+          } else if (data.indicatorList) {
+            self.indicators = data.indicatorList
+          } else {
+            self.indicators = []
+          }
+        } else {
+          uni.showToast({ title: res.message || '加载失败', icon: 'none' })
+        }
+      }).catch(function(e) {
+        console.log('获取报告详情失败', e)
+        uni.showToast({ title: '加载失败', icon: 'none' })
+      }).finally(function() {
+        self.loading = false
+      })
+    },
+    getCategoryIcon: function(cat) {
+      var icons = {
+        '血常规': '🩸',
+        '肝功能': '🫁',
+        '肾功能': '🫘',
+        '血脂': '🫧',
+        '血糖': '🍬',
+        '尿常规': '💧',
+        '便常规': '💩',
+        '甲状腺功能': '🦋',
+        '肿瘤标志物': '🔬',
+        '维生素': '🍊',
+        '微量元素': '⚗️',
+        '营养状况': '🥗',
+        '炎症指标': '🔥',
+        '免疫指标': '🛡️',
+      }
+      return icons[cat] || '📊'
+    },
+    getStatusClass: function(ind) {
+      var status = (ind.status || '').toLowerCase()
+      if (status.indexOf('偏高') !== -1 || status.indexOf('高') !== -1 || status.indexOf('过多') !== -1) {
+        return 'status-high'
+      }
+      if (status.indexOf('偏低') !== -1 || status.indexOf('低') !== -1 || status.indexOf('缺乏') !== -1) {
+        return 'status-low'
+      }
+      if (status.indexOf('注意') !== -1 || status.indexOf('风险') !== -1) {
+        return 'status-warn'
+      }
+      if (status.indexOf('正常') !== -1 || status.indexOf('未检出') !== -1) {
+        return 'status-normal'
+      }
+      return ''
+    },
+    goBack: function() {
+      uni.navigateBack()
+    }
+  }
+}
+</script>
+
+<style scoped>
+.detail-container {
+  min-height: 100vh;
+  background: #F5F7FA;
+}
+.nav-bar {
+  position: sticky;
+  top: 0;
+  background: #fff;
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 20rpx 30rpx;
+  box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06);
+  z-index: 100;
+}
+.nav-back { font-size: 32rpx; color: #5B9BD5; }
+.nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
+.nav-placeholder { width: 80rpx; }
+
+.loading-wrap {
+  display: flex;
+  justify-content: center;
+  padding: 80rpx 0;
+}
+.loading-text { font-size: 28rpx; color: #999; }
+
+/* 摘要卡片 */
+.summary-card {
+  background: linear-gradient(135deg, #E3F2FD, #BBDEFB);
+  border-radius: 20rpx;
+  margin: 20rpx 30rpx;
+  padding: 28rpx 24rpx;
+}
+.summary-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin-bottom: 20rpx;
+}
+.summary-icon { font-size: 40rpx; margin-right: 12rpx; }
+.summary-title { font-size: 32rpx; font-weight: bold; color: #1565C0; }
+.summary-body { display: flex; flex-direction: column; gap: 14rpx; }
+.summary-row {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+}
+.summary-label { font-size: 26rpx; color: #555; }
+.summary-value { font-size: 26rpx; color: #333; font-weight: 500; }
+.summary-score { font-size: 40rpx; font-weight: bold; }
+.score-unit { font-size: 22rpx; font-weight: normal; }
+.score-good { color: #2E7D32; }
+.score-warn { color: #E65100; }
+.score-bad { color: #C62828; }
+
+/* 指标分组 */
+.indicator-section {
+  margin: 0 30rpx 20rpx;
+}
+.group-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  padding: 16rpx 20rpx;
+  background: #fff;
+  border-radius: 16rpx 16rpx 0 0;
+  border-bottom: 2rpx solid #E2E8F0;
+}
+.group-icon { font-size: 28rpx; margin-right: 8rpx; }
+.group-title { font-size: 28rpx; font-weight: bold; color: #333; flex: 1; }
+.group-count { font-size: 22rpx; color: #999; }
+
+.indicator-list {
+  background: #fff;
+  border-radius: 0 0 16rpx 16rpx;
+  overflow: hidden;
+}
+.indicator-item {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  justify-content: space-between;
+  padding: 18rpx 20rpx;
+  border-bottom: 1rpx solid #F0F0F0;
+}
+.indicator-item:last-child { border-bottom: none; }
+.indicator-left {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 4rpx;
+}
+.indicator-name { font-size: 28rpx; color: #333; font-weight: 500; }
+.indicator-range { font-size: 22rpx; color: #999; }
+.indicator-right {
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+  gap: 4rpx;
+  margin-left: 20rpx;
+  min-width: 120rpx;
+}
+.indicator-value { font-size: 32rpx; font-weight: bold; }
+.indicator-unit { font-size: 22rpx; color: #999; }
+.indicator-status { font-size: 20rpx; padding: 2rpx 10rpx; border-radius: 10rpx; }
+
+/* 状态颜色 */
+.status-high { color: #C62828; }
+.status-low { color: #E65100; }
+.status-warn { color: #F57F17; }
+.status-normal { color: #2E7D32; }
+
+/* 空状态 */
+.empty-wrap {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  padding: 100rpx 0;
+}
+.empty-icon { font-size: 80rpx; margin-bottom: 20rpx; }
+.empty-text { font-size: 28rpx; color: #999; }
+
+.bottom-spacer { height: 60rpx; }
+</style>

+ 6 - 4
cfc-frontend/pages/health/report-list.vue

@@ -78,11 +78,13 @@ export default {
       uni.navigateBack()
     },
     goToDetail(reportId, reportType) {
-      if (reportType === 'gut_flora') {
-        uni.showToast({ title: '报告详情页开发中', icon: 'none' })
-      } else {
-        uni.navigateTo({ url: '/pages/body/health-report?reportId=' + reportId })
+      var detailPages = {
+        'gut_flora': '/pages/health/gut-flora-detail',
+        'physical_exam': '/pages/health/physical-exam-detail',
+        'tongue': '/pages/health/tongue-index',
       }
+      var url = detailPages[reportType] || '/pages/body/health-report'
+      uni.navigateTo({ url: url + '?reportId=' + reportId })
     },
     goToUpload() {
       uni.navigateTo({ url: '/pages/health/report-upload' })

+ 456 - 45
cfc-frontend/pages/health/report-survey.vue

@@ -15,14 +15,21 @@
     <!-- 提示说明 -->
     <view class="tip-banner">
       <text class="tip-icon">💡</text>
-      <text class="tip-text">请根据孩子最近的实际情况填写,帮助AI更准确地解读报告并提供个性化方案</text>
+      <text class="tip-text">请根据孩子和家庭的实际情况填写,帮助AI更准确地解读报告</text>
     </view>
 
     <!-- 表单 -->
     <view class="form-section">
-      <!-- Q1: 饮食情况 -->
+
+      <!-- ===================== 第一部分:生活习惯 ===================== -->
+      <view class="section-header">
+        <text class="section-icon">🍚</text>
+        <text class="section-title">生活习惯</text>
+      </view>
+
+      <!-- Q1: 饮食结构 -->
       <view class="question-card">
-        <text class="q-number">1/6</text>
+        <text class="q-number">1/12</text>
         <text class="q-title">孩子平时的饮食结构是怎样的?</text>
         <view class="q-options">
           <view class="q-option" :class="{ 'q-selected': survey.diet === 'balanced' }" @click="survey.diet = 'balanced'">
@@ -46,23 +53,23 @@
 
       <!-- Q2: 消化状况 -->
       <view class="question-card">
-        <text class="q-number">2/6</text>
+        <text class="q-number">2/12</text>
         <text class="q-title">孩子近期消化状况如何?</text>
         <view class="q-options">
           <view class="q-option" :class="{ 'q-selected': survey.digestion === 'good' }" @click="survey.digestion = 'good'">
-            <text class="q-icon">😊</text>
+            <text>😊</text>
             <text class="q-text">正常,消化良好</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.digestion === 'constipation' }" @click="survey.digestion = 'constipation'">
-            <text class="q-icon">😣</text>
+            <text>😣</text>
             <text class="q-text">容易便秘</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.digestion === 'diarrhea' }" @click="survey.digestion = 'diarrhea'">
-            <text class="q-icon">😟</text>
+            <text>😟</text>
             <text class="q-text">容易腹泻</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.digestion === 'irregular' }" @click="survey.digestion = 'irregular'">
-            <text class="q-icon">🤔</text>
+            <text>🤔</text>
             <text class="q-text">不太规律</text>
           </view>
         </view>
@@ -70,23 +77,23 @@
 
       <!-- Q3: 睡眠质量 -->
       <view class="question-card">
-        <text class="q-number">3/6</text>
+        <text class="q-number">3/12</text>
         <text class="q-title">孩子近期的睡眠质量如何?</text>
         <view class="q-options">
           <view class="q-option" :class="{ 'q-selected': survey.sleep === 'good' }" @click="survey.sleep = 'good'">
-            <text class="q-icon">😴</text>
+            <text>😴</text>
             <text class="q-text">睡得好,作息规律</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.sleep === 'late' }" @click="survey.sleep = 'late'">
-            <text class="q-icon">🦉</text>
+            <text>🦉</text>
             <text class="q-text">睡得太晚</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.sleep === 'restless' }" @click="survey.sleep = 'restless'">
-            <text class="q-icon">😰</text>
+            <text>😰</text>
             <text class="q-text">翻来覆去睡不安稳</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.sleep === 'short' }" @click="survey.sleep = 'short'">
-            <text class="q-icon">⏰</text>
+            <text>⏰</text>
             <text class="q-text">睡眠时间不足</text>
           </view>
         </view>
@@ -94,69 +101,281 @@
 
       <!-- Q4: 运动习惯 -->
       <view class="question-card">
-        <text class="q-number">4/6</text>
+        <text class="q-number">4/12</text>
         <text class="q-title">孩子每周运动情况?</text>
         <view class="q-options">
           <view class="q-option" :class="{ 'q-selected': survey.exercise === 'frequent' }" @click="survey.exercise = 'frequent'">
-            <text class="q-icon">🏃</text>
+            <text>🏃</text>
             <text class="q-text">经常运动(3次以上/周)</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.exercise === 'moderate' }" @click="survey.exercise = 'moderate'">
-            <text class="q-icon">🚶</text>
+            <text>🚶</text>
             <text class="q-text">偶尔运动(1-2次/周)</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.exercise === 'rare' }" @click="survey.exercise = 'rare'">
-            <text class="q-icon">🪑</text>
+            <text>🪑</text>
             <text class="q-text">很少运动</text>
           </view>
         </view>
       </view>
 
-      <!-- Q5: 过敏/不适 -->
+      <!-- Q5: 饮水习惯 -->
       <view class="question-card">
-        <text class="q-number">5/6</text>
-        <text class="q-title">孩子是否有以下过敏或不适症状?(可多选)</text>
+        <text class="q-number">5/12</text>
+        <text class="q-title">孩子每日饮水量如何?</text>
         <view class="q-options">
+          <view class="q-option" :class="{ 'q-selected': survey.water === 'enough' }" @click="survey.water = 'enough'">
+            <text>💧</text>
+            <text class="q-text">充足(>1500ml)</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.water === 'moderate' }" @click="survey.water = 'moderate'">
+            <text>🥤</text>
+            <text class="q-text">一般(500-1500ml)</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.water === 'little' }" @click="survey.water = 'little'">
+            <text>😥</text>
+            <text class="q-text">偏少(<500ml)</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- Q6: 用药情况 -->
+      <view class="question-card">
+        <text class="q-number">6/12</text>
+        <text class="q-title">孩子近期是否有以下用药情况?(可多选)</text>
+        <view class="q-options multi">
+          <view class="q-option" :class="{ 'q-selected': hasMedication('antibiotic') }" @click="toggleMedication('antibiotic')">
+            <text class="q-text">抗生素(近3个月内使用过)</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasMedication('probiotic') }" @click="toggleMedication('probiotic')">
+            <text class="q-text">益生菌/益生元</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasMedication('cold') }" @click="toggleMedication('cold')">
+            <text class="q-text">感冒药/退烧药</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasMedication('stomach') }" @click="toggleMedication('stomach')">
+            <text class="q-text">肠胃药</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasMedication('none') }" @click="toggleMedication('none')">
+            <text class="q-text">无特殊用药</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- ===================== 第二部分:疾病史 ===================== -->
+      <view class="section-header">
+        <text class="section-icon">🏥</text>
+        <text class="section-title">疾病史</text>
+      </view>
+
+      <!-- Q7: 慢性病/系统性疾病 -->
+      <view class="question-card">
+        <text class="q-number">7/12</text>
+        <text class="q-title">孩子是否有以下慢性或系统性疾病?(可多选)</text>
+        <view class="q-options multi">
+          <view class="q-option" :class="{ 'q-selected': hasChronic('none') }" @click="toggleChronic('none')">
+            <text class="q-text">无已知疾病</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('asthma') }" @click="toggleChronic('asthma')">
+            <text class="q-text">哮喘/过敏性鼻炎</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('eczema') }" @click="toggleChronic('eczema')">
+            <text class="q-text">湿疹/特应性皮炎</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('anemia') }" @click="toggleChronic('anemia')">
+            <text class="q-text">贫血</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('obesity') }" @click="toggleChronic('obesity')">
+            <text class="q-text">超重/肥胖</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('underweight') }" @click="toggleChronic('underweight')">
+            <text class="q-text">偏瘦/营养不良</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('thyroid') }" @click="toggleChronic('thyroid')">
+            <text class="q-text">甲状腺问题</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('digestive') }" @click="toggleChronic('digestive')">
+            <text class="q-text">消化系统疾病(胃炎/肠炎等)</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': hasChronic('other') }" @click="toggleChronic('other')">
+            <text class="q-text">其他(请在备注说明)</text>
+          </view>
+        </view>
+        <!-- 其他疾病描述 -->
+        <textarea v-if="hasChronic('other')" class="extra-input" v-model="survey.diseaseHistory.otherDesc" placeholder="请描述其他疾病情况..." maxlength="200" />
+      </view>
+
+      <!-- Q8: 过敏史 -->
+      <view class="question-card">
+        <text class="q-number">8/12</text>
+        <text class="q-title">孩子是否有以下过敏情况?(可多选)</text>
+        <view class="q-options multi">
+          <view class="q-option" :class="{ 'q-selected': survey.allergies.indexOf('none') !== -1 }" @click="toggleAllergy('none')">
+            <text class="q-text">无明显过敏</text>
+          </view>
           <view class="q-option" :class="{ 'q-selected': survey.allergies.indexOf('food') !== -1 }" @click="toggleAllergy('food')">
-            <text class="q-text">食物过敏</text>
+            <text class="q-text">食物过敏(牛奶/鸡蛋/海鲜等)</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.allergies.indexOf('drug') !== -1 }" @click="toggleAllergy('drug')">
+            <text class="q-text">药物过敏</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.allergies.indexOf('pollen') !== -1 }" @click="toggleAllergy('pollen')">
             <text class="q-text">花粉/尘螨过敏</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.allergies.indexOf('skin') !== -1 }" @click="toggleAllergy('skin')">
-            <text class="q-text">皮肤湿疹/皮疹</text>
-          </view>
-          <view class="q-option" :class="{ 'q-selected': survey.allergies.indexOf('none') !== -1 }" @click="toggleAllergy('none')">
-            <text class="q-text">无明显过敏</text>
+            <text class="q-text">皮肤过敏/荨麻疹</text>
           </view>
         </view>
+        <!-- 过敏详情 -->
+        <textarea v-if="survey.allergies.length > 0 && survey.allergies.indexOf('none') === -1" class="extra-input" v-model="survey.allergyDetail" placeholder="请补充过敏详情(过敏原、症状等)..." maxlength="200" />
       </view>
 
-      <!-- Q6: 健康目标 -->
+      <!-- Q9: 手术/住院史 -->
       <view class="question-card">
-        <text class="q-number">6/6</text>
-        <text class="q-title">您希望通过精准营养改善什么?(可多选)</text>
+        <text class="q-number">9/12</text>
+        <text class="q-title">孩子是否有手术或住院史?</text>
         <view class="q-options">
+          <view class="q-option" :class="{ 'q-selected': survey.surgery === 'none' }" @click="survey.surgery = 'none'">
+            <text class="q-text">无</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.surgery === 'yes' }" @click="survey.surgery = 'yes'">
+            <text class="q-text">有</text>
+          </view>
+        </view>
+        <textarea v-if="survey.surgery === 'yes'" class="extra-input" v-model="survey.surgeryDetail" placeholder="请描述手术/住院原因和时间..." maxlength="200" />
+      </view>
+
+      <!-- ===================== 第三部分:家族史 ===================== -->
+      <view class="section-header">
+        <text class="section-icon">🧬</text>
+        <text class="section-title">家族病史</text>
+      </view>
+
+      <!-- Q10: 家族病史 -->
+      <view class="question-card">
+        <text class="q-number">10/12</text>
+        <text class="q-title">直系亲属是否有以下疾病史?(可多选)</text>
+        <view class="family-grid">
+          <view class="family-col">
+            <text class="family-role">父亲</text>
+            <view class="family-options">
+              <view v-for="opt in familyDiseaseOptions" :key="'f_' + opt.value"
+                class="family-tag" :class="{ 'tag-selected': hasFamilyDisease('father', opt.value) }"
+                @click="toggleFamilyDisease('father', opt.value)">
+                <text>{{ opt.label }}</text>
+              </view>
+              <view class="family-tag" :class="{ 'tag-selected': hasFamilyDisease('father', 'other') }"
+                @click="toggleFamilyDisease('father', 'other')">
+                <text>其他</text>
+              </view>
+            </view>
+          </view>
+          <view class="family-col">
+            <text class="family-role">母亲</text>
+            <view class="family-options">
+              <view v-for="opt in familyDiseaseOptions" :key="'m_' + opt.value"
+                class="family-tag" :class="{ 'tag-selected': hasFamilyDisease('mother', opt.value) }"
+                @click="toggleFamilyDisease('mother', opt.value)">
+                <text>{{ opt.label }}</text>
+              </view>
+              <view class="family-tag" :class="{ 'tag-selected': hasFamilyDisease('mother', 'other') }"
+                @click="toggleFamilyDisease('mother', 'other')">
+                <text>其他</text>
+              </view>
+            </view>
+          </view>
+          <view class="family-col">
+            <text class="family-role">兄弟姐妹</text>
+            <view class="family-options">
+              <view v-for="opt in familyDiseaseOptions" :key="'s_' + opt.value"
+                class="family-tag" :class="{ 'tag-selected': hasFamilyDisease('sibling', opt.value) }"
+                @click="toggleFamilyDisease('sibling', opt.value)">
+                <text>{{ opt.label }}</text>
+              </view>
+              <view class="family-tag" :class="{ 'tag-selected': hasFamilyDisease('sibling', 'other') }"
+                @click="toggleFamilyDisease('sibling', 'other')">
+                <text>其他</text>
+              </view>
+            </view>
+          </view>
+        </view>
+      </view>
+
+      <!-- Q11: 祖父母病史 -->
+      <view class="question-card">
+        <text class="q-number">11/12</text>
+        <text class="q-title">祖父母/外祖父母是否有以下疾病史?(可多选)</text>
+        <view class="q-options multi">
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('unknown') !== -1 }" @click="toggleGrandparent('unknown')">
+            <text class="q-text">不清楚</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('hypertension') !== -1 }" @click="toggleGrandparent('hypertension')">
+            <text class="q-text">高血压</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('diabetes') !== -1 }" @click="toggleGrandparent('diabetes')">
+            <text class="q-text">糖尿病</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('heart') !== -1 }" @click="toggleGrandparent('heart')">
+            <text class="q-text">心脏病</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('stroke') !== -1 }" @click="toggleGrandparent('stroke')">
+            <text class="q-text">脑卒中</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('cancer') !== -1 }" @click="toggleGrandparent('cancer')">
+            <text class="q-text">癌症</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('autoimmune') !== -1 }" @click="toggleGrandparent('autoimmune')">
+            <text class="q-text">自身免疫病</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.grandparentHistory.indexOf('none') !== -1 }" @click="toggleGrandparent('none')">
+            <text class="q-text">无明显疾病</text>
+          </view>
+        </view>
+      </view>
+
+      <!-- ===================== 第四部分:健康目标 ===================== -->
+      <view class="section-header">
+        <text class="section-icon">🎯</text>
+        <text class="section-title">健康目标</text>
+      </view>
+
+      <!-- Q12: 健康目标 -->
+      <view class="question-card">
+        <text class="q-number">12/12</text>
+        <text class="q-title">您希望通过本次健康管理改善什么?(可多选)</text>
+        <view class="q-options multi">
           <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('immunity') !== -1 }" @click="toggleGoal('immunity')">
-            <text class="q-icon">🛡️</text>
+            <text>🛡️</text>
             <text class="q-text">增强免疫力</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('digestion') !== -1 }" @click="toggleGoal('digestion')">
-            <text class="q-icon">🌿</text>
+            <text>🌿</text>
             <text class="q-text">改善消化</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('growth') !== -1 }" @click="toggleGoal('growth')">
-            <text class="q-icon">📈</text>
+            <text>📈</text>
             <text class="q-text">促进生长发育</text>
           </view>
           <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('focus') !== -1 }" @click="toggleGoal('focus')">
-            <text class="q-icon">🎯</text>
+            <text>🎯</text>
             <text class="q-text">提升专注力</text>
           </view>
+          <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('sleep') !== -1 }" @click="toggleGoal('sleep')">
+            <text>😴</text>
+            <text class="q-text">改善睡眠</text>
+          </view>
           <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('weight') !== -1 }" @click="toggleGoal('weight')">
-            <text class="q-icon">⚖️</text>
+            <text>⚖️</text>
             <text class="q-text">体重管理</text>
           </view>
+          <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('allergy') !== -1 }" @click="toggleGoal('allergy')">
+            <text>🌸</text>
+            <text class="q-text">改善过敏</text>
+          </view>
+          <view class="q-option" :class="{ 'q-selected': survey.goals.indexOf('mood') !== -1 }" @click="toggleGoal('mood')">
+            <text>😊</text>
+            <text class="q-text">改善情绪</text>
+          </view>
         </view>
       </view>
 
@@ -181,29 +400,73 @@ export default {
       childId: null,
       surveyId: null,
       survey: {
+        // 生活习惯
         diet: '',
         digestion: '',
         sleep: '',
         exercise: '',
+        water: '',
+        medications: [],
+        // 疾病史
+        diseaseHistory: {
+          chronic: [],
+          otherDesc: ''
+        },
         allergies: [],
+        allergyDetail: '',
+        surgery: '',
+        surgeryDetail: '',
+        // 家族史
+        familyHistory: {
+          father: [],
+          mother: [],
+          sibling: []
+        },
+        grandparentHistory: [],
+        // 健康目标
         goals: []
       },
+      familyDiseaseOptions: [
+        { value: 'none', label: '无' },
+        { value: 'hypertension', label: '高血压' },
+        { value: 'diabetes', label: '糖尿病' },
+        { value: 'heart', label: '心脏病' },
+        { value: 'stroke', label: '脑卒中' },
+        { value: 'cancer', label: '癌症' },
+        { value: 'allergy', label: '过敏性疾病' },
+        { value: 'autoimmune', label: '自身免疫病' },
+        { value: 'mental', label: '精神/心理疾病' },
+        { value: 'obesity', label: '肥胖' },
+      ],
       submitting: false
     }
   },
   computed: {
     progressPercent: function() {
       var answered = 0
+      var total = 12
       if (this.survey.diet) answered++
       if (this.survey.digestion) answered++
       if (this.survey.sleep) answered++
       if (this.survey.exercise) answered++
+      if (this.survey.water) answered++
+      if (this.survey.medications.length > 0) answered++
+      if (this.survey.diseaseHistory.chronic.length > 0) answered++
       if (this.survey.allergies.length > 0) answered++
+      if (this.survey.surgery) answered++
+      // 家族史:至少填了父亲或母亲或兄弟姐妹或祖父母之一
+      if (this.survey.familyHistory.father.length > 0
+        || this.survey.familyHistory.mother.length > 0
+        || this.survey.familyHistory.sibling.length > 0) answered++
+      if (this.survey.grandparentHistory.length > 0) answered++
       if (this.survey.goals.length > 0) answered++
-      return Math.round((answered / 6) * 100)
+      return Math.round((answered / total) * 100)
     },
     canSubmit: function() {
-      return this.survey.diet && this.survey.digestion && this.survey.sleep && this.survey.exercise
+      return this.survey.diet && this.survey.sleep && this.survey.exercise
+        && this.survey.diseaseHistory.chronic.length > 0
+        && this.survey.allergies.length > 0
+        && this.survey.goals.length > 0
     }
   },
   onLoad: function(options) {
@@ -215,7 +478,6 @@ export default {
     initSurvey: function() {
       var self = this
       if (!self.reportId) return
-      // 先查状态
       getSurveyStatus(self.reportId).then(function(res) {
         if (res.code === 200 && res.data) {
           if (res.data.completed) {
@@ -227,7 +489,6 @@ export default {
             return
           }
         }
-        // 未创建,则创建
         return createReportSurvey(self.reportId, self.childId || 0)
       }).then(function(res) {
         if (res && res.code === 200 && res.data && res.data.id) {
@@ -237,12 +498,55 @@ export default {
         console.log('初始化问卷失败', e)
       })
     },
+    // === 用药多选 ===
+    hasMedication: function(val) {
+      return this.survey.medications.indexOf(val) !== -1
+    },
+    toggleMedication: function(val) {
+      var idx = this.survey.medications.indexOf(val)
+      if (val === 'none') {
+        this.survey.medications = ['none']
+      } else {
+        var noneIdx = this.survey.medications.indexOf('none')
+        if (noneIdx !== -1) this.survey.medications.splice(noneIdx, 1)
+        if (idx === -1) {
+          this.survey.medications.push(val)
+        } else {
+          this.survey.medications.splice(idx, 1)
+        }
+        if (this.survey.medications.length === 0) {
+          this.survey.medications.push('none')
+        }
+      }
+    },
+    // === 慢性病多选 ===
+    hasChronic: function(val) {
+      return this.survey.diseaseHistory.chronic.indexOf(val) !== -1
+    },
+    toggleChronic: function(val) {
+      var arr = this.survey.diseaseHistory.chronic
+      var idx = arr.indexOf(val)
+      if (val === 'none') {
+        this.survey.diseaseHistory.chronic = ['none']
+      } else {
+        var noneIdx = arr.indexOf('none')
+        if (noneIdx !== -1) arr.splice(noneIdx, 1)
+        if (idx === -1) {
+          arr.push(val)
+        } else {
+          arr.splice(idx, 1)
+        }
+        if (arr.length === 0) {
+          arr.push('none')
+        }
+      }
+    },
+    // === 过敏多选 ===
     toggleAllergy: function(val) {
       var idx = this.survey.allergies.indexOf(val)
       if (val === 'none') {
         this.survey.allergies = ['none']
       } else {
-        // 移除 none
         var noneIdx = this.survey.allergies.indexOf('none')
         if (noneIdx !== -1) this.survey.allergies.splice(noneIdx, 1)
         if (idx === -1) {
@@ -250,12 +554,52 @@ export default {
         } else {
           this.survey.allergies.splice(idx, 1)
         }
-        // 如果全删了,恢复 none
         if (this.survey.allergies.length === 0) {
           this.survey.allergies.push('none')
         }
       }
     },
+    // === 家族病史 ===
+    hasFamilyDisease: function(role, val) {
+      return this.survey.familyHistory[role].indexOf(val) !== -1
+    },
+    toggleFamilyDisease: function(role, val) {
+      var arr = this.survey.familyHistory[role]
+      var idx = arr.indexOf(val)
+      if (val === 'none') {
+        this.survey.familyHistory[role] = ['none']
+      } else {
+        var noneIdx = arr.indexOf('none')
+        if (noneIdx !== -1) arr.splice(noneIdx, 1)
+        if (idx === -1) {
+          arr.push(val)
+        } else {
+          arr.splice(idx, 1)
+        }
+        if (arr.length === 0) {
+          arr.push('none')
+        }
+      }
+    },
+    // === 祖父母病史 ===
+    toggleGrandparent: function(val) {
+      var idx = this.survey.grandparentHistory.indexOf(val)
+      if (val === 'unknown') {
+        this.survey.grandparentHistory = ['unknown']
+      } else {
+        var unkIdx = this.survey.grandparentHistory.indexOf('unknown')
+        if (unkIdx !== -1) this.survey.grandparentHistory.splice(unkIdx, 1)
+        if (idx === -1) {
+          this.survey.grandparentHistory.push(val)
+        } else {
+          this.survey.grandparentHistory.splice(idx, 1)
+        }
+        if (this.survey.grandparentHistory.length === 0) {
+          this.survey.grandparentHistory.push('unknown')
+        }
+      }
+    },
+    // === 健康目标 ===
     toggleGoal: function(val) {
       var idx = this.survey.goals.indexOf(val)
       if (idx === -1) {
@@ -273,15 +617,13 @@ export default {
       self.submitting = true
       uni.showLoading({ title: '提交中...' })
 
-      // 确保 surveyId 存在
       var doSubmit = function() {
         submitReportSurvey(self.surveyId, JSON.stringify(self.survey)).then(function(res) {
           uni.hideLoading()
           if (res.code === 200) {
             uni.showToast({ title: '提交成功', icon: 'success' })
-            // 跳转到 AI 对话页,带入报告和问卷数据
             setTimeout(function() {
-              var params = 'reportId=' + self.reportId + '&surveyId=' + self.surveyId + '&firstMessage=' + encodeURIComponent('请帮我解读孩子的健康报告')
+              var params = 'reportId=' + self.reportId + '&surveyId=' + self.surveyId + '&firstMessage=' + encodeURIComponent('请根据报告和问卷信息,提供个性化的健康建议')
               uni.redirectTo({ url: '/pages/ai/chat?' + params })
             }, 500)
           } else {
@@ -299,7 +641,6 @@ export default {
       if (this.surveyId) {
         doSubmit()
       } else {
-        // 先创建再提交
         createReportSurvey(this.reportId, this.childId || 0).then(function(res) {
           if (res.code === 200 && res.data && res.data.id) {
             self.surveyId = res.data.id
@@ -370,6 +711,21 @@ export default {
 
 .form-section { margin: 10rpx 30rpx 30rpx; }
 
+/* 分段标题 */
+.section-header {
+  display: flex;
+  flex-direction: row;
+  align-items: center;
+  margin: 30rpx 0 16rpx;
+  padding-left: 8rpx;
+}
+.section-icon { font-size: 36rpx; margin-right: 12rpx; }
+.section-title {
+  font-size: 32rpx;
+  font-weight: bold;
+  color: #333;
+}
+
 .question-card {
   background: #fff;
   border-radius: 20rpx;
@@ -396,6 +752,9 @@ export default {
   flex-direction: column;
   gap: 12rpx;
 }
+.q-options.multi {
+  gap: 10rpx;
+}
 .q-option {
   display: flex;
   flex-direction: row;
@@ -411,9 +770,61 @@ export default {
   border-color: #66BB6A;
 }
 .q-option:active { opacity: 0.7; }
-.q-icon { font-size: 32rpx; }
+.q-icon { font-size: 32rpx; flex-shrink: 0; }
 .q-text { font-size: 26rpx; color: #333; }
 
+/* 家族史网格 */
+.family-grid {
+  display: flex;
+  flex-direction: column;
+  gap: 20rpx;
+}
+.family-col {
+  background: #F5F7FA;
+  border-radius: 14rpx;
+  padding: 16rpx;
+}
+.family-role {
+  font-size: 26rpx;
+  font-weight: bold;
+  color: #5B9BD5;
+  display: block;
+  margin-bottom: 12rpx;
+}
+.family-options {
+  display: flex;
+  flex-direction: row;
+  flex-wrap: wrap;
+  gap: 8rpx;
+}
+.family-tag {
+  padding: 8rpx 16rpx;
+  border-radius: 20rpx;
+  font-size: 22rpx;
+  color: #666;
+  background: #fff;
+  border: 2rpx solid #E2E8F0;
+}
+.family-tag:active { opacity: 0.7; }
+.tag-selected {
+  background: #E3F2FD;
+  border-color: #5B9BD5;
+  color: #1565C0;
+}
+
+/* 额外文本输入 */
+.extra-input {
+  width: 100%;
+  min-height: 100rpx;
+  background: #F5F7FA;
+  border-radius: 12rpx;
+  padding: 16rpx 20rpx;
+  font-size: 24rpx;
+  color: #333;
+  margin-top: 16rpx;
+  box-sizing: border-box;
+}
+
 .submit-btn {
   background: linear-gradient(135deg, #43A047, #2E7D32);
   color: #fff;
@@ -428,4 +839,4 @@ export default {
 .btn-disabled { opacity: 0.5; }
 
 .bottom-spacer { height: 60rpx; }
-</style>
+</style>

+ 14 - 12
cfc-frontend/pages/health/report-upload.vue

@@ -492,18 +492,20 @@ export default {
       }).then(function(res) {
         uni.hideLoading()
         if (res.code === 200) {
-          uni.showToast({ title: '提交成功', icon: 'success' })
-          var reportId = res.data && res.data.id
-          if (reportId) {
-            setTimeout(function() {
-              if (self.form.reportType === 'gut_flora') {
-                uni.redirectTo({
-                  url: '/pages/health/report-survey?reportId=' + reportId + '&childId=' + (self.childId || '')
-                })
-              } else {
-                uni.redirectTo({ url: '/pages/body/health-report?reportId=' + reportId })
-              }
-            }, 500)
+uni.showToast({ title: '提交成功', icon: 'success' })
+              var reportId = res.data && res.data.id
+              if (reportId) {
+                setTimeout(function() {
+                  // 需要调研问卷的类型走问卷页
+                  var surveyTypes = ['gut_flora', 'physical_exam', 'blood_test', 'urine_test']
+                  if (surveyTypes.indexOf(self.form.reportType) !== -1) {
+                    uni.redirectTo({
+                      url: '/pages/health/report-survey?reportId=' + reportId + '&childId=' + (self.childId || '')
+                    })
+                  } else {
+                    uni.redirectTo({ url: '/pages/body/health-report?reportId=' + reportId })
+                  }
+                }, 500)
           } else {
             setTimeout(function() {
               uni.navigateBack()