|
|
@@ -4,380 +4,380 @@
|
|
|
<view class="nav-back" @tap="goBack">
|
|
|
<text class="back-text">‹ 返回</text>
|
|
|
</view>
|
|
|
- <text class="nav-title">菌群报告详情</text>
|
|
|
+ <text class="nav-title">肠道菌群报告</text>
|
|
|
</view>
|
|
|
|
|
|
<scroll-view class="content" scroll-y>
|
|
|
<!-- 报告头部 -->
|
|
|
<view class="report-header">
|
|
|
<text class="report-title">肠道菌群检测报告</text>
|
|
|
- <text class="report-date" v-if="report.reportDate">报告日期: {{ report.reportDate }}</text>
|
|
|
- <text class="report-number" v-if="report.reportNumber">报告编号: {{ report.reportNumber }}</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">日期: {{ report.reportDate }}</text>
|
|
|
+ <text class="meta-item" v-if="report.reportNumber">编号: {{ report.reportNumber }}</text>
|
|
|
+ </view>
|
|
|
+ <view class="badge-row" v-if="report.gutAge || report.gutType">
|
|
|
+ <text class="badge" v-if="report.gutAge">肠龄: {{ report.gutAge }}</text>
|
|
|
+ <text class="badge" v-if="report.gutType">肠型: {{ report.gutType }}</text>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 5个评分圆环 -->
|
|
|
- <view class="scores-section">
|
|
|
- <view class="score-ring" v-for="(score, idx) in scoreRings" :key="idx">
|
|
|
- <view class="ring-wrapper">
|
|
|
- <canvas class="ring-canvas" :canvas-id="'ring_' + idx" style="width:120rpx;height:120rpx;"></canvas>
|
|
|
- <text class="ring-value">{{ score.value }}</text>
|
|
|
+ <!-- 评分圆环 -->
|
|
|
+ <scroll-view class="scores-scroll" scroll-x>
|
|
|
+ <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="ring-label">{{ score.label }}</text>
|
|
|
+ <text class="score-label">{{ score.label }}</text>
|
|
|
+ </view>
|
|
|
+ </scroll-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">{{ floraCount }}种</text>
|
|
|
+ </view>
|
|
|
+ <view class="nav-card" @tap="goToRisks">
|
|
|
+ <text class="nav-icon">⚠️</text>
|
|
|
+ <text class="nav-label">疾病风险</text>
|
|
|
+ <text class="nav-count">{{ 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" @tap="goToIndicators">
|
|
|
+ <text class="nav-icon">📋</text>
|
|
|
+ <text class="nav-label">全部指标</text>
|
|
|
+ <text class="nav-count">{{ indicatorCount }}项</text>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 菌群分类 -->
|
|
|
- <view class="section-card" v-for="(group, gIdx) in bacteriaGroups" :key="gIdx">
|
|
|
- <view class="section-title">
|
|
|
- <text>{{ group.name }}</text>
|
|
|
- <text class="section-count">{{ group.items.length }}种</text>
|
|
|
+ <!-- 肠道功能手风琴 -->
|
|
|
+ <view class="accordion-section">
|
|
|
+ <view class="accordion-header" @tap="toggleAccordion('gut')">
|
|
|
+ <text class="accordion-title">肠道功能与代谢物</text>
|
|
|
+ <text class="accordion-arrow">{{ accordionOpen.gut ? '收起' : '展开' }}</text>
|
|
|
</view>
|
|
|
- <view class="bacteria-item" v-for="(item, iIdx) in group.items" :key="iIdx">
|
|
|
- <view class="bacteria-header">
|
|
|
- <text class="bacteria-name">{{ item.name }}</text>
|
|
|
- <text class="bacteria-status" :class="'status-' + (item.status || 'normal')">{{ statusText(item.status) }}</text>
|
|
|
- </view>
|
|
|
- <view class="bar-track">
|
|
|
- <view class="bar-fill" :style="'width:' + (item.abundance || 0) + '%'"></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="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.status">{{ item.indicatorValue || '--' }}</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>
|
|
|
</view>
|
|
|
- <text class="bar-label">{{ (item.abundance || 0).toFixed(1) }}%</text>
|
|
|
+ <view class="empty-hint" v-if="gutIndicators.length === 0">暂无数据</view>
|
|
|
</view>
|
|
|
- <view class="empty-hint" v-if="group.items.length === 0">暂无数据</view>
|
|
|
</view>
|
|
|
|
|
|
- <!-- 饮食推荐 -->
|
|
|
- <view class="section-card" v-if="foods.length > 0">
|
|
|
- <view class="section-title">
|
|
|
- <text>饮食推荐</text>
|
|
|
- <text class="section-count">{{ foods.length }}项</text>
|
|
|
+ <!-- 营养指标手风琴 -->
|
|
|
+ <view class="accordion-section">
|
|
|
+ <view class="accordion-header" @tap="toggleAccordion('nutri')">
|
|
|
+ <text class="accordion-title">营养指标</text>
|
|
|
+ <text class="accordion-arrow">{{ accordionOpen.nutri ? '收起' : '展开' }}</text>
|
|
|
</view>
|
|
|
- <view class="food-item" v-for="(food, fIdx) in foods" :key="fIdx">
|
|
|
- <text class="food-name">{{ food.foodName || food.name }}</text>
|
|
|
- <text class="food-category" v-if="food.category">{{ food.category }}</text>
|
|
|
- <text class="food-reason" v-if="food.reason">{{ food.reason }}</text>
|
|
|
+ <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="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.status">{{ item.indicatorValue || '--' }}</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>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <view class="empty-hint" v-if="nutritionGroups.length === 0">暂无数据</view>
|
|
|
</view>
|
|
|
</view>
|
|
|
|
|
|
<view class="bottom-spacer"></view>
|
|
|
</scroll-view>
|
|
|
|
|
|
- <!-- 营养咨询 FAB -->
|
|
|
- <view class="fab" @tap="goToNutritionChat">
|
|
|
- <text class="fab-icon">💬</text>
|
|
|
+ <health-knowledge-popup :visible="knowledgeVisible" :data="knowledgeData" @close="closeKnowledge" />
|
|
|
+
|
|
|
+ <view class="fab" v-if="!isEditing" @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>
|
|
|
</template>
|
|
|
|
|
|
<script>
|
|
|
-import { getReportDetail } from '../../utils/api.js'
|
|
|
-import { createReportSurvey } from '../../utils/api.js'
|
|
|
+import { getReportDetail, editHealthReport, queryKnowledgeBase } from '../../utils/api.js'
|
|
|
+import HealthKnowledgePopup from '../../components/health-knowledge-popup.vue'
|
|
|
+
|
|
|
+var INDICATOR_TYPE_MAP = {
|
|
|
+ '维生素': 'vitamin',
|
|
|
+ '氨基酸': 'amino_acid',
|
|
|
+ '微量元素': 'indicator',
|
|
|
+ '肠道屏障与代谢物': 'indicator',
|
|
|
+ '短链脂肪酸': 'indicator',
|
|
|
+ '神经递质与激素': 'indicator',
|
|
|
+ '抗生素风险评估': 'indicator'
|
|
|
+}
|
|
|
|
|
|
export default {
|
|
|
+ components: { HealthKnowledgePopup },
|
|
|
data() {
|
|
|
return {
|
|
|
reportId: null,
|
|
|
+ reportData: null,
|
|
|
report: {},
|
|
|
- scoreRings: [],
|
|
|
- bacteriaGroups: [
|
|
|
- { name: '有益菌', items: [] },
|
|
|
- { name: '有害菌', items: [] },
|
|
|
- { name: '中性菌', items: [] }
|
|
|
- ],
|
|
|
- foods: []
|
|
|
+ isEditing: false,
|
|
|
+ accordionOpen: { gut: false, nutri: false },
|
|
|
+ scoreItems: [],
|
|
|
+ allIndicators: [],
|
|
|
+ gutIndicators: [],
|
|
|
+ nutritionGroups: [],
|
|
|
+ floraCount: 0,
|
|
|
+ riskCount: 0,
|
|
|
+ foodCount: 0,
|
|
|
+ indicatorCount: 0,
|
|
|
+ knowledgeVisible: false,
|
|
|
+ knowledgeData: {},
|
|
|
+ indStatuses: ['正常', '偏低', '偏高', '缺乏', '不足', '过多', '异常']
|
|
|
}
|
|
|
},
|
|
|
onLoad: function(options) {
|
|
|
this.reportId = options.reportId ? parseInt(options.reportId) : null
|
|
|
if (this.reportId) {
|
|
|
- this.loadReportDetail()
|
|
|
+ this.loadData()
|
|
|
}
|
|
|
},
|
|
|
- onReady: function() {
|
|
|
- // canvas需要DOM就绪后绘制
|
|
|
- setTimeout(() => {
|
|
|
- this.drawRings()
|
|
|
- }, 500)
|
|
|
- },
|
|
|
methods: {
|
|
|
- goBack: function() {
|
|
|
- uni.navigateBack()
|
|
|
- },
|
|
|
- loadReportDetail: function() {
|
|
|
+ goBack: function() { uni.navigateBack() },
|
|
|
+ loadData: function() {
|
|
|
var self = this
|
|
|
- uni.showLoading({ title: '加载中...' })
|
|
|
getReportDetail(this.reportId).then(function(res) {
|
|
|
- uni.hideLoading()
|
|
|
- if (res && res.code === 200 && res.data) {
|
|
|
- var data = res.data
|
|
|
- self.report = {
|
|
|
- reportDate: data.reportDate || '',
|
|
|
- reportNumber: data.reportNumber || '',
|
|
|
- personName: data.personName || ''
|
|
|
- }
|
|
|
- // 解析评分
|
|
|
- self.scoreRings = [
|
|
|
- { label: '综合评分', value: data.overallScore || 0, max: 100 },
|
|
|
- { label: '肠道健康', value: data.gutHealthScore || 0, max: 100 },
|
|
|
- { label: '营养评分', value: data.nutritionScore || 0, max: 100 },
|
|
|
- { label: '有益菌', value: data.beneficialScore || 0, max: 100 },
|
|
|
- { label: '多样性', value: data.diversityScore || 0, max: 100 }
|
|
|
- ]
|
|
|
- // 解析 payload 中的菌群数据
|
|
|
- var payload = data.payload
|
|
|
- if (payload) {
|
|
|
- self.parseBacteria(payload)
|
|
|
- if (payload.foods) {
|
|
|
- self.foods = payload.foods
|
|
|
- }
|
|
|
- }
|
|
|
- // 延迟绘制canvas
|
|
|
- self.$nextTick(function() {
|
|
|
- setTimeout(function() { self.drawRings() }, 300)
|
|
|
- })
|
|
|
- } else {
|
|
|
- uni.showToast({ title: '加载失败', icon: 'none' })
|
|
|
+ if (res.code === 200 && res.data) {
|
|
|
+ self.reportData = res.data
|
|
|
+ self.report = res.data.report || res.data
|
|
|
+ self.buildScores(res.data)
|
|
|
+ self.buildIndicators(res.data)
|
|
|
+ self.floraCount = (res.data.gutFlora || []).length
|
|
|
+ self.riskCount = (res.data.diseaseRisks || []).length
|
|
|
+ self.foodCount = (res.data.foods || []).length
|
|
|
+ self.indicatorCount = (res.data.indicators || []).length
|
|
|
}
|
|
|
- }).catch(function() {
|
|
|
- uni.hideLoading()
|
|
|
- uni.showToast({ title: '加载失败', icon: 'none' })
|
|
|
})
|
|
|
},
|
|
|
- parseBacteria: function(payload) {
|
|
|
- var species = payload.probioticSpecies || payload.gutFlora || []
|
|
|
- var groups = [
|
|
|
- { name: '有益菌', items: [] },
|
|
|
- { name: '有害菌', items: [] },
|
|
|
- { name: '中性菌', items: [] }
|
|
|
+ buildScores: function(data) {
|
|
|
+ var r = data.report || data
|
|
|
+ var items = []
|
|
|
+ var scoreFields = [
|
|
|
+ { key: 'overallScore', label: '综合', color: '#4A9BD7' },
|
|
|
+ { key: 'gutHealthScore', label: '菌群健康', color: '#10B981' },
|
|
|
+ { key: 'chronicDiseaseScore', label: '慢病控制', color: '#F59E0B' },
|
|
|
+ { key: 'nutritionScore', label: '营养均衡', color: '#FF8C42' },
|
|
|
+ { key: 'gutBalanceScore', label: '平衡', color: '#6366F1' },
|
|
|
+ { key: 'gutDiversityScore', label: '多样性', color: '#FF6B9D' },
|
|
|
+ { key: 'beneficialBacteriaScore', label: '有益菌', color: '#10B981' },
|
|
|
+ { key: 'harmfulBacteriaScore', label: '有害菌', color: '#C62828' },
|
|
|
+ { key: 'coreSpeciesScore', label: '核心菌属', color: '#4A9BD7' }
|
|
|
]
|
|
|
- for (var i = 0; i < species.length; i++) {
|
|
|
- var item = species[i]
|
|
|
- var category = item.category || 'beneficial'
|
|
|
- var groupIdx = 0
|
|
|
- if (category === 'beneficial') groupIdx = 0
|
|
|
- else if (category === 'harmful') groupIdx = 1
|
|
|
- else groupIdx = 2
|
|
|
- groups[groupIdx].items.push({
|
|
|
- name: item.name || item.speciesName || '未知',
|
|
|
- abundance: item.abundance || item.relativeAbundance || 0,
|
|
|
- status: item.status || 'normal'
|
|
|
- })
|
|
|
+ for (var i = 0; i < scoreFields.length; i++) {
|
|
|
+ var f = scoreFields[i]
|
|
|
+ if (r[f.key] != null) {
|
|
|
+ items.push({ value: r[f.key], label: f.label, color: f.color })
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.scoreItems = items
|
|
|
+ },
|
|
|
+ buildIndicators: function(data) {
|
|
|
+ var indicators = data.indicators || []
|
|
|
+ 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.bacteriaGroups = groups
|
|
|
+ this.nutritionGroups = groups
|
|
|
},
|
|
|
- drawRings: function() {
|
|
|
+ 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
|
|
|
- this.scoreRings.forEach(function(score, idx) {
|
|
|
- var ctx = uni.createCanvasContext('ring_' + idx, self)
|
|
|
- var size = 120
|
|
|
- var cx = size / 2
|
|
|
- var cy = size / 2
|
|
|
- var radius = 40
|
|
|
- var lineWidth = 10
|
|
|
- var percent = Math.min(score.value / (score.max || 100), 1)
|
|
|
-
|
|
|
- // 背景圆环
|
|
|
- ctx.setStrokeStyle('#E8E8E8')
|
|
|
- ctx.setLineWidth(lineWidth)
|
|
|
- ctx.setLineCap('round')
|
|
|
- ctx.beginPath()
|
|
|
- ctx.arc(cx, cy, radius, 0, 2 * Math.PI)
|
|
|
- ctx.stroke()
|
|
|
-
|
|
|
- // 前景圆环
|
|
|
- var colors = ['#5B9BD5', '#FF8C42', '#10B981', '#6366F1', '#FF6B9D']
|
|
|
- ctx.setStrokeStyle(colors[idx % colors.length])
|
|
|
- ctx.setLineWidth(lineWidth)
|
|
|
- ctx.setLineCap('round')
|
|
|
- ctx.beginPath()
|
|
|
- ctx.arc(cx, cy, radius, -Math.PI / 2, -Math.PI / 2 + 2 * Math.PI * percent)
|
|
|
- ctx.stroke()
|
|
|
-
|
|
|
- ctx.draw()
|
|
|
+ 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
|
|
|
+ }
|
|
|
})
|
|
|
},
|
|
|
- statusText: function(status) {
|
|
|
- if (status === 'high') return '偏高'
|
|
|
- if (status === 'low') return '偏低'
|
|
|
- if (status === 'abnormal') return '异常'
|
|
|
- return '正常'
|
|
|
+ closeKnowledge: function() {
|
|
|
+ this.knowledgeVisible = false
|
|
|
+ this.knowledgeData = {}
|
|
|
+ },
|
|
|
+ toggleEdit: function() {
|
|
|
+ this.isEditing = true
|
|
|
+ },
|
|
|
+ cancelEdit: function() {
|
|
|
+ this.isEditing = false
|
|
|
+ this.loadData()
|
|
|
},
|
|
|
- goToNutritionChat: function() {
|
|
|
- if (!this.reportId) return
|
|
|
- createReportSurvey(this.reportId, '').then(function(res) {
|
|
|
- if (res && res.code === 200) {
|
|
|
- uni.navigateTo({
|
|
|
- url: '/pages/health/nutrition-chat?reportId=' + (res.data && res.data.surveyId ? res.data.surveyId : '')
|
|
|
- })
|
|
|
+ saveEdit: function() {
|
|
|
+ var self = this
|
|
|
+ var payload = {
|
|
|
+ overallScore: this.report.overallScore,
|
|
|
+ gutHealthScore: this.report.gutHealthScore,
|
|
|
+ nutritionScore: this.report.nutritionScore,
|
|
|
+ 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' })
|
|
|
}
|
|
|
- }).catch(function() {
|
|
|
- uni.navigateTo({
|
|
|
- url: '/pages/health/nutrition-chat?reportId=' + this.reportId
|
|
|
- })
|
|
|
})
|
|
|
+ },
|
|
|
+ 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 })
|
|
|
+ },
|
|
|
+ goToIndicators: function() {
|
|
|
+ this.accordionOpen.nutri = true
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
-.page {
|
|
|
- min-height: 100vh;
|
|
|
- background-color: #F5F7FA;
|
|
|
-}
|
|
|
-.nav-bar {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- height: 88rpx;
|
|
|
- padding-top: var(--status-bar-height, 0);
|
|
|
- background-color: #FFFFFF;
|
|
|
- position: relative;
|
|
|
- border-bottom: 1rpx solid #f0f0f0;
|
|
|
-}
|
|
|
-.nav-back {
|
|
|
- position: absolute;
|
|
|
- left: 30rpx;
|
|
|
- top: 50%;
|
|
|
- transform: translateY(-50%);
|
|
|
- padding: 10rpx;
|
|
|
-}
|
|
|
-.back-text { font-size: 28rpx; color: #333; }
|
|
|
-.nav-title { font-size: 32rpx; font-weight: 600; color: #333; }
|
|
|
+.page { min-height: 100vh; background: #f8f8f8; }
|
|
|
+.nav-bar { display: flex; align-items: center; padding: 20rpx 30rpx; background: #fff; position: relative; }
|
|
|
+.nav-back { padding: 10rpx 0; }
|
|
|
+.back-text { font-size: 28rpx; color: #4A9BD7; }
|
|
|
+.nav-title { flex: 1; text-align: center; font-size: 32rpx; font-weight: 600; }
|
|
|
+.content { height: calc(100vh - 100rpx); }
|
|
|
|
|
|
-.content {
|
|
|
- flex: 1;
|
|
|
- padding: 30rpx;
|
|
|
-}
|
|
|
+.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; }
|
|
|
|
|
|
-.report-header {
|
|
|
- background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 30rpx;
|
|
|
- margin-bottom: 30rpx;
|
|
|
-}
|
|
|
-.report-title { font-size: 34rpx; font-weight: bold; color: #fff; display: block; margin-bottom: 12rpx; }
|
|
|
-.report-date { font-size: 24rpx; color: rgba(255,255,255,0.85); display: block; margin-bottom: 6rpx; }
|
|
|
-.report-number { font-size: 22rpx; color: rgba(255,255,255,0.7); display: block; }
|
|
|
+.scores-scroll { padding: 20rpx 0; white-space: nowrap; background: #fff; }
|
|
|
+.score-item { display: inline-flex; flex-direction: column; align-items: center; margin: 0 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; }
|
|
|
|
|
|
-.scores-section {
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- justify-content: space-around;
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 30rpx 20rpx;
|
|
|
- margin-bottom: 30rpx;
|
|
|
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
|
|
-}
|
|
|
-.score-ring {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- min-width: 120rpx;
|
|
|
-}
|
|
|
-.ring-wrapper {
|
|
|
- position: relative;
|
|
|
- width: 120rpx;
|
|
|
- height: 120rpx;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
-}
|
|
|
-.ring-canvas {
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
-}
|
|
|
-.ring-value {
|
|
|
- font-size: 28rpx;
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- z-index: 1;
|
|
|
-}
|
|
|
-.ring-label {
|
|
|
- font-size: 22rpx;
|
|
|
- color: #666;
|
|
|
- margin-top: 10rpx;
|
|
|
-}
|
|
|
+.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; }
|
|
|
+.nav-icon { font-size: 40rpx; margin-bottom: 6rpx; }
|
|
|
+.nav-label { font-size: 24rpx; color: #333; }
|
|
|
+.nav-count { font-size: 20rpx; color: #999; }
|
|
|
|
|
|
-.section-card {
|
|
|
- background: #fff;
|
|
|
- border-radius: 20rpx;
|
|
|
- padding: 30rpx;
|
|
|
- margin-bottom: 30rpx;
|
|
|
- box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.04);
|
|
|
-}
|
|
|
-.section-title {
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- font-size: 30rpx;
|
|
|
- font-weight: 600;
|
|
|
- color: #333;
|
|
|
- margin-bottom: 24rpx;
|
|
|
- padding-bottom: 16rpx;
|
|
|
- border-bottom: 2rpx solid #f0f0f0;
|
|
|
-}
|
|
|
-.section-count { font-size: 24rpx; color: #999; font-weight: normal; }
|
|
|
+.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-arrow { font-size: 24rpx; color: #999; }
|
|
|
+.accordion-body { padding: 10rpx 30rpx 20rpx; }
|
|
|
|
|
|
-.bacteria-item {
|
|
|
- margin-bottom: 20rpx;
|
|
|
-}
|
|
|
-.bacteria-header {
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- align-items: center;
|
|
|
- justify-content: space-between;
|
|
|
- margin-bottom: 8rpx;
|
|
|
-}
|
|
|
-.bacteria-name { font-size: 26rpx; color: #333; font-weight: 500; }
|
|
|
-.bacteria-status { font-size: 22rpx; padding: 2rpx 12rpx; border-radius: 6rpx; }
|
|
|
-.status-normal { background: #E8F5E9; color: #2E7D32; }
|
|
|
-.status-high { background: #FFEBEE; color: #C62828; }
|
|
|
-.status-low { background: #FFF3E0; color: #E65100; }
|
|
|
-.status-abnormal { background: #FFF3E0; color: #E65100; }
|
|
|
+.sub-group { margin-bottom: 16rpx; }
|
|
|
+.sub-group-title { font-size: 26rpx; color: #4A9BD7; font-weight: 600; display: block; margin: 12rpx 0 8rpx; }
|
|
|
|
|
|
-.bar-track {
|
|
|
- height: 12rpx;
|
|
|
- background: #F0F0F0;
|
|
|
- border-radius: 6rpx;
|
|
|
- overflow: hidden;
|
|
|
-}
|
|
|
-.bar-fill {
|
|
|
- height: 100%;
|
|
|
- background: linear-gradient(90deg, #5B9BD5, #3A7CC4);
|
|
|
- border-radius: 6rpx;
|
|
|
-}
|
|
|
-.bar-label { font-size: 22rpx; color: #999; margin-top: 4rpx; display: block; }
|
|
|
-.empty-hint { font-size: 26rpx; color: #999; text-align: center; padding: 40rpx 0; }
|
|
|
+.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; }
|
|
|
|
|
|
-.food-item {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- padding: 16rpx 0;
|
|
|
- border-bottom: 1rpx solid #f5f5f5;
|
|
|
-}
|
|
|
-.food-item:last-child { border-bottom: none; }
|
|
|
-.food-name { font-size: 26rpx; color: #333; font-weight: 500; }
|
|
|
-.food-category { font-size: 22rpx; color: #5B9BD5; margin-top: 4rpx; }
|
|
|
-.food-reason { font-size: 22rpx; color: #666; margin-top: 4rpx; }
|
|
|
+.val-text { font-size: 28rpx; font-weight: 600; color: #333; }
|
|
|
+.val-偏高 { color: #C62828; }
|
|
|
+.val-过多 { color: #C62828; }
|
|
|
+.val-偏低 { color: #E65100; }
|
|
|
+.val-缺乏 { color: #E65100; }
|
|
|
+.val-不足 { color: #E65100; }
|
|
|
+.val-异常 { color: #C62828; }
|
|
|
+.val-注意 { color: #F59E0B; }
|
|
|
|
|
|
-.bottom-spacer { height: 40rpx; }
|
|
|
+.indicator-range { font-size: 22rpx; color: #bbb; display: block; margin-top: 4rpx; }
|
|
|
|
|
|
-.fab {
|
|
|
- position: fixed;
|
|
|
- right: 30rpx;
|
|
|
- bottom: 100rpx;
|
|
|
- width: 100rpx;
|
|
|
- height: 100rpx;
|
|
|
- border-radius: 50%;
|
|
|
- background: linear-gradient(135deg, #5B9BD5, #3A7CC4);
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- box-shadow: 0 4rpx 20rpx rgba(91, 155, 213, 0.4);
|
|
|
- z-index: 100;
|
|
|
-}
|
|
|
-.fab-icon { font-size: 44rpx; }
|
|
|
+.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; }
|
|
|
+
|
|
|
+.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; }
|
|
|
+.bottom-spacer { height: 40rpx; }
|
|
|
</style>
|