Ver Fonte

新增卡路里分析

liuziting há 7 meses atrás
pai
commit
bdc5a88441

+ 109 - 0
src/components/NutritionAnalysis.vue

@@ -0,0 +1,109 @@
+<template>
+    <div v-if="nutritionAnalysis" class="nutrition-analysis">
+        <h4 class="text-sm font-bold text-dark-800 mb-3 flex items-center gap-1">🥗 营养分析</h4>
+
+        <div class="bg-green-50 border border-green-200 rounded-lg p-4 space-y-4">
+            <!-- 基础营养信息 -->
+            <div class="grid grid-cols-2 gap-3">
+                <div class="bg-white rounded-lg p-3 border border-green-300">
+                    <div class="text-center">
+                        <div class="text-2xl font-bold text-orange-600">{{ nutritionAnalysis.nutrition.calories }}</div>
+                        <div class="text-xs text-gray-600">卡路里</div>
+                    </div>
+                </div>
+                <div class="bg-white rounded-lg p-3 border border-green-300">
+                    <div class="text-center">
+                        <div class="text-lg font-bold text-green-600">{{ nutritionAnalysis.healthScore }}/10</div>
+                        <div class="text-xs text-gray-600">健康评分</div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 营养成分详情 -->
+            <div class="bg-white rounded-lg p-3 border border-green-300">
+                <h5 class="text-xs font-bold text-gray-700 mb-2">营养成分 ({{ nutritionAnalysis.servingSize }})</h5>
+                <div class="grid grid-cols-2 gap-2 text-xs">
+                    <div class="flex justify-between">
+                        <span class="text-gray-600">蛋白质</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.protein }}g</span>
+                    </div>
+                    <div class="flex justify-between">
+                        <span class="text-gray-600">碳水化合物</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.carbs }}g</span>
+                    </div>
+                    <div class="flex justify-between">
+                        <span class="text-gray-600">脂肪</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.fat }}g</span>
+                    </div>
+                    <div class="flex justify-between">
+                        <span class="text-gray-600">膳食纤维</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.fiber }}g</span>
+                    </div>
+                    <div class="flex justify-between">
+                        <span class="text-gray-600">钠</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.sodium }}mg</span>
+                    </div>
+                    <div class="flex justify-between">
+                        <span class="text-gray-600">糖</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.sugar }}g</span>
+                    </div>
+                    <div v-if="nutritionAnalysis.nutrition.vitaminC" class="flex justify-between">
+                        <span class="text-gray-600">维生素C</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.vitaminC }}mg</span>
+                    </div>
+                    <div v-if="nutritionAnalysis.nutrition.calcium" class="flex justify-between">
+                        <span class="text-gray-600">钙</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.calcium }}mg</span>
+                    </div>
+                    <div v-if="nutritionAnalysis.nutrition.iron" class="flex justify-between">
+                        <span class="text-gray-600">铁</span>
+                        <span class="font-medium">{{ nutritionAnalysis.nutrition.iron }}mg</span>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 饮食标签 -->
+            <div v-if="nutritionAnalysis.dietaryTags.length > 0">
+                <h5 class="text-xs font-bold text-gray-700 mb-2">饮食特点</h5>
+                <div class="flex flex-wrap gap-1">
+                    <span
+                        v-for="tag in nutritionAnalysis.dietaryTags"
+                        :key="tag"
+                        class="bg-green-200 text-green-800 px-2 py-1 rounded-full text-xs font-medium border border-green-300"
+                    >
+                        {{ tag }}
+                    </span>
+                </div>
+            </div>
+
+            <!-- 营养均衡建议 -->
+            <div v-if="nutritionAnalysis.balanceAdvice.length > 0">
+                <h5 class="text-xs font-bold text-gray-700 mb-2">营养建议</h5>
+                <div class="bg-blue-50 border border-blue-200 rounded p-3">
+                    <ul class="space-y-1">
+                        <li v-for="advice in nutritionAnalysis.balanceAdvice" :key="advice" class="flex items-start gap-2 text-blue-800">
+                            <span class="text-blue-600 mt-1 text-xs">💡</span>
+                            <span class="text-xs">{{ advice }}</span>
+                        </li>
+                    </ul>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import type { NutritionAnalysis } from '@/types'
+
+interface Props {
+    nutritionAnalysis: NutritionAnalysis | undefined
+}
+
+defineProps<Props>()
+</script>
+
+<style scoped>
+.nutrition-analysis {
+    @apply transition-all duration-300;
+}
+</style>

+ 7 - 1
src/components/RecipeCard.vue

@@ -66,7 +66,7 @@
             </div>
 
             <!-- 烹饪技巧 -->
-            <div v-if="recipe.tips && recipe.tips.length > 0 && isExpanded">
+            <div v-if="recipe.tips && recipe.tips.length > 0 && isExpanded" class="mb-4">
                 <h4 class="text-sm font-bold text-dark-800 mb-2 flex items-center gap-1">💡 烹饪技巧</h4>
                 <div class="bg-yellow-100 border-l-4 border-yellow-400 p-3 rounded-r">
                     <ul class="space-y-1">
@@ -78,6 +78,11 @@
                 </div>
             </div>
 
+            <!-- 营养分析 -->
+            <div v-if="isExpanded" class="mb-4">
+                <NutritionAnalysis :nutritionAnalysis="recipe.nutritionAnalysis" />
+            </div>
+
             <!-- 效果图区域 -->
             <div class="mt-4 pt-4 border-t border-gray-200">
                 <div class="flex items-center justify-between mb-3">
@@ -128,6 +133,7 @@
 import { computed, ref, onUnmounted } from 'vue'
 import type { Recipe } from '@/types'
 import { generateRecipeImage, type GeneratedImage } from '@/services/imageService'
+import NutritionAnalysis from './NutritionAnalysis.vue'
 
 interface Props {
     recipe: Recipe

+ 99 - 10
src/services/aiService.ts

@@ -1,5 +1,5 @@
 import axios from 'axios'
-import type { Recipe, CuisineType } from '@/types'
+import type { Recipe, CuisineType, NutritionAnalysis } from '@/types'
 
 // AI服务配置 - 智谱AI
 const AI_CONFIG = {
@@ -12,7 +12,7 @@ const AI_CONFIG = {
     apiKey: 'sk-78d4fed678fa4a5ebc5f7beac54b1a78',
     model: 'deepseek-chat',
     temperature: 0.7,
-    timeout: 30000
+    timeout: 300000
 }
 
 // 创建axios实例
@@ -48,7 +48,7 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
 
         prompt += `
 
-请按照以下JSON格式返回菜谱:
+请按照以下JSON格式返回菜谱,包含营养分析
 {
   "name": "菜品名称",
   "ingredients": ["食材1", "食材2"],
@@ -62,7 +62,25 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
   ],
   "cookingTime": 30,
   "difficulty": "medium",
-  "tips": ["技巧1", "技巧2"]
+  "tips": ["技巧1", "技巧2"],
+  "nutritionAnalysis": {
+    "nutrition": {
+      "calories": 350,
+      "protein": 25,
+      "carbs": 45,
+      "fat": 12,
+      "fiber": 8,
+      "sodium": 800,
+      "sugar": 6,
+      "vitaminC": 30,
+      "calcium": 150,
+      "iron": 3
+    },
+    "healthScore": 8,
+    "balanceAdvice": ["建议搭配蔬菜沙拉增加维生素", "可适量减少盐分"],
+    "dietaryTags": ["高蛋白", "低脂"],
+    "servingSize": "1人份"
+  }
 }`
 
         // 调用智谱AI接口
@@ -108,7 +126,8 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
             ],
             cookingTime: recipeData.cookingTime || 25,
             difficulty: recipeData.difficulty || 'medium',
-            tips: recipeData.tips || ['注意火候控制', '调味要适中']
+            tips: recipeData.tips || ['注意火候控制', '调味要适中'],
+            nutritionAnalysis: recipeData.nutritionAnalysis || generateFallbackNutrition(ingredients)
         }
 
         return recipe
@@ -130,7 +149,8 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
             ],
             cookingTime: 22,
             difficulty: 'medium',
-            tips: ['火候要掌握好,避免炒糊', '调料要适量,突出食材本味', '炒制过程中要勤翻动']
+            tips: ['火候要掌握好,避免炒糊', '调料要适量,突出食材本味', '炒制过程中要勤翻动'],
+            nutritionAnalysis: generateFallbackNutrition(ingredients)
         }
 
         return fallbackRecipe
@@ -184,7 +204,7 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
 
 用户的特殊要求:${customPrompt}
 
-请按照以下JSON格式返回菜谱:
+请按照以下JSON格式返回菜谱,包含营养分析
 {
   "name": "菜品名称",
   "ingredients": ["食材1", "食材2"],
@@ -198,7 +218,25 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
   ],
   "cookingTime": 30,
   "difficulty": "medium",
-  "tips": ["技巧1", "技巧2"]
+  "tips": ["技巧1", "技巧2"],
+  "nutritionAnalysis": {
+    "nutrition": {
+      "calories": 350,
+      "protein": 25,
+      "carbs": 45,
+      "fat": 12,
+      "fiber": 8,
+      "sodium": 800,
+      "sugar": 6,
+      "vitaminC": 30,
+      "calcium": 150,
+      "iron": 3
+    },
+    "healthScore": 8,
+    "balanceAdvice": ["建议搭配蔬菜沙拉增加维生素", "可适量减少盐分"],
+    "dietaryTags": ["高蛋白", "低脂"],
+    "servingSize": "1人份"
+  }
 }`
 
         // 调用智谱AI接口
@@ -244,7 +282,8 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
             ],
             cookingTime: recipeData.cookingTime || 25,
             difficulty: recipeData.difficulty || 'medium',
-            tips: recipeData.tips || ['根据个人口味调整', '注意火候控制']
+            tips: recipeData.tips || ['根据个人口味调整', '注意火候控制'],
+            nutritionAnalysis: recipeData.nutritionAnalysis || generateFallbackNutrition(ingredients)
         }
 
         return recipe
@@ -265,12 +304,62 @@ export const generateCustomRecipe = async (ingredients: string[], customPrompt:
             ],
             cookingTime: 25,
             difficulty: 'medium',
-            tips: ['根据个人喜好调整口味', '注意食材的新鲜度', '掌握好火候']
+            tips: ['根据个人喜好调整口味', '注意食材的新鲜度', '掌握好火候'],
+            nutritionAnalysis: generateFallbackNutrition(ingredients)
         }
 
         return fallbackRecipe
     }
 }
 
+/**
+ * 生成后备营养分析数据
+ * @param ingredients 食材列表
+ * @returns NutritionAnalysis
+ */
+const generateFallbackNutrition = (ingredients: string[]): NutritionAnalysis => {
+    // 基于食材数量和类型估算营养成分
+    const baseCalories = ingredients.length * 50 + Math.floor(Math.random() * 100) + 200
+    const hasVegetables = ingredients.some(ing => ['菜', '瓜', '豆', '萝卜', '白菜', '菠菜', '西红柿', '黄瓜', '茄子', '土豆'].some(veg => ing.includes(veg)))
+    const hasMeat = ingredients.some(ing => ['肉', '鸡', '鱼', '虾', '蛋', '牛', '猪', '羊'].some(meat => ing.includes(meat)))
+    const hasGrains = ingredients.some(ing => ['米', '面', '粉', '饭', '面条', '馒头'].some(grain => ing.includes(grain)))
+
+    // 生成饮食标签
+    const dietaryTags: string[] = []
+    if (hasVegetables && !hasMeat) dietaryTags.push('素食')
+    if (hasMeat) dietaryTags.push('高蛋白')
+    if (hasVegetables) dietaryTags.push('富含维生素')
+    if (!hasGrains) dietaryTags.push('低碳水')
+    if (baseCalories < 300) dietaryTags.push('低卡路里')
+
+    // 生成营养建议
+    const balanceAdvice: string[] = []
+    if (!hasVegetables) balanceAdvice.push('建议搭配新鲜蔬菜增加维生素和膳食纤维')
+    if (!hasMeat && !ingredients.some(ing => ['豆', '蛋', '奶'].some(protein => ing.includes(protein)))) {
+        balanceAdvice.push('建议增加蛋白质来源,如豆类或蛋类')
+    }
+    if (hasGrains && hasMeat) balanceAdvice.push('营养搭配均衡,适合日常食用')
+    if (ingredients.length > 5) balanceAdvice.push('食材丰富,营养全面')
+
+    return {
+        nutrition: {
+            calories: baseCalories,
+            protein: hasMeat ? 20 + Math.floor(Math.random() * 15) : 8 + Math.floor(Math.random() * 8),
+            carbs: hasGrains ? 35 + Math.floor(Math.random() * 20) : 15 + Math.floor(Math.random() * 10),
+            fat: hasMeat ? 12 + Math.floor(Math.random() * 8) : 5 + Math.floor(Math.random() * 5),
+            fiber: hasVegetables ? 6 + Math.floor(Math.random() * 4) : 2 + Math.floor(Math.random() * 2),
+            sodium: 600 + Math.floor(Math.random() * 400),
+            sugar: 3 + Math.floor(Math.random() * 5),
+            vitaminC: hasVegetables ? 20 + Math.floor(Math.random() * 30) : undefined,
+            calcium: hasMeat || ingredients.some(ing => ['奶', '豆'].some(ca => ing.includes(ca))) ? 100 + Math.floor(Math.random() * 100) : undefined,
+            iron: hasMeat ? 2 + Math.floor(Math.random() * 3) : undefined
+        },
+        healthScore: Math.floor(Math.random() * 3) + (hasVegetables ? 6 : 4) + (hasMeat ? 1 : 0),
+        balanceAdvice: balanceAdvice.length > 0 ? balanceAdvice : ['营养搭配合理,可以放心享用'],
+        dietaryTags: dietaryTags.length > 0 ? dietaryTags : ['家常菜'],
+        servingSize: '1人份'
+    }
+}
+
 // 导出配置更新函数,供外部使用
 export { AI_CONFIG }

+ 24 - 0
src/types/index.ts

@@ -25,6 +25,7 @@ export interface Recipe {
     cookingTime: number
     difficulty: 'easy' | 'medium' | 'hard'
     tips: string[]
+    nutritionAnalysis?: NutritionAnalysis // 营养分析
 }
 
 // 制作步骤
@@ -36,6 +37,29 @@ export interface RecipeStep {
     image?: string
 }
 
+// 营养信息
+export interface NutritionInfo {
+    calories: number // 卡路里
+    protein: number // 蛋白质 (g)
+    carbs: number // 碳水化合物 (g)
+    fat: number // 脂肪 (g)
+    fiber: number // 膳食纤维 (g)
+    sodium: number // 钠 (mg)
+    sugar: number // 糖 (g)
+    vitaminC?: number // 维生素C (mg)
+    calcium?: number // 钙 (mg)
+    iron?: number // 铁 (mg)
+}
+
+// 营养分析
+export interface NutritionAnalysis {
+    nutrition: NutritionInfo
+    healthScore: number // 健康评分 (1-10)
+    balanceAdvice: string[] // 营养均衡建议
+    dietaryTags: string[] // 饮食标签,如"低脂"、"高蛋白"等
+    servingSize: string // 建议份量
+}
+
 // AI响应类型
 export interface AIResponse {
     success: boolean

+ 54 - 7
src/views/Home.vue

@@ -192,7 +192,7 @@ import { ref, onUnmounted } from 'vue'
 import { cuisines } from '@/config/cuisines'
 import RecipeCard from '@/components/RecipeCard.vue'
 import { generateMultipleRecipes, generateCustomRecipe } from '@/services/aiService'
-import type { Recipe, CuisineType } from '@/types'
+import type { Recipe, CuisineType, NutritionAnalysis } from '@/types'
 
 // 响应式数据
 const ingredients = ref<string[]>([])
@@ -279,9 +279,9 @@ const generateRecipes = async () => {
             let selectedCuisineObjects = cuisines.filter(c => selectedCuisines.value.includes(c.id))
 
             if (selectedCuisineObjects.length === 0) {
-                // 随机选择2-3个菜系
+                // 随机选择2个菜系
                 const shuffled = [...cuisines].sort(() => 0.5 - Math.random())
-                selectedCuisineObjects = shuffled.slice(0, Math.floor(Math.random() * 2) + 2)
+                selectedCuisineObjects = shuffled.slice(0, 2)
             }
 
             // 调用AI服务生成菜谱
@@ -309,9 +309,9 @@ const simulateAICall = async () => {
             // 获取要使用的菜系
             let cuisinesToUse = cuisines.filter(c => selectedCuisines.value.includes(c.id))
             if (cuisinesToUse.length === 0) {
-                // 随机选择2-3个菜系
+                // 随机选择2个菜系
                 const shuffled = [...cuisines].sort(() => 0.5 - Math.random())
-                cuisinesToUse = shuffled.slice(0, Math.floor(Math.random() * 2) + 2)
+                cuisinesToUse = shuffled.slice(0, 2)
             }
 
             // 检查是否有自定义提示词
@@ -333,7 +333,8 @@ const simulateAICall = async () => {
                         ],
                         cookingTime: 25,
                         difficulty: 'medium',
-                        tips: ['根据个人喜好调整口味', '注意食材的新鲜度', '掌握好火候']
+                        tips: ['根据个人喜好调整口味', '注意食材的新鲜度', '掌握好火候'],
+                        nutritionAnalysis: generateMockNutrition(ingredients.value)
                     }
                 ]
             } else {
@@ -353,7 +354,8 @@ const simulateAICall = async () => {
                         ],
                         cookingTime: 22,
                         difficulty: 'medium',
-                        tips: ['火候要掌握好,避免炒糊', '调料要适量,突出食材本味', '炒制过程中要勤翻动']
+                        tips: ['火候要掌握好,避免炒糊', '调料要适量,突出食材本味', '炒制过程中要勤翻动'],
+                        nutritionAnalysis: generateMockNutrition(ingredients.value)
                     }
                 })
             }
@@ -364,6 +366,51 @@ const simulateAICall = async () => {
     })
 }
 
+// 生成模拟营养分析数据
+const generateMockNutrition = (ingredients: string[]): NutritionAnalysis => {
+    // 基于食材数量和类型估算营养成分
+    const baseCalories = ingredients.length * 50 + Math.floor(Math.random() * 100) + 200
+    const hasVegetables = ingredients.some(ing => ['菜', '瓜', '豆', '萝卜', '白菜', '菠菜', '西红柿', '黄瓜', '茄子', '土豆'].some(veg => ing.includes(veg)))
+    const hasMeat = ingredients.some(ing => ['肉', '鸡', '鱼', '虾', '蛋', '牛', '猪', '羊'].some(meat => ing.includes(meat)))
+    const hasGrains = ingredients.some(ing => ['米', '面', '粉', '饭', '面条', '馒头'].some(grain => ing.includes(grain)))
+
+    // 生成饮食标签
+    const dietaryTags: string[] = []
+    if (hasVegetables && !hasMeat) dietaryTags.push('素食')
+    if (hasMeat) dietaryTags.push('高蛋白')
+    if (hasVegetables) dietaryTags.push('富含维生素')
+    if (!hasGrains) dietaryTags.push('低碳水')
+    if (baseCalories < 300) dietaryTags.push('低卡路里')
+
+    // 生成营养建议
+    const balanceAdvice: string[] = []
+    if (!hasVegetables) balanceAdvice.push('建议搭配新鲜蔬菜增加维生素和膳食纤维')
+    if (!hasMeat && !ingredients.some(ing => ['豆', '蛋', '奶'].some(protein => ing.includes(protein)))) {
+        balanceAdvice.push('建议增加蛋白质来源,如豆类或蛋类')
+    }
+    if (hasGrains && hasMeat) balanceAdvice.push('营养搭配均衡,适合日常食用')
+    if (ingredients.length > 5) balanceAdvice.push('食材丰富,营养全面')
+
+    return {
+        nutrition: {
+            calories: baseCalories,
+            protein: hasMeat ? 20 + Math.floor(Math.random() * 15) : 8 + Math.floor(Math.random() * 8),
+            carbs: hasGrains ? 35 + Math.floor(Math.random() * 20) : 15 + Math.floor(Math.random() * 10),
+            fat: hasMeat ? 12 + Math.floor(Math.random() * 8) : 5 + Math.floor(Math.random() * 5),
+            fiber: hasVegetables ? 6 + Math.floor(Math.random() * 4) : 2 + Math.floor(Math.random() * 2),
+            sodium: 600 + Math.floor(Math.random() * 400),
+            sugar: 3 + Math.floor(Math.random() * 5),
+            vitaminC: hasVegetables ? 20 + Math.floor(Math.random() * 30) : undefined,
+            calcium: hasMeat || ingredients.some(ing => ['奶', '豆'].some(ca => ing.includes(ca))) ? 100 + Math.floor(Math.random() * 100) : undefined,
+            iron: hasMeat ? 2 + Math.floor(Math.random() * 3) : undefined
+        },
+        healthScore: Math.floor(Math.random() * 3) + (hasVegetables ? 6 : 4) + (hasMeat ? 1 : 0),
+        balanceAdvice: balanceAdvice.length > 0 ? balanceAdvice : ['营养搭配合理,可以放心享用'],
+        dietaryTags: dietaryTags.length > 0 ? dietaryTags : ['家常菜'],
+        servingSize: '1人份'
+    }
+}
+
 onUnmounted(() => {
     if (loadingInterval) {
         clearInterval(loadingInterval)