|
|
@@ -13,6 +13,49 @@
|
|
|
<text class="tip-text">上传体检报告或肠道检测报告,AI 将为您解读并提供个性化营养方案</text>
|
|
|
</view>
|
|
|
|
|
|
+ <!-- PDF解析预览(提取到信息时显示) -->
|
|
|
+ <view class="parsed-preview" v-if="parsedInfo">
|
|
|
+ <view class="parsed-title">PDF解析结果</view>
|
|
|
+ <view class="parsed-row" v-if="parsedInfo.personName">
|
|
|
+ <text class="parsed-label">被检测人</text>
|
|
|
+ <text class="parsed-value">{{ parsedInfo.personName }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="parsed-row" v-if="parsedInfo.reportNumber">
|
|
|
+ <text class="parsed-label">报告编号</text>
|
|
|
+ <text class="parsed-value">{{ parsedInfo.reportNumber }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="parsed-row" v-if="parsedInfo.reportDate">
|
|
|
+ <text class="parsed-label">报告日期</text>
|
|
|
+ <text class="parsed-value">{{ parsedInfo.reportDate }}</text>
|
|
|
+ </view>
|
|
|
+ <!-- 匹配状态 -->
|
|
|
+ <view class="match-status" v-if="parsedInfo.needManualSelect">
|
|
|
+ <text class="match-icon">⚠️</text>
|
|
|
+ <text class="match-text">无法自动匹配到家庭成员,请手动选择</text>
|
|
|
+ </view>
|
|
|
+ <view class="match-status success" v-else-if="parsedInfo.matchedMemberName">
|
|
|
+ <text class="match-icon">✅</text>
|
|
|
+ <text class="match-text">已自动匹配: {{ parsedInfo.matchedMemberName }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 家庭成员选择器(需要手动选择时显示) -->
|
|
|
+ <view class="form-item member-selector" v-if="showMemberSelector">
|
|
|
+ <text class="form-label">选择成员</text>
|
|
|
+ <view class="member-list">
|
|
|
+ <view
|
|
|
+ class="member-item"
|
|
|
+ v-for="member in familyMembers"
|
|
|
+ :key="member.id"
|
|
|
+ :class="{ 'member-selected': selectedMemberId === member.userId }"
|
|
|
+ @click="selectMember(member)"
|
|
|
+ >
|
|
|
+ <text class="member-avatar">{{ member.nickname ? member.nickname.substring(0, 1) : '?' }}</text>
|
|
|
+ <text class="member-name">{{ member.nickname || '未知' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- 表单 -->
|
|
|
<view class="form-section">
|
|
|
<!-- 报告类型 -->
|
|
|
@@ -38,6 +81,28 @@
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
+ <!-- 文件上传 -->
|
|
|
+ <view class="form-item">
|
|
|
+ <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>
|
|
|
+ <text class="file-remove" @click.stop="removeFile">移除</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
<!-- 报告日期 -->
|
|
|
<view class="form-item">
|
|
|
<text class="form-label">报告日期</text>
|
|
|
@@ -93,11 +158,13 @@
|
|
|
|
|
|
<script>
|
|
|
import { createHealthReport } from '../../utils/api.js'
|
|
|
+import config from '@/config.js'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
childId: null,
|
|
|
+ familyId: null,
|
|
|
form: {
|
|
|
reportType: 'physical_exam',
|
|
|
reportDate: '',
|
|
|
@@ -105,11 +172,21 @@ export default {
|
|
|
description: ''
|
|
|
},
|
|
|
showDatePicker: false,
|
|
|
- submitting: false
|
|
|
+ submitting: false,
|
|
|
+ selectedFile: null,
|
|
|
+ parsingPreview: false,
|
|
|
+ parsedInfo: null,
|
|
|
+ showMemberSelector: false,
|
|
|
+ selectedMemberId: null,
|
|
|
+ familyMembers: []
|
|
|
}
|
|
|
},
|
|
|
computed: {
|
|
|
canSubmit() {
|
|
|
+ // 有文件时需要先解析并匹配成功(或已选成员)
|
|
|
+ if (this.selectedFile) {
|
|
|
+ return this.parsedInfo && !this.parsedInfo.needManualSelect
|
|
|
+ }
|
|
|
return this.form.reportType && this.form.reportDate
|
|
|
}
|
|
|
},
|
|
|
@@ -122,19 +199,235 @@ export default {
|
|
|
var d = ('' + now.getDate()).padStart(2, '0')
|
|
|
this.form.reportDate = y + '-' + m + '-' + d
|
|
|
},
|
|
|
+ onShow: function() {
|
|
|
+ // 每次显示时从store获取familyId和家庭成员
|
|
|
+ this.loadFamilyInfo()
|
|
|
+ },
|
|
|
methods: {
|
|
|
+ loadFamilyInfo() {
|
|
|
+ var self = this
|
|
|
+ // 从Vuex获取familyId和家庭成员
|
|
|
+ var userInfo = uni.getStorageSync('userInfo')
|
|
|
+ if (userInfo) {
|
|
|
+ try {
|
|
|
+ var info = typeof userInfo === 'string' ? JSON.parse(userInfo) : userInfo
|
|
|
+ this.familyId = info.familyId || null
|
|
|
+ // 加载家庭成员列表(从本地存储或请求)
|
|
|
+ var storedMembers = uni.getStorageSync('familyMembers')
|
|
|
+ if (storedMembers) {
|
|
|
+ try {
|
|
|
+ this.familyMembers = typeof storedMembers === 'string' ? JSON.parse(storedMembers) : storedMembers
|
|
|
+ } catch (e) {
|
|
|
+ this.familyMembers = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (e) {
|
|
|
+ console.log('解析userInfo失败', e)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
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
|
|
|
+
|
|
|
+ // 从微信聊天记录中选择文件
|
|
|
+ uni.chooseMessageFile({
|
|
|
+ count: 1,
|
|
|
+ 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()
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function(e) {
|
|
|
+ console.log('选择文件取消或失败', e)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ previewParse() {
|
|
|
+ var self = this
|
|
|
+ if (!this.selectedFile || !this.familyId) return
|
|
|
+ this.parsingPreview = true
|
|
|
+
|
|
|
+ uni.uploadFile({
|
|
|
+ url: config.API_BASE_URL + '/api/health/report/upload',
|
|
|
+ filePath: this.selectedFile.path,
|
|
|
+ name: 'file',
|
|
|
+ formData: {
|
|
|
+ childId: this.childId || '',
|
|
|
+ familyId: this.familyId || ''
|
|
|
+ },
|
|
|
+ header: {
|
|
|
+ Authorization: uni.getStorageSync('token') || ''
|
|
|
+ },
|
|
|
+ success: function(res) {
|
|
|
+ var data
|
|
|
+ try {
|
|
|
+ data = JSON.parse(res.data)
|
|
|
+ } catch (e) {
|
|
|
+ data = res.data
|
|
|
+ }
|
|
|
+ if (data && data.code === 200) {
|
|
|
+ var detail = data.data
|
|
|
+ self.parsedInfo = {
|
|
|
+ personName: detail.extractedName || detail.matchedMemberName,
|
|
|
+ reportNumber: detail.extractedReportNumber,
|
|
|
+ reportDate: detail.extractedReportDate,
|
|
|
+ needManualSelect: detail.needManualSelect
|
|
|
+ }
|
|
|
+ if (detail.needManualSelect) {
|
|
|
+ self.showMemberSelector = true
|
|
|
+ self.loadFamilyMembers()
|
|
|
+ }
|
|
|
+ } else if (data && data.message) {
|
|
|
+ uni.showToast({ title: '解析失败: ' + data.message, icon: 'none' })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function(e) {
|
|
|
+ console.log('预览解析失败', e)
|
|
|
+ },
|
|
|
+ complete: function() {
|
|
|
+ self.parsingPreview = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
+ loadFamilyMembers() {
|
|
|
+ var self = this
|
|
|
+ // 从Vuex store获取家庭成员
|
|
|
+ var store = uni.getStorageSync('familyChildren')
|
|
|
+ if (store) {
|
|
|
+ try {
|
|
|
+ this.familyMembers = typeof store === 'string' ? JSON.parse(store) : store
|
|
|
+ } catch (e) {
|
|
|
+ this.familyMembers = []
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ selectMember(member) {
|
|
|
+ this.selectedMemberId = member.userId || member.id
|
|
|
+ this.parsedInfo = this.parsedInfo || {}
|
|
|
+ this.parsedInfo.needManualSelect = false
|
|
|
+ this.showMemberSelector = false
|
|
|
+ },
|
|
|
+ removeFile() {
|
|
|
+ this.selectedFile = null
|
|
|
+ this.parsedInfo = null
|
|
|
+ this.showMemberSelector = false
|
|
|
+ this.selectedMemberId = null
|
|
|
+ },
|
|
|
+ formatFileSize(bytes) {
|
|
|
+ if (!bytes) return ''
|
|
|
+ if (bytes < 1024) return bytes + 'B'
|
|
|
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + 'KB'
|
|
|
+ return (bytes / 1024 / 1024).toFixed(1) + 'MB'
|
|
|
+ },
|
|
|
goBack() {
|
|
|
uni.navigateBack()
|
|
|
},
|
|
|
doSubmit() {
|
|
|
- if (!this.canSubmit || this.submitting) return
|
|
|
+ if (this.submitting) return
|
|
|
var self = this
|
|
|
+
|
|
|
+ // 有文件则走上传流程
|
|
|
+ if (self.selectedFile) {
|
|
|
+ // 如果需要手动选择但未选,不允许提交
|
|
|
+ if (self.parsedInfo && self.parsedInfo.needManualSelect && !self.selectedMemberId) {
|
|
|
+ uni.showToast({ title: '请先选择成员', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ self.submitting = true
|
|
|
+ uni.showLoading({ title: '上传中...' })
|
|
|
+
|
|
|
+ // 确定使用的childId
|
|
|
+ var submitChildId = self.selectedMemberId || self.childId || ''
|
|
|
+
|
|
|
+ uni.uploadFile({
|
|
|
+ url: config.API_BASE_URL + '/api/health/report/upload',
|
|
|
+ filePath: self.selectedFile.path,
|
|
|
+ name: 'file',
|
|
|
+ formData: {
|
|
|
+ childId: String(submitChildId),
|
|
|
+ familyId: self.familyId || '',
|
|
|
+ reportType: self.form.reportType,
|
|
|
+ reportDate: self.form.reportDate,
|
|
|
+ overallScore: String(self.form.overallScore || 0),
|
|
|
+ description: self.form.description || ''
|
|
|
+ },
|
|
|
+ header: {
|
|
|
+ Authorization: uni.getStorageSync('token') || ''
|
|
|
+ },
|
|
|
+ success: function(res) {
|
|
|
+ uni.hideLoading()
|
|
|
+ var data
|
|
|
+ try {
|
|
|
+ data = JSON.parse(res.data)
|
|
|
+ } catch (e) {
|
|
|
+ data = res.data
|
|
|
+ }
|
|
|
+ if (data && data.code === 200) {
|
|
|
+ var detail = data.data
|
|
|
+ // 如果后端仍返回needManualSelect,说明需要重新选择
|
|
|
+ if (detail && detail.needManualSelect) {
|
|
|
+ self.parsedInfo = {
|
|
|
+ personName: detail.extractedName,
|
|
|
+ reportNumber: detail.extractedReportNumber,
|
|
|
+ reportDate: detail.extractedReportDate,
|
|
|
+ needManualSelect: true
|
|
|
+ }
|
|
|
+ self.showMemberSelector = true
|
|
|
+ self.loadFamilyMembers()
|
|
|
+ uni.showToast({ title: '请手动选择成员', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ uni.showToast({ title: '上传成功', icon: 'success' })
|
|
|
+ var reportId = detail && (detail.report ? detail.report.id : detail.id)
|
|
|
+ if (reportId) {
|
|
|
+ setTimeout(function() {
|
|
|
+ if (self.form.reportType === 'gut_flora') {
|
|
|
+ uni.redirectTo({ url: '/pages/health/report-survey?reportId=' + reportId + '&childId=' + submitChildId })
|
|
|
+ } else {
|
|
|
+ uni.redirectTo({ url: '/pages/body/health-report?reportId=' + reportId })
|
|
|
+ }
|
|
|
+ }, 500)
|
|
|
+ } else {
|
|
|
+ setTimeout(function() {
|
|
|
+ uni.navigateBack()
|
|
|
+ }, 500)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ var msg = (data && data.message) || '上传失败'
|
|
|
+ uni.showToast({ title: msg, icon: 'none' })
|
|
|
+ }
|
|
|
+ },
|
|
|
+ fail: function(e) {
|
|
|
+ uni.hideLoading()
|
|
|
+ uni.showToast({ title: '上传失败,请重试', icon: 'none' })
|
|
|
+ console.log('上传文件失败', e)
|
|
|
+ },
|
|
|
+ complete: function() {
|
|
|
+ self.submitting = false
|
|
|
+ }
|
|
|
+ })
|
|
|
+ return
|
|
|
+ }
|
|
|
+
|
|
|
+ // 无文件,走手动录入
|
|
|
self.submitting = true
|
|
|
uni.showLoading({ title: '提交中...' })
|
|
|
createHealthReport({
|
|
|
@@ -280,6 +573,67 @@ export default {
|
|
|
font-weight: 500;
|
|
|
}
|
|
|
|
|
|
+/* 文件上传 */
|
|
|
+.file-upload-area {
|
|
|
+ border: 2rpx dashed #CBD5E1;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 32rpx 20rpx;
|
|
|
+ text-align: center;
|
|
|
+ background: #FAFBFC;
|
|
|
+ cursor: pointer;
|
|
|
+}
|
|
|
+.file-upload-area:active {
|
|
|
+ background: #F1F5F9;
|
|
|
+}
|
|
|
+.file-upload-placeholder {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+.file-upload-icon {
|
|
|
+ font-size: 48rpx;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+.file-upload-text {
|
|
|
+ font-size: 28rpx;
|
|
|
+ color: #5B9BD5;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+.file-upload-hint {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+ margin-top: 8rpx;
|
|
|
+}
|
|
|
+.file-info {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ background: #E3F2FD;
|
|
|
+ border-radius: 12rpx;
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ margin-top: 16rpx;
|
|
|
+}
|
|
|
+.file-name {
|
|
|
+ flex: 1;
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #1565C0;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+.file-size {
|
|
|
+ font-size: 22rpx;
|
|
|
+ color: #64748B;
|
|
|
+ margin: 0 16rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.file-remove {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #EF4444;
|
|
|
+ flex-shrink: 0;
|
|
|
+ padding: 4rpx 8rpx;
|
|
|
+}
|
|
|
+
|
|
|
/* 日期 */
|
|
|
.date-display {
|
|
|
display: flex;
|
|
|
@@ -345,6 +699,103 @@ export default {
|
|
|
opacity: 0.5;
|
|
|
}
|
|
|
|
|
|
+/* ===== PDF解析预览 ===== */
|
|
|
+.parsed-preview {
|
|
|
+ background: #E8F5E9;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ margin: 0 30rpx 20rpx;
|
|
|
+ padding: 20rpx 24rpx;
|
|
|
+ border: 2rpx solid #A5D6A7;
|
|
|
+}
|
|
|
+.parsed-title {
|
|
|
+ font-size: 24rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ color: #2E7D32;
|
|
|
+ margin-bottom: 12rpx;
|
|
|
+}
|
|
|
+.parsed-row {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.parsed-label {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #388E3C;
|
|
|
+ width: 140rpx;
|
|
|
+ flex-shrink: 0;
|
|
|
+}
|
|
|
+.parsed-value {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #1B5E20;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+.match-status {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ align-items: center;
|
|
|
+ margin-top: 12rpx;
|
|
|
+ padding: 12rpx 16rpx;
|
|
|
+ background: #FFF3E0;
|
|
|
+ border-radius: 8rpx;
|
|
|
+}
|
|
|
+.match-status.success {
|
|
|
+ background: #E8F5E9;
|
|
|
+}
|
|
|
+.match-icon {
|
|
|
+ font-size: 24rpx;
|
|
|
+ margin-right: 8rpx;
|
|
|
+}
|
|
|
+.match-text {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #E65100;
|
|
|
+}
|
|
|
+.match-status.success .match-text {
|
|
|
+ color: #2E7D32;
|
|
|
+}
|
|
|
+
|
|
|
+/* ===== 家庭成员选择器 ===== */
|
|
|
+.member-selector {
|
|
|
+ margin: 0 30rpx 20rpx;
|
|
|
+}
|
|
|
+.member-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+.member-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 20rpx 24rpx;
|
|
|
+ background: #F5F7FA;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ border: 2rpx solid transparent;
|
|
|
+ min-width: 120rpx;
|
|
|
+}
|
|
|
+.member-selected {
|
|
|
+ background: #E3F2FD;
|
|
|
+ border-color: #5B9BD5;
|
|
|
+}
|
|
|
+.member-avatar {
|
|
|
+ width: 64rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #5B9BD5;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.member-name {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+
|
|
|
/* ===== 底部 ===== */
|
|
|
.bottom-spacer {
|
|
|
height: 60rpx;
|