Files
msh-system/msh_single_uniapp/pages/tool/nutrient-detail.vue
Developer 24f75d198c fix: 营养素详情页修复 API 空响应未走本地兜底的问题
- loadNutrientData() 判断由 if(res.data) 改为
  if(res.data && Object.keys(res.data).length > 0 && res.data.name)
- 当后端 v2_knowledge 表无该营养素记录时(返回{})
  自动降级到本地 nutrientMap 展示内置数据
- 不影响正常有数据时的 API 优先逻辑

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 15:14:14 +08:00

676 lines
18 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="nutrient-detail-page">
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 营养素头部横幅 -->
<view class="nutrient-header">
<view class="header-icon">
<text>{{ nutrientData.icon }}</text>
</view>
<view class="header-info">
<view class="header-name">{{ nutrientData.name }}</view>
<view class="header-english">{{ nutrientData.english }}</view>
<view class="header-desc">{{ nutrientData.description }}</view>
</view>
</view>
<!-- 适量控制状态卡片 -->
<view class="status-card">
<text class="status-icon"></text>
<text class="status-title">{{ nutrientData.status }}</text>
<text class="status-desc">{{ nutrientData.statusDesc }}</text>
</view>
<!-- 为什么重要 -->
<view class="info-card">
<view class="card-header">
<view class="card-icon">
<image class="icon-img" :src="iconWhyImportant" mode="aspectFit"></image>
</view>
<text class="card-title">为什么重要</text>
</view>
<text class="card-content">{{ nutrientData.importance }}</text>
</view>
<!-- 推荐摄入量 -->
<view class="info-card recommended">
<view class="card-header">
<view class="card-icon">
<image class="icon-img" :src="iconRecommendation" mode="aspectFit"></image>
</view>
<text class="card-title">推荐摄入量</text>
</view>
<view class="recommendation-box">
<text class="recommendation-text">{{ nutrientData.recommendation }}</text>
</view>
</view>
<!-- 主要食物来源 -->
<view class="info-card">
<view class="card-header">
<text class="card-icon-emoji">🥗</text>
<text class="card-title">主要食物来源</text>
</view>
<view class="food-sources">
<view
class="source-tag"
v-for="(source, index) in nutrientData.foodSources"
:key="index"
>
{{ source }}
</view>
</view>
</view>
<!-- 风险提示 -->
<view class="warning-card">
<view class="card-header">
<view class="warning-icon">!</view>
<text class="card-title">风险提示</text>
</view>
<text class="card-content">{{ nutrientData.riskWarning }}</text>
</view>
<!-- 实用建议 -->
<view class="info-card suggestions">
<view class="card-header">
<view class="card-icon">
<image class="icon-img" :src="iconSuggestions" mode="aspectFit"></image>
</view>
<text class="card-title">实用建议</text>
</view>
<view class="suggestions-list">
<view
class="suggestion-item"
v-for="(suggestion, index) in nutrientData.suggestions"
:key="index"
>
<view class="suggestion-number">{{ index + 1 }}</view>
<text class="suggestion-text">{{ suggestion }}</text>
</view>
</view>
</view>
<!-- 免责声明 -->
<view class="disclaimer-card">
<text class="disclaimer-icon">💡</text>
<text class="disclaimer-text">{{ nutrientData.disclaimer }}</text>
</view>
<!-- 底部安全距离 -->
<view class="safe-bottom"></view>
</scroll-view>
</view>
</template>
<script>
export default {
data() {
return {
// 图标资源
iconWhyImportant: '/static/images/icon-why-important.png',
iconRecommendation: '/static/images/icon-recommendation.png',
iconSuggestions: '/static/images/icon-suggestions.png',
nutrientName: '',
nutrientData: {}
}
},
onLoad(options) {
// 根据传入的营养素名称加载对应数据
if (options.name) {
this.nutrientName = decodeURIComponent(options.name)
this.loadNutrientData(this.nutrientName)
}
},
methods: {
async loadNutrientData(name) {
// 优先调用后端接口获取营养素详情
try {
const { getNutrientDetail } = await import('@/api/tool.js')
const res = await getNutrientDetail(name)
// 后端返回空对象 {} 时不视为有效数据,继续走本地兜底
if (res && res.data && typeof res.data === 'object' && Object.keys(res.data).length > 0 && res.data.name) {
this.nutrientData = res.data
return
}
} catch (e) {
console.warn('API获取营养素详情失败使用本地数据', e)
}
// 兜底:使用本地 nutrientMap
const nutrientMap = {
'钠': {
name: '钠',
english: 'Sodium (Na)',
icon: '🧂',
description: '调节体液平衡的电解质',
status: '适量控制',
statusDesc: '可适量补充,保持均衡',
importance: '钠参与调节体液平衡和血压,过多摄入会导致水肿和高血压,增加心血管负担。',
recommendation: 'CKD患者2-3g食盐/天相当于800-1200mg钠',
foodSources: ['食盐', '酱油', '腌制食品', '加工肉类', '咸菜', '味精'],
riskWarning: '摄入过多会导致水肿、高血压、心力衰竭等问题。',
suggestions: [
'每日食盐控制在3-5g约一啤酒瓶盖',
'避免腌制、熏制食品',
'少用酱油、味精等调味品',
'可用葱姜蒜、柠檬汁调味',
'查看食品标签,选择低钠产品',
'透析患者控制饮水量'
],
disclaimer: '以上建议仅供参考,具体方案请咨询您的主治医生或营养师'
},
'蛋白质': {
name: '蛋白质',
english: 'Protein',
icon: '🥩',
description: '构成人体组织的重要营养素',
status: '需控制',
statusDesc: '根据CKD分期调整摄入量',
importance: '蛋白质是人体细胞的基本组成成分,参与免疫功能和组织修复。但过多蛋白质会增加肾脏代谢负担,加速肾功能恶化。',
recommendation: 'CKD 1-2期0.8-1.0g/kg/天\nCKD 3-5期未透析0.6-0.8g/kg/天\n透析患者1.0-1.2g/kg/天',
foodSources: ['鸡蛋', '鱼类', '瘦肉', '牛奶', '豆腐', '鸡胸肉'],
riskWarning: '过多蛋白质摄入会产生大量含氮废物,加重肾脏负担;过少则导致营养不良、免疫力下降。',
suggestions: [
'优先选择优质蛋白(鸡蛋、鱼、瘦肉)',
'控制植物蛋白摄入(豆类适量)',
'每餐均匀分配蛋白质',
'透析患者需适当增加蛋白质摄入',
'定期监测白蛋白水平',
'咨询营养师制定个性化方案'
],
disclaimer: '以上建议仅供参考,具体方案请咨询您的主治医生或营养师'
},
'钾': {
name: '钾',
english: 'Potassium (K)',
icon: '🍌',
description: '维持神经肌肉功能的重要元素',
status: '严格控制',
statusDesc: '高钾血症可危及生命',
importance: '钾离子参与维持心脏节律、神经传导和肌肉收缩。肾功能下降时,钾排泄减少,血钾升高可导致心律失常甚至心脏骤停。',
recommendation: 'CKD 3-5期1500-2000mg/天\n透析患者2000-2500mg/天(透析间期严格控制)',
foodSources: ['香蕉', '橙子', '土豆', '番茄', '菠菜', '蘑菇'],
riskWarning: '高钾血症可导致心律失常、肌肉无力,严重时危及生命。低钾同样有害,需保持平衡。',
suggestions: [
'避免高钾水果(香蕉、橙子、猕猴桃)',
'蔬菜先焯水再烹饪可减少30-50%的钾',
'避免饮用浓缩果汁和菜汤',
'少吃坚果、巧克力、干果',
'定期监测血钾水平',
'透析日可适当放宽限制'
],
disclaimer: '以上建议仅供参考,具体方案请咨询您的主治医生或营养师'
},
'磷': {
name: '磷',
english: 'Phosphorus (P)',
icon: '🥜',
description: '骨骼健康的重要矿物质',
status: '严格控制',
statusDesc: '高磷可导致骨病和血管钙化',
importance: '磷与钙共同维持骨骼健康。肾功能下降时磷排泄减少,血磷升高可导致继发性甲状旁腺功能亢进、骨质疏松和血管钙化。',
recommendation: 'CKD 3-5期及透析患者800-1000mg/天\n血磷目标1.13-1.78mmol/L',
foodSources: ['坚果', '动物内脏', '可乐', '加工食品', '奶酪', '蛋黄'],
riskWarning: '高磷血症可导致皮肤瘙痒、骨痛、血管钙化,增加心血管疾病风险。',
suggestions: [
'避免含磷添加剂的加工食品',
'限制坚果、动物内脏摄入',
'少喝碳酸饮料(含磷酸)',
'按医嘱服用磷结合剂(随餐服用)',
'选择低磷蛋白质来源(蛋白、鸡胸肉)',
'注意查看食品标签中的磷含量'
],
disclaimer: '以上建议仅供参考,具体方案请咨询您的主治医生或营养师'
},
'钙': {
name: '钙',
english: 'Calcium (Ca)',
icon: '🥛',
description: '骨骼和牙齿的主要成分',
status: '适量补充',
statusDesc: '需在医生指导下补充',
importance: '钙是骨骼和牙齿的主要成分参与神经传导、肌肉收缩和血液凝固。CKD患者因维生素D代谢异常常出现低钙高磷状态。',
recommendation: 'CKD患者根据血钙水平调整\n一般建议800-1000mg/天(含饮食和补充剂)',
foodSources: ['牛奶', '酸奶', '豆腐', '小白菜', '虾皮', '芝麻'],
riskWarning: '过量补钙可导致高钙血症和血管钙化;不足则加重骨质疏松。需根据血钙磷水平调整。',
suggestions: [
'根据血钙水平决定是否补充',
'优先通过食物获取钙质',
'避免同时大量补钙和补磷',
'按医嘱服用活性维生素D',
'定期监测血钙、血磷和PTH',
'钙剂与磷结合剂错开服用'
],
disclaimer: '以上建议仅供参考,具体方案请咨询您的主治医生或营养师'
},
'水分': {
name: '水分',
english: 'Water',
icon: '💧',
description: '生命活动必需的基础物质',
status: '需控制',
statusDesc: '根据尿量和透析情况调整',
importance: '水是人体最重要的组成部分,参与所有生理活动。肾功能下降时水分排泄减少,过多水分摄入可导致水肿、高血压和心力衰竭。',
recommendation: '非透析CKD患者量出为入前日尿量+500ml\n血液透析患者透析间期体重增加不超过干体重的3-5%\n腹膜透析患者根据超滤量调整',
foodSources: ['饮用水', '汤类', '粥', '水果', '蔬菜', '饮料'],
riskWarning: '水分摄入过多可导致水肿、呼吸困难、血压升高;过少则可能引起脱水和低血压。',
suggestions: [
'记录每日饮水量和尿量',
'口渴时小口慢饮,避免大量饮水',
'减少含水量高的食物(西瓜、汤面)',
'用冰块含服缓解口渴',
'避免过咸食物(会增加渴感)',
'透析患者严格控制两次透析间体重增长'
],
disclaimer: '以上建议仅供参考,具体方案请咨询您的主治医生或营养师'
}
}
this.nutrientData = nutrientMap[name] || {}
}
}
}
</script>
<style lang="scss" scoped>
.nutrient-detail-page {
min-height: 100vh;
background: linear-gradient(180deg, #f8f9fa 0%, #f4f5f7 100%);
display: flex;
flex-direction: column;
}
/* 内容滚动区域 */
.content-scroll {
flex: 1;
padding: 0;
}
/* 营养素头部横幅 */
.nutrient-header {
background: linear-gradient(135deg, #ff8844 0%, #ee6600 50%, #ff7722 100%);
padding: 48rpx 32rpx;
display: flex;
align-items: center;
gap: 32rpx;
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
right: -80rpx;
top: -80rpx;
width: 256rpx;
height: 256rpx;
background: rgba(255, 255, 255, 0.1);
border-radius: 50%;
filter: blur(60rpx);
}
.header-icon {
width: 160rpx;
height: 160rpx;
background: #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 0 0 8rpx rgba(255, 255, 255, 0.2), 0 16rpx 60rpx rgba(0, 0, 0, 0.15);
flex-shrink: 0;
position: relative;
z-index: 1;
text {
font-size: 96rpx;
}
}
.header-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
position: relative;
z-index: 1;
}
.header-name {
font-size: 40rpx;
color: #ffffff;
font-weight: 500;
text-shadow: 0 6rpx 12rpx rgba(0, 0, 0, 0.12);
}
.header-english {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
}
.header-desc {
font-size: 28rpx;
color: rgba(255, 255, 255, 0.95);
text-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.15);
}
}
/* 适量控制状态卡片 */
.status-card {
background: linear-gradient(135deg, #fff8e1 0%, #fff4e6 100%);
border: 2rpx solid #ffa500;
border-radius: 24rpx;
margin: 30rpx 32rpx 32rpx;
padding: 20rpx;
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
box-shadow: 0 16rpx 60rpx rgba(255, 71, 87, 0.15);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
right: -60rpx;
top: -60rpx;
width: 192rpx;
height: 192rpx;
background: rgba(255, 255, 255, 0.3);
border-radius: 50%;
filter: blur(40rpx);
}
.status-icon {
font-size: 60rpx;
position: relative;
z-index: 1;
}
.status-title {
font-size: 32rpx;
color: #2e3e5c;
font-weight: 500;
position: relative;
z-index: 1;
}
.status-desc {
font-size: 24rpx;
color: #9fa5c0;
position: relative;
z-index: 1;
}
}
/* 通用信息卡片 */
.info-card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
margin: 0 32rpx 24rpx;
padding: 32rpx;
}
.card-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
.card-icon {
width: 64rpx;
height: 64rpx;
background: linear-gradient(135deg, #ffa500 0%, #ff8c00 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 12rpx rgba(0, 0, 0, 0.1);
.icon-img {
width: 32rpx;
height: 32rpx;
}
}
.card-icon-emoji {
font-size: 36rpx;
line-height: 1;
}
.card-title {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
}
.card-content {
font-size: 28rpx;
color: #3e5481;
line-height: 1.6;
}
/* 推荐摄入量卡片 */
.recommended {
border-left: 6rpx solid #ff7722;
border-top: 1rpx solid #ff7722;
border-right: 1rpx solid #ff7722;
border-bottom: 1rpx solid #ff7722;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.06);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
right: -60rpx;
top: -60rpx;
width: 160rpx;
height: 160rpx;
background: rgba(255, 244, 230, 0.5);
border-radius: 50%;
filter: blur(40rpx);
}
.card-icon {
background: linear-gradient(135deg, #ff8844 0%, #ff7722 100%);
}
}
.recommendation-box {
background: linear-gradient(135deg, #fff8e1 0%, #fff4e6 100%);
border: 1rpx solid rgba(255, 165, 0, 0.2);
border-radius: 24rpx;
padding: 32rpx;
}
.recommendation-text {
font-size: 28rpx;
color: #3e5481;
line-height: 1.6;
}
/* 主要食物来源 */
.food-sources {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-top: 24rpx;
}
.source-tag {
background: linear-gradient(135deg, #fff5f0 0%, #ffe8dc 100%);
border: 1rpx solid rgba(255, 136, 68, 0.3);
border-radius: 50rpx;
padding: 16rpx 28rpx;
font-size: 28rpx;
color: #ff7722;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
/* 风险提示卡片 */
.warning-card {
background: linear-gradient(135deg, #ffebee 0%, #ffe0e3 100%);
border-left: 6rpx solid #ff6b35;
border-top: 2rpx solid #ff6b35;
border-right: 2rpx solid #ff6b35;
border-bottom: 2rpx solid #ff6b35;
border-radius: 24rpx;
margin: 0 32rpx 24rpx;
padding: 32rpx;
box-shadow: 0 16rpx 60rpx rgba(255, 100, 100, 0.15);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
right: -60rpx;
top: -60rpx;
width: 192rpx;
height: 192rpx;
background: rgba(255, 100, 100, 0.1);
border-radius: 50%;
filter: blur(40rpx);
}
.card-header {
margin-bottom: 24rpx;
display: flex;
align-items: center;
gap: 16rpx;
}
.warning-icon {
width: 48rpx;
height: 48rpx;
background: #ff6b35;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #ffffff;
font-weight: bold;
flex-shrink: 0;
}
.card-title {
color: #ff6b35;
font-size: 28rpx;
}
}
/* 实用建议卡片 */
.suggestions {
background: linear-gradient(135deg, #fff5f0 0%, #ffe8dc 100%);
border: 2rpx solid rgba(255, 136, 68, 0.4);
box-shadow: 0 16rpx 60rpx rgba(255, 136, 68, 0.15);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
right: -60rpx;
top: -60rpx;
width: 192rpx;
height: 192rpx;
background: rgba(255, 136, 68, 0.1);
border-radius: 50%;
filter: blur(40rpx);
}
.card-icon {
background: linear-gradient(135deg, #ff9966 0%, #ff8844 100%);
}
}
.suggestions-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.suggestion-item {
display: flex;
align-items: flex-start;
gap: 20rpx;
}
.suggestion-number {
width: 40rpx;
height: 40rpx;
background: #ff8844;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #ffffff;
font-weight: 500;
flex-shrink: 0;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
.suggestion-text {
flex: 1;
font-size: 28rpx;
color: #3e5481;
line-height: 1.6;
}
/* 免责声明卡片 */
.disclaimer-card {
background: linear-gradient(135deg, #fff7f3 0%, #ffe8e0 100%);
border: 2rpx solid rgba(255, 140, 90, 0.3);
border-radius: 32rpx;
margin: 0 32rpx 32rpx;
padding: 32rpx;
display: flex;
align-items: center;
gap: 16rpx;
box-shadow: 0 8rpx 40rpx rgba(255, 107, 53, 0.1);
position: relative;
overflow: hidden;
&::before {
content: '';
position: absolute;
right: -60rpx;
top: -60rpx;
width: 160rpx;
height: 160rpx;
background: rgba(255, 107, 53, 0.1);
border-radius: 50%;
filter: blur(40rpx);
}
.disclaimer-icon {
font-size: 36rpx;
flex-shrink: 0;
position: relative;
z-index: 1;
}
.disclaimer-text {
flex: 1;
font-size: 28rpx;
color: #ff6b35;
line-height: 1.6;
text-align: center;
position: relative;
z-index: 1;
}
}
/* 底部安全距离 */
.safe-bottom {
height: 40rpx;
}
</style>