| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249 |
- <template>
- <view class="recipe-container">
- <view class="nav-bar">
- <text class="nav-back" @click="goBack">‹ 返回</text>
- <text class="nav-title">食谱推荐</text>
- <view class="nav-placeholder"></view>
- </view>
- <view class="meal-tabs">
- <text class="tab-item" v-for="mt in mealTypes" :key="mt.value"
- :class="{ 'tab-active': selectedMeal === mt.value }"
- @click="selectMeal(mt.value)">
- <text class="tab-icon">{{ mt.icon }}</text>
- <text class="tab-label">{{ mt.label }}</text>
- </text>
- </view>
- <view class="section-card">
- <view class="card-header">
- <text class="card-title">🥗 AI 智能食谱</text>
- <text class="card-subtitle">基于健康数据个性化推荐</text>
- </view>
- <view class="recommend-btn" :class="{ 'recommend-btn-loading': loading }" @click="doRecommend">
- <text class="recommend-btn-text">{{ loading ? 'AI思考中...' : '生成今日食谱' }}</text>
- <text class="recommend-btn-icon">✨</text>
- </view>
- </view>
- <view class="section-card" v-if="recipeResult">
- <view class="card-header">
- <text class="card-title">📋 推荐食谱</text>
- </view>
- <view class="daily-meals">
- <view class="meal-block" v-for="(meal, idx) in parsedMeals" :key="idx">
- <view class="meal-block-header">
- <text class="meal-block-icon">{{ mealTypeIcon(meal.mealType) }}</text>
- <text class="meal-block-title">{{ meal.mealType || meal.recipeName }}</text>
- </view>
- <text class="meal-reason" v-if="meal.reason">{{ meal.reason }}</text>
- <view class="meal-ingredients" v-if="meal.ingredients">
- <text class="ingredient-label">食材:</text>
- <text class="ingredient-text">{{ meal.ingredients }}</text>
- </view>
- <text class="meal-tips" v-if="meal.tips">💡 {{ meal.tips }}</text>
- </view>
- </view>
- <view class="nutrition-tip" v-if="parsedNutritionTip">
- <text class="tip-label">营养建议</text>
- <text class="tip-text">{{ parsedNutritionTip }}</text>
- </view>
- </view>
- <view class="section-card" v-if="recipeResult">
- <view class="card-header">
- <text class="card-title">🔄 食材替换建议</text>
- <text class="card-subtitle">一键替换推荐食材</text>
- </view>
- <view class="replace-options">
- <view class="replace-item" v-for="(food, idx) in replaceOptions" :key="idx" @click="replaceFood(food.id)">
- <text class="replace-name">{{ food.name }}</text>
- <text class="replace-score" v-if="food.score">{{ food.score }}分</text>
- <text class="replace-icon">↻</text>
- </view>
- <view class="replace-empty" v-if="!replaceOptions || replaceOptions.length === 0">
- <text>暂无替换建议</text>
- </view>
- </view>
- </view>
- <view class="section-card" v-if="recipeResult">
- <view class="card-header">
- <text class="card-title">📊 营养摄取概要</text>
- </view>
- <view class="nutrition-summary">
- <view class="nutri-stat" v-for="(val, key) in nutritionSummary" :key="key">
- <text class="nutri-value">{{ val }}</text>
- <text class="nutri-label">{{ key }}</text>
- </view>
- <view class="nutri-empty" v-if="!nutritionSummary || Object.keys(nutritionSummary).length === 0">
- <text>暂无数据</text>
- </view>
- </view>
- </view>
- <view class="bottom-spacer"></view>
- </view>
- </template>
- <script>
- import config from '@/config.js'
- export default {
- data() {
- return {
- childId: null,
- selectedMeal: 'daily',
- loading: false,
- recipeResult: null,
- replaceOptions: [],
- nutritionSummary: null,
- mealTypes: [
- { label: '每日', value: 'daily', icon: '📅' },
- { label: '早餐', value: 'breakfast', icon: '🌅' },
- { label: '午餐', value: 'lunch', icon: '☀️' },
- { label: '晚餐', value: 'dinner', icon: '🌙' },
- { label: '加餐', value: 'snack', icon: '🍪' }
- ]
- }
- },
- computed: {
- parsedMeals: function() {
- if (!this.recipeResult || !this.recipeResult.answer) return []
- try {
- var parsed = JSON.parse(this.recipeResult.answer)
- if (parsed.dailyMeals) return parsed.dailyMeals
- if (parsed.meals) return parsed.meals
- return [{ recipeName: 'AI推荐', reason: this.recipeResult.answer.substring(0, 100) + '...' }]
- } catch (e) {
- return [{ recipeName: 'AI推荐方案', reason: this.recipeResult.answer }]
- }
- },
- parsedNutritionTip: function() {
- if (!this.recipeResult || !this.recipeResult.answer) return ''
- try {
- var parsed = JSON.parse(this.recipeResult.answer)
- return parsed.nutritionTip || parsed.tips || ''
- } catch (e) {
- return ''
- }
- }
- },
- onLoad: function(options) {
- this.childId = options.childId ? parseInt(options.childId) : null
- },
- methods: {
- selectMeal: function(value) {
- this.selectedMeal = value
- },
- doRecommend: function() {
- this.loading = true
- var self = this
- healthRequest('/api/meal/recommend', {
- mealType: this.selectedMeal !== 'daily' ? this.selectedMeal : null,
- childId: this.childId
- }).then(function(res) {
- self.loading = false
- if (res.data) {
- self.recipeResult = res.data
- self.replaceOptions = res.data.replaceOptions || []
- self.nutritionSummary = res.data.nutritionSummary || null
- }
- }).catch(function(e) {
- self.loading = false
- var mockData = {
- answer: JSON.stringify({
- dailyMeals: [
- { mealType: '早餐', recipeName: '全麦三明治+牛奶', reason: '搭配优质碳水+蛋白质', ingredients: '全麦面包2片、鸡蛋1个、生菜2片、牛奶200ml', tips: '全麦面包提供膳食纤维,牛奶补充钙质' },
- { mealType: '午餐', recipeName: '清蒸鱼+杂粮饭', reason: '优质蛋白+复合碳水', ingredients: '鲈鱼150g、西兰花100g、杂粮米50g', tips: '鱼类富含DHA,有助于大脑发育' },
- { mealType: '晚餐', recipeName: '番茄鸡肉意面', reason: '均衡营养易消化', ingredients: '鸡胸肉100g、番茄1个、意面80g', tips: '晚餐适量减少碳水量' }
- ],
- nutritionTip: '建议每日摄入:蛋白质50-60g,碳水150-200g,蔬果500g以上'
- }),
- conversationId: '',
- replaceOptions: [{ id: 1, name: '鸡胸肉', score: 85 }, { id: 2, name: '三文鱼', score: 90 }, { id: 3, name: '豆腐', score: 75 }],
- nutritionSummary: { 蛋白质: '55g', 碳水: '180g', 脂肪: '40g', 膳食纤维: '15g', 热量: '1450kcal' }
- }
- self.recipeResult = mockData
- self.replaceOptions = mockData.replaceOptions
- self.nutritionSummary = mockData.nutritionSummary
- })
- },
- replaceFood: function(foodId) {
- var self = this
- healthRequest('/api/meal/replace', { foodId: foodId, childId: this.childId }).then(function(res) {
- if (res.data) {
- uni.showToast({ title: '已替换:' + (res.data.name || ''), icon: 'success' })
- }
- }).catch(function() {
- uni.showToast({ title: '替换失败', icon: 'none' })
- })
- },
- mealTypeIcon: function(type) {
- var map = { '早餐': '🌅', '午餐': '☀️', '晚餐': '🌙', '加餐': '🍪', 'breakfast': '🌅', 'lunch': '☀️', 'dinner': '🌙', 'snack': '🍪' }
- return map[type] || '🍽️'
- },
- goBack: function() { uni.navigateBack() }
- }
- }
- function healthRequest(url, data) {
- var token = uni.getStorageSync('token')
- return new Promise(function(resolve, reject) {
- uni.request({
- url: config.API_BASE_URL + url,
- method: 'POST',
- data: data,
- header: { 'Content-Type': 'application/json', 'Authorization': token ? 'Bearer ' + token : '' },
- success: function(res) { if (res.data.code === 200) resolve(res.data); else reject(res.data) },
- fail: function(err) { reject(err) }
- })
- })
- }
- </script>
- <style scoped>
- .recipe-container { min-height: 100vh; background: #f5f7fa; padding-bottom: 60rpx; }
- .nav-bar { position: sticky; top: 0; background: #fff; display: flex; flex-direction: row; align-items: center; justify-content: space-between; padding: 20rpx 30rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.06); z-index: 100; }
- .nav-back { font-size: 32rpx; color: #5B9BD5; }
- .nav-title { font-size: 32rpx; font-weight: bold; color: #333; }
- .nav-placeholder { width: 80rpx; }
- .meal-tabs { display: flex; flex-direction: row; margin: 16rpx 30rpx; }
- .tab-item { display: flex; flex-direction: column; align-items: center; padding: 12rpx 20rpx; border-radius: 16rpx; background: #fff; margin-right: 12rpx; box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04); }
- .tab-active { background: #FFF3E0; border: 2rpx solid #FF8C42; }
- .tab-icon { font-size: 28rpx; }
- .tab-label { font-size: 22rpx; color: #666; margin-top: 4rpx; }
- .section-card { margin: 16rpx 30rpx; background: #fff; border-radius: 20rpx; padding: 24rpx; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.06); }
- .card-header { margin-bottom: 16rpx; }
- .card-title { font-size: 30rpx; font-weight: bold; color: #333; }
- .card-subtitle { font-size: 24rpx; color: #999; margin-top: 4rpx; }
- .recommend-btn { display: flex; flex-direction: row; align-items: center; justify-content: center; background: linear-gradient(135deg, #FF8C42, #FFA726); color: #fff; padding: 24rpx; border-radius: 40rpx; }
- .recommend-btn-loading { opacity: 0.7; }
- .recommend-btn-text { font-size: 32rpx; font-weight: bold; margin-right: 12rpx; }
- .recommend-btn-icon { font-size: 36rpx; }
- .daily-meals { display: flex; flex-direction: column; }
- .meal-block { background: #FFFBEB; border-radius: 16rpx; padding: 16rpx; margin-bottom: 12rpx; }
- .meal-block-header { display: flex; flex-direction: row; align-items: center; margin-bottom: 8rpx; }
- .meal-block-icon { font-size: 32rpx; margin-right: 8rpx; }
- .meal-block-title { font-size: 28rpx; font-weight: 600; color: #333; }
- .meal-reason { font-size: 24rpx; color: #666; margin-bottom: 8rpx; }
- .meal-ingredients { display: flex; flex-direction: row; flex-wrap: wrap; margin-bottom: 6rpx; }
- .ingredient-label { font-size: 24rpx; color: #999; }
- .ingredient-text { font-size: 24rpx; color: #333; flex: 1; }
- .meal-tips { font-size: 24rpx; color: #E65100; }
- .nutrition-tip { background: #E8F5E9; border-radius: 12rpx; padding: 16rpx; margin-top: 12rpx; }
- .tip-label { font-size: 24rpx; font-weight: bold; color: #2E7D32; display: block; margin-bottom: 4rpx; }
- .tip-text { font-size: 24rpx; color: #333; }
- .replace-options { display: flex; flex-direction: column; }
- .replace-item { display: flex; flex-direction: row; align-items: center; padding: 14rpx 0; border-bottom: 1rpx solid #f5f5f5; }
- .replace-name { flex: 1; font-size: 28rpx; color: #333; }
- .replace-score { font-size: 24rpx; color: #4CAF50; margin-right: 16rpx; }
- .replace-icon { font-size: 28rpx; color: #5B9BD5; }
- .replace-empty { text-align: center; padding: 20rpx; color: #999; }
- .nutrition-summary { display: flex; flex-direction: row; flex-wrap: wrap; }
- .nutri-stat { display: flex; flex-direction: column; align-items: center; min-width: 25%; margin-bottom: 12rpx; }
- .nutri-value { font-size: 32rpx; font-weight: bold; color: #FF8C42; }
- .nutri-label { font-size: 22rpx; color: #666; }
- .nutri-empty { text-align: center; padding: 20rpx; color: #999; }
- .bottom-spacer { height: 60rpx; }
- </style>
|