|
|
@@ -213,18 +213,32 @@
|
|
|
|
|
|
<feedback-popup :visible="feedbackVisible" :report-id="reportId" @close="closeFeedback" />
|
|
|
|
|
|
- <view class="fab" v-if="!isEditing" @tap="toggleEdit">
|
|
|
+ <view class="fab" v-if="!isEditing && !isDraftMode" @tap="toggleEdit">
|
|
|
<text class="fab-icon">✏️</text>
|
|
|
</view>
|
|
|
<view class="bottom-bar" v-if="isEditing">
|
|
|
<view class="btn-cancel" @tap="cancelEdit">取消</view>
|
|
|
<view class="btn-save" @tap="saveEdit">保存</view>
|
|
|
</view>
|
|
|
+ <view class="draft-bar" v-if="isDraftMode && !confirming && !parsing">
|
|
|
+ <view class="btn-discard" @tap="doDiscard">放弃</view>
|
|
|
+ <view class="btn-confirm" @tap="doConfirm">确认入库</view>
|
|
|
+ </view>
|
|
|
+ <view class="draft-bar" v-if="parsing">
|
|
|
+ <view class="btn-parsing">
|
|
|
+ <text class="parsing-text">AI 解析中...</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="draft-bar" v-if="confirming">
|
|
|
+ <view class="btn-parsing">
|
|
|
+ <text class="parsing-text">确认中...</text>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis } from '../../utils/api.js'
|
|
|
+import { getReportDetail, editHealthReport, queryKnowledgeBase, queryIndicatorKnowledge, getGutFloraAnalysis, confirmReportPreview, discardReportDraft, parseReportDraft } from '../../utils/api.js'
|
|
|
import HealthKnowledgePopup from '../../components/health-knowledge-popup.vue'
|
|
|
import FeedbackPopup from '../../components/feedback-popup.vue'
|
|
|
|
|
|
@@ -262,12 +276,22 @@ export default {
|
|
|
knowledgeData: {},
|
|
|
feedbackVisible: false,
|
|
|
indStatuses: ['正常', '偏低', '偏高', '缺乏', '不足', '过多', '异常'],
|
|
|
- analysisData: null
|
|
|
+ analysisData: null,
|
|
|
+ // draft 模式字段
|
|
|
+ draftId: null,
|
|
|
+ isDraftMode: false,
|
|
|
+ payload: null,
|
|
|
+ confirming: false,
|
|
|
+ parsing: false
|
|
|
}
|
|
|
},
|
|
|
onLoad: function(options) {
|
|
|
this.reportId = options.reportId ? parseInt(options.reportId) : null
|
|
|
- if (this.reportId) {
|
|
|
+ this.draftId = options.draftId ? parseInt(options.draftId) : null
|
|
|
+ if (this.draftId) {
|
|
|
+ this.isDraftMode = true
|
|
|
+ this.loadDraftData()
|
|
|
+ } else if (this.reportId) {
|
|
|
this.loadData()
|
|
|
}
|
|
|
},
|
|
|
@@ -322,24 +346,109 @@ export default {
|
|
|
}
|
|
|
}).catch(function() {})
|
|
|
},
|
|
|
+ loadDraftData: function() {
|
|
|
+ var self = this
|
|
|
+ self.parsing = true
|
|
|
+ parseReportDraft(self.draftId).then(function(res) {
|
|
|
+ self.parsing = false
|
|
|
+ if (res.code !== 200 || !res.data) {
|
|
|
+ uni.showToast({ title: res.message || '加载失败', icon: 'none' })
|
|
|
+ return
|
|
|
+ }
|
|
|
+ self.payload = res.data.payload || {}
|
|
|
+ self.isDraftMode = true
|
|
|
+ self.applyDraftPayload(self.payload)
|
|
|
+ }).catch(function() {
|
|
|
+ self.parsing = false
|
|
|
+ uni.showToast({ title: '加载异常', icon: 'none' })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ applyDraftPayload: function(payload) {
|
|
|
+ // 将 draft payload 映射到模板所需的数据结构(report/scoreItems/indicators)
|
|
|
+ var report = {}
|
|
|
+ if (payload.summary) {
|
|
|
+ var s = payload.summary
|
|
|
+ report.personName = s.personName || ''
|
|
|
+ report.reportNumber = s.reportNumber || ''
|
|
|
+ report.gutAge = s.gutAge || ''
|
|
|
+ report.gutType = s.gutType || ''
|
|
|
+ report.overallScore = s.overallScore
|
|
|
+ report.gutHealthScore = s.gutHealthScore
|
|
|
+ report.chronicDiseaseScore = s.chronicDiseaseScore
|
|
|
+ report.nutritionScore = s.nutritionScore
|
|
|
+ report.balanceScore = s.balanceScore
|
|
|
+ report.diversityScore = s.diversityScore
|
|
|
+ report.beneficialScore = s.beneficialScore
|
|
|
+ report.harmfulScore = s.harmfulScore
|
|
|
+ report.coreGenusScore = s.coreGenusScore
|
|
|
+ }
|
|
|
+ this.report = report
|
|
|
+ this.buildScores({ report: report })
|
|
|
+ this.buildDraftIndicators(payload.indicators || [])
|
|
|
+ },
|
|
|
+ buildDraftIndicators: function(indicators) {
|
|
|
+ // 复用 buildIndicators 的分组逻辑,输入为 payload.indicators
|
|
|
+ var all = []
|
|
|
+ for (var i = 0; i < indicators.length; i++) {
|
|
|
+ var ind = indicators[i]
|
|
|
+ var item = {
|
|
|
+ indicatorName: ind.indicatorName || ind.name || '',
|
|
|
+ indicatorValue: ind.indicatorValue || ind.value || '',
|
|
|
+ status: ind.status || '正常',
|
|
|
+ refRange: ind.refRange || '',
|
|
|
+ category: ind.category || '其他'
|
|
|
+ }
|
|
|
+ item._cssClass = this._cssForStatus(item.status)
|
|
|
+ all.push(item)
|
|
|
+ }
|
|
|
+ this.allIndicators = all
|
|
|
+ this.indicatorCount = all.length
|
|
|
+ this._groupIndicators(all)
|
|
|
+ },
|
|
|
+ _groupIndicators: function(all) {
|
|
|
+ var self = this
|
|
|
+ var gut = []
|
|
|
+ var nutriGroups = []
|
|
|
+ var nutriMap = {}
|
|
|
+ for (var i = 0; i < all.length; i++) {
|
|
|
+ var ind = all[i]
|
|
|
+ var g = ind.category
|
|
|
+ var isGutBarrier = g === '肠道屏障与代谢物' || g === '短链脂肪酸' ||
|
|
|
+ g === '神经递质与激素' || g === '抗生素风险评估'
|
|
|
+ if (isGutBarrier) {
|
|
|
+ gut.push(ind)
|
|
|
+ } else {
|
|
|
+ if (!nutriMap[g]) nutriMap[g] = []
|
|
|
+ nutriMap[g].push(ind)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.gutIndicators = gut
|
|
|
+ for (var key in nutriMap) {
|
|
|
+ nutriGroups.push({ category: key, items: nutriMap[key] })
|
|
|
+ }
|
|
|
+ this.nutritionGroups = nutriGroups
|
|
|
+ },
|
|
|
buildScores: function(data) {
|
|
|
+ // 兼容两套字段名:实体存 balanceScore/diversityScore 等,旧视图读 gutBalanceScore 等
|
|
|
var r = data.report || data
|
|
|
+ var s = r.summary || r
|
|
|
var items = []
|
|
|
var scoreFields = [
|
|
|
- { key: 'overallScore', label: '综合', color: '#4A9BD7' },
|
|
|
- { key: 'gutHealthScore', label: '菌群健康', color: '#10B981' },
|
|
|
- { key: 'chronicDiseaseScore', label: '慢病控制', color: '#F59E0B' },
|
|
|
- { key: 'nutritionScore', label: '营养均衡', color: '#FF8C42' },
|
|
|
- { key: 'gutBalanceScore', label: '平衡', color: '#6366F1' },
|
|
|
- { key: 'gutDiversityScore', label: '多样性', color: '#FF6B9D' },
|
|
|
- { key: 'beneficialBacteriaScore', label: '有益菌', color: '#10B981' },
|
|
|
- { key: 'harmfulBacteriaScore', label: '有害菌', color: '#C62828' },
|
|
|
- { key: 'coreSpeciesScore', label: '核心菌属', color: '#4A9BD7' }
|
|
|
+ { key: 'overallScore', label: '综合', color: '#4A9BD7' },
|
|
|
+ { key: 'gutHealthScore', label: '菌群健康', color: '#10B981' },
|
|
|
+ { key: 'chronicDiseaseScore', label: '慢病控制', color: '#F59E0B' },
|
|
|
+ { key: 'nutritionScore', label: '营养均衡', color: '#FF8C42' },
|
|
|
+ { key: 'balanceScore', label: '平衡', color: '#6366F1' },
|
|
|
+ { key: 'diversityScore', label: '多样性', color: '#FF6B9D' },
|
|
|
+ { key: 'beneficialScore', label: '有益菌', color: '#10B981' },
|
|
|
+ { key: 'harmfulScore', label: '有害菌', color: '#C62828' },
|
|
|
+ { key: 'coreGenusScore', label: '核心菌属', color: '#4A9BD7' }
|
|
|
]
|
|
|
for (var i = 0; i < scoreFields.length; i++) {
|
|
|
var f = scoreFields[i]
|
|
|
- if (r[f.key] != null) {
|
|
|
- items.push({ value: r[f.key], label: f.label, color: f.color })
|
|
|
+ var val = s[f.key] != null ? s[f.key] : r[f.key]
|
|
|
+ if (val != null) {
|
|
|
+ items.push({ value: val, label: f.label, color: f.color })
|
|
|
}
|
|
|
}
|
|
|
this.scoreItems = items
|
|
|
@@ -442,9 +551,17 @@ export default {
|
|
|
saveEdit: function() {
|
|
|
var self = this
|
|
|
var payload = {
|
|
|
- overallScore: this.report.overallScore,
|
|
|
- gutHealthScore: this.report.gutHealthScore,
|
|
|
- nutritionScore: this.report.nutritionScore,
|
|
|
+ summary: {
|
|
|
+ overallScore: this.report.overallScore,
|
|
|
+ gutHealthScore: this.report.gutHealthScore,
|
|
|
+ chronicDiseaseScore: this.report.chronicDiseaseScore,
|
|
|
+ nutritionScore: this.report.nutritionScore,
|
|
|
+ balanceScore: this.report.balanceScore,
|
|
|
+ diversityScore: this.report.diversityScore,
|
|
|
+ beneficialScore: this.report.beneficialScore,
|
|
|
+ harmfulScore: this.report.harmfulScore,
|
|
|
+ coreGenusScore: this.report.coreGenusScore
|
|
|
+ },
|
|
|
indicators: this.allIndicators
|
|
|
}
|
|
|
editHealthReport(this.reportId, payload, 0).then(function(res) {
|
|
|
@@ -457,6 +574,45 @@ export default {
|
|
|
}
|
|
|
})
|
|
|
},
|
|
|
+ doConfirm: function() {
|
|
|
+ var self = this
|
|
|
+ if (!self.draftId) return
|
|
|
+ self.confirming = true
|
|
|
+ confirmReportPreview({ draftId: self.draftId, payload: self.payload }).then(function(res) {
|
|
|
+ self.confirming = false
|
|
|
+ if (res.code === 200) {
|
|
|
+ var reportId = res.data && res.data.reportId
|
|
|
+ if (reportId) {
|
|
|
+ uni.redirectTo({ url: '/pages/health/gut-flora-detail?reportId=' + reportId })
|
|
|
+ } else {
|
|
|
+ uni.navigateBack()
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res.message || '确认失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ }).catch(function() {
|
|
|
+ self.confirming = false
|
|
|
+ uni.showToast({ title: '确认异常', icon: 'none' })
|
|
|
+ })
|
|
|
+ },
|
|
|
+ doDiscard: function() {
|
|
|
+ var self = this
|
|
|
+ if (!self.draftId) return
|
|
|
+ uni.showModal({
|
|
|
+ title: '提示',
|
|
|
+ content: '确定放弃该报告草稿吗?',
|
|
|
+ success: function(res) {
|
|
|
+ if (!res.confirm) return
|
|
|
+ discardReportDraft(self.draftId).then(function(res2) {
|
|
|
+ if (res2.code === 200) {
|
|
|
+ uni.navigateBack()
|
|
|
+ } else {
|
|
|
+ uni.showToast({ title: res2.message || '操作失败', icon: 'none' })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
goToSpecies: function() {
|
|
|
uni.navigateTo({ url: '/pages/health/gut-flora-species-detail?reportId=' + this.reportId })
|
|
|
},
|
|
|
@@ -546,6 +702,12 @@ export default {
|
|
|
.fab { position: fixed; right: 30rpx; bottom: 40rpx; width: 100rpx; height: 100rpx; background: #4A9BD7; border-radius: 50%; display: flex; align-items: center; justify-content: center; box-shadow: 0 4rpx 16rpx rgba(74,155,215,0.4); }
|
|
|
.fab-icon { font-size: 40rpx; color: #fff; }
|
|
|
|
|
|
+.draft-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; gap: 20rpx; z-index: 100; }
|
|
|
+.btn-discard { flex: 1; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #666; }
|
|
|
+.btn-confirm { flex: 1; padding: 20rpx; background: #4A9BD7; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #fff; }
|
|
|
+.btn-parsing { flex: 1; padding: 20rpx; background: #f0f0f0; border-radius: 12rpx; text-align: center; }
|
|
|
+.parsing-text { font-size: 28rpx; color: #999; }
|
|
|
+
|
|
|
.bottom-bar { position: fixed; bottom: 0; left: 0; right: 0; display: flex; padding: 20rpx 30rpx; background: #fff; border-top: 1rpx solid #eee; gap: 20rpx; z-index: 100; }
|
|
|
.btn-cancel { flex: 1; padding: 20rpx; border: 1rpx solid #ddd; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #666; }
|
|
|
.btn-save { flex: 1; padding: 20rpx; background: #4A9BD7; border-radius: 12rpx; text-align: center; font-size: 28rpx; color: #fff; }
|