Files
msh-system/msh_single_uniapp/pages/tool/recipe-detail.vue

1054 lines
25 KiB
Vue
Raw Normal View History

<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" @click="toggleFollow">
<text>+ 关注</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))
// T09: 若无营养数据,调用 AI 回填
this.fillNutritionFromAI(id)
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;
}
}
/* 介绍卡片 */
.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>