Explorar el Código

feat(health-report): refactor upload to 2 methods (file/photo), add collectionDate, indicator dedup, image support

- Frontend: simplify to 2 upload entry cards (file/photo), remove tongue/type selector
- Backend: HealthIndicator add collectionDate field + migration 103
- Backend: parsePreview supports image upload (type=image)
- Backend: createReport indicator dedup by (category|indicatorName)
- Backend: ParsedReportPayload.Indicator add collectionDate
liaoxg hace 1 mes
padre
commit
b21a58ee56

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/config/DatabaseInitializer.java

@@ -6502,5 +6502,8 @@ private void runMigration100() {
 		} catch (Exception e) {
 			log.warn("创建health_data_sources表可能已存在: {}", e.getMessage());
 		}
+
+		// 迁移103: health_indicators表添加collection_date列(指标采集日期)
+		ensureColumn("health_indicators", "collection_date", "DATETIME COMMENT '指标采集日期(报告检测日期)'");
 	}
 }

+ 49 - 10
cfc-backend/src/main/java/com/etotem/cfc/controller/HealthReportController.java

@@ -394,13 +394,13 @@ public class HealthReportController {
 
     /**
      * 两阶段入库 Phase 1: 上传PDF → 解析 → 存草稿 → 返回draftId + 解析载荷 + 成员匹配
-     * 不落库到 health_reports 等正式表。
+     * 不落库到 health_reports 等正式表。支持 PDF 解析(auto)和图片上传(image)。
      */
     @Operation(summary = "上传解析报告(预览,不入库)")
     @PostMapping("/report/parse-preview")
     public Result<Map<String, Object>> parsePreview(
             @RequestParam("file") MultipartFile file,
-            @RequestParam(value = "type", defaultValue = "pdf") String type,
+            @RequestParam(value = "type", defaultValue = "auto") String type,
             @RequestParam(value = "familyId", required = false) Long familyId,
             @RequestParam(value = "memberId", required = false) Long memberId,
             @RequestParam(value = "childId", required = false) Long childId,
@@ -420,12 +420,35 @@ public class HealthReportController {
 
         String contentType = file.getContentType();
         String originalFilename = file.getOriginalFilename();
-        if (originalFilename == null || (!originalFilename.toLowerCase().endsWith(".pdf")
-                && (contentType == null || !contentType.equalsIgnoreCase("application/pdf")))) {
-            return Result.error("仅支持PDF文件");
-        }
 
         try {
+            // 图片上传:保存文件 + 创建空草稿(无自动解析)
+            if ("image".equals(type) || isImageContent(contentType)) {
+                String fileUrl = saveUploadFile(file, userId);
+                String reportType = familyId != null ? "physical_exam" : "gut_flora";
+
+                // 创建空载荷草稿,后续由 confirm 阶段用户补充
+                ParsedReportPayload.Payload emptyPayload = new ParsedReportPayload.Payload();
+                String payloadJson = objectMapper.writeValueAsString(emptyPayload);
+
+                HealthReportDraft draft = healthReportDraftService.createDraft(
+                        userId, familyId, reportType, fileUrl, originalFilename, payloadJson);
+
+                Map<String, Object> result = new LinkedHashMap<>();
+                result.put("draftId", draft.getId());
+                result.put("payload", emptyPayload);
+                result.put("fileUrl", fileUrl);
+                result.put("needBind", false);
+                return Result.success(result);
+            }
+
+            // PDF 解析(默认路径)
+            String lcName = originalFilename != null ? originalFilename.toLowerCase() : "";
+            if (!lcName.endsWith(".pdf")
+                    && (contentType == null || !contentType.equalsIgnoreCase("application/pdf"))) {
+                return Result.error("仅支持PDF文件");
+            }
+
             ParsedReportResult parsed = pdfParseService.parse(file.getInputStream());
             if (parsed.getOverallScore() == null && parsed.getGutHealthScore() == null) {
                 return Result.error("无法解析PDF文件,请确认是募极生物肠道菌群报告");
@@ -459,7 +482,7 @@ public class HealthReportController {
             result.put("extractedGender", parsed.getGender());
             result.put("extractedAge", parsed.getAge());
             result.put("needBind", matchResult.getMatchedMemberId() == null && familyId != null);
-            
+
             // 保存结果到本地 JSON 文件
             try {
                 saveResultToJsonFile(result);
@@ -467,15 +490,23 @@ public class HealthReportController {
                 log.warn("保存结果到 JSON 文件失败:{}", e.getMessage());
                 // 不影响主要流程,只是记录警告
             }
-            
+
             return Result.success(result);
 
         } catch (IOException e) {
-            log.error("PDF解析失败", e);
-            return Result.error("PDF解析失败: " + e.getMessage());
+            log.error("文件处理失败", e);
+            return Result.error("文件处理失败: " + e.getMessage());
         }
     }
 
+    /**
+     * 判断是否为图片 MIME 类型
+     */
+    private boolean isImageContent(String contentType) {
+        if (contentType == null) return false;
+        return contentType.startsWith("image/");
+    }
+
     /**
      * 两阶段入库 Phase 2: 确认草稿 → 写入正式表 → 刷新7维评分
      * 前端可回传修改后的 payload(用户在线编辑指标后整体提交)。
@@ -1181,6 +1212,14 @@ public class HealthReportController {
             ind.setStatus(pi.getStatus());
             ind.setSymptoms(pi.getSymptoms());
             ind.setSortOrder(i);
+            if (pi.getCollectionDate() != null && !pi.getCollectionDate().isEmpty()) {
+                try {
+                    java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
+                    ind.setCollectionDate(sdf.parse(pi.getCollectionDate()));
+                } catch (Exception e) {
+                    // ignore parse error
+                }
+            }
             indicators.add(ind);
         }
         return indicators;

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/dto/ParsedReportPayload.java

@@ -72,6 +72,7 @@ public class ParsedReportPayload {
         private String refRange;
         private String status;
         private String symptoms;
+        private String collectionDate;
 
         public String getCategory() { return category; }
         public void setCategory(String category) { this.category = category; }
@@ -87,6 +88,8 @@ public class ParsedReportPayload {
         public void setStatus(String status) { this.status = status; }
         public String getSymptoms() { return symptoms; }
         public void setSymptoms(String symptoms) { this.symptoms = symptoms; }
+        public String getCollectionDate() { return collectionDate; }
+        public void setCollectionDate(String collectionDate) { this.collectionDate = collectionDate; }
     }
 
     public static class Flora {

+ 3 - 0
cfc-backend/src/main/java/com/etotem/cfc/entity/HealthIndicator.java

@@ -46,5 +46,8 @@ public class HealthIndicator implements Serializable {
     /** 排序 */
     private Integer sortOrder;
 
+    /** 指标采集日期(从报告提取的检测日期,非入库时间) */
+    private Date collectionDate;
+
     private Date createdAt;
 }

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

@@ -80,8 +80,17 @@ public class HealthReportService {
         healthReportMapper.insert(report);
 
         if (indicators != null && !indicators.isEmpty()) {
+            // 指标去重: 同一批中相同 indicatorName 的只保留最后一条
+            java.util.Map<String, HealthIndicator> dedupMap = new java.util.LinkedHashMap<>();
             for (HealthIndicator ind : indicators) {
+                String key = (ind.getCategory() != null ? ind.getCategory() : "") + "|" + (ind.getIndicatorName() != null ? ind.getIndicatorName() : "");
+                dedupMap.put(key, ind);
+            }
+            for (HealthIndicator ind : dedupMap.values()) {
                 ind.setReportId(report.getId());
+                if (ind.getCollectionDate() == null) {
+                    ind.setCollectionDate(report.getReportDate());
+                }
                 ind.setCreatedAt(new Date());
                 healthIndicatorMapper.insert(ind);
             }
@@ -1040,6 +1049,14 @@ public class HealthReportService {
             ind.setStatus(pi.getStatus());
             ind.setSymptoms(pi.getSymptoms());
             ind.setSortOrder(i);
+            if (pi.getCollectionDate() != null && !pi.getCollectionDate().isEmpty()) {
+                try {
+                    java.text.SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd");
+                    ind.setCollectionDate(sdf.parse(pi.getCollectionDate()));
+                } catch (Exception e) {
+                    // ignore parse error
+                }
+            }
             indicators.add(ind);
         }
         return indicators;

+ 1 - 0
cfc-backend/src/main/resources/schema.sql

@@ -1555,6 +1555,7 @@ CREATE TABLE IF NOT EXISTS health_indicators (
     ref_range VARCHAR(200) COMMENT '参考范围',
     status VARCHAR(50) COMMENT '评估状态: 正常/偏低/偏高/过多/注意/低风险/未检出',
     sort_order INT DEFAULT 0 COMMENT '显示排序',
+    collection_date DATETIME COMMENT '指标采集日期(报告检测日期)',
     created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
     INDEX idx_report_id (report_id),
     INDEX idx_category (category)

+ 147 - 309
cfc-frontend/pages/health/report-upload.vue

@@ -61,33 +61,19 @@
 
     <!-- 表单 -->
     <view class="form-section">
-      <!-- 报告类型 -->
+      <!-- 上传方式:两个入口 -->
       <view class="form-item">
-        <text class="form-label">报告类型</text>
-        <view class="type-selector">
-          <view
-            class="type-option"
-            :class="{ 'type-selected': form.reportType === 'physical_exam' }"
-            @click="selectType('physical_exam')"
-          >
-            <text class="type-icon">🏥</text>
-            <text class="type-name">体检报告</text>
-          </view>
-          <view
-            class="type-option"
-            :class="{ 'type-selected': form.reportType === 'gut_flora' }"
-            @click="selectType('gut_flora')"
-          >
-            <text class="type-icon">🔬</text>
-            <text class="type-name">肠道检测</text>
+        <text class="form-label">上传方式</text>
+        <view class="upload-type-row">
+          <view class="upload-type-card" @click="pickFile">
+            <text class="upload-type-icon">📄</text>
+            <text class="upload-type-name">上传文件</text>
+            <text class="upload-type-hint">PDF / 图片</text>
           </view>
-          <view
-            class="type-option"
-            :class="{ 'type-selected': form.reportType === 'tongue' }"
-            @click="selectType('tongue')"
-          >
-            <text class="type-icon">👅</text>
-            <text class="type-name">舌诊拍照</text>
+          <view class="upload-type-card" @click="takePhoto">
+            <text class="upload-type-icon">📷</text>
+            <text class="upload-type-name">拍照上传</text>
+            <text class="upload-type-hint">舌象 / 报告照片</text>
           </view>
         </view>
       </view>
@@ -115,74 +101,21 @@
         </scroll-view>
       </view>
 
-      <!-- 舌诊拍照(仅 tongue 类型显示) -->
-      <view class="form-item" v-if="isTongue">
-        <text class="form-label">拍摄舌象</text>
-        <view class="photo-area">
-          <view class="photo-placeholder" v-if="!photoPath && !analyzing" @click="takePhoto">
-            <text class="photo-icon">📷</text>
-            <text class="photo-text">点击拍摄舌象</text>
-            <text class="photo-sub">自然光线下拍摄舌面,避免有色光线</text>
-          </view>
-          <image class="photo-preview" v-else-if="photoPath && !analyzing" :src="photoPath" mode="aspectFit" @click="takePhoto"></image>
-          <view class="photo-analyzing" v-else>
-            <text class="analyzing-text">AI 舌象分析中...</text>
-          </view>
-        </view>
-        <view class="photo-retake" v-if="photoPath && !analyzing">
-          <text @click="takePhoto">点击重新拍摄</text>
-        </view>
-      </view>
-
-      <!-- PDF文件上传(非舌诊类型显示) -->
-      <view class="form-item" v-if="!isTongue">
-        <text class="form-label">上传PDF报告文件</text>
-        <view class="file-upload-area" @click="pickFile">
-          <view class="file-upload-placeholder" v-if="!selectedFile">
-            <text class="file-upload-icon">📄</text>
-            <text class="file-upload-text">点击选择PDF文件</text>
-            <text class="file-upload-hint">支持从聊天记录中选择</text>
-          </view>
-          <view class="file-uploading" v-else>
-            <text class="file-upload-icon">⏳</text>
-            <text class="file-upload-text">正在解析...</text>
-          </view>
-        </view>
-        <!-- 已选择的文件 -->
-        <view class="file-info" v-if="selectedFile && !parsingPreview">
-          <text class="file-name">{{ selectedFile.name }}</text>
-          <text class="file-size">{{ formatFileSize(selectedFile.size) }}</text>
+      <!-- 已选文件 -->
+      <view class="form-item" v-if="selectedFile || photoPath">
+        <text class="form-label">已选文件</text>
+        <view class="file-info">
+          <text class="file-name">{{ selectedFile ? (selectedFile.name || '文件') : '照片' }}</text>
+          <text class="file-size" v-if="selectedFile && selectedFile.size">{{ formatFileSize(selectedFile.size) }}</text>
           <text class="file-remove" @click.stop="removeFile">移除</text>
         </view>
       </view>
 
-      <!-- 报告日期(文件解析后填写) -->
-      <view class="form-item" v-if="selectedFile">
-        <text class="form-label">报告日期</text>
-        <view class="date-display" @click="showDatePicker = true">
-          <text>{{ form.reportDate || '请选择日期' }}</text>
-          <text class="date-arrow">›</text>
-        </view>
-      </view>
-
-      <!-- 综合评分(文件解析后填写) -->
-      <view class="form-item" v-if="selectedFile">
-        <text class="form-label">综合评分(选填)</text>
-        <view class="score-input-wrap">
-          <slider
-            class="score-slider"
-            min="0"
-            max="100"
-            step="1"
-            :value="form.overallScore"
-            @changing="onScoreChanging"
-            @change="onScoreChange"
-            activeColor="#5B9BD5"
-            backgroundColor="#E2E8F0"
-            block-size="16"
-            show-value
-          />
-          <text class="score-value">{{ form.overallScore || 0 }}分</text>
+      <!-- 上传中/分析中 -->
+      <view class="form-item" v-if="parsingPreview">
+        <text class="form-label">AI分析中...</text>
+        <view class="analyzing-status">
+          <text class="analyzing-text">正在解析文件内容,请稍候...</text>
         </view>
       </view>
 
@@ -199,8 +132,7 @@
 
       <!-- 提交按钮 -->
       <view class="submit-btn" :class="{ 'btn-disabled': !canSubmit }" @click="doSubmit">
-        <text v-if="analyzing">分析中...</text>
-        <text v-else-if="isTongue">开始分析</text>
+        <text v-if="parsingPreview">解析中...</text>
         <text v-else-if="submitting">提交中...</text>
         <text v-else>提交报告</text>
       </view>
@@ -212,15 +144,7 @@
 </template>
 
 <script>
-import { createHealthReport, parseReportPreview, productList, getFamilyMembers, addFamilyMember } from '../../utils/api.js'
-import config from '@/config.js'
-
-// 报告类型 → 维度映射(用于推荐商品)
-var REPORT_DOMAIN_MAP = {
-  physical_exam: 'body',
-  gut_flora: 'body',
-  tongue: 'body'
-}
+import { productList, getFamilyMembers, addFamilyMember } from '../../utils/api.js'
 
 export default {
   data() {
@@ -228,68 +152,43 @@ export default {
       childId: null,
       familyId: null,
       form: {
-        reportType: 'physical_exam',
         reportDate: '',
         overallScore: 0,
         description: ''
       },
-      showDatePicker: false,
       submitting: false,
-      analyzing: false,
       selectedFile: null,
       parsingPreview: false,
       parsedInfo: null,
       showMemberSelector: false,
       selectedMemberId: null,
       familyMembers: [],
-      // 舌诊
+      // 拍照
       photoPath: null,
-      tongueResult: null,
       // 购买推荐
       recommendedProducts: [],
       loadingProducts: false
     }
   },
   computed: {
-    isTongue() {
-      return this.form.reportType === 'tongue'
-    },
     canSubmit() {
-      if (this.isTongue) return this.photoPath && !this.analyzing
-      if (this.selectedFile) {
+      if (this.parsingPreview) return false
+      if (this.selectedFile || this.photoPath) {
+        // 文件已选且解析成功(有draftId且不需要手动选成员)
         return this.parsedInfo && this.parsedInfo.draftId && !this.parsedInfo.needManualSelect
       }
-      return this.form.reportType && this.form.reportDate
+      return false
     }
   },
   onLoad: function(options) {
     this.childId = options.childId ? parseInt(options.childId) : null
-    if (options.reportType && ['physical_exam', 'gut_flora', 'tongue'].indexOf(options.reportType) !== -1) {
-      this.form.reportType = options.reportType
-    }
-    this.loadRecommendProducts(this.form.reportType)
-    // 不默认填充日期 — 待解析后从报告中提取
-    this.form.reportDate = ''
+    this.loadRecommendProducts()
   },
   onShow: function() {
     // 每次显示时从store获取familyId和家庭成员
     this.loadFamilyInfo()
   },
   methods: {
-    selectType(type) {
-      if (this.form.reportType === type) return
-      this.form.reportType = type
-      this.loadRecommendProducts(type)
-      // 切类型时清理其他类型的状态
-      if (type === 'tongue') {
-        this.selectedFile = null
-        this.parsedInfo = null
-      } else {
-        this.photoPath = null
-        this.tongueResult = null
-        this.analyzing = false
-      }
-    },
     loadFamilyInfo() {
       var self = this
       // 从Vuex获取familyId和家庭成员
@@ -312,7 +211,7 @@ export default {
         }
       }
     },
-    // ===== 舌诊拍照 =====
+    // ===== 拍照上传 =====
     takePhoto() {
       var self = this
       uni.chooseImage({
@@ -320,78 +219,33 @@ export default {
         sourceType: ['camera', 'album'],
         success: function(res) {
           self.photoPath = res.tempFilePaths[0]
-          self.tongueResult = null
-        }
-      })
-    },
-    analyzeTongue() {
-      if (!this.photoPath || this.analyzing) return
-      var self = this
-      this.analyzing = true
-      var memberId = this.childId || ''
-      uni.uploadFile({
-        url: getApp().globalData.baseUrl + '/api/health/report/parse-preview?type=tongue' + (memberId ? '&memberId=' + memberId : ''),
-        filePath: this.photoPath,
-        name: 'file',
-        header: {
-          'Authorization': 'Bearer ' + (uni.getStorageSync('token') || '')
-        },
-        success: function(res) {
-          var data = JSON.parse(res.data)
-          if (data.code === 200 && data.data) {
-            self.tongueResult = data.data
-            // 跳转到确认页
-            var payload = {
-              type: 'tongue',
-              indicators: (data.data.indicators || []).map(function(ind) {
-                return { code: ind.code, name: ind.name, value: ind.value }
-              }),
-              summary: { overallAssessment: data.data.overallAssessment || '' }
-            }
-            var params = 'draftId=' + data.data.recordId
-              + '&payload=' + encodeURIComponent(JSON.stringify(payload))
-              + '&reportType=tongue'
-              + (self.childId ? '&childId=' + self.childId : '')
-              + (self.familyId ? '&familyId=' + self.familyId : '')
-            uni.navigateTo({ url: '/pages/health/report-confirm?' + params })
-          } else {
-            uni.showToast({ title: data.message || '分析失败', icon: 'none' })
+          // 拍照后直接解析
+          if (self.familyId) {
+            self.previewParse()
           }
-        },
-        fail: function() {
-          uni.showToast({ title: '上传失败', icon: 'none' })
-        },
-        complete: function() {
-          self.analyzing = false
         }
       })
     },
-    onScoreChanging(e) {
-      this.form.overallScore = e.detail.value
-    },
-    onScoreChange(e) {
-      this.form.overallScore = e.detail.value
-    },
+    // ===== 文件上传 =====
     pickFile() {
       var self = this
       // 重置解析状态
       this.parsedInfo = null
       this.showMemberSelector = false
       this.selectedMemberId = null
-      // 重置日期和评分
       this.form.reportDate = ''
       this.form.overallScore = 0
+      this.photoPath = null
 
       // 从微信聊天记录中选择文件
       uni.chooseMessageFile({
         count: 1,
-        type: 'all',
+        type: 'file',
         extension: ['pdf'],
         success: function(res) {
           var files = res.tempFiles
           if (files && files.length > 0) {
             self.selectedFile = files[0]
-            // 如果选择了文件且有familyId,先预览解析
             if (self.familyId) {
               self.previewParse()
             }
@@ -404,47 +258,86 @@ export default {
     },
     previewParse() {
       var self = this
-      if (!this.selectedFile || !this.familyId) return
+      if (!this.familyId) return
+      var filePath = this.selectedFile ? this.selectedFile.path : this.photoPath
+      if (!filePath) return
       this.parsingPreview = true
 
-      parseReportPreview(this.selectedFile.path, this.childId, this.familyId).then(function(res) {
-        var detail = res.data
-        self.parsedInfo = {
-          draftId: detail.draftId,
-          personName: detail.extractedName || '',
-          extractedGender: detail.extractedGender || '',
-          extractedAge: detail.extractedAge || '',
-          reportNumber: detail.extractedReportNumber,
-          reportDate: detail.extractedReportDate,
-          needManualSelect: detail.needBind,
-          payload: detail.payload || {},
-          matchedMemberId: detail.matchedMemberId,
-          confidence: detail.confidence,
-          matchLabel: detail.matchLabel,
-          candidates: detail.candidates || []
-        }
-        // 从解析结果自动填充报告日期
-        if (detail.extractedReportDate) {
-          self.form.reportDate = detail.extractedReportDate
-        }
-        if (detail.needBind) {
-          self.showMemberSelector = true
-          self.loadFamilyMembers()
-          // 未匹配到成员时,主动询问是否把解析到的人添加为新成员
-          if (self.parsedInfo.personName) {
-            self.promptAddParsedMember()
+      var apiPath = getApp().globalData.baseUrl + '/api/health/report/parse-preview'
+      if (self.selectedFile) {
+        // PDF文件上传
+        uni.uploadFile({
+          url: apiPath + (self.childId ? '?memberId=' + self.childId : ''),
+          filePath: filePath,
+          name: 'file',
+          header: {
+            'Authorization': 'Bearer ' + (uni.getStorageSync('token') || '')
+          },
+          success: function(uploadRes) {
+            var data = JSON.parse(uploadRes.data)
+            if (data.code === 200 && data.data) {
+              self.handleParsedResult(data.data)
+            } else {
+              self.parsingPreview = false
+              uni.showToast({ title: data.message || '解析失败', icon: 'none' })
+            }
+          },
+          fail: function() {
+            self.parsingPreview = false
+            uni.showToast({ title: '上传失败', icon: 'none' })
           }
+        })
+      } else if (self.photoPath) {
+        // 图片上传
+        uni.uploadFile({
+          url: apiPath + '?type=image' + (self.childId ? '&memberId=' + self.childId : ''),
+          filePath: filePath,
+          name: 'file',
+          header: {
+            'Authorization': 'Bearer ' + (uni.getStorageSync('token') || '')
+          },
+          success: function(uploadRes) {
+            var data = JSON.parse(uploadRes.data)
+            if (data.code === 200 && data.data) {
+              self.handleParsedResult(data.data)
+            } else {
+              self.parsingPreview = false
+              uni.showToast({ title: data.message || '解析失败', icon: 'none' })
+            }
+          },
+          fail: function() {
+            self.parsingPreview = false
+            uni.showToast({ title: '上传失败', icon: 'none' })
+          }
+        })
+      }
+    },
+    handleParsedResult: function(detail) {
+      this.parsingPreview = false
+      this.parsedInfo = {
+        draftId: detail.draftId,
+        personName: detail.extractedName || '',
+        extractedGender: detail.extractedGender || '',
+        extractedAge: detail.extractedAge || '',
+        reportNumber: detail.extractedReportNumber,
+        reportDate: detail.extractedReportDate,
+        needManualSelect: detail.needBind,
+        payload: detail.payload || {},
+        matchedMemberId: detail.matchedMemberId,
+        confidence: detail.confidence,
+        matchLabel: detail.matchLabel,
+        candidates: detail.candidates || []
+      }
+      if (detail.extractedReportDate) {
+        this.form.reportDate = detail.extractedReportDate
+      }
+      if (detail.needBind) {
+        this.showMemberSelector = true
+        this.loadFamilyMembers()
+        if (this.parsedInfo.personName) {
+          this.promptAddParsedMember()
         }
-        // 从解析结果自动填充报告日期
-        if (detail.extractedReportDate) {
-          self.form.reportDate = detail.extractedReportDate
-        }
-      }).catch(function(e) {
-        var msg = (e && e.message) || '解析失败'
-        uni.showToast({ title: msg, icon: 'none' })
-      }).finally(function() {
-        self.parsingPreview = false
-      })
+      }
     },
     loadFamilyMembers() {
       var self = this
@@ -558,19 +451,17 @@ export default {
     },
     removeFile() {
       this.selectedFile = null
+      this.photoPath = null
       this.parsedInfo = null
       this.showMemberSelector = false
       this.selectedMemberId = null
+      this.form.reportDate = ''
+      this.form.overallScore = 0
     },
-    loadRecommendProducts: function(reportType) {
-      var domain = REPORT_DOMAIN_MAP[reportType]
-      if (!domain) {
-        this.recommendedProducts = []
-        return
-      }
+    loadRecommendProducts: function() {
       var self = this
       self.loadingProducts = true
-      productList({ domain: domain, productType: 'assessment', size: 4 }).then(function(res) {
+      productList({ domain: 'body', productType: 'assessment', size: 4 }).then(function(res) {
         if (res && res.data && res.data.records) {
           self.recommendedProducts = res.data.records
         } else {
@@ -602,93 +493,40 @@ export default {
       if (this.submitting) return
       var self = this
 
-      // 舌诊:拍照后分析
-      if (self.isTongue) {
-        if (!self.photoPath) {
-          uni.showToast({ title: '请先拍摄舌象', icon: 'none' })
-          return
-        }
-        self.analyzeTongue()
+      if (!self.parsedInfo || !self.parsedInfo.draftId) {
+        uni.showToast({ title: '请先上传文件并等待解析完成', icon: 'none' })
         return
       }
-
-      if (self.selectedFile) {
-        if (self.parsedInfo && self.parsedInfo.needManualSelect && !self.selectedMemberId) {
-          uni.showToast({ title: '请先选择成员', icon: 'none' })
-          return
-        }
-        if (!self.parsedInfo || !self.parsedInfo.draftId) {
-          uni.showToast({ title: '请先等待解析完成', icon: 'none' })
-          return
-        }
-
-        var payloadStr = encodeURIComponent(JSON.stringify(self.parsedInfo.payload || {}))
-        var candidatesStr = (self.parsedInfo.candidates && self.parsedInfo.candidates.length > 0)
-          ? encodeURIComponent(JSON.stringify(self.parsedInfo.candidates))
-          : ''
-        var params = 'draftId=' + self.parsedInfo.draftId
-          + '&extractedName=' + encodeURIComponent(self.parsedInfo.personName || '')
-          + '&extractedGender=' + encodeURIComponent(self.parsedInfo.extractedGender || '')
-          + '&extractedAge=' + (self.parsedInfo.extractedAge || '')
-          + '&payload=' + payloadStr
-          + '&needBind=' + (self.parsedInfo.needManualSelect ? 'true' : 'false')
-          + '&matchConfidence=' + (self.parsedInfo.confidence || '')
-          + '&matchLabel=' + encodeURIComponent(self.parsedInfo.matchLabel || '')
-          + '&candidates=' + candidatesStr
-          + '&extractedReportDate=' + encodeURIComponent(self.parsedInfo.reportDate || '')
-        if (self.parsedInfo.matchedMemberId) {
-          params += '&matchedMemberId=' + self.parsedInfo.matchedMemberId
-        }
-        if (self.childId) {
-          params += '&childId=' + self.childId
-        }
-        if (self.familyId) {
-          params += '&familyId=' + self.familyId
-        }
-
-        uni.navigateTo({ url: '/pages/health/report-confirm?' + params })
+      if (self.parsedInfo.needManualSelect && !self.selectedMemberId) {
+        uni.showToast({ title: '请先选择成员', icon: 'none' })
         return
       }
 
-      self.submitting = true
-      uni.showLoading({ title: '提交中...' })
-      createHealthReport({
-        reportType: self.form.reportType,
-        reportDate: self.form.reportDate,
-        overallScore: self.form.overallScore,
-        description: self.form.description
-      }).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() {
-                  // 需要调研问卷的类型走问卷页
-                  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()
-            }, 500)
-          }
-        } else {
-          uni.showToast({ title: res.message || '提交失败', icon: 'none' })
-        }
-      }).catch(function(e) {
-        uni.hideLoading()
-        uni.showToast({ title: '提交失败,请重试', icon: 'none' })
-        console.log('创建报告失败', e)
-      }).finally(function() {
-        self.submitting = false
-      })
+      var payloadStr = encodeURIComponent(JSON.stringify(self.parsedInfo.payload || {}))
+      var candidatesStr = (self.parsedInfo.candidates && self.parsedInfo.candidates.length > 0)
+        ? encodeURIComponent(JSON.stringify(self.parsedInfo.candidates))
+        : ''
+      var params = 'draftId=' + self.parsedInfo.draftId
+        + '&extractedName=' + encodeURIComponent(self.parsedInfo.personName || '')
+        + '&extractedGender=' + encodeURIComponent(self.parsedInfo.extractedGender || '')
+        + '&extractedAge=' + (self.parsedInfo.extractedAge || '')
+        + '&payload=' + payloadStr
+        + '&needBind=' + (self.parsedInfo.needManualSelect ? 'true' : 'false')
+        + '&matchConfidence=' + (self.parsedInfo.confidence || '')
+        + '&matchLabel=' + encodeURIComponent(self.parsedInfo.matchLabel || '')
+        + '&candidates=' + candidatesStr
+        + '&extractedReportDate=' + encodeURIComponent(self.parsedInfo.reportDate || '')
+      if (self.parsedInfo.matchedMemberId) {
+        params += '&matchedMemberId=' + self.parsedInfo.matchedMemberId
+      }
+      if (self.childId) {
+        params += '&childId=' + self.childId
+      }
+      if (self.familyId) {
+        params += '&familyId=' + self.familyId
+      }
+
+      uni.navigateTo({ url: '/pages/health/report-confirm?' + params })
     }
   }
 }