Files
msh-system/msh_single_uniapp/pages/tool/recipe-detail.vue
msh-agent c1857ce852 fix: 修复关注按钮相关问题
- 食谱详情页: 修复 applyDefaultData 中未定义变量 id 的问题
- 帖子详情页: 优化 toggleFollow 方法,提前校验 author.id,兼容多种后端字段
- 为帖子详情页已关注状态添加灰色样式
2026-03-09 18:56:53 +08:00

1058 lines
26 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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="recipe-detail-page">
<!-- 内容区域 -->
<scroll-view class="content-scroll" scroll-y>
<!-- 食谱封面 -->
<view class="recipe-cover">
<image class="cover-image" :src="recipeData.coverImage" mode="aspectFill"></image>
<view class="difficulty-badge">
<text class="difficulty-icon"></text>
<text>简单</text>
</view>
</view>
<!-- 食谱基本信息 -->
<view class="recipe-info-card">
<view class="recipe-title">{{ recipeData.title }}</view>
<view class="recipe-subtitle">{{ recipeData.subtitle }}</view>
<view class="recipe-meta">
<view class="meta-item">
<text class="meta-icon"></text>
<text class="meta-text">{{ recipeData.time }}</text>
</view>
<view class="meta-item">
<text class="meta-icon">👤</text>
<text class="meta-text">{{ recipeData.servings }}</text>
</view>
<view class="meta-item">
<text class="meta-icon"></text>
<text class="meta-text">{{ recipeData.rating }} ({{ recipeData.reviews }})</text>
</view>
</view>
<view class="recipe-tags">
<view class="tag" v-for="(tag, index) in recipeData.tags" :key="index">
<text>{{ tag }}</text>
</view>
</view>
</view>
<!-- 营养师团队卡片 -->
<view class="team-card">
<view class="team-info">
<view class="team-avatar">
<text class="avatar-icon">👨</text>
</view>
<view class="team-details">
<view class="team-name">
<text>慢生活营养师团队</text>
<text class="verified-icon"></text>
</view>
<text class="team-role">专业营养师</text>
</view>
</view>
<view class="follow-btn" :class="{ followed: isFollowing }" @click="toggleFollow">
<text>{{ isFollowing ? '已关注' : '+ 关注' }}</text>
</view>
</view>
<!-- 食谱介绍 -->
<view class="intro-card">
<view class="card-header">
<text class="header-icon">📝</text>
<text class="header-title">食谱介绍</text>
</view>
<text class="intro-text">{{ recipeData.introduction }}</text>
</view>
<!-- 营养成分 -->
<view class="nutrition-card">
<view class="card-header">
<text class="header-icon">📊</text>
<text class="header-title">营养成分全天</text>
</view>
<view class="nutrition-grid">
<view class="nutrition-item" v-for="(item, index) in nutritionData.main" :key="index">
<text class="nutrition-value">{{ item.value }}</text>
<text class="nutrition-label">{{ item.label }}</text>
</view>
</view>
<view class="nutrition-row">
<view class="nutrition-item" v-for="(item, index) in nutritionData.minor" :key="index">
<text class="nutrition-value">{{ item.value }}</text>
<text class="nutrition-label">{{ item.label }}</text>
</view>
</view>
</view>
<!-- 三餐配餐方案 -->
<view class="meal-plan-section">
<view class="section-title">
<text class="title-icon">🍽</text>
<text class="title-text">三餐配餐方案</text>
</view>
<!-- 早餐 -->
<view class="meal-card">
<view class="meal-header">
<view class="meal-icon">🌅</view>
<text class="meal-name">早餐</text>
</view>
<view class="meal-dishes">
<view
class="dish-item"
v-for="(dish, index) in mealPlan.breakfast"
:key="index"
>
<view class="dish-image-wrapper">
<image class="dish-image" :src="dish.image" mode="aspectFill"></image>
<view class="dish-number">{{ index + 1 }}</view>
</view>
<view class="dish-info">
<text class="dish-name">{{ dish.name }}</text>
<view class="dish-ingredients">
<view
class="ingredient-tag"
v-for="(ing, idx) in dish.ingredients"
:key="idx"
>
<text>{{ ing }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 午餐 -->
<view class="meal-card">
<view class="meal-header">
<view class="meal-icon"></view>
<text class="meal-name">午餐</text>
</view>
<view class="meal-dishes">
<view
class="dish-item"
v-for="(dish, index) in mealPlan.lunch"
:key="index"
>
<view class="dish-image-wrapper">
<image class="dish-image" :src="dish.image" mode="aspectFill"></image>
<view class="dish-number">{{ index + 1 }}</view>
</view>
<view class="dish-info">
<text class="dish-name">{{ dish.name }}</text>
<view class="dish-ingredients">
<view
class="ingredient-tag"
v-for="(ing, idx) in dish.ingredients"
:key="idx"
>
<text>{{ ing }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 晚餐 -->
<view class="meal-card">
<view class="meal-header">
<view class="meal-icon">🌙</view>
<text class="meal-name">晚餐</text>
</view>
<view class="meal-dishes">
<view
class="dish-item"
v-for="(dish, index) in mealPlan.dinner"
:key="index"
>
<view class="dish-image-wrapper">
<image class="dish-image" :src="dish.image" mode="aspectFill"></image>
<view class="dish-number">{{ index + 1 }}</view>
</view>
<view class="dish-info">
<text class="dish-name">{{ dish.name }}</text>
<view class="dish-ingredients">
<view
class="ingredient-tag"
v-for="(ing, idx) in dish.ingredients"
:key="idx"
>
<text>{{ ing }}</text>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 重要提示 -->
<view class="warning-card">
<view class="card-header">
<text class="header-icon"></text>
<text class="header-title">重要提示</text>
</view>
<view class="warning-list">
<view class="warning-item" v-for="(item, index) in warningList" :key="index">
<text class="bullet"></text>
<text class="warning-text">{{ item }}</text>
</view>
</view>
</view>
<!-- 温馨提示 -->
<view class="tip-card">
<text class="tip-text">💡 以上配餐方案仅供参考具体方案请咨询您的主治医生或营养师</text>
</view>
<!-- 底部安全距离 -->
<view class="safe-bottom"></view>
</scroll-view>
<!-- 底部操作栏 -->
<view class="bottom-bar">
<view class="action-btn-small" @click="toggleLike">
<text class="action-icon-small" :class="{ active: isLiked }">{{ isLiked ? '❤️' : '🤍' }}</text>
</view>
<view class="action-btn-small" @click="toggleFavorite">
<text class="action-icon-small" :class="{ active: isFavorite }"></text>
</view>
<view class="checkin-btn" @click="handleCheckin">
<text>一键打卡</text>
</view>
</view>
</view>
</template>
<script>
import { getRecipeDetail, toggleRecipeFavorite, fillRecipeNutrition } from '@/api/tool.js';
export default {
data() {
return {
recipeId: null,
loading: false,
isFavorite: false,
isLiked: false,
isFollowing: false,
recipeData: {
coverImage: '',
title: '',
subtitle: '',
time: '',
servings: '',
rating: '',
reviews: '',
tags: [],
introduction: ''
},
nutritionData: {
main: [],
minor: []
},
mealPlan: {
breakfast: [],
lunch: [],
dinner: []
},
warningList: [],
// 默认展示数据API 未返回时使用)
defaultRecipeData: {
coverImage: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/bass.jpg',
title: '透析患者一日营养配餐方案',
subtitle: '低钾、低磷、优质蛋白',
time: '15分钟',
servings: '1人份',
rating: '4.8',
reviews: '328',
tags: ['低钾饮食', '透析期', '优质蛋白', '营养均衡'],
introduction: '专为透析患者设计的一日三餐配餐方案,严格控制钾、磷摄入,保证优质蛋白供应,帮助患者维持营养平衡。'
},
defaultNutritionData: {
main: [
{ value: '1850', label: '热量(kcal)' },
{ value: '65g', label: '蛋白质' },
{ value: '45g', label: '脂肪' },
{ value: '280g', label: '碳水' }
],
minor: [
{ value: '4.2g', label: '钠' },
{ value: '1800mg', label: '钾' },
{ value: '850mg', label: '磷' }
]
},
defaultMealPlan: {
breakfast: [
{ name: '牛奶', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/milk.jpg', ingredients: ['牛奶 120g'] },
{ name: '鸡蛋拌面', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/egg-noodle.jpg', ingredients: ['面条 90g', '鸡蛋 120g', '葱花 5g'] },
{ name: '凉拌黄瓜', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/cucumber.jpg', ingredients: ['黄瓜 100g'] }
],
lunch: [
{ name: '米饭', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/rice.jpg', ingredients: ['大米 100g'] },
{ name: '清蒸鲈鱼', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/bass.jpg', ingredients: ['鲈鱼 120g', '生姜 5g', '葱 5g'] },
{ name: '蒜蓉西兰花', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/broccoli.jpg', ingredients: ['西兰花 150g', '大蒜 5g', '植物油 8g'] },
{ name: '冬瓜汤', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/wax-gourd-soup.jpg', ingredients: ['冬瓜 150g'] }
],
dinner: [
{ name: '杂粮饭', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/mixed-rice.jpg', ingredients: ['大米 70g', '小米 30g'] },
{ name: '香菇炒鸡丁', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/chicken-mushroom.jpg', ingredients: ['鸡胸肉 100g', '香菇 50g', '植物油 8g'] },
{ name: '清炒油菜', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/bok-choy.jpg', ingredients: ['油菜 150g', '植物油 5g'] },
{ name: '番茄蛋花汤', image: 'https://uthink2025.oss-cn-shanghai.aliyuncs.com/recipes/tomato-egg-soup.jpg', ingredients: ['番茄 100g', '鸡蛋 60g'] }
]
},
defaultWarningList: [
'以上配餐由 AI 生成,仅适用于无其他并发症的单纯尿毒症人群',
'透析患者需严格控制水分摄入',
'建议低盐饮食(每日少于 5g',
'注意限制高钾食物(香蕉、橙子、土豆等)',
'限制高磷食物(坚果、动物内脏等)',
'特别提醒:尿毒症合并其他并发症人群不适用此配餐,请务必咨询专业营养师调整方案;也可参考十年以上透析肾友的经验再确认是否适配。'
]
}
},
onLoad(options) {
if (options.id) {
this.recipeId = options.id
this.loadRecipeData(options.id)
} else {
this.applyDefaultData()
}
},
methods: {
// T09: AI 回填食谱营养
async fillNutritionFromAI(recipeId) {
try {
uni.showLoading({ title: 'AI分析中...' })
const res = await fillRecipeNutrition(recipeId)
const data = res.data || res
if (data) {
// 更新营养数据
this.nutritionData = {
main: [
{ value: String(data.energyKcal || '--'), label: '热量(kcal)' },
{ value: (data.proteinG || '--') + 'g', label: '蛋白质' },
{ value: '--', label: '脂肪' },
{ value: '--', label: '碳水' }
],
minor: [
{ value: data.sodiumMg ? data.sodiumMg + 'mg' : '--', label: '钠' },
{ value: data.potassiumMg ? data.potassiumMg + 'mg' : '--', label: '钾' },
{ value: data.phosphorusMg ? data.phosphorusMg + 'mg' : '--', label: '磷' }
]
}
}
} catch (e) {
console.error('AI营养回填失败', e)
} finally {
uni.hideLoading()
}
},
applyDefaultData() {
this.recipeData = { ...this.defaultRecipeData }
this.nutritionData = JSON.parse(JSON.stringify(this.defaultNutritionData))
if (this.recipeId) {
this.fillNutritionFromAI(this.recipeId)
}
this.mealPlan = JSON.parse(JSON.stringify(this.defaultMealPlan))
this.warningList = [...this.defaultWarningList]
},
/**
* 安全解析 JSON 字符串,失败返回 fallback
*/
safeJsonParse(str, fallback) {
if (!str || typeof str !== 'string') return fallback
try {
return JSON.parse(str)
} catch (e) {
return fallback
}
},
async loadRecipeData(id) {
this.loading = true
try {
const res = await getRecipeDetail(id)
const data = res.data || res
// 兼容后端 V2Recipe 字段名name和旧字段名title
if (data && (data.name || data.title)) {
// 基本信息映射
this.recipeData = {
coverImage: data.coverImage || data.image || this.defaultRecipeData.coverImage,
title: data.name || data.title || '',
subtitle: data.description || data.subtitle || '',
time: data.time || data.cookTime || '每日配餐',
servings: data.servings || '1人份',
rating: String(data.rating || '4.8'),
reviews: String(data.viewCount || data.reviews || data.reviewCount || '0'),
tags: this.safeJsonParse(data.tagsJson, null) || data.tags || [],
introduction: data.description || data.introduction || data.content || ''
}
// 解析营养数据:优先使用结构化字段,其次从 V2Recipe 数值字段组装
if (data.nutritionData || data.nutrition) {
const nd = data.nutritionData || data.nutrition
this.nutritionData = {
main: nd.main || this.defaultNutritionData.main,
minor: nd.minor || this.defaultNutritionData.minor
}
} else if (data.totalEnergy || data.totalProtein) {
// 从 V2Recipe 数值字段组装营养数据
this.nutritionData = {
main: [
{ value: String(data.totalEnergy || '--'), label: '热量(kcal)' },
{ value: (data.totalProtein || '--') + 'g', label: '蛋白质' },
{ value: '--', label: '脂肪' },
{ value: '--', label: '碳水' }
],
minor: [
{ value: data.totalSodium ? data.totalSodium + 'mg' : '--', label: '钠' },
{ value: data.totalPotassium ? data.totalPotassium + 'mg' : '--', label: '钾' },
{ value: data.totalPhosphorus ? data.totalPhosphorus + 'mg' : '--', label: '磷' }
]
}
} else {
this.nutritionData = JSON.parse(JSON.stringify(this.defaultNutritionData))
// T09: 若无营养数据,调用 AI 回填
this.fillNutritionFromAI(id)
}
// 解析三餐配餐:
// 1. 计算器来源食谱stepsJson 中存储了完整的 mealPlan JSON
// 2. 普通食谱:可能有 mealPlan 或 meals 字段
let mealPlanParsed = null
if (data.stepsJson) {
mealPlanParsed = this.safeJsonParse(data.stepsJson, null)
}
if (!mealPlanParsed && (data.mealPlan || data.meals)) {
mealPlanParsed = data.mealPlan || data.meals
}
if (mealPlanParsed && (mealPlanParsed.breakfast || mealPlanParsed.lunch || mealPlanParsed.dinner)) {
this.mealPlan = {
breakfast: mealPlanParsed.breakfast || [],
lunch: mealPlanParsed.lunch || [],
dinner: mealPlanParsed.dinner || []
}
} else {
this.mealPlan = JSON.parse(JSON.stringify(this.defaultMealPlan))
}
// 解析提示
this.warningList = data.warnings || data.warningList || [...this.defaultWarningList]
// 收藏状态
this.isFavorite = data.isFavorite || data.collected || false
this.isLiked = data.isLiked || data.liked || false
} else {
this.applyDefaultData()
}
} catch (error) {
console.error('加载食谱数据失败:', error)
this.applyDefaultData()
uni.showToast({
title: '数据加载失败',
icon: 'none'
})
} finally {
this.loading = false
}
},
handleShare() {
uni.showToast({
title: '分享功能开发中',
icon: 'none'
})
},
async toggleFavorite() {
const newState = !this.isFavorite
this.isFavorite = newState
try {
if (this.recipeId) {
await toggleRecipeFavorite(this.recipeId, newState)
}
uni.showToast({
title: newState ? '已收藏' : '已取消收藏',
icon: 'none'
})
} catch (error) {
// 回滚状态
this.isFavorite = !newState
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
},
async toggleLike() {
const newState = !this.isLiked
this.isLiked = newState
try {
if (this.recipeId) {
const { toggleLike } = await import('@/api/tool.js')
await toggleLike(this.recipeId, newState)
}
uni.showToast({
title: newState ? '已点赞' : '已取消点赞',
icon: 'none'
})
} catch (error) {
this.isLiked = !newState
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
},
async toggleFollow() {
const newState = !this.isFollowing
this.isFollowing = newState
try {
if (this.recipeData && this.recipeData.authorId) {
const { toggleFollow } = await import('@/api/tool.js')
await toggleFollow(this.recipeData.authorId, newState)
}
uni.showToast({
title: newState ? '已关注' : '已取消关注',
icon: 'none'
})
} catch (error) {
this.isFollowing = !newState
uni.showToast({
title: '操作失败',
icon: 'none'
})
}
},
handleCheckin() {
uni.navigateTo({
url: '/pages/tool/checkin'
})
}
}
}
</script>
<style lang="scss" scoped>
.recipe-detail-page {
min-height: 100vh;
background: linear-gradient(180deg, #f8f9fa 0%, #f4f5f7 100%);
}
/* 内容滚动区域 */
.content-scroll {
height: calc(100vh - 120rpx);
}
/* 食谱封面 */
.recipe-cover {
position: relative;
width: 100%;
height: 467rpx;
background: #f4f5f7;
}
.cover-image {
width: 100%;
height: 100%;
}
.difficulty-badge {
position: absolute;
top: 32rpx;
right: 32rpx;
background: rgba(255, 255, 255, 0.9);
border: 1rpx solid rgba(255, 255, 255, 0.8);
border-radius: 50rpx;
padding: 8rpx 24rpx;
display: flex;
align-items: center;
gap: 8rpx;
.difficulty-icon {
font-size: 28rpx;
}
text {
font-size: 28rpx;
color: #3e5481;
}
}
/* 食谱信息卡片 */
.recipe-info-card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 32rpx;
margin: 32rpx;
box-shadow: 0 8rpx 40rpx rgba(0, 0, 0, 0.06);
}
.recipe-title {
font-size: 36rpx;
color: #2e3e5c;
font-weight: 500;
margin-bottom: 16rpx;
}
.recipe-subtitle {
font-size: 28rpx;
color: #9fa5c0;
margin-bottom: 24rpx;
}
.recipe-meta {
display: flex;
gap: 32rpx;
margin-bottom: 24rpx;
}
.meta-item {
display: flex;
align-items: center;
gap: 8rpx;
.meta-icon {
font-size: 28rpx;
}
.meta-text {
font-size: 24rpx;
color: #9fa5c0;
}
}
.recipe-tags {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.tag {
background: linear-gradient(180deg, #fff5f0 0%, #ffe8dc 100%);
border: 1rpx solid rgba(255, 136, 68, 0.3);
border-radius: 12rpx;
padding: 8rpx 16rpx;
text {
font-size: 24rpx;
color: #ff7722;
font-weight: 500;
}
}
/* 营养师团队卡片 */
.team-card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 32rpx;
margin: 0 32rpx 32rpx;
display: flex;
align-items: center;
justify-content: space-between;
}
.team-info {
display: flex;
align-items: center;
gap: 32rpx;
}
.team-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
background: linear-gradient(180deg, #fff5f0 0%, #ffe8dc 100%);
border: 2rpx solid #ff7722;
display: flex;
align-items: center;
justify-content: center;
.avatar-icon {
font-size: 48rpx;
}
}
.team-details {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.team-name {
display: flex;
align-items: center;
gap: 8rpx;
text {
font-size: 28rpx;
color: #2e3e5c;
}
.verified-icon {
color: #ff7722;
font-size: 24rpx;
}
}
.team-role {
font-size: 24rpx;
color: #9fa5c0;
}
.follow-btn {
background: linear-gradient(180deg, #ff7722 0%, #ff6611 100%);
border-radius: 50rpx;
padding: 16rpx 32rpx;
box-shadow: 0 8rpx 12rpx -4rpx rgba(0, 0, 0, 0.1), 0 4rpx 6rpx -2rpx rgba(0, 0, 0, 0.1);
text {
font-size: 28rpx;
color: #ffffff;
font-weight: 500;
}
}
.follow-btn.followed {
background: linear-gradient(180deg, #9fa5c0 0%, #8a90a8 100%);
}
/* 介绍卡片 */
.intro-card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 32rpx;
margin: 0 32rpx 32rpx;
}
.card-header {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 24rpx;
.header-icon {
width: 48rpx;
height: 48rpx;
background: linear-gradient(180deg, #ff9966 0%, #ff8844 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
box-shadow: 0 8rpx 12rpx -4rpx rgba(0, 0, 0, 0.1), 0 4rpx 6rpx -2rpx rgba(0, 0, 0, 0.1);
}
.header-title {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
}
.intro-text {
font-size: 28rpx;
color: #3e5481;
line-height: 1.6;
}
/* 营养成分卡片 */
.nutrition-card {
background: linear-gradient(180deg, #fff5f0 0%, #ffe8dc 50%, #ffffff 100%);
border: 2rpx solid rgba(255, 136, 68, 0.3);
border-radius: 24rpx;
padding: 32rpx;
margin: 0 32rpx 32rpx;
box-shadow: 0 16rpx 60rpx rgba(255, 136, 68, 0.15);
}
.nutrition-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 16rpx;
margin-bottom: 24rpx;
}
.nutrition-row {
display: flex;
justify-content: space-around;
padding-top: 24rpx;
border-top: 1rpx solid rgba(255, 136, 68, 0.2);
}
.nutrition-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
background: rgba(255, 255, 255, 0.8);
border: 1rpx solid rgba(255, 136, 68, 0.2);
border-radius: 24rpx;
padding: 16rpx;
.nutrition-value {
font-size: 32rpx;
color: #2e3e5c;
font-weight: 500;
}
.nutrition-label {
font-size: 24rpx;
color: #9fa5c0;
}
}
/* 三餐配餐方案 */
.meal-plan-section {
padding: 0 32rpx 32rpx;
}
.section-title {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 32rpx;
.title-icon {
width: 56rpx;
height: 56rpx;
background: linear-gradient(180deg, #ff8c5a 0%, #ff6b35 100%);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 28rpx;
box-shadow: 0 8rpx 12rpx -4rpx rgba(0, 0, 0, 0.1), 0 4rpx 6rpx -2rpx rgba(0, 0, 0, 0.1);
}
.title-text {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
}
.meal-card {
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
margin-bottom: 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-name {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
.meal-dishes {
padding: 32rpx;
display: flex;
flex-direction: column;
gap: 24rpx;
}
.dish-item {
display: flex;
gap: 24rpx;
background: #ffffff;
border: 1rpx solid #d0dbea;
border-radius: 24rpx;
padding: 16rpx;
}
.dish-image-wrapper {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 24rpx;
overflow: hidden;
background: #f4f5f7;
flex-shrink: 0;
}
.dish-image {
width: 100%;
height: 100%;
}
.dish-number {
position: absolute;
top: 12rpx;
left: 12rpx;
width: 48rpx;
height: 48rpx;
background: linear-gradient(180deg, #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;
}
.dish-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.dish-name {
font-size: 28rpx;
color: #2e3e5c;
font-weight: 500;
}
.dish-ingredients {
display: flex;
flex-wrap: wrap;
gap: 12rpx;
}
.ingredient-tag {
background: linear-gradient(180deg, #fff8f0 0%, #fff5eb 100%);
border: 1rpx solid rgba(255, 136, 68, 0.3);
border-radius: 12rpx;
padding: 4rpx 16rpx;
text {
font-size: 24rpx;
color: #ff7722;
}
}
/* 重要提示卡片 */
.warning-card {
background: #fff3e0;
border: 1rpx solid #ffb74d;
border-radius: 24rpx;
padding: 32rpx;
margin: 0 32rpx 32rpx;
}
.warning-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.warning-item {
display: flex;
gap: 16rpx;
align-items: flex-start;
.bullet {
font-size: 28rpx;
color: #e65100;
flex-shrink: 0;
}
.warning-text {
font-size: 28rpx;
color: #f57c00;
line-height: 1.6;
flex: 1;
}
}
/* 温馨提示卡片 */
.tip-card {
background: #e3f2fd;
border: 1rpx solid #64b5f6;
border-radius: 24rpx;
padding: 32rpx;
margin: 0 32rpx 32rpx;
text-align: center;
}
.tip-text {
font-size: 28rpx;
color: #1976d2;
line-height: 1.6;
}
/* 底部操作栏 */
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #ffffff;
border-top: 1rpx solid #d0dbea;
padding: 24rpx 32rpx;
padding-bottom: calc(24rpx + env(safe-area-inset-bottom));
display: flex;
align-items: center;
gap: 16rpx;
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.06);
}
.action-btn-small {
width: 96rpx;
height: 96rpx;
border: 2rpx solid #d0dbea;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
.action-icon-small {
font-size: 40rpx;
&.active {
color: #ff6b35;
}
}
}
.checkin-btn {
flex: 1;
height: 96rpx;
background: #ff6b35;
border-radius: 50rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 20rpx 30rpx -6rpx rgba(0, 0, 0, 0.1), 0 8rpx 12rpx -4rpx rgba(0, 0, 0, 0.1);
text {
font-size: 32rpx;
color: #ffffff;
font-weight: 500;
}
}
/* 底部安全距离 */
.safe-bottom {
height: 160rpx;
}
</style>