| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572 |
- <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>
- <!-- AI 解读 -->
- <view class="interpretation-box ai-box" v-if="aiAnalysisText">
- <text class="interp-label">🤖 AI 解读</text>
- <text class="interp-text">{{ aiAnalysisText }}</text>
- </view>
- <!-- 报告总结描述 -->
- <view class="summary-box" v-if="reportSummaryText">
- <text class="summary-title">报告总结</text>
- <text class="summary-text">{{ reportSummaryText }}</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="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="accordion-section" v-if="analysisData && analysisData.riskIndicators && analysisData.riskIndicators.length > 0">
- <view class="accordion-header" @tap="toggleAccordion('riskGroup')">
- <text class="accordion-title">菌群风险分组</text>
- <text class="accordion-stats" v-if="analysisData.riskIndicators.length > 0">异常 <text class="abnormal-num">{{ riskGroupAbnormalCount }}</text> / 共 {{ analysisData.riskIndicators.length }} 组</text>
- <text class="accordion-arrow">{{ accordionOpen.riskGroup ? '收起' : '展开' }}</text>
- </view>
- <view class="accordion-body" v-if="accordionOpen.riskGroup">
- <view class="risk-group-item" v-for="(risk, ri) in analysisData.riskIndicators" :key="ri">
- <view class="risk-group-header" @tap="toggleRiskGroup(ri)">
- <text class="risk-group-name">{{ risk.name }}</text>
- <text :class="'risk-level risk-' + statusKey(risk.level)">{{ risk.level }}</text>
- <text class="risk-group-arrow">{{ riskGroupOpen[ri] ? '收起' : '展开' }}</text>
- </view>
- <view class="risk-group-body" v-if="riskGroupOpen[ri]">
- <text class="risk-bacteria" v-if="risk.relatedBacteria && risk.relatedBacteria.length > 0">
- 相关菌群:{{ risk.relatedBacteria.join('、') }}
- </text>
- <text class="risk-bacteria" v-else>暂无异常相关菌群</text>
- </view>
- </view>
- </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">异常 <text class="abnormal-num">{{ gutStats.abnormal }}</text> / 共 {{ 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="nutrition-group-section" v-for="(group, gIdx) in nutritionGroups" :key="gIdx">
- <view class="accordion-section">
- <view class="accordion-header" @tap="toggleNutriGroup(gIdx)">
- <text class="accordion-title">{{ group.category }}</text>
- <text class="accordion-stats" v-if="group.items.length > 0">异常 <text class="abnormal-num">{{ group.abnormalCount }}</text> / 共 {{ group.items.length }} 项</text>
- <text class="accordion-arrow">{{ nutriGroupOpen[gIdx] ? '收起' : '展开' }}</text>
- </view>
- <view class="accordion-body" v-if="nutriGroupOpen[gIdx]">
- <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 class="empty-hint" v-if="group.items.length === 0">暂无数据</view>
- </view>
- </view>
- </view>
- <view class="feedback-section" @tap="openQuickAnalyze">
- <text class="feedback-icon">💬</text>
- <text class="feedback-text">对这个报告有疑问或建议?告诉我们</text>
- <text class="feedback-arrow">›</text>
- </view>
- <view class="bottom-spacer"></view>
- </scroll-view>
- <!-- 快速分析浮层(由「有疑问或建议」入口唤起) -->
- <view class="quick-analyze-overlay" v-if="quickAnalyze.show" @tap="closeQuickAnalyze"></view>
- <view class="quick-analyze-panel" v-if="quickAnalyze.show">
- <view class="quick-analyze-header">
- <text class="quick-analyze-title">报告快速分析</text>
- <view class="quick-analyze-header-right">
- <text class="quick-analyze-feedback" @tap="openFeedbackFromAnalyze">提交反馈</text>
- <text class="quick-analyze-close" @tap="closeQuickAnalyze">✕</text>
- </view>
- </view>
- <view class="quick-analyze-hint" v-if="quickAnalyze.usedToday">免费版今日次数已用完,升级会员解锁更多分析</view>
- <scroll-view class="quick-analyze-messages" scroll-y>
- <view class="quick-analyze-message" v-for="(msg, qidx) in quickAnalyze.messages" :key="qidx" :class="'msg-' + msg.role">
- <text class="quick-analyze-msg-text">{{ msg.content }}</text>
- </view>
- <view class="quick-analyze-loading" v-if="quickAnalyze.sending">
- <text class="quick-analyze-loading-text">AI 正在分析中...</text>
- </view>
- </scroll-view>
- <view class="quick-analyze-chips" v-if="quickAnalyze.messages.length === 0">
- <view class="quick-analyze-chip" v-for="(q, qi) in quickQuestions" :key="qi" @tap="sendQuickQuestion(q)">
- <text class="quick-analyze-chip-text">{{ q }}</text>
- </view>
- </view>
- <view class="quick-analyze-input-bar">
- <input class="quick-analyze-input" v-model="quickAnalyze.inputText" placeholder="输入你想问的问题..."
- :disabled="quickAnalyze.sending || quickAnalyze.usedToday" confirm-type="send" @confirm="sendQuickAnalyze" />
- <view class="quick-analyze-send" :class="{ 'send-disabled': quickAnalyze.sending || quickAnalyze.usedToday }" @tap="sendQuickAnalyze">
- <text class="quick-analyze-send-text">发送</text>
- </view>
- </view>
- </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>
- <!-- draft 模式:成员选择 -->
- <view class="member-select-wrap" v-if="isDraftMode && !confirming && !parsing">
- <view class="member-select-row" @tap="toggleMemberPanel">
- <text class="field-label">被检测人</text>
- <text class="field-value">{{ draftMemberText }}</text>
- <text class="member-select-arrow">{{ selectedMemberObj ? '修改 ›' : '选择 ›' }}</text>
- </view>
- <view class="member-panel" v-if="showMemberPanel">
- <!-- 候选匹配列表 -->
- <view class="candidate-section" v-if="candidates.length > 0">
- <text class="candidate-section-hint">以下成员与报告信息匹配,请选择:</text>
- <view
- class="candidate-item"
- v-for="(cand, idx) in candidates"
- :key="idx"
- :class="{ 'candidate-selected': selectedMemberId === (cand.id || cand.userId) }"
- @tap="selectAndCloseDraft(cand)"
- >
- <view class="candidate-left">
- <text class="candidate-avatar">{{ (cand.nickname || cand.name || '?').substring(0, 1) }}</text>
- <view class="candidate-info">
- <text class="candidate-name">{{ cand.nickname || cand.name || '未知' }}</text>
- <text class="candidate-detail" v-if="cand.gender || cand.age">{{ genderText(cand.gender) }}{{ cand.age ? ' · ' + cand.age + '岁' : '' }}</text>
- </view>
- </view>
- </view>
- </view>
- <!-- 全量成员 -->
- <view class="member-list" v-if="familyMembers.length > 0">
- <text class="member-list-hint">家庭成员</text>
- <view
- class="member-item"
- v-for="(member, mIdx) in familyMembers"
- :key="mIdx"
- :class="{ 'member-selected': selectedMemberId === (member.userId || member.id) }"
- @tap="selectAndCloseDraft(member)"
- >
- <text class="member-avatar">{{ (member.nickname || '?').substring(0, 1) }}</text>
- <text class="member-name">{{ member.nickname || '未知' }}</text>
- </view>
- </view>
- <!-- 不绑定成员 -->
- <view class="member-item unbind-member" @tap="unbindDraftMember">
- <text class="member-avatar unbind-avatar">✕</text>
- <text class="member-name">不绑定成员</text>
- </view>
- <!-- 新建成员入口 -->
- <view class="create-member-entry" @tap="toggleAutoCreateDraft">
- <text class="create-member-icon">+</text>
- <text class="create-member-text">{{ showAutoCreateForm ? '收起新建表单' : '不在列表中?新建家庭成员' }}</text>
- </view>
- <!-- 新建成员表单 -->
- <view class="auto-create-inline" v-if="showAutoCreateForm">
- <view class="form-item">
- <text class="form-item-label">姓名</text>
- <input class="form-item-input" v-model="newMember.name" placeholder="请输入姓名" />
- </view>
- <view class="form-item">
- <text class="form-item-label">性别</text>
- <view class="gender-selector">
- <text class="gender-option" :class="{ 'gender-selected': newMember.gender === 'male' }" @tap="newMember.gender = 'male'">男</text>
- <text class="gender-option" :class="{ 'gender-selected': newMember.gender === 'female' }" @tap="newMember.gender = 'female'">女</text>
- </view>
- </view>
- <view class="form-item">
- <text class="form-item-label">年龄</text>
- <input class="form-item-input" type="number" v-model="newMember.age" placeholder="请输入年龄" />
- </view>
- <view class="btn-create-submit" :class="{ 'btn-disabled': creatingMember }" @tap="doAutoCreateDraft">
- <text v-if="creatingMember">创建中...</text>
- <text v-else>确认创建</text>
- </view>
- </view>
- </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, getFamilyMemberList, addFamilyMember, reportQuickAnalyze, getMyMembership } 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,
- aiAnalysisText: '',
- isEditing: false,
- accordionOpen: { gut: false, risks: false, riskGroup: false },
- riskGroupOpen: [],
- nutriGroupOpen: [],
- scoreItems: [],
- allIndicators: [],
- gutIndicators: [],
- nutritionGroups: [],
- diseaseRisks: [],
- floraCount: 0,
- floraAbnormalCount: 0,
- riskCount: 0,
- riskAttentionCount: 0,
- foodCount: 0,
- cautionCount: 0,
- indicatorCount: 0,
- knowledgeVisible: false,
- knowledgeData: {},
- feedbackVisible: false,
- indStatuses: ['正常', '偏低', '偏高', '缺乏', '不足', '过多', '异常'],
- analysisData: null,
- // draft 模式字段
- draftId: null,
- isDraftMode: false,
- payload: null,
- confirming: false,
- parsing: false,
- // 被检测人选择(draft 模式)
- needBind: false,
- selectedMemberId: null,
- selectedMemberObj: null,
- familyMembers: [],
- showMemberPanel: false,
- showAutoCreateForm: false,
- newMember: { name: '', gender: 'male', age: '' },
- creatingMember: false,
- createdMemberId: null,
- matchConfidence: null,
- matchLabel: '',
- candidates: [],
- extractedName: '',
- extractedGender: '',
- extractedAge: '',
- quickAnalyze: {
- show: false,
- sending: false,
- messages: [],
- inputText: '',
- conversationId: '',
- usedToday: false,
- checkingMembership: 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 }
- },
- diseaseRisksAbnormal: function() {
- var list = this.diseaseRisks || []
- var count = 0
- for (var i = 0; i < list.length; i++) {
- var level = list[i].riskLevel || ''
- if (level !== '低风险') count++
- }
- return count
- },
- riskGroupAbnormalCount: function() {
- if (!this.analysisData || !this.analysisData.riskIndicators) return 0
- var list = this.analysisData.riskIndicators
- var count = 0
- for (var i = 0; i < list.length; i++) {
- var lv = list[i].level || ''
- if (lv !== '良好' && lv !== '正常') count++
- }
- return count
- },
- /** 报告日期格式化 — 计算属性避免模板中方法引用失效 */
- 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
- },
- /** 报告总结描述 */
- reportSummaryText: function() {
- var parts = []
- if (this.report.healthRating) parts.push('综合评级: ' + this.report.healthRating)
- if (this.reportInterpretation && this.reportInterpretation.gutAgeAssessment)
- parts.push(this.reportInterpretation.gutAgeAssessment)
- if (this.analysisData && this.analysisData.dietaryPattern && this.analysisData.dietaryPattern.detail)
- parts.push(this.analysisData.dietaryPattern.detail)
- var funcAnalysis = this.analysisData && this.analysisData.functionalAnalysis || []
- var abnDims = funcAnalysis.filter(function(d) { return d.status && d.status !== '良好' })
- if (abnDims.length > 0)
- parts.push('需关注维度:' + abnDims.map(function(d) { return d.name }).join('、'))
- return parts.join(';')
- },
- draftMemberText: function() {
- if (this.selectedMemberObj) return this.selectedMemberObj.nickname || this.selectedMemberObj.name || '已选择'
- if (this.extractedName) return this.extractedName + '(待绑定)'
- return '未选择'
- },
- quickQuestions: function() {
- return [
- '这份报告最需要关注哪些异常指标?',
- '报告中提示了哪些健康风险?',
- '有哪些改善建议?'
- ]
- }
- },
- 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: {
- // ===== 报告快速分析 =====
- openQuickAnalyze: function() {
- this.quickAnalyze.show = true
- var self = this
- if (self.quickAnalyze.checkingMembership) return
- self.quickAnalyze.checkingMembership = true
- getMyMembership().then(function(res) {
- var level = res.data && res.data.memberLevel
- if (level && level !== 'FREE') {
- self.quickAnalyze.usedToday = false
- }
- }).catch(function() {}).finally(function() {
- self.quickAnalyze.checkingMembership = false
- })
- },
- closeQuickAnalyze: function() {
- this.quickAnalyze.show = false
- },
- sendQuickQuestion: function(q) {
- if (q && q.trim()) {
- this.quickAnalyze.inputText = q
- this.sendQuickAnalyze()
- }
- },
- sendQuickAnalyze: function() {
- var self = this
- var text = (this.quickAnalyze.inputText || '').trim()
- if (!text || this.quickAnalyze.sending) return
- this.quickAnalyze.messages.push({ role: 'user', content: text })
- this.quickAnalyze.inputText = ''
- this.quickAnalyze.sending = true
- reportQuickAnalyze(text, this.reportId, this.quickAnalyze.conversationId, this.draftId).then(function(res) {
- if (res.code === 200 && res.data) {
- self.quickAnalyze.messages.push({ role: 'ai', content: res.data.answer || '(无回答)' })
- if (res.data.conversationId) {
- self.quickAnalyze.conversationId = res.data.conversationId
- }
- } else {
- var msg = res.message || '分析失败'
- if (msg.indexOf('每日仅限') !== -1) {
- self.quickAnalyze.usedToday = true
- }
- self.quickAnalyze.messages.push({ role: 'ai', content: msg })
- }
- }).catch(function() {
- self.quickAnalyze.messages.push({ role: 'ai', content: 'AI 服务暂不可用,请稍后重试' })
- }).finally(function() {
- self.quickAnalyze.sending = false
- })
- },
- goBack: function() { uni.navigateBack() },
- /** 状态 → 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'
- },
- genderText: function(g) {
- if (g === 'male') return '男'
- if (g === 'female') return '女'
- return g || ''
- },
- toggleMemberPanel: function() {
- this.showMemberPanel = !this.showMemberPanel
- if (this.showMemberPanel && this.familyMembers.length === 0) {
- this.loadFamilyMembers()
- }
- },
- loadFamilyMembers: function() {
- var self = this
- getFamilyMemberList({ visibleOnly: true }).then(function(res) {
- if (res.code === 200) {
- self.familyMembers = res.data || []
- }
- }).catch(function() {})
- },
- selectAndCloseDraft: function(member) {
- this.selectedMemberId = member.id || member.userId
- this.selectedMemberObj = member
- this.createdMemberId = null
- this.needBind = false
- this.showMemberPanel = false
- this.showAutoCreateForm = false
- },
- unbindDraftMember: function() {
- this.selectedMemberId = null
- this.selectedMemberObj = null
- this.createdMemberId = null
- this.needBind = false
- this.showMemberPanel = false
- this.showAutoCreateForm = false
- },
- toggleAutoCreateDraft: function() {
- this.showAutoCreateForm = !this.showAutoCreateForm
- },
- doAutoCreateDraft: function() {
- var self = this
- if (!self.newMember.name) {
- uni.showToast({ title: '请填写姓名', icon: 'none' })
- return
- }
- self.creatingMember = true
- addFamilyMember({
- name: self.newMember.name,
- gender: self.newMember.gender,
- age: self.newMember.age ? parseInt(self.newMember.age) : null
- }).then(function(res) {
- self.creatingMember = false
- if (res.code === 200) {
- var member = res.data || {}
- self.selectedMemberId = member.id || member.userId
- self.selectedMemberObj = member
- self.createdMemberId = self.selectedMemberId
- self.needBind = false
- self.showMemberPanel = false
- self.showAutoCreateForm = false
- self.loadFamilyMembers()
- uni.showToast({ title: '创建成功', icon: 'success' })
- } else {
- uni.showToast({ title: res.message || '创建失败', icon: 'none' })
- }
- }).catch(function() {
- self.creatingMember = false
- uni.showToast({ title: '创建异常', icon: 'none' })
- })
- },
- loadData: function() {
- var self = this
- getReportDetail(this.reportId).then(function(res) {
- if (res.code === 200 && res.data) {
- self.reportData = res.data
- var reportObj = res.data.report || res.data
- // HealthReport 实体无 gender/age 字段,从 payload.summary 补齐(采集到但未落库)
- var ps = (res.data.payload && res.data.payload.summary) || {}
- self.report = Object.assign({}, reportObj, {
- gender: reportObj.gender || ps.gender || '',
- age: reportObj.age != null ? reportObj.age : (ps.age != null ? ps.age : '')
- })
- self.reportInterpretation = res.data.reportInterpretation || null
- // 解析 AI 解读(aiAnalysis 为 JSON 字符串 {analysis, traceId, ...})
- var aiRaw = reportObj.aiAnalysis
- if (aiRaw) {
- try {
- var aiObj = typeof aiRaw === 'string' ? JSON.parse(aiRaw) : aiRaw
- self.aiAnalysisText = (aiObj && aiObj.analysis) || ''
- } catch (e) {
- self.aiAnalysisText = ''
- }
- }
- 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.diseaseRisks = risks.map(function(r) {
- r._cssClass = self._cssForRiskLevel(r.riskLevel)
- return r
- })
- 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() {})
- // 异步加载戒备食材数量(按报告 subjectId 查,家长 userId 查不到孩子的推荐指数)
- var cautionUserId = (self.report && self.report.subjectId) || uni.getStorageSync('userId')
- if (cautionUserId) {
- getFoodCautionList(cautionUserId).then(function(cautionRes) {
- if (cautionRes && cautionRes.code === 200) {
- self.cautionCount = (cautionRes.data || []).length
- }
- }).catch(function() {})
- }
- self.indicatorCount = (res.data.indicators || []).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.extractedName = res.data.extractedName || ''
- self.extractedGender = res.data.extractedGender || ''
- self.extractedAge = res.data.extractedAge || ''
- self.matchedMemberId = res.data.matchedMemberId || null
- self.needBind = !!res.data.needBind
- self.matchConfidence = res.data.confidence !== undefined ? parseFloat(res.data.confidence) : null
- self.matchLabel = res.data.matchLabel || ''
- self.candidates = res.data.candidates || []
- // 高置信度自动选中
- if (self.matchedMemberId && self.matchConfidence >= 0.8) {
- self.selectedMemberId = self.matchedMemberId
- // 在 candidates 中找对应对象
- for (var i = 0; i < self.candidates.length; i++) {
- if ((self.candidates[i].id || self.candidates[i].userId) === self.selectedMemberId) {
- self.selectedMemberObj = self.candidates[i]
- break
- }
- }
- }
- self.applyDraftPayload(self.payload)
- self.loadFamilyMembers()
- }).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.gender = s.gender || ''
- report.age = s.age != null ? s.age : ''
- report.reportNumber = s.reportNumber || ''
- report.gutAge = s.gutAge || ''
- report.gutType = s.gutType || ''
- report.shannonIndex = s.shannonIndex || ''
- report.reportDate = s.reportDate || ''
- 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
- }
- }
- }
- for (var gi = 0; gi < groups.length; gi++) {
- var abnormal = 0
- for (var ii = 0; ii < groups[gi].items.length; ii++) {
- if (this.isAbnormalStatus(groups[gi].items[ii].status)) abnormal++
- }
- groups[gi].abnormalCount = abnormal
- }
- this.nutritionGroups = groups
- },
- toggleAccordion: function(key) {
- this.accordionOpen[key] = !this.accordionOpen[key]
- },
- toggleNutriGroup: function(idx) {
- var arr = this.nutriGroupOpen
- arr[idx] = !arr[idx]
- this.nutriGroupOpen = [].concat(arr)
- },
- toggleRiskGroup: function(idx) {
- var arr = this.riskGroupOpen
- arr[idx] = !arr[idx]
- this.riskGroupOpen = [].concat(arr)
- },
- /** 中文风险等级 → CSS安全英文 */
- _cssForRiskLevel: function(level) {
- var map = { '低风险': 'low', '注意': 'warning', '需注意': 'warning', '高风险': 'high', '异常': 'abnormal' }
- return map[level] || 'low'
- },
- 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 = {}
- },
- closeFeedback: function() {
- this.feedbackVisible = false
- },
- openFeedbackFromAnalyze: function() {
- this.quickAnalyze.show = false
- this.feedbackVisible = true
- },
- 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
- // 有选中成员时传 subjectId,否则传 null:后端确认入库但不绑定任何用户/家庭,也不重算维度
- var req = { draftId: self.draftId, payload: self.payload }
- if (self.selectedMemberId) req.subjectId = self.selectedMemberId
- confirmReportPreview(req).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 })
- },
- 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() {
- // 报告详情入口:传 reportId(按报告快照返回戒备食材)+ memberId(报告绑定主体 subjectId)
- var mid = (this.report && this.report.subjectId) || uni.getStorageSync('currentMemberId') || ''
- var url = '/pages/growth/caution-detail?reportId=' + this.reportId
- if (mid) url += '&memberId=' + mid
- uni.navigateTo({ url: url })
- },
- goToIndicators: function() {
- // 展开所有营养分组手风琴
- var arr = []
- for (var i = 0; i < this.nutritionGroups.length; i++) arr.push(true)
- this.nutriGroupOpen = arr
- if (this.nutritionGroups.length === 0) uni.showToast({ title: '暂无营养指标', icon: 'none' })
- },
- // 问答功能已屏蔽
- 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; }
- .abnormal-num { color: #C62828; font-weight: 700; }
- .accordion-arrow { font-size: 24rpx; color: #999; }
- .accordion-body { padding: 10rpx 30rpx 20rpx; }
- /* 疾病风险内联卡片 */
- .risk-card-inline {
- display: flex;
- align-items: center;
- padding: 20rpx 16rpx;
- border-bottom: 1rpx solid #f5f5f5;
- border-left: 8rpx solid #10B981;
- margin-bottom: 8rpx;
- }
- .risk-card-inline.risk-level-warning { border-left-color: #F59E0B; }
- .risk-card-inline.risk-level-high { border-left-color: #EF4444; }
- .risk-card-inline.risk-level-abnormal { border-left-color: #C62828; }
- .risk-name-inline { font-size: 28rpx; color: #333; flex: 1; }
- .risk-badge-inline { padding: 4rpx 16rpx; border-radius: 20rpx; font-size: 22rpx; flex-shrink: 0; }
- .risk-badge-inline.badge-low { background: #E8F5E9; color: #2E7D32; }
- .risk-badge-inline.badge-warning { background: #FFF3E0; color: #E65100; }
- .risk-badge-inline.badge-high { background: #FFEBEE; color: #C62828; }
- .risk-badge-inline.badge-abnormal { background: #C62828; color: #fff; }
- .risk-value-inline { font-size: 22rpx; color: #999; margin-left: 16rpx; }
- /* 菌群风险分组手风琴 */
- .risk-group-item { padding: 12rpx 0; border-bottom: 1rpx solid #f5f5f5; }
- .risk-group-header { display: flex; align-items: center; gap: 12rpx; padding: 8rpx 0; }
- .risk-group-name { font-size: 28rpx; color: #333; flex: 1; }
- .risk-group-arrow { font-size: 24rpx; color: #999; }
- .risk-group-body { padding: 8rpx 0 12rpx; }
- .risk-level { font-size: 22rpx; padding: 4rpx 14rpx; border-radius: 8rpx; }
- .risk-level.risk-good { background: #E8F5E9; color: #2E7D32; }
- .risk-level.risk-warning { background: #FFF3E0; color: #E65100; }
- .risk-level.risk-danger { background: #FFEBEE; color: #C62828; }
- .risk-bacteria { font-size: 24rpx; color: #666; line-height: 1.6; }
- .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 解读框 */
- .ai-box {
- background: #E0F2F1;
- border-left-color: #00897B;
- }
- .interp-label {
- font-size: 24rpx;
- font-weight: 600;
- color: #00695C;
- display: block;
- margin-bottom: 6rpx;
- }
- .ai-box .interp-text {
- color: #004D40;
- }
- /* 报告总结描述 */
- .summary-box {
- background: rgba(255,255,255,0.15);
- border-radius: 12rpx;
- padding: 16rpx 20rpx;
- margin: 16rpx 0 0;
- border-left: 6rpx solid #A5D6A7;
- display: flex;
- flex-direction: column;
- gap: 8rpx;
- }
- .summary-title {
- font-size: 24rpx;
- font-weight: 600;
- color: #fff;
- opacity: 0.95;
- }
- .summary-text {
- font-size: 24rpx;
- color: #fff;
- line-height: 1.6;
- opacity: 0.95;
- }
- /* 解读入口 */
- .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;
- }
- /* ===== 报告快速分析 ===== */
- .quick-analyze-overlay {
- position: fixed;
- top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0, 0, 0, 0.45);
- z-index: 98;
- }
- .quick-analyze-panel {
- position: fixed;
- left: 30rpx; right: 30rpx;
- bottom: 40rpx;
- background: #fff;
- border-radius: 20rpx;
- padding: 24rpx;
- z-index: 99;
- display: flex;
- flex-direction: column;
- max-height: 70vh;
- }
- .quick-analyze-header {
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 16rpx;
- }
- .quick-analyze-title { font-size: 30rpx; font-weight: bold; color: #333; }
- .quick-analyze-header-right {
- display: flex;
- flex-direction: row;
- align-items: center;
- gap: 16rpx;
- }
- .quick-analyze-feedback {
- font-size: 24rpx;
- color: #5B9BD5;
- padding: 8rpx;
- }
- .quick-analyze-feedback:active { opacity: 0.7; }
- .quick-analyze-close { font-size: 32rpx; color: #999; padding: 8rpx; }
- .quick-analyze-hint {
- font-size: 24rpx;
- color: #E65100;
- background: #FFF3E0;
- border-radius: 12rpx;
- padding: 12rpx 16rpx;
- margin-bottom: 12rpx;
- }
- .quick-analyze-messages {
- max-height: 50vh;
- margin-bottom: 16rpx;
- }
- .quick-analyze-message {
- margin-bottom: 12rpx;
- display: flex;
- flex-direction: column;
- }
- .quick-analyze-message.msg-user { align-items: flex-end; }
- .quick-analyze-message.msg-ai { align-items: flex-start; }
- .quick-analyze-msg-text {
- font-size: 26rpx;
- color: #333;
- background: #F3F4F6;
- border-radius: 14rpx;
- padding: 14rpx 18rpx;
- max-width: 80%;
- line-height: 1.5;
- }
- .msg-user .quick-analyze-msg-text { background: #5B9BD5; color: #fff; }
- .quick-analyze-loading { display: flex; justify-content: center; padding: 12rpx 0; }
- .quick-analyze-loading-text { font-size: 24rpx; color: #999; }
- .quick-analyze-chips { display: flex; flex-direction: column; margin-bottom: 16rpx; }
- .quick-analyze-chip {
- background: #EFF6FF;
- border: 1rpx solid #BFDBFE;
- border-radius: 12rpx;
- padding: 14rpx 18rpx;
- margin-bottom: 10rpx;
- }
- .quick-analyze-chip:active { opacity: 0.8; }
- .quick-analyze-chip-text { font-size: 26rpx; color: #1D4ED8; }
- .quick-analyze-input-bar {
- display: flex;
- flex-direction: row;
- align-items: center;
- }
- .quick-analyze-input {
- flex: 1;
- height: 72rpx;
- background: #F3F4F6;
- border-radius: 36rpx;
- padding: 0 28rpx;
- font-size: 26rpx;
- color: #333;
- margin-right: 16rpx;
- }
- .quick-analyze-send {
- background: #5B9BD5;
- border-radius: 36rpx;
- padding: 16rpx 34rpx;
- }
- .quick-analyze-send.send-disabled { opacity: 0.5; }
- .quick-analyze-send-text { font-size: 26rpx; color: #fff; }
- </style>
|