|
|
@@ -3,7 +3,50 @@
|
|
|
<!-- 说明提示 -->
|
|
|
<view class="tip-banner">
|
|
|
<text class="tip-icon">📋</text>
|
|
|
- <text class="tip-text">上传体检/菌群/DAN测评等报告,后台自动识别类型并解析</text>
|
|
|
+ <text class="tip-text">{{ tipText }}</text>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- 报告类型 -->
|
|
|
+ <view class="form-section type-section">
|
|
|
+ <text class="form-label">请选择报告类型</text>
|
|
|
+ <view class="type-methods">
|
|
|
+ <view
|
|
|
+ class="type-card type-gut"
|
|
|
+ :class="{ 'type-card-active': reportKind === 'gut' }"
|
|
|
+ @click="selectKind('gut')">
|
|
|
+ <text class="type-card-icon">🦠</text>
|
|
|
+ <text class="type-card-title">菌群检测报告</text>
|
|
|
+ <text class="type-card-desc">肠道菌群 PDF,自动解析菌群指标</text>
|
|
|
+ </view>
|
|
|
+ <view
|
|
|
+ class="type-card type-a2"
|
|
|
+ :class="{ 'type-card-active': reportKind === 'a2' }"
|
|
|
+ @click="selectKind('a2')">
|
|
|
+ <text class="type-card-icon">🧠</text>
|
|
|
+ <text class="type-card-title">素质测评 A2</text>
|
|
|
+ <text class="type-card-desc">核心素养 PDF,解析大五人格等维度</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+
|
|
|
+ <!-- A2 需指定成员 -->
|
|
|
+ <view class="form-section" v-if="reportKind === 'a2'">
|
|
|
+ <view class="member-box">
|
|
|
+ <text class="form-label">报告属于哪位成员</text>
|
|
|
+ <view class="member-list" v-if="familyMembers.length > 0">
|
|
|
+ <view
|
|
|
+ class="member-item"
|
|
|
+ v-for="(member, mIdx) in familyMembers"
|
|
|
+ :key="getMemberKey(member)"
|
|
|
+ :class="member._selected ? 'member-selected' : ''"
|
|
|
+ hover-class="member-hover"
|
|
|
+ @click="selectMemberByIndex(mIdx)">
|
|
|
+ <text class="member-avatar">{{ memberInitial(member) }}</text>
|
|
|
+ <text class="member-name">{{ member.nickname || '未命名' }}</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <text class="member-empty" v-else>暂无家庭成员,请先在家庭中添加</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
|
|
|
<!-- 上传方式 -->
|
|
|
@@ -12,12 +55,12 @@
|
|
|
<view class="upload-card card-chat" @click="pickFromChat">
|
|
|
<view class="upload-card-icon">💬</view>
|
|
|
<text class="upload-card-title">从聊天选取</text>
|
|
|
- <text class="upload-card-desc">微信聊天中的 PDF/图片</text>
|
|
|
+ <text class="upload-card-desc">{{ reportKind === 'a2' ? '微信聊天中的 A2 PDF' : '微信聊天中的 PDF/图片' }}</text>
|
|
|
</view>
|
|
|
<view class="upload-card card-photo" @click="takePhoto">
|
|
|
<view class="upload-card-icon">📷</view>
|
|
|
<text class="upload-card-title">拍照上传</text>
|
|
|
- <text class="upload-card-desc">拍摄纸质报告或舌象</text>
|
|
|
+ <text class="upload-card-desc">{{ reportKind === 'a2' ? 'A2 仅支持 PDF 文件' : '拍摄纸质报告或舌象' }}</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
@@ -72,22 +115,22 @@
|
|
|
<view class="guide-item">
|
|
|
<view class="guide-icon-box guide-icon-blue"><text class="guide-icon">📄</text></view>
|
|
|
<view class="guide-info">
|
|
|
- <text class="guide-name">体检报告</text>
|
|
|
- <text class="guide-desc">血常规、尿常规、生化检验等,支持 PDF 或清晰照片</text>
|
|
|
+ <text class="guide-name">菌群检测报告</text>
|
|
|
+ <text class="guide-desc">肠道菌群 PDF,后台采集解析菌群指标</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="guide-item">
|
|
|
- <view class="guide-icon-box guide-icon-green"><text class="guide-icon">🦠</text></view>
|
|
|
+ <view class="guide-icon-box guide-icon-purple"><text class="guide-icon">🧠</text></view>
|
|
|
<view class="guide-info">
|
|
|
- <text class="guide-name">肠道菌群检测报告</text>
|
|
|
- <text class="guide-desc">支持 PDF 或清晰照片,AI 自动解读菌群健康</text>
|
|
|
+ <text class="guide-name">素质测评 A2</text>
|
|
|
+ <text class="guide-desc">核心素养 PDF,解析认知、情绪、大五人格等</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="guide-item">
|
|
|
- <view class="guide-icon-box guide-icon-purple"><text class="guide-icon">🧠</text></view>
|
|
|
+ <view class="guide-icon-box guide-icon-blue"><text class="guide-icon">📄</text></view>
|
|
|
<view class="guide-info">
|
|
|
- <text class="guide-name">DAN 测评报告</text>
|
|
|
- <text class="guide-desc">A2/B4 等成长测评 PDF,自动解析维度数据</text>
|
|
|
+ <text class="guide-name">体检报告</text>
|
|
|
+ <text class="guide-desc">血常规、尿常规、生化检验等,支持 PDF 或清晰照片</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
<view class="guide-item">
|
|
|
@@ -107,7 +150,8 @@
|
|
|
</view>
|
|
|
<view class="guide-tips">
|
|
|
<text class="guide-tips-title">温馨提示</text>
|
|
|
- <text class="guide-tips-item">· 上传后系统自动识别报告类型,无需手动选择</text>
|
|
|
+ <text class="guide-tips-item">· 请先选择报告类型,再从聊天选取文件</text>
|
|
|
+ <text class="guide-tips-item">· 素质测评 A2 仅支持 PDF,需指定家庭成员</text>
|
|
|
<text class="guide-tips-item">· 请确保报告清晰完整,以便准确解析</text>
|
|
|
<text class="guide-tips-item">· 确认入库后将自动生成成长方案,可再分解为每日任务</text>
|
|
|
</view>
|
|
|
@@ -116,12 +160,14 @@
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { uploadReportOnly, getReportCollectStatus } from '../../utils/api.js'
|
|
|
+import { uploadReportOnly, getReportCollectStatus, danParsePreview, danConfirmReport, getFamilyMemberList } from '../../utils/api.js'
|
|
|
|
|
|
export default {
|
|
|
data() {
|
|
|
return {
|
|
|
memberId: null,
|
|
|
+ reportKind: '',
|
|
|
+ familyMembers: [],
|
|
|
uploading: false,
|
|
|
agreedToTerms: false,
|
|
|
uploadingText: '正在上传文件...',
|
|
|
@@ -133,13 +179,93 @@ export default {
|
|
|
collectPollCount: 0
|
|
|
}
|
|
|
},
|
|
|
+ computed: {
|
|
|
+ tipText: function() {
|
|
|
+ if (this.reportKind === 'a2') {
|
|
|
+ return '已选择素质测评 A2,请指定成员后上传核心素养 PDF'
|
|
|
+ }
|
|
|
+ if (this.reportKind === 'gut') {
|
|
|
+ return '已选择菌群检测报告,上传后后台自动解析菌群指标'
|
|
|
+ }
|
|
|
+ return '请先选择报告类型:菌群检测,或素质测评 A2'
|
|
|
+ }
|
|
|
+ },
|
|
|
onLoad: function(options) {
|
|
|
this.memberId = options.memberId ? parseInt(options.memberId) : null
|
|
|
+ if (options.type === 'a2' || options.reportKind === 'a2') {
|
|
|
+ this.reportKind = 'a2'
|
|
|
+ } else if (options.type === 'gut' || options.reportKind === 'gut') {
|
|
|
+ this.reportKind = 'gut'
|
|
|
+ }
|
|
|
+ this.loadFamilyMembers()
|
|
|
},
|
|
|
onUnload: function() {
|
|
|
this.stopCollectPolling()
|
|
|
},
|
|
|
methods: {
|
|
|
+ selectKind: function(kind) {
|
|
|
+ this.reportKind = kind
|
|
|
+ },
|
|
|
+ resolveMemberId: function(member) {
|
|
|
+ if (!member) return null
|
|
|
+ var raw = member.id || member.userId
|
|
|
+ if (raw === 0 || raw === '0') return 0
|
|
|
+ if (!raw) return null
|
|
|
+ var id = parseInt(raw)
|
|
|
+ return isNaN(id) ? null : id
|
|
|
+ },
|
|
|
+ selectMemberByIndex: function(idx) {
|
|
|
+ var list = this.familyMembers
|
|
|
+ if (!list || idx < 0 || idx >= list.length) return
|
|
|
+ var id = this.resolveMemberId(list[idx])
|
|
|
+ if (id === null) {
|
|
|
+ uni.showToast({ title: '该成员缺少编号,无法选择', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ this.memberId = id
|
|
|
+ this.markSelectedMember(id)
|
|
|
+ },
|
|
|
+ markSelectedMember: function(memberId) {
|
|
|
+ var list = this.familyMembers || []
|
|
|
+ var target = memberId === null || memberId === undefined ? null : parseInt(memberId)
|
|
|
+ for (var i = 0; i < list.length; i++) {
|
|
|
+ var cur = this.resolveMemberId(list[i])
|
|
|
+ this.$set(list[i], '_selected', target !== null && !isNaN(target) && cur === target)
|
|
|
+ }
|
|
|
+ },
|
|
|
+ getMemberKey: function(member) {
|
|
|
+ var id = this.resolveMemberId(member)
|
|
|
+ return id === null ? 'm0' : ('m' + id)
|
|
|
+ },
|
|
|
+ memberInitial: function(member) {
|
|
|
+ var name = (member && member.nickname) || '?'
|
|
|
+ return name.substring(0, 1)
|
|
|
+ },
|
|
|
+ loadFamilyMembers: function() {
|
|
|
+ var self = this
|
|
|
+ getFamilyMemberList({ visibleOnly: true }).then(function(res) {
|
|
|
+ var list = (res && res.data) || []
|
|
|
+ self.familyMembers = list instanceof Array ? list : []
|
|
|
+ if (!self.memberId && self.familyMembers.length === 1) {
|
|
|
+ self.memberId = self.resolveMemberId(self.familyMembers[0])
|
|
|
+ }
|
|
|
+ self.markSelectedMember(self.memberId)
|
|
|
+ }).catch(function() {
|
|
|
+ self.familyMembers = []
|
|
|
+ })
|
|
|
+ },
|
|
|
+ ensureTypeSelected: function() {
|
|
|
+ if (this.reportKind === 'gut' || this.reportKind === 'a2') {
|
|
|
+ return true
|
|
|
+ }
|
|
|
+ uni.showToast({ title: '请先选择报告类型', icon: 'none' })
|
|
|
+ return false
|
|
|
+ },
|
|
|
+ ensureA2Member: function() {
|
|
|
+ if (this.memberId) return true
|
|
|
+ uni.showToast({ title: '请先选择报告所属成员', icon: 'none' })
|
|
|
+ return false
|
|
|
+ },
|
|
|
toggleAgree: function() {
|
|
|
this.agreedToTerms = !this.agreedToTerms
|
|
|
},
|
|
|
@@ -164,7 +290,13 @@ export default {
|
|
|
},
|
|
|
pickFromChat: function() {
|
|
|
var self = this
|
|
|
+ if (!this.ensureTypeSelected()) return
|
|
|
+ if (this.reportKind === 'a2' && !this.ensureA2Member()) return
|
|
|
this.ensureAgreed(function() {
|
|
|
+ if (self.reportKind === 'a2') {
|
|
|
+ self.chooseChatFile('file', '聊天文件')
|
|
|
+ return
|
|
|
+ }
|
|
|
// type='all' 的选择器默认停留在图片 tab,用户难以发现可切换选文件。
|
|
|
// 改为先弹 actionSheet 让用户明确选择图片或文件,分别走对应 type。
|
|
|
uni.showActionSheet({
|
|
|
@@ -200,6 +332,11 @@ export default {
|
|
|
},
|
|
|
takePhoto: function() {
|
|
|
var self = this
|
|
|
+ if (!this.ensureTypeSelected()) return
|
|
|
+ if (this.reportKind === 'a2') {
|
|
|
+ uni.showToast({ title: '素质测评 A2 请上传 PDF 文件', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
this.ensureAgreed(function() {
|
|
|
uni.chooseImage({
|
|
|
count: 1,
|
|
|
@@ -214,12 +351,16 @@ export default {
|
|
|
},
|
|
|
uploadReport: function(filePath, fileName) {
|
|
|
if (!filePath) return
|
|
|
+ if (this.reportKind === 'a2') {
|
|
|
+ this.uploadA2Report(filePath, fileName)
|
|
|
+ return
|
|
|
+ }
|
|
|
var self = this
|
|
|
self.uploading = true
|
|
|
self.uploadingText = '正在上传文件...'
|
|
|
uni.showLoading({ title: '上传中...' })
|
|
|
|
|
|
- // 不传 type,让后端通过指纹自动判断报告类型
|
|
|
+ // 菌群检测:走健康报告异步采集
|
|
|
uploadReportOnly(filePath).then(function(res) {
|
|
|
uni.hideLoading()
|
|
|
self.uploading = false
|
|
|
@@ -259,6 +400,58 @@ export default {
|
|
|
uni.showToast({ title: msg, icon: 'none' })
|
|
|
})
|
|
|
},
|
|
|
+ uploadA2Report: function(filePath, fileName) {
|
|
|
+ if (!this.ensureA2Member()) return
|
|
|
+ var name = (fileName || '').toLowerCase()
|
|
|
+ if (name && name.indexOf('.pdf') < 0 && name !== '聊天文件') {
|
|
|
+ uni.showToast({ title: '素质测评 A2 仅支持 PDF', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ var self = this
|
|
|
+ self.uploading = true
|
|
|
+ self.uploadingText = '正在解析 A2 报告...'
|
|
|
+ uni.showLoading({ title: '解析中...' })
|
|
|
+ danParsePreview(filePath, 'mind', self.memberId).then(function(res) {
|
|
|
+ var data = (res && res.data) || {}
|
|
|
+ var uploadId = data.uploadId || data.draftId
|
|
|
+ if (!uploadId) {
|
|
|
+ uni.hideLoading()
|
|
|
+ self.uploading = false
|
|
|
+ uni.showToast({ title: (res && res.message) || 'A2 解析失败', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ var extra = {}
|
|
|
+ try {
|
|
|
+ extra = typeof data.extraJson === 'string' ? JSON.parse(data.extraJson) : (data.extraJson || {})
|
|
|
+ } catch (e) {
|
|
|
+ extra = {}
|
|
|
+ }
|
|
|
+ var childName = extra.name || ''
|
|
|
+ var assessmentDate = extra.reportDate || ''
|
|
|
+ if (assessmentDate && assessmentDate.length >= 10) {
|
|
|
+ assessmentDate = assessmentDate.substring(0, 10)
|
|
|
+ }
|
|
|
+ return danConfirmReport({
|
|
|
+ draftId: uploadId,
|
|
|
+ childName: childName,
|
|
|
+ assessmentDate: assessmentDate
|
|
|
+ }).then(function(confirmRes) {
|
|
|
+ uni.hideLoading()
|
|
|
+ self.uploading = false
|
|
|
+ var confirmData = (confirmRes && confirmRes.data) || {}
|
|
|
+ var reportId = confirmData.uploadId || uploadId
|
|
|
+ uni.showToast({ title: 'A2 报告已入库', icon: 'success' })
|
|
|
+ setTimeout(function() {
|
|
|
+ uni.redirectTo({ url: '/pages/dan-assessment/report-result?reportId=' + reportId })
|
|
|
+ }, 600)
|
|
|
+ })
|
|
|
+ }).catch(function(err) {
|
|
|
+ uni.hideLoading()
|
|
|
+ self.uploading = false
|
|
|
+ var msg = (err && err.message) || 'A2 解析失败,请重试'
|
|
|
+ uni.showToast({ title: msg, icon: 'none', duration: 3000 })
|
|
|
+ })
|
|
|
+ },
|
|
|
stopCollectPolling: function() {
|
|
|
if (this.collectTimer) {
|
|
|
clearInterval(this.collectTimer)
|
|
|
@@ -367,6 +560,100 @@ export default {
|
|
|
.form-section {
|
|
|
margin: 0 30rpx;
|
|
|
}
|
|
|
+.type-section {
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.type-methods {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+.type-card {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding: 24rpx 20rpx;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ background: #fff;
|
|
|
+ border: 2rpx solid #E2E8F0;
|
|
|
+}
|
|
|
+.type-card-active {
|
|
|
+ border-color: #5B9BD5;
|
|
|
+ background: #F0F7FF;
|
|
|
+}
|
|
|
+.type-gut.type-card-active {
|
|
|
+ border-color: #22C55E;
|
|
|
+ background: #F0FDF4;
|
|
|
+}
|
|
|
+.type-a2.type-card-active {
|
|
|
+ border-color: #8B5CF6;
|
|
|
+ background: #F5F3FF;
|
|
|
+}
|
|
|
+.type-card-icon {
|
|
|
+ font-size: 40rpx;
|
|
|
+ margin-bottom: 10rpx;
|
|
|
+}
|
|
|
+.type-card-title {
|
|
|
+ font-size: 26rpx;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #1E293B;
|
|
|
+ margin-bottom: 6rpx;
|
|
|
+}
|
|
|
+.type-card-desc {
|
|
|
+ font-size: 20rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+ line-height: 1.5;
|
|
|
+}
|
|
|
+.member-box {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ padding: 24rpx;
|
|
|
+ margin-bottom: 20rpx;
|
|
|
+}
|
|
|
+.member-list {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: row;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16rpx;
|
|
|
+}
|
|
|
+.member-item {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ padding: 16rpx 20rpx;
|
|
|
+ background: #F5F7FA;
|
|
|
+ border-radius: 16rpx;
|
|
|
+ border: 2rpx solid transparent;
|
|
|
+ min-width: 120rpx;
|
|
|
+}
|
|
|
+.member-selected {
|
|
|
+ background: #EDE9FE;
|
|
|
+ border-color: #8B5CF6;
|
|
|
+}
|
|
|
+.member-hover {
|
|
|
+ opacity: 0.85;
|
|
|
+}
|
|
|
+.member-avatar {
|
|
|
+ width: 64rpx;
|
|
|
+ height: 64rpx;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: #8B5CF6;
|
|
|
+ color: #fff;
|
|
|
+ font-size: 28rpx;
|
|
|
+ font-weight: bold;
|
|
|
+ text-align: center;
|
|
|
+ line-height: 64rpx;
|
|
|
+ margin-bottom: 8rpx;
|
|
|
+}
|
|
|
+.member-name {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #333;
|
|
|
+}
|
|
|
+.member-empty {
|
|
|
+ font-size: 24rpx;
|
|
|
+ color: #94A3B8;
|
|
|
+}
|
|
|
.form-item {
|
|
|
background: #fff;
|
|
|
border-radius: 16rpx;
|