Files
msh-system/msh_single_uniapp/pages/tool/calculator-result.vue

989 lines
20 KiB
Vue
Raw Normal View History

<template>
<view class="result-page">
<!-- 加载中 -->
<view v-if="loading" class="loading-wrap">
<view class="loading-inner">
<text class="loading-text">加载中...</text>
</view>
</view>
<!-- 加载失败 -->
<view v-else-if="loadError" class="error-wrap">
<text class="error-text">{{ loadError }}</text>
<view class="retry-btn" v-if="resultId" @click="loadResult">重新加载</view>
<view class="retry-btn" v-else @click="goBackToCalculator">返回重新计算</view>
</view>
<!-- 正常内容 -->
<template v-else>
<!-- Tab切换 -->
<view class="tab-container">
<view
class="tab-item"
:class="{ active: currentTab === 'overview' }"
@click="switchTab('overview')"
>
<text class="tab-icon">📊</text>
<text class="tab-text">健康概览</text>
</view>
<view
class="tab-item"
:class="{ active: currentTab === 'meal' }"
@click="switchTab('meal')"
>
<text class="tab-icon">🍽</text>
<text class="tab-text">营养配餐</text>
</view>
</view>
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 健康概览Tab内容 -->
<view v-if="currentTab === 'overview'" class="tab-content">
<!-- 您的健康数据卡片 -->
<view class="data-card">
<view class="card-header">
<text class="card-icon">💪</text>
<text class="card-title">您的健康数据</text>
</view>
<view class="data-grid">
<view class="data-item">
<text class="data-label">eGFR数值</text>
<text class="data-value">{{ healthData.eGFR }}</text>
<text class="data-unit">ml/min/1.73²m</text>
</view>
<view class="data-item">
<text class="data-label">标准体重</text>
<text class="data-value">{{ healthData.standardWeight }}</text>
<text class="data-unit">东方人标准</text>
</view>
<view class="data-item">
<text class="data-label">体重指数</text>
<text class="data-value">{{ healthData.bmi }}</text>
<text class="data-unit">{{ healthData.bmiStatus }}</text>
</view>
<view class="data-item">
<text class="data-label">CKD分期</text>
<text class="data-value ckd-stage">{{ healthData.ckdStage }}</text>
</view>
</view>
</view>
<!-- 每日营养目标卡片 -->
<view class="data-card">
<view class="card-header">
<text class="card-icon">🎯</text>
<text class="card-title">每日营养目标</text>
</view>
<view class="nutrition-goals">
<view class="goal-item protein">
<text class="goal-label">蛋白质</text>
<text class="goal-value">{{ nutritionGoals.protein }}</text>
<text class="goal-unit">/</text>
</view>
<view class="goal-item energy">
<text class="goal-label">能量</text>
<text class="goal-value">{{ nutritionGoals.energy }}</text>
<text class="goal-unit">千卡/</text>
</view>
</view>
<view class="nutrition-hint">
<text class="hint-icon">💡</text>
<text class="hint-text">相当于下方表中食物的推荐摄入量</text>
</view>
</view>
<!-- 食物份数建议卡片 -->
<view class="data-card">
<view class="card-header">
<text class="card-icon">🥗</text>
<text class="card-title">食物份数建议</text>
</view>
<view class="food-list">
<view
class="food-item"
v-for="(item, index) in foodList"
:key="index"
>
<view class="food-info">
<view class="food-number">{{ item.number }}</view>
<text class="food-name">{{ item.name }}</text>
</view>
<text class="food-portion">{{ item.portion }} </text>
</view>
</view>
</view>
<!-- 温馨提示 -->
<view class="tip-box">
<text class="tip-icon">💡</text>
<view class="tip-content">
<text class="tip-title">温馨提示</text>
<text class="tip-text">以上计算结果仅供参考具体饮食方案请咨询专业营养师或医生</text>
</view>
</view>
<!-- 联系专业营养师按钮 -->
<view class="contact-btn" @click="contactNutritionist">
<text>联系专业营养师</text>
</view>
</view>
<!-- 营养配餐Tab内容 -->
<view v-if="currentTab === 'meal'" class="tab-content meal-content">
<!-- 早餐 -->
<view class="meal-section">
<view class="meal-header">
<view class="meal-icon">🌅</view>
<text class="meal-title">早餐</text>
</view>
<view class="meal-items">
<view
class="meal-item"
v-for="(item, index) in mealPlan.breakfast"
:key="index"
>
<view class="meal-image">
<image :src="item.image" mode="aspectFill"></image>
<view class="meal-number">{{ index + 1 }}</view>
</view>
<view class="meal-info">
<text class="meal-name">{{ item.name }}</text>
<view class="ingredient-tags">
<view
class="ingredient-tag"
v-for="(ingredient, i) in item.ingredients"
:key="i"
>
{{ ingredient }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 午餐 -->
<view class="meal-section">
<view class="meal-header">
<view class="meal-icon"></view>
<text class="meal-title">午餐</text>
</view>
<view class="meal-items">
<view
class="meal-item"
v-for="(item, index) in mealPlan.lunch"
:key="index"
>
<view class="meal-image">
<image :src="item.image" mode="aspectFill"></image>
<view class="meal-number">{{ index + 1 }}</view>
</view>
<view class="meal-info">
<text class="meal-name">{{ item.name }}</text>
<view class="ingredient-tags">
<view
class="ingredient-tag"
v-for="(ingredient, i) in item.ingredients"
:key="i"
>
{{ ingredient }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 晚餐 -->
<view class="meal-section">
<view class="meal-header">
<view class="meal-icon">🌙</view>
<text class="meal-title">晚餐</text>
</view>
<view class="meal-items">
<view
class="meal-item"
v-for="(item, index) in mealPlan.dinner"
:key="index"
>
<view class="meal-image">
<image :src="item.image" mode="aspectFill"></image>
<view class="meal-number">{{ index + 1 }}</view>
</view>
<view class="meal-info">
<text class="meal-name">{{ item.name }}</text>
<view class="ingredient-tags">
<view
class="ingredient-tag"
v-for="(ingredient, i) in item.ingredients"
:key="i"
>
{{ ingredient }}
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 重要提示 -->
<view class="tips-card">
<view class="tips-header">
<text class="tips-icon"></text>
<text class="tips-title">重要提示</text>
</view>
<view class="tips-list">
<view
class="tip-item"
v-for="(tip, index) in importantTips"
:key="index"
>
<text class="tip-dot"></text>
<text class="tip-text">{{ tip }}</text>
</view>
</view>
</view>
<!-- 采纳计划按钮 -->
<view class="adopt-btn" @click="adoptPlan">
<text class="adopt-icon"></text>
<text class="adopt-text">采纳计划开始打卡</text>
</view>
<!-- 联系专业营养师按钮 -->
<view class="contact-btn" @click="contactNutritionist">
<text>联系专业营养师</text>
</view>
</view>
<!-- 底部安全距离 -->
<view class="safe-bottom"></view>
</scroll-view>
</template>
</view>
</template>
<script>
import { getCalculatorResult } from '@/api/tool.js'
export default {
data() {
return {
currentTab: 'overview',
loading: true,
loadError: '',
resultId: null,
healthData: {
eGFR: '--',
standardWeight: '--',
bmi: '--',
bmiStatus: '--',
ckdStage: '--'
},
nutritionGoals: {
protein: '--',
energy: '--'
},
foodList: [],
mealPlan: {
breakfast: [],
lunch: [],
dinner: []
},
importantTips: []
}
},
onLoad(options) {
const id = options.id || options.resultId
if (id) {
this.resultId = Number(id)
this.loadResult()
} else {
this.loading = false
this.loadError = '暂无计算结果,请先完成营养计算'
}
},
methods: {
async loadResult() {
if (!this.resultId) return
this.loading = true
this.loadError = ''
try {
const res = await getCalculatorResult(this.resultId)
const data = res.data || res
this.applyResult(data)
} catch (e) {
const msg = (e && (e.message || e.msg)) || '加载失败,请重试'
this.loadError = msg
}
this.loading = false
},
applyResult(data) {
if (!data) return
if (data.healthData) {
this.healthData = {
eGFR: data.healthData.eGFR ?? '--',
standardWeight: data.healthData.standardWeight ?? '--',
bmi: data.healthData.bmi ?? '--',
bmiStatus: data.healthData.bmiStatus ?? '--',
ckdStage: data.healthData.ckdStage ?? '--'
}
}
if (data.nutritionGoals) {
this.nutritionGoals = {
protein: data.nutritionGoals.protein ?? '--',
energy: data.nutritionGoals.energy ?? '--'
}
}
if (Array.isArray(data.foodList)) {
this.foodList = data.foodList
}
if (data.mealPlan) {
this.mealPlan = {
breakfast: Array.isArray(data.mealPlan.breakfast) ? data.mealPlan.breakfast : [],
lunch: Array.isArray(data.mealPlan.lunch) ? data.mealPlan.lunch : [],
dinner: Array.isArray(data.mealPlan.dinner) ? data.mealPlan.dinner : []
}
}
if (Array.isArray(data.importantTips)) {
this.importantTips = data.importantTips
}
},
goBackToCalculator() {
uni.navigateBack({
delta: 1,
fail: () => {
uni.redirectTo({
url: '/pages/tool/calculator'
})
}
})
},
switchTab(tab) {
this.currentTab = tab
},
contactNutritionist() {
// #ifdef MP-WEIXIN
const openConversation =
typeof uni.openCustomerServiceConversation === 'function'
? uni.openCustomerServiceConversation
: (typeof wx !== 'undefined' && typeof wx.openCustomerServiceConversation === 'function'
? wx.openCustomerServiceConversation
: null);
if (!openConversation) {
uni.showToast({
title: '当前微信版本暂不支持客服会话,请到「我的-联系客服」',
icon: 'none'
});
return;
}
openConversation({
showMessageCard: true,
success: () => {},
fail: () => {
uni.showToast({
title: '打开客服失败,请到「我的-联系客服」',
icon: 'none'
});
}
});
// #endif
// #ifndef MP-WEIXIN
uni.showToast({
title: '请在微信小程序中联系客服',
icon: 'none'
});
// #endif
},
async adoptPlan() {
if (!this.resultId) {
uni.showToast({ title: '暂无计算结果', icon: 'none' })
return
}
// 防止重复点击
if (this._adoptingPlan) return
this._adoptingPlan = true
try {
const { adoptNutritionPlan } = await import('@/api/tool.js')
await adoptNutritionPlan(this.resultId)
uni.showToast({ title: '采纳成功', icon: 'success' })
uni.navigateTo({
url: `/pages/tool/checkin-publish?planId=${this.resultId}`
})
} catch (e) {
const msg = (e && (e.message || e.msg)) || '采纳失败'
uni.showToast({ title: msg, icon: 'none' })
} finally {
this._adoptingPlan = false
}
}
}
}
</script>
<style lang="scss" scoped>
.result-page {
min-height: 100vh;
background-color: #f4f5f7;
}
/* 加载中 / 错误态 */
.loading-wrap,
.error-wrap {
min-height: 60vh;
display: flex;
align-items: center;
justify-content: center;
padding: 48rpx;
}
.loading-inner {
text-align: center;
}
.loading-text {
font-size: 28rpx;
color: #9fa5c0;
}
.error-wrap {
flex-direction: column;
gap: 24rpx;
}
.error-text {
font-size: 28rpx;
color: #666;
text-align: center;
}
.retry-btn {
padding: 20rpx 48rpx;
background: #ff6b35;
color: #fff;
font-size: 28rpx;
border-radius: 48rpx;
}
/* Tab切换 */
.tab-container {
background: #ffffff;
border-bottom: 1rpx solid #d0dbea;
display: flex;
padding: 0 32rpx;
height: 96rpx;
align-items: center;
position: sticky;
// top: 88rpx;
z-index: 99;
}
.tab-item {
flex: 1;
height: 100%;
min-height: 75rpx;
border-radius: 0;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
transition: all 0.3s;
border-bottom: 3px solid transparent;
box-sizing: border-box;
color: #9ca3af;
.tab-icon {
font-size: 28rpx;
color: #9ca3af;
}
.tab-text {
font-size: 28rpx;
color: #9ca3af;
}
&.active {
background: transparent;
border-bottom: 3px solid #f97316;
color: #f97316;
font-weight: 700;
.tab-text {
color: #f97316;
font-weight: 700;
}
.tab-icon {
color: #f97316;
}
}
}
/* 内容滚动区域 */
.content-scroll {
height: calc(100vh - 184rpx);
}
.tab-content {
padding: 32rpx;
display: flex;
flex-direction: column;
gap: 32rpx;
}
/* 数据卡片 */
.data-card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 40rpx;
}
.card-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
.card-icon {
font-size: 48rpx;
}
.card-title {
font-size: 32rpx;
color: #2e3e5c;
font-weight: 500;
}
}
/* 健康数据网格 */
.data-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 24rpx;
}
.data-item {
background: #f4f5f7;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 30rpx 24rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
.data-label {
font-size: 24rpx;
color: #9fa5c0;
}
.data-value {
font-size: 40rpx;
color: #3e5481;
font-weight: 500;
&.ckd-stage {
font-size: 32rpx;
}
}
.data-unit {
font-size: 24rpx;
color: #9fa5c0;
}
}
/* 营养目标 */
.nutrition-goals {
display: flex;
gap: 24rpx;
margin-bottom: 24rpx;
}
.goal-item {
flex: 1;
border-radius: 24rpx;
padding: 32rpx 24rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
&.protein {
background: #fff5f0;
border: 1rpx solid rgba(255, 107, 53, 0.3);
.goal-label {
color: #ff6b35;
}
.goal-value {
color: #ff6b35;
}
.goal-unit {
color: rgba(255, 107, 53, 0.7);
}
}
&.energy {
background: #fff4e6;
border: 1rpx solid rgba(255, 165, 0, 0.3);
.goal-label {
color: #ff9800;
}
.goal-value {
color: #ff9800;
}
.goal-unit {
color: rgba(255, 152, 0, 0.7);
}
}
.goal-label {
font-size: 24rpx;
}
.goal-value {
font-size: 48rpx;
font-weight: 500;
}
.goal-unit {
font-size: 24rpx;
}
}
.nutrition-hint {
background: #f4f5f7;
border: 1rpx solid #d0dbea;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
align-items: center;
gap: 16rpx;
.hint-icon {
font-size: 32rpx;
}
.hint-text {
font-size: 24rpx;
color: #9fa5c0;
flex: 1;
}
}
/* 食物列表 */
.food-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.food-item {
background: #f4f5f7;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 24rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.food-info {
display: flex;
align-items: center;
gap: 24rpx;
.food-number {
width: 56rpx;
height: 56rpx;
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #3e5481;
flex-shrink: 0;
}
.food-name {
font-size: 28rpx;
color: #3e5481;
}
}
.food-portion {
font-size: 28rpx;
color: #ff6b35;
font-weight: 500;
}
/* 温馨提示 */
.tip-box {
background: #e3f2fd;
border: 1rpx solid #64b5f6;
border-radius: 24rpx;
padding: 32rpx;
display: flex;
gap: 20rpx;
align-items: flex-start;
.tip-icon {
font-size: 40rpx;
flex-shrink: 0;
}
.tip-content {
flex: 1;
display: flex;
flex-direction: column;
gap: 8rpx;
.tip-title {
font-size: 28rpx;
color: #1976d2;
font-weight: 500;
}
.tip-text {
font-size: 24rpx;
color: #1565c0;
line-height: 1.6;
}
}
}
/* 联系营养师按钮 */
.contact-btn {
width: calc(100% - 64rpx);
height: 96rpx;
background: #ffffff;
border: 2rpx solid #ff6b35;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
margin: 0 32rpx 24rpx;
text {
font-size: 28rpx;
color: #ff6b35;
font-weight: 500;
}
}
/* 空状态 */
.empty-placeholder {
padding: 200rpx 0;
text-align: center;
color: #9fa5c0;
font-size: 28rpx;
}
/* 营养配餐内容 */
.meal-content {
padding: 0;
gap: 0;
}
/* 餐次区块 */
.meal-section {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
margin: 0 32rpx 32rpx;
overflow: hidden;
}
.meal-header {
background: linear-gradient(180deg, #f8f9fa 0%, #f4f5f7 100%);
border-bottom: 1rpx solid #d0dbea;
padding: 32rpx;
display: flex;
align-items: center;
gap: 24rpx;
}
.meal-icon {
width: 80rpx;
height: 80rpx;
background: #ffffff;
border: 2rpx solid rgba(255, 136, 68, 0.3);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 48rpx;
}
.meal-title {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
/* 菜品列表 */
.meal-items {
padding: 32rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
}
.meal-item {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 16rpx;
display: flex;
gap: 24rpx;
}
.meal-image {
width: 160rpx;
height: 160rpx;
border-radius: 24rpx;
overflow: hidden;
position: relative;
background: #f4f5f7;
flex-shrink: 0;
image {
width: 100%;
height: 100%;
}
.meal-number {
position: absolute;
left: 12rpx;
top: 12rpx;
width: 48rpx;
height: 48rpx;
background: linear-gradient(135deg, #ff8844 0%, #ff7722 100%);
border: 2rpx solid #ffffff;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 24rpx;
color: #ffffff;
font-weight: 500;
}
}
.meal-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
padding: 8rpx 0;
}
.meal-name {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
.ingredient-tags {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.ingredient-tag {
background: linear-gradient(135deg, #fff8f0 0%, #fff5eb 100%);
border: 1rpx solid rgba(255, 136, 68, 0.3);
border-radius: 12rpx;
padding: 8rpx 16rpx;
font-size: 24rpx;
color: #ff7722;
}
/* 重要提示卡片 */
.tips-card {
background: #fff3e0;
border: 1rpx solid #ffb74d;
border-radius: 24rpx;
margin: 0 32rpx 32rpx;
padding: 32rpx;
}
.tips-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
.tips-icon {
font-size: 40rpx;
}
.tips-title {
font-size: 28rpx;
color: #e65100;
font-weight: 500;
}
}
.tips-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.tip-item {
display: flex;
gap: 16rpx;
align-items: flex-start;
.tip-dot {
font-size: 28rpx;
color: #e65100;
line-height: 1;
margin-top: 4rpx;
}
.tip-text {
flex: 1;
font-size: 28rpx;
color: #f57c00;
line-height: 1.6;
}
}
/* 采纳计划按钮 */
.adopt-btn {
width: calc(100% - 64rpx);
height: 96rpx;
background: #ff6b35;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
margin: 0 32rpx 24rpx;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1), 0 2rpx 4rpx rgba(0, 0, 0, 0.1);
.adopt-icon {
font-size: 28rpx;
}
.adopt-text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
/* 底部安全距离 */
.safe-bottom {
height: 40rpx;
}
</style>