2026-02-28 05:40:21 +08:00
|
|
|
|
<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>
|
2026-03-09 18:56:53 +08:00
|
|
|
|
<view class="follow-btn" :class="{ followed: isFollowing }" @click="toggleFollow">
|
|
|
|
|
|
<text>{{ isFollowing ? '已关注' : '+ 关注' }}</text>
|
2026-02-28 05:40:21 +08:00
|
|
|
|
</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">
|
2026-03-05 09:35:00 +08:00
|
|
|
|
<text class="action-icon-small" :class="{ active: isLiked }">{{ isLiked ? '❤️' : '🤍' }}</text>
|
2026-02-28 05:40:21 +08:00
|
|
|
|
</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>
|
2026-03-05 09:35:00 +08:00
|
|
|
|
import { getRecipeDetail, toggleRecipeFavorite, fillRecipeNutrition } from '@/api/tool.js';
|
2026-02-28 05:40:21 +08:00
|
|
|
|
|
|
|
|
|
|
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: {
|
2026-03-05 09:35:00 +08:00
|
|
|
|
// 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()
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
|
2026-02-28 05:40:21 +08:00
|
|
|
|
applyDefaultData() {
|
|
|
|
|
|
this.recipeData = { ...this.defaultRecipeData }
|
|
|
|
|
|
this.nutritionData = JSON.parse(JSON.stringify(this.defaultNutritionData))
|
2026-03-09 18:56:53 +08:00
|
|
|
|
if (this.recipeId) {
|
|
|
|
|
|
this.fillNutritionFromAI(this.recipeId)
|
|
|
|
|
|
}
|
2026-02-28 05:40:21 +08:00
|
|
|
|
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))
|
2026-03-05 09:35:00 +08:00
|
|
|
|
|
|
|
|
|
|
// T09: 若无营养数据,调用 AI 回填
|
|
|
|
|
|
this.fillNutritionFromAI(id)
|
2026-02-28 05:40:21 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 解析三餐配餐:
|
|
|
|
|
|
// 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;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-03-09 18:56:53 +08:00
|
|
|
|
.follow-btn.followed {
|
|
|
|
|
|
background: linear-gradient(180deg, #9fa5c0 0%, #8a90a8 100%);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-28 05:40:21 +08:00
|
|
|
|
/* 介绍卡片 */
|
|
|
|
|
|
.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>
|