| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013 |
- <template>
- <view class="page">
- <scroll-view class="content" scroll-y>
- <!-- 报告头部 -->
- <view class="report-header">
- <text class="report-title">肠道菌群检测报告</text>
- <view class="report-meta">
- <text class="meta-item" v-if="report.personName">{{ report.personName }}</text>
- <text class="meta-item" v-if="report.gender">{{ report.gender }}</text>
- <text class="meta-item" v-if="report.age">{{ report.age }}岁</text>
- </view>
- <view class="report-meta">
- <text class="meta-item" v-if="report.reportDate">日期: {{ reportDateText }}</text>
- <text class="meta-item" v-if="report.reportNumber">编号: {{ report.reportNumber }}</text>
- </view>
- <view class="badge-row" v-if="report.gutAge || report.gutType || report.shannonIndex">
- <text class="badge" v-if="report.gutAge">肠龄: {{ report.gutAge }}</text>
- <text class="badge" v-if="report.gutType">肠型: {{ report.gutType }}</text>
- <text class="badge" v-if="report.shannonIndex">多样性指数: {{ report.shannonIndex }}</text>
- </view>
- <view class="rating-line" v-if="report.healthRating">
- <text class="rating-text">综合评级: {{ report.healthRating }}</text>
- </view>
- <!-- 报告解读 -->
- <view class="interpretation-box" v-if="reportInterpretation && reportInterpretation.gutAgeAssessment">
- <text class="interp-text">{{ reportInterpretation.gutAgeAssessment }}</text>
- </view>
- </view>
- <!-- 评分圆环(折行显示,无横向滚动) -->
- <view class="scores-wrap">
- <view class="score-item" v-for="(score, idx) in scoreItems" :key="idx">
- <view class="score-ring" :style="'border-color: ' + score.color + '; color: ' + score.color">
- <text class="score-val">{{ score.value }}</text>
- </view>
- <text class="score-label">{{ score.label }}</text>
- </view>
- </view>
- <!-- 快速跳转 -->
- <view class="quick-nav">
- <view class="nav-card" @tap="goToSpecies">
- <text class="nav-icon">🦠</text>
- <text class="nav-label">菌群详情</text>
- <text class="nav-count">{{ floraAbnormalCount }}/{{ floraCount }}种</text>
- </view>
- <view class="nav-card" @tap="goToPathogen">
- <text class="nav-icon">🔬</text>
- <text class="nav-label">病原菌</text>
- <text class="nav-count">{{ pathogenCount }}项</text>
- </view>
- <view class="nav-card" @tap="goToRisks">
- <text class="nav-icon">⚠️</text>
- <text class="nav-label">疾病风险</text>
- <text class="nav-count">{{ riskAttentionCount }}/{{ riskCount }}项</text>
- </view>
- <view class="nav-card" @tap="goToFoods">
- <text class="nav-icon">🍽️</text>
- <text class="nav-label">饮食推荐</text>
- <text class="nav-count">{{ foodCount }}项</text>
- </view>
- <view class="nav-card caution-card" @tap="goToCaution" v-if="cautionCount > 0">
- <text class="nav-icon">⚠️</text>
- <text class="nav-label">食材戒备</text>
- <text class="nav-count caution-count">{{ cautionCount }}项</text>
- </view>
- </view>
- <!-- 菌群功能分析 -->
- <view class="analysis-section" v-if="analysisData">
- <view class="section-title-bar">
- <text class="analysis-title">🧬 菌群功能分析</text>
- <text class="analysis-subtitle">基于知识库的功能维度解读</text>
- </view>
- <!-- 功能维度聚合 -->
- <view class="analysis-card">
- <text class="card-label">功能维度评估</text>
- <view class="func-dim" v-for="(dim, di) in analysisData.functionalAnalysis" :key="di">
- <view class="func-dim-head">
- <text class="func-dim-name">{{ dim.name }}</text>
- <text :class="'func-badge func-' + statusKey(dim.status)">{{ dim.status }}</text>
- </view>
- <view class="func-bar">
- <view class="func-bar-fill" :class="'fill-' + statusKey(dim.status)" :style="'width:' + dim.level + '%'"></view>
- </view>
- <text class="func-dim-summary">{{ dim.summary }}</text>
- </view>
- </view>
- <!-- P/B 比值 -->
- <view class="analysis-card" v-if="analysisData.pbRatio && analysisData.pbRatio.ratio !== undefined">
- <text class="card-label">饮食模式(P/B 比值)</text>
- <view class="pb-row">
- <text class="pb-label">Prevotella: {{ analysisData.pbRatio.prevotellaValue }}</text>
- <text class="pb-label">Bacteroides: {{ analysisData.pbRatio.bacteroidesValue }}</text>
- <text class="pb-ratio">比值 {{ analysisData.pbRatio.ratio }}</text>
- </view>
- <text class="func-dim-summary">{{ analysisData.pbRatio.interpretation }}</text>
- </view>
- <!-- 饮食模式推断 -->
- <view class="analysis-card" v-if="analysisData.dietaryPattern">
- <text class="card-label">饮食模式推断</text>
- <text class="pattern-name">{{ analysisData.dietaryPattern.pattern }}</text>
- <text class="func-dim-summary">{{ analysisData.dietaryPattern.detail }}</text>
- </view>
- <!-- 风险提示 -->
- <view class="analysis-card">
- <text class="card-label">风险提示</text>
- <view class="risk-item" v-for="(risk, ri) in analysisData.riskIndicators" :key="ri">
- <text class="risk-name">{{ risk.name }}</text>
- <text :class="'risk-level risk-' + statusKey(risk.level)">{{ risk.level }}</text>
- <text class="risk-bacteria" v-if="risk.relatedBacteria && risk.relatedBacteria.length > 0">
- {{ risk.relatedBacteria.join('、') }}
- </text>
- </view>
- </view>
- <!-- 益生菌指导 -->
- <view class="analysis-card" v-if="analysisData.probioticAdvice">
- <text class="card-label">益生菌指导</text>
- <view class="probiotic-item" v-for="(advice, key) in analysisData.probioticAdvice" :key="key">
- <text class="probiotic-name">{{ key === 'bifidobacterium' ? '双歧杆菌' : '乳杆菌' }}</text>
- <text :class="'func-badge func-' + statusKey(advice.status)">{{ advice.status }}</text>
- <text class="probiotic-advice">{{ advice.advice }}</text>
- </view>
- </view>
- </view>
- <!-- 肠道功能手风琴 -->
- <view class="accordion-section">
- <view class="accordion-header" @tap="toggleAccordion('gut')">
- <text class="accordion-title">肠道功能与代谢物</text>
- <text class="accordion-stats" v-if="gutStats.total > 0">有效 {{ gutStats.valid }} / 异常 {{ gutStats.abnormal }} / 共 {{ gutStats.total }} 项</text>
- <text class="accordion-arrow">{{ accordionOpen.gut ? '收起' : '展开' }}</text>
- </view>
- <view class="accordion-body" v-if="accordionOpen.gut">
- <view class="indicator-card" v-for="(item, idx) in gutIndicators" :key="idx">
- <view class="indicator-row">
- <text class="ind-help-btn" @tap.stop="showKnowledge(indicatorType(item), item.indicatorName)">?</text>
- <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
- {{ item.indicatorName }}
- <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
- <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
- <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
- </text>
- <view class="indicator-value" v-if="!isEditing">
- <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
- <text class="indicator-unit" v-if="item.unit">{{ item.unit }}</text>
- </view>
- <view class="indicator-edit" v-if="isEditing">
- <input class="edit-input" type="text" v-model="item.indicatorValue" />
- <picker :value="indStatusIndex(item.status)" :range="indStatuses" @change="onIndStatusChange($event, item)">
- <text class="edit-picker">{{ item.status || '正常' }}</text>
- </picker>
- </view>
- </view>
- <text class="indicator-range" v-if="item.refRange">参考: {{ item.refRange }}</text>
- <text class="indicator-symptoms" v-if="item.symptoms">症状: {{ item.symptoms }}</text>
- </view>
- <view class="empty-hint" v-if="gutIndicators.length === 0">暂无数据</view>
- </view>
- </view>
- <!-- 营养指标手风琴 -->
- <view class="accordion-section">
- <view class="accordion-header" @tap="toggleAccordion('nutri')">
- <text class="accordion-title">营养指标</text>
- <text class="accordion-stats" v-if="nutriStats.total > 0">有效 {{ nutriStats.valid }} / 异常 {{ nutriStats.abnormal }} / 共 {{ nutriStats.total }} 项</text>
- <text class="accordion-arrow">{{ accordionOpen.nutri ? '收起' : '展开' }}</text>
- </view>
- <view class="accordion-body" v-if="accordionOpen.nutri">
- <view class="sub-group" v-for="(group, gIdx) in nutritionGroups" :key="gIdx">
- <text class="sub-group-title">{{ group.category }}</text>
- <view class="indicator-card" v-for="(item, idx) in group.items" :key="idx">
- <view class="indicator-row">
- <text class="ind-help-btn" @tap.stop="showKnowledge(indicatorType(item), item.indicatorName)">?</text>
- <text class="indicator-name" @tap="showKnowledge(indicatorType(item), item.indicatorName)">
- {{ item.indicatorName }}
- <text class="arrow-up" v-if="item.status === '偏高' || item.status === '过多'"> ↑</text>
- <text class="arrow-down" v-else-if="item.status === '偏低' || item.status === '缺乏' || item.status === '不足'"> ↓</text>
- <text class="arrow-warn" v-else-if="item.status === '异常'"> ⚠</text>
- </text>
- <view class="indicator-value" v-if="!isEditing">
- <text :class="'val-text val-' + (item._cssClass || 'normal')">{{ item.indicatorValue || '--' }}</text>
- <text class="indicator-unit" v-if="item.unit">{{ item.unit }}</text>
- </view>
- <view class="indicator-edit" v-if="isEditing">
- <input class="edit-input" type="text" v-model="item.indicatorValue" />
- <picker :value="indStatusIndex(item.status)" :range="indStatuses" @change="onIndStatusChange($event, item)">
- <text class="edit-picker">{{ item.status || '正常' }}</text>
- </picker>
- </view>
- </view>
- <text class="indicator-range" v-if="item.refRange">参考: {{ item.refRange }}</text>
- <text class="indicator-symptoms" v-if="item.symptoms">症状: {{ item.symptoms }}</text>
- </view>
- </view>
- <view class="empty-hint" v-if="nutritionGroups.length === 0">暂无数据</view>
- </view>
- </view>
- <view class="feedback-section" @tap="openFeedback">
- <text class="feedback-icon">💬</text>
- <text class="feedback-text">对这个报告有疑问或建议?告诉我们</text>
- <text class="feedback-arrow">›</text>
- </view>
- <view class="bottom-spacer"></view>
- </scroll-view>
- <health-knowledge-popup :visible="knowledgeVisible" :data="knowledgeData" @close="closeKnowledge" />
- <feedback-popup :visible="feedbackVisible" :report-id="reportId" @close="closeFeedback" />
- <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, confirmReportPreview, discardReportDraft, parseReportDraft, getHealthFoods, getFoodCautionList } from '../../utils/api.js'
- import HealthKnowledgePopup from '../../components/health-knowledge-popup.vue'
- import FeedbackPopup from '../../components/feedback-popup.vue'
- import { parseDate } from '../../utils/format.js'
- var INDICATOR_TYPE_MAP = {
- '维生素': 'vitamin',
- '氨基酸': 'amino_acid',
- '微量元素': 'indicator',
- '肠道屏障与代谢物': 'indicator',
- '短链脂肪酸': 'indicator',
- '神经递质与激素': 'indicator',
- '抗生素风险评估': 'indicator'
- }
- export default {
- components: { HealthKnowledgePopup, FeedbackPopup },
- data() {
- return {
- reportId: null,
- reportData: null,
- report: {},
- reportInterpretation: null,
- isEditing: false,
- accordionOpen: { gut: false, nutri: false },
- scoreItems: [],
- allIndicators: [],
- gutIndicators: [],
- nutritionGroups: [],
- floraCount: 0,
- floraAbnormalCount: 0,
- riskCount: 0,
- riskAttentionCount: 0,
- foodCount: 0,
- cautionCount: 0,
- indicatorCount: 0,
- pathogenCount: 0,
- knowledgeVisible: false,
- knowledgeData: {},
- feedbackVisible: false,
- indStatuses: ['正常', '偏低', '偏高', '缺乏', '不足', '过多', '异常'],
- analysisData: null,
- // draft 模式字段
- draftId: null,
- isDraftMode: false,
- payload: null,
- confirming: false,
- parsing: false
- }
- },
- computed: {
- gutStats: function() {
- var list = this.gutIndicators || []
- var valid = 0, abnormal = 0
- for (var i = 0; i < list.length; i++) {
- var item = list[i]
- if (item.indicatorValue && String(item.indicatorValue).trim() !== '') valid++
- if (this.isAbnormalStatus(item.status)) abnormal++
- }
- return { total: list.length, valid: valid, abnormal: abnormal }
- },
- nutriStats: function() {
- var list = [], groups = this.nutritionGroups || []
- for (var g = 0; g < groups.length; g++) {
- var items = groups[g].items
- if (items) for (var i = 0; i < items.length; i++) list.push(items[i])
- }
- var valid = 0, abnormal = 0
- for (var i = 0; i < list.length; i++) {
- var item = list[i]
- if (item.indicatorValue && String(item.indicatorValue).trim() !== '') valid++
- if (this.isAbnormalStatus(item.status)) abnormal++
- }
- return { total: list.length, valid: valid, abnormal: abnormal }
- }
- },
- onLoad: function(options) {
- this.reportId = options.reportId ? parseInt(options.reportId) : null
- this.draftId = options.draftId ? parseInt(options.draftId) : null
- if (this.draftId) {
- this.isDraftMode = true
- this.loadDraftData()
- } else if (this.reportId) {
- this.loadData()
- }
- },
- methods: {
- goBack: function() { uni.navigateBack() },
- /** 报告日期仅显示 yyyy-MM-dd(去掉时间部分) */
- reportDateText: function() {
- var d = this.report.reportDate
- if (!d) return ''
- var date = parseDate(d)
- if (!date) return String(d).substring(0, 10)
- var y = date.getFullYear()
- var m = ('' + (date.getMonth() + 1)).padStart(2, '0')
- var day = ('' + date.getDate()).padStart(2, '0')
- return y + '-' + m + '-' + day
- },
- /** 状态 → CSS安全英文(兼容中英文,与 report-confirm 保持一致) */
- _cssForStatus: function(s) {
- var k = String(s || '').trim()
- var map = { 'high': 'high', 'low': 'low', 'abnormal': 'abnormal', 'normal': 'normal', '缺乏': 'deficient', '偏低': 'low', '不足': 'insufficient', '正常': 'normal', '注意': 'warning', '偏高': 'high', '异常': 'abnormal', '过多': 'excess', '高风险': 'high-risk', '低风险': 'low-risk', '未检出': 'not-detected' }
- return map[k] || 'normal'
- },
- // 与 _cssForStatus 保持同一白名单:仅明确异常/需关注的状态计入异常数,未知状态视为正常,避免“样式正常却计入异常”
- isAbnormalStatus: function(status) {
- if (!status) return false
- var k = String(status).trim()
- var abnormalMap = { 'high': 1, 'low': 1, 'abnormal': 1, '偏高': 1, '偏低': 1, '异常': 1, '缺乏': 1, '不足': 1, '注意': 1, '高风险': 1, '过多': 1 }
- return !!abnormalMap[k]
- },
- statusKey: function(s) {
- var map = { '良好': 'good', '注意': 'warning', '需关注': 'danger', '关注': 'danger', '正常': 'normal', '偏高': 'high', '偏低': 'low' }
- return map[s] || 'normal'
- },
- loadData: function() {
- var self = this
- getReportDetail(this.reportId).then(function(res) {
- if (res.code === 200 && res.data) {
- self.reportData = res.data
- self.report = res.data.report || res.data
- self.reportInterpretation = res.data.reportInterpretation || null
- self.buildScores(res.data)
- self.buildIndicators(res.data)
- self.floraCount = (res.data.gutFlora || []).length
- // 菌群详情:统计异常/需关注的数量(白名单,与手风琴异常口径一致)
- var flora = res.data.gutFlora || []
- self.floraAbnormalCount = flora.filter(function(i) {
- return self.isAbnormalStatus(i.status)
- }).length
- self.riskCount = (res.data.diseaseRisks || []).length
- // 疾病风险:统计需关注的数量(风险等级非"低风险"即视为需关注)
- var risks = res.data.diseaseRisks || []
- self.riskAttentionCount = risks.filter(function(r) {
- return r.riskLevel && r.riskLevel !== '低风险'
- }).length
- // 饮食推荐:共用 /api/health/foods 接口获取数量
- self.foodCount = 0
- getHealthFoods(self.reportId).then(function(foodRes) {
- if (foodRes && foodRes.code === 200 && foodRes.data) {
- self.foodCount = (Array.isArray(foodRes.data) ? foodRes.data : []).length
- }
- }).catch(function() {})
- // 异步加载戒备食材数量
- var userId = uni.getStorageSync('userId')
- if (userId) {
- getFoodCautionList().then(function(cautionRes) {
- if (cautionRes && cautionRes.code === 200) {
- self.cautionCount = (cautionRes.data || []).length
- }
- }).catch(function() {})
- }
- self.indicatorCount = (res.data.indicators || []).length
- // 计算病原菌总数
- var flora = res.data.gutFlora || []
- self.pathogenCount = flora.filter(function(i) {
- return i.category === 'pathogen_genus' || i.category === 'pathogen_detection'
- }).length
- }
- })
- // 加载菌群功能分析
- this.loadAnalysis()
- },
- loadAnalysis: function() {
- var self = this
- getGutFloraAnalysis(this.reportId).then(function(res) {
- if (res.code === 200 && res.data && !res.data.error) {
- self.analysisData = res.data
- }
- }).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.shannonIndex = s.shannonIndex || ''
- 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: '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]
- 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
- },
- buildIndicators: function(data) {
- var indicators = data.indicators || []
- // 为每个指标添加_cssClass字段(wxss不支持中文类名)
- var self = this
- indicators.forEach(function(item) {
- item._cssClass = self._cssForStatus(item.status)
- })
- this.allIndicators = indicators
- var gutCategories = ['肠道屏障与代谢物', '短链脂肪酸', '神经递质与激素', '抗生素风险评估']
- this.gutIndicators = indicators.filter(function(item) {
- return gutCategories.indexOf(item.category) >= 0
- })
- var seen = {}
- var groups = []
- for (var i = 0; i < indicators.length; i++) {
- var item = indicators[i]
- if (gutCategories.indexOf(item.category) >= 0) continue
- if (!seen[item.category]) {
- seen[item.category] = true
- groups.push({ category: item.category, items: [] })
- }
- for (var g = 0; g < groups.length; g++) {
- if (groups[g].category === item.category) {
- groups[g].items.push(item)
- break
- }
- }
- }
- this.nutritionGroups = groups
- },
- toggleAccordion: function(key) {
- this.accordionOpen[key] = !this.accordionOpen[key]
- },
- indicatorType: function(item) {
- return INDICATOR_TYPE_MAP[item.category] || 'nutrient'
- },
- indStatusIndex: function(status) {
- var idx = this.indStatuses.indexOf(status)
- return idx >= 0 ? idx : 0
- },
- onIndStatusChange: function(e, item) {
- item.status = this.indStatuses[e.detail.value]
- },
- showKnowledge: function(type, name) {
- var self = this
- // 优先使用 KB v3 查询指标调整建议
- queryIndicatorKnowledge(name).then(function(res) {
- if (res.code === 200 && res.data) {
- var v3 = res.data
- var high = v3['偏高影响'] || ''
- var low = v3['偏低影响'] || ''
- self.knowledgeData = {
- itemName: v3['名称'] || name,
- category: v3['说明'] || '',
- description: (high ? '偏高影响:' + high + '\n' : '') + (low ? '偏低影响:' + low : ''),
- suggestion: v3['调整建议'] || ''
- }
- self.knowledgeVisible = true
- } else {
- // 回退旧版 DB 查询
- self.queryOldKnowledgeBase(type, name)
- }
- }).catch(function() {
- self.queryOldKnowledgeBase(type, name)
- })
- },
- queryOldKnowledgeBase: function(type, name) {
- var self = this
- queryKnowledgeBase(type, name).then(function(res) {
- if (res.code === 200 && res.data) {
- self.knowledgeData = res.data
- self.knowledgeVisible = true
- } else {
- self.knowledgeData = { itemName: name }
- self.knowledgeVisible = true
- }
- })
- },
- closeKnowledge: function() {
- this.knowledgeVisible = false
- this.knowledgeData = {}
- },
- openFeedback: function() {
- this.feedbackVisible = true
- },
- closeFeedback: function() {
- this.feedbackVisible = false
- },
- toggleEdit: function() {
- this.isEditing = true
- },
- cancelEdit: function() {
- this.isEditing = false
- this.loadData()
- },
- saveEdit: function() {
- var self = this
- var payload = {
- 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) {
- if (res.code === 200) {
- uni.showToast({ title: '已保存', icon: 'success' })
- self.isEditing = false
- self.loadData()
- } else {
- uni.showToast({ title: res.message || '保存失败', icon: 'none' })
- }
- })
- },
- 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
- var planId = res.data && res.data.planId
- var goDetail = function() {
- if (reportId) {
- uni.redirectTo({ url: '/pages/health/gut-flora-detail?reportId=' + reportId })
- } else {
- uni.navigateBack()
- }
- }
- if (planId) {
- uni.showModal({
- title: '报告已入库',
- content: '健康方案已自动生成,是否立即查看并确认任务?',
- confirmText: '查看方案',
- cancelText: '看报告',
- success: function(modalRes) {
- if (modalRes.confirm) {
- uni.redirectTo({ url: '/pages/health/health-plan-summary?planId=' + planId })
- } else {
- goDetail()
- }
- }
- })
- } else {
- goDetail()
- }
- } 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 })
- },
- goToPathogen: function() {
- uni.navigateTo({ url: '/pages/health/gut-flora-species-detail?reportId=' + this.reportId + '&tab=pathogen_genus' })
- },
- goToTaxonomy: function() {
- uni.navigateTo({ url: '/pages/health/gut-flora-species-detail?reportId=' + this.reportId + '&tab=taxonomy_class' })
- },
- goToRisks: function() {
- uni.navigateTo({ url: '/pages/health/gut-flora-risks-detail?reportId=' + this.reportId })
- },
- goToFoods: function() {
- uni.navigateTo({ url: '/pages/health/gut-flora-foods-detail?reportId=' + this.reportId })
- },
- goToCaution: function() {
- uni.navigateTo({ url: '/pages/growth/caution-detail' })
- },
- goToIndicators: function() {
- this.accordionOpen.nutri = true
- },
- // 问答功能已屏蔽
- goAIChat: function() {}
- }
- }
- </script>
- <style scoped>
- .page { min-height: 100vh; background: #f8f8f8; }
- .content { height: calc(100vh - 100rpx); }
- .report-header { background: linear-gradient(135deg, #4A9BD7, #6366F1); padding: 40rpx 30rpx; color: #fff; }
- .report-title { font-size: 36rpx; font-weight: 700; display: block; margin-bottom: 16rpx; }
- .report-meta { display: flex; gap: 20rpx; margin-bottom: 6rpx; }
- .meta-item { font-size: 24rpx; opacity: 0.9; }
- .badge-row { display: flex; gap: 16rpx; margin-top: 12rpx; }
- .badge { padding: 6rpx 20rpx; background: rgba(255,255,255,0.2); border-radius: 20rpx; font-size: 22rpx; }
- .scores-wrap { display: flex; flex-wrap: wrap; padding: 20rpx 10rpx; background: #fff; }
- .score-item { display: flex; flex-direction: column; align-items: center; margin: 12rpx 10rpx; width: calc(25% - 20rpx); }
- .score-ring { width: 100rpx; height: 100rpx; border-radius: 50%; border: 6rpx solid; display: flex; align-items: center; justify-content: center; font-size: 32rpx; font-weight: 700; margin-bottom: 8rpx; }
- .score-label { font-size: 22rpx; color: #666; }
- .quick-nav { display: flex; padding: 20rpx; background: #fff; margin-bottom: 16rpx; }
- .nav-card { flex: 1; display: flex; flex-direction: column; align-items: center; padding: 16rpx 0; }
- .caution-card {
- background: #FEF2F2;
- border-radius: 16rpx;
- border: 2rpx solid #FECACA;
- }
- .caution-count { color: #DC2626; font-weight: 700; }
- .nav-icon { font-size: 40rpx; margin-bottom: 6rpx; }
- .nav-label { font-size: 24rpx; color: #333; }
- .nav-count { font-size: 20rpx; color: #999; }
- .accordion-section { background: #fff; margin-bottom: 16rpx; }
- .accordion-header { display: flex; justify-content: space-between; padding: 30rpx; border-bottom: 1rpx solid #f0f0f0; }
- .accordion-title { font-size: 30rpx; font-weight: 600; color: #333; }
- .accordion-stats { font-size: 22rpx; color: #999; margin-left: 16rpx; }
- .accordion-arrow { font-size: 24rpx; color: #999; }
- .accordion-body { padding: 10rpx 30rpx 20rpx; }
- .sub-group { margin-bottom: 16rpx; }
- .sub-group-title { font-size: 26rpx; color: #4A9BD7; font-weight: 600; display: block; margin: 12rpx 0 8rpx; }
- .indicator-card { padding: 16rpx 0; border-bottom: 1rpx solid #f5f5f5; }
- .indicator-row { display: flex; align-items: center; justify-content: space-between; }
- .indicator-name { font-size: 28rpx; color: #333; flex: 1; }
- .arrow-up { color: #C62828; font-size: 22rpx; }
- .arrow-down { color: #E65100; font-size: 22rpx; }
- .arrow-warn { color: #F59E0B; font-size: 22rpx; }
- .val-text { font-size: 28rpx; font-weight: 600; color: #333; }
- .val-high { color: #C62828; }
- .val-excess { color: #C62828; }
- .val-low { color: #C62828; }
- .val-deficient { color: #C62828; }
- .val-insufficient { color: #C62828; }
- .val-abnormal { color: #C62828; }
- .val-warning { color: #F59E0B; }
- .indicator-range { font-size: 22rpx; color: #bbb; display: block; margin-top: 4rpx; }
- /* 指标知识库 "?" 按钮(与通用报告页一致) */
- .ind-help-btn { display: inline-flex; align-items: center; justify-content: center; width: 36rpx; height: 36rpx; border-radius: 50%; background: #E3F2FD; color: #1976D2; font-size: 22rpx; font-weight: 600; margin-left: 8rpx; flex-shrink: 0; }
- /* 综合评级行 */
- .rating-line { margin: 12rpx 0 0; }
- .rating-text { font-size: 24rpx; color: #666; display: block; }
- /* 指标单位与症状 */
- .indicator-unit { font-size: 22rpx; color: #999; margin-left: 6rpx; }
- .indicator-symptoms { font-size: 22rpx; color: #E65100; display: block; margin-top: 4rpx; }
- .indicator-edit { display: flex; gap: 8rpx; align-items: center; }
- .edit-input { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 6rpx 10rpx; font-size: 26rpx; width: 100rpx; }
- .edit-picker { border: 1rpx solid #ddd; border-radius: 8rpx; padding: 6rpx 10rpx; font-size: 24rpx; color: #333; }
- .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; }
- .feedback-section {
- display: flex;
- align-items: center;
- padding: 24rpx 30rpx;
- background: #fff;
- margin: 16rpx;
- border-radius: 12rpx;
- border: 1rpx solid #f0f0f0;
- }
- .feedback-icon { font-size: 32rpx; margin-right: 12rpx; }
- .feedback-text { flex: 1; font-size: 26rpx; color: #4A9BD7; }
- .feedback-arrow { font-size: 28rpx; color: #ccc; }
- .bottom-spacer { height: 40rpx; }
- /* 报告解读 */
- .interpretation-box {
- background: #FFF8E1;
- border-radius: 12rpx;
- padding: 16rpx 20rpx;
- margin: 16rpx 0 0;
- border-left: 6rpx solid #FFB300;
- }
- .interp-text {
- font-size: 24rpx;
- color: #795548;
- line-height: 1.6;
- }
- /* 解读入口 */
- .ai-fab {
- position: fixed;
- right: 30rpx;
- bottom: 100rpx;
- display: flex;
- flex-direction: row;
- align-items: center;
- background: linear-gradient(135deg, #6366F1, #8B5CF6);
- border-radius: 40rpx;
- padding: 16rpx 28rpx;
- box-shadow: 0 4rpx 16rpx rgba(99, 102, 241, 0.4);
- z-index: 100;
- }
- .ai-fab .fab-icon { font-size: 32rpx; margin-right: 12rpx; }
- .ai-fab .fab-text { font-size: 26rpx; color: #fff; font-weight: 500; }
- /* ===== 菌群功能分析 ===== */
- .analysis-section {
- margin: 20rpx 30rpx;
- }
- .section-title-bar {
- margin-bottom: 16rpx;
- }
- .analysis-title {
- font-size: 30rpx;
- font-weight: bold;
- color: #333;
- display: block;
- }
- .analysis-subtitle {
- font-size: 22rpx;
- color: #999;
- display: block;
- margin-top: 4rpx;
- }
- .analysis-card {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 16rpx;
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06);
- }
- .card-label {
- font-size: 26rpx;
- font-weight: 600;
- color: #333;
- display: block;
- margin-bottom: 16rpx;
- }
- .func-dim {
- margin-bottom: 20rpx;
- }
- .func-dim:last-child { margin-bottom: 0; }
- .func-dim-head {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-bottom: 8rpx;
- }
- .func-dim-name {
- font-size: 24rpx;
- color: #333;
- font-weight: 500;
- }
- .func-badge {
- font-size: 20rpx;
- padding: 2rpx 14rpx;
- border-radius: 20rpx;
- color: #fff;
- }
- .func-good { background: #10B981; }
- .func-warning { background: #F59E0B; }
- .func-danger { background: #EF4444; }
- .func-normal { background: #6B7280; }
- .func-high { background: #EF4444; }
- .func-low { background: #F59E0B; }
- .func-bar {
- height: 8rpx;
- background: #f0f0f0;
- border-radius: 4rpx;
- overflow: hidden;
- margin-bottom: 6rpx;
- }
- .func-bar-fill {
- height: 100%;
- border-radius: 4rpx;
- transition: width 0.5s ease;
- }
- .fill-good { background: #10B981; }
- .fill-warning { background: #F59E0B; }
- .fill-danger { background: #EF4444; }
- .fill-normal { background: #6B7280; }
- .func-dim-summary {
- font-size: 22rpx;
- color: #888;
- line-height: 1.5;
- }
- .pb-row {
- display: flex;
- gap: 20rpx;
- align-items: center;
- margin-bottom: 8rpx;
- }
- .pb-label {
- font-size: 24rpx;
- color: #666;
- }
- .pb-ratio {
- font-size: 24rpx;
- color: #6366F1;
- font-weight: bold;
- }
- .pattern-name {
- font-size: 28rpx;
- font-weight: bold;
- color: #FF8C42;
- display: block;
- margin-bottom: 8rpx;
- }
- .risk-item {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12rpx;
- padding: 12rpx 0;
- border-bottom: 1rpx solid #f0f0f0;
- }
- .risk-item:last-child { border-bottom: none; }
- .risk-name {
- font-size: 24rpx;
- color: #333;
- font-weight: 500;
- }
- .risk-level {
- font-size: 20rpx;
- padding: 2rpx 14rpx;
- border-radius: 20rpx;
- color: #fff;
- }
- .risk-normal { background: #10B981; }
- .risk-warning { background: #F59E0B; }
- .risk-danger { background: #EF4444; }
- .risk-bacteria {
- font-size: 22rpx;
- color: #999;
- width: 100%;
- margin-top: 4rpx;
- }
- .probiotic-item {
- display: flex;
- flex-wrap: wrap;
- align-items: center;
- gap: 12rpx;
- padding: 12rpx 0;
- border-bottom: 1rpx solid #f0f0f0;
- }
- .probiotic-item:last-child { border-bottom: none; }
- .probiotic-name {
- font-size: 24rpx;
- color: #333;
- font-weight: 500;
- width: 120rpx;
- }
- .probiotic-advice {
- font-size: 22rpx;
- color: #888;
- width: 100%;
- margin-top: 4rpx;
- line-height: 1.5;
- }
- </style>
|