- 食谱详情页: 修复 applyDefaultData 中未定义变量 id 的问题 - 帖子详情页: 优化 toggleFollow 方法,提前校验 author.id,兼容多种后端字段 - 为帖子详情页已关注状态添加灰色样式
1058 lines
26 KiB
Vue
1058 lines
26 KiB
Vue
<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>
|