Browse Source

优化界面

liuziting 7 months ago
parent
commit
c198bb0fa6
5 changed files with 467 additions and 228 deletions
  1. 59 60
      src/components/RecipeCard.vue
  2. 119 5
      src/services/aiService.ts
  3. 1 1
      src/style.css
  4. 250 140
      src/views/Home.vue
  5. 38 22
      tailwind.config.js

+ 59 - 60
src/components/RecipeCard.vue

@@ -1,82 +1,81 @@
 <template>
-    <div class="recipe-card p-6">
+    <div class="recipe-card bg-white">
         <!-- 菜谱头部 -->
-        <div class="mb-5">
-            <div class="flex items-start justify-between mb-3">
+        <div class="bg-pink-400 text-white p-4 border-b-2 border-black">
+            <div class="flex items-center justify-between">
                 <div class="flex-1">
-                    <h3 class="text-2xl font-bold text-neutral-800 mb-3 line-clamp-2">{{ recipe.name }}</h3>
-                    <div class="flex items-center gap-3 text-sm text-neutral-600 flex-wrap">
-                        <span class="flex items-center gap-1 bg-accent-100 px-3 py-1 rounded-full text-sm"> 👨‍🍳 {{ recipe.cuisine }} </span>
-                        <span class="flex items-center gap-1"> ⏱️ {{ recipe.cookingTime }}分钟 </span>
-                        <span class="flex items-center gap-1"> 📊 {{ difficultyText }} </span>
+                    <h3 class="text-lg font-bold mb-1 line-clamp-2">{{ recipe.name }}</h3>
+                    <div class="flex items-center gap-3 text-sm">
+                        <span class="bg-white/20 px-2 py-1 rounded text-xs"> 👨‍🍳 {{ recipe.cuisine }} </span>
+                        <span>⏱️ {{ recipe.cookingTime }}分钟</span>
+                        <span>📊 {{ difficultyText }}</span>
                     </div>
                 </div>
-                <div class="text-4xl ml-3">🍽️</div>
+                <div class="text-2xl ml-2">🍽️</div>
             </div>
         </div>
 
-        <!-- 食材列表 -->
-        <div class="mb-5">
-            <h4 class="text-lg font-semibold text-neutral-800 mb-3 flex items-center gap-2">🥬 所需食材</h4>
-            <div class="flex flex-wrap gap-2">
-                <span v-for="ingredient in recipe.ingredients" :key="ingredient" class="bg-accent-100 text-accent-800 px-3 py-2 rounded-full text-sm font-medium">
-                    {{ ingredient }}
-                </span>
+        <div class="p-4">
+            <!-- 食材列表 -->
+            <div class="mb-4">
+                <h4 class="text-sm font-bold text-dark-800 mb-2 flex items-center gap-1">🥬 所需食材</h4>
+                <div class="flex flex-wrap gap-1">
+                    <span v-for="ingredient in recipe.ingredients" :key="ingredient" class="bg-yellow-400 text-dark-800 px-2 py-1 rounded text-xs font-medium border border-black">
+                        {{ ingredient }}
+                    </span>
+                </div>
             </div>
-        </div>
 
-        <!-- 制作步骤预览 -->
-        <div class="mb-5">
-            <div class="flex items-center justify-between mb-3">
-                <h4 class="text-lg font-semibold text-neutral-800 flex items-center gap-2">📝 制作步骤</h4>
-                <button
-                    @click="toggleExpanded"
-                    class="text-accent-600 hover:text-accent-700 text-sm font-medium transition-colors px-3 py-1 rounded-full bg-accent-50 hover:bg-accent-100"
-                >
-                    {{ isExpanded ? '收起' : '展开' }}
-                </button>
-            </div>
+            <!-- 制作步骤预览 -->
+            <div class="mb-4">
+                <div class="flex items-center justify-between mb-2">
+                    <h4 class="text-sm font-bold text-dark-800 flex items-center gap-1">📝 制作步骤</h4>
+                    <button @click="toggleExpanded" class="bg-gray-100 hover:bg-gray-200 text-dark-800 text-xs px-2 py-1 rounded border border-black transition-colors">
+                        {{ isExpanded ? '收起' : '展开' }}
+                    </button>
+                </div>
 
-            <!-- 简化步骤预览 -->
-            <div v-if="!isExpanded" class="space-y-3">
-                <div v-for="step in recipe.steps.slice(0, 3)" :key="step.step" class="flex gap-3 p-3 bg-neutral-50 rounded-lg">
-                    <div class="flex-shrink-0 w-7 h-7 bg-accent-500 text-white rounded-full flex items-center justify-center font-bold text-sm">
-                        {{ step.step }}
+                <!-- 简化步骤预览 -->
+                <div v-if="!isExpanded" class="space-y-2">
+                    <div v-for="step in recipe.steps.slice(0, 3)" :key="step.step" class="flex gap-2 p-2 bg-gray-50 rounded border border-gray-200">
+                        <div class="flex-shrink-0 w-5 h-5 bg-dark-800 text-white rounded flex items-center justify-center font-bold text-xs">
+                            {{ step.step }}
+                        </div>
+                        <p class="text-dark-700 text-xs line-clamp-2">{{ step.description }}</p>
+                    </div>
+                    <div v-if="recipe.steps.length > 3" class="text-center py-1">
+                        <span class="text-gray-500 text-xs">还有 {{ recipe.steps.length - 3 }} 个步骤...</span>
                     </div>
-                    <p class="text-neutral-700 text-sm line-clamp-2 leading-relaxed">{{ step.description }}</p>
-                </div>
-                <div v-if="recipe.steps.length > 3" class="text-center py-2">
-                    <span class="text-neutral-500 text-sm">还有 {{ recipe.steps.length - 3 }} 个步骤...</span>
                 </div>
-            </div>
 
-            <!-- 完整步骤 -->
-            <div v-else class="space-y-3">
-                <div v-for="step in recipe.steps" :key="step.step" class="flex gap-3 p-3 bg-gradient-to-r from-neutral-50 to-primary-50 rounded-lg border border-neutral-200">
-                    <div class="flex-shrink-0 w-8 h-8 bg-gradient-to-r from-accent-500 to-accent-600 text-white rounded-full flex items-center justify-center font-bold text-sm">
-                        {{ step.step }}
-                    </div>
-                    <div class="flex-1">
-                        <p class="text-neutral-800 mb-1 text-sm leading-relaxed">{{ step.description }}</p>
-                        <div v-if="step.time || step.temperature" class="flex gap-2 text-xs text-neutral-600">
-                            <span v-if="step.time" class="flex items-center gap-1 bg-white px-2 py-1 rounded"> ⏱️ {{ step.time }}分钟 </span>
-                            <span v-if="step.temperature" class="flex items-center gap-1 bg-white px-2 py-1 rounded"> 🌡️ {{ step.temperature }} </span>
+                <!-- 完整步骤 -->
+                <div v-else class="space-y-2">
+                    <div v-for="step in recipe.steps" :key="step.step" class="flex gap-3 p-3 bg-gray-50 rounded border border-gray-200">
+                        <div class="flex-shrink-0 w-6 h-6 bg-dark-800 text-white rounded flex items-center justify-center font-bold text-xs">
+                            {{ step.step }}
+                        </div>
+                        <div class="flex-1">
+                            <p class="text-dark-800 mb-1 text-sm">{{ step.description }}</p>
+                            <div v-if="step.time || step.temperature" class="flex gap-2 text-xs text-gray-600">
+                                <span v-if="step.time" class="bg-white px-2 py-1 rounded border"> ⏱️ {{ step.time }}分钟 </span>
+                                <span v-if="step.temperature" class="bg-white px-2 py-1 rounded border"> 🌡️ {{ step.temperature }} </span>
+                            </div>
                         </div>
                     </div>
                 </div>
             </div>
-        </div>
 
-        <!-- 烹饪技巧 -->
-        <div v-if="recipe.tips && recipe.tips.length > 0 && isExpanded">
-            <h4 class="text-lg font-semibold text-neutral-800 mb-3 flex items-center gap-2">💡 烹饪技巧</h4>
-            <div class="bg-gradient-to-r from-accent-50 to-primary-50 border-l-4 border-accent-400 p-4 rounded-r-xl">
-                <ul class="space-y-2">
-                    <li v-for="tip in recipe.tips" :key="tip" class="flex items-start gap-3 text-neutral-700">
-                        <span class="text-accent-500 mt-1 text-base">•</span>
-                        <span class="text-sm leading-relaxed">{{ tip }}</span>
-                    </li>
-                </ul>
+            <!-- 烹饪技巧 -->
+            <div v-if="recipe.tips && recipe.tips.length > 0 && isExpanded">
+                <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">
+                        <li v-for="tip in recipe.tips" :key="tip" class="flex items-start gap-2 text-dark-700">
+                            <span class="text-yellow-600 mt-1 text-xs">•</span>
+                            <span class="text-xs">{{ tip }}</span>
+                        </li>
+                    </ul>
+                </div>
             </div>
         </div>
     </div>

+ 119 - 5
src/services/aiService.ts

@@ -24,14 +24,24 @@ const aiClient = axios.create({
  * 调用AI接口生成菜谱
  * @param ingredients 食材列表
  * @param cuisine 菜系信息
+ * @param customPrompt 自定义提示词(可选)
  * @returns Promise<Recipe>
  */
-export const generateRecipe = async (ingredients: string[], cuisine: CuisineType): Promise<Recipe> => {
+export const generateRecipe = async (ingredients: string[], cuisine: CuisineType, customPrompt?: string): Promise<Recipe> => {
     try {
         // 构建提示词
-        const prompt = `${cuisine.prompt}
+        let prompt = `${cuisine.prompt}
 
-用户提供的食材:${ingredients.join('、')}
+用户提供的食材:${ingredients.join('、')}`
+
+        // 如果有自定义要求,添加到提示词中
+        if (customPrompt) {
+            prompt += `
+
+用户的特殊要求:${customPrompt}`
+        }
+
+        prompt += `
 
 请按照以下JSON格式返回菜谱:
 {
@@ -126,12 +136,13 @@ export const generateRecipe = async (ingredients: string[], cuisine: CuisineType
  * 批量生成多个菜系的菜谱
  * @param ingredients 食材列表
  * @param cuisines 菜系列表
+ * @param customPrompt 自定义提示词(可选)
  * @returns Promise<Recipe[]>
  */
-export const generateMultipleRecipes = async (ingredients: string[], cuisines: CuisineType[]): Promise<Recipe[]> => {
+export const generateMultipleRecipes = async (ingredients: string[], cuisines: CuisineType[], customPrompt?: string): Promise<Recipe[]> => {
     try {
         // 并发调用多个AI接口
-        const promises = cuisines.map(cuisine => generateRecipe(ingredients, cuisine))
+        const promises = cuisines.map(cuisine => generateRecipe(ingredients, cuisine, customPrompt))
 
         const recipes = await Promise.all(promises)
         return recipes
@@ -153,5 +164,108 @@ export const updateAIConfig = (config: Partial<typeof AI_CONFIG>) => {
     aiClient.defaults.headers['Authorization'] = `Bearer ${AI_CONFIG.apiKey}`
 }
 
+/**
+ * 使用自定义提示词生成菜谱
+ * @param ingredients 食材列表
+ * @param customPrompt 自定义提示词
+ * @returns Promise<Recipe>
+ */
+export const generateCustomRecipe = async (ingredients: string[], customPrompt: string): Promise<Recipe> => {
+    try {
+        // 构建自定义提示词
+        const prompt = `你是一位专业的厨师,请根据用户提供的食材和特殊要求,生成详细的菜谱。请严格按照JSON格式返回,不要包含任何其他文字。
+
+用户提供的食材:${ingredients.join('、')}
+
+用户的特殊要求:${customPrompt}
+
+请按照以下JSON格式返回菜谱:
+{
+  "name": "菜品名称",
+  "ingredients": ["食材1", "食材2"],
+  "steps": [
+    {
+      "step": 1,
+      "description": "步骤描述",
+      "time": 5,
+      "temperature": "中火"
+    }
+  ],
+  "cookingTime": 30,
+  "difficulty": "medium",
+  "tips": ["技巧1", "技巧2"]
+}`
+
+        // 调用智谱AI接口
+        const response = await aiClient.post('/chat/completions', {
+            model: AI_CONFIG.model,
+            messages: [
+                {
+                    role: 'system',
+                    content: '你是一位专业的厨师,请根据用户提供的食材和特殊要求,生成详细的菜谱。请严格按照JSON格式返回,不要包含任何其他文字。'
+                },
+                {
+                    role: 'user',
+                    content: prompt
+                }
+            ],
+            temperature: AI_CONFIG.temperature,
+            max_tokens: 2000,
+            stream: false
+        })
+
+        // 解析AI响应
+        const aiResponse = response.data.choices[0].message.content
+
+        // 清理响应内容,提取JSON部分
+        let cleanResponse = aiResponse.trim()
+        if (cleanResponse.startsWith('```json')) {
+            cleanResponse = cleanResponse.replace(/```json\s*/, '').replace(/```\s*$/, '')
+        } else if (cleanResponse.startsWith('```')) {
+            cleanResponse = cleanResponse.replace(/```\s*/, '').replace(/```\s*$/, '')
+        }
+
+        const recipeData = JSON.parse(cleanResponse)
+
+        // 构建完整的Recipe对象
+        const recipe: Recipe = {
+            id: `recipe-custom-${Date.now()}`,
+            name: recipeData.name || '自定义菜品',
+            cuisine: '自定义',
+            ingredients: recipeData.ingredients || ingredients,
+            steps: recipeData.steps || [
+                { step: 1, description: '准备所有食材', time: 5 },
+                { step: 2, description: '按照要求烹饪', time: 20 }
+            ],
+            cookingTime: recipeData.cookingTime || 25,
+            difficulty: recipeData.difficulty || 'medium',
+            tips: recipeData.tips || ['根据个人口味调整', '注意火候控制']
+        }
+
+        return recipe
+    } catch (error) {
+        console.error('生成自定义菜谱失败:', error)
+
+        // 如果AI调用失败,返回一个基础的菜谱模板
+        const fallbackRecipe: Recipe = {
+            id: `recipe-custom-${Date.now()}`,
+            name: `自定义:${ingredients.join('')}料理`,
+            cuisine: '自定义',
+            ingredients: ingredients,
+            steps: [
+                { step: 1, description: '准备所有食材,清洗干净', time: 5 },
+                { step: 2, description: '根据要求进行烹饪处理', time: 10 },
+                { step: 3, description: '调味并完成最后的制作', time: 8 },
+                { step: 4, description: '装盘即可享用', time: 2 }
+            ],
+            cookingTime: 25,
+            difficulty: 'medium',
+            tips: ['根据个人喜好调整口味', '注意食材的新鲜度', '掌握好火候']
+        }
+
+        return fallbackRecipe
+    }
+}
+
 // 导出配置更新函数,供外部使用
 export { AI_CONFIG }

+ 1 - 1
src/style.css

@@ -6,7 +6,7 @@
 
 body {
     font-family: 'Noto Sans SC', sans-serif;
-    background: linear-gradient(135deg, #fef7ed 0%, #fdedd3 50%, #fbd9a5 100%);
+    background: #fbbf24;
     min-height: 100vh;
 }
 

+ 250 - 140
src/views/Home.vue

@@ -1,150 +1,220 @@
 <template>
-    <div class="min-h-screen bg-gradient-to-br from-primary-50 to-primary-200">
-        <!-- 头部 -->
-        <header class="relative overflow-hidden">
-            <div class="absolute inset-0 bg-gradient-to-r from-accent-600 via-accent-500 to-primary-500"></div>
-            <div class="absolute inset-0 bg-black/10"></div>
-            <div class="relative max-w-6xl mx-auto px-4 py-8">
-                <div class="text-center">
-                    <div class="inline-flex items-center gap-4 mb-3">
-                        <div class="text-4xl cooking-animation">🍳</div>
-                        <h1 class="text-4xl font-black text-white tracking-tight">一饭封神</h1>
+    <div class="min-h-screen bg-yellow-400">
+        <!-- 头部 - 粉色区域 -->
+        <header class="bg-pink-400 border-4 border-black mx-4 mt-4 rounded-lg relative">
+            <div class="absolute top-2 right-2">
+                <button class="bg-white/20 hover:bg-white/30 rounded-full px-3 py-1 text-sm text-white transition-colors">中文</button>
+            </div>
+            <div class="text-center py-8">
+                <h1 class="text-5xl font-black text-yellow-300 mb-2 tracking-wider">一饭封神</h1>
+                <p class="text-white text-lg font-medium">UPLOAD YOUR INGREDIENTS | SPIT OUT RECIPES!</p>
+            </div>
+        </header>
+
+        <!-- 使用量显示 -->
+        <!-- <div class="mx-4 mt-4">
+            <div class="bg-white border-2 border-black rounded-lg p-4 max-w-sm mx-auto">
+                <div class="flex items-center justify-between">
+                    <div class="flex items-center gap-2">
+                        <span class="text-lg">⚡</span>
+                        <span class="font-bold text-dark-800">DAILY USAGE</span>
                     </div>
-                    <p class="text-white/80 text-lg font-medium">让AI厨艺大师为您的食材创造美味</p>
+                    <span class="font-bold text-xl">0/5</span>
+                </div>
+                <div class="mt-2 bg-gray-200 rounded-full h-2">
+                    <div class="bg-dark-800 h-2 rounded-full" style="width: 0%"></div>
                 </div>
             </div>
-        </header>
+        </div> -->
 
-        <main class="max-w-6xl mx-auto px-4 py-8">
-            <!-- 主要输入区域 -->
-            <div class="bg-white/95 backdrop-blur-sm rounded-3xl shadow-2xl p-12 mb-8 pulse-glow">
-                <div class="text-center mb-10">
-                    <h2 class="text-4xl font-bold text-neutral-800 mb-4">🥬 告诉我你有什么食材</h2>
-                    <p class="text-neutral-600 text-xl">输入你现有的食材,让AI大师为你创造美味</p>
+        <div class="max-w-4xl mx-auto px-4 py-6">
+            <!-- 步骤1: 输入食材 -->
+            <div class="mb-6">
+                <div class="bg-pink-400 text-white px-4 py-2 rounded-t-lg border-2 border-black border-b-0 inline-block">
+                    <span class="font-bold">1. 输入食材</span>
                 </div>
+                <div class="bg-white border-2 border-black rounded-lg rounded-tl-none p-8">
+                    <div class="text-center mb-6">
+                        <div class="w-16 h-16 bg-black rounded-lg flex items-center justify-center mx-auto mb-4">
+                            <span class="text-white text-2xl">🥬</span>
+                        </div>
+                        <h2 class="text-2xl font-bold text-dark-800 mb-2">添加食材</h2>
+                        <p class="text-gray-600">输入你现有的食材,按回车添加</p>
+                        <p class="text-xs text-gray-500 mt-1">支持蔬菜、肉类、调料等 (最多10种)</p>
+                    </div>
+
+                    <!-- 食材输入区域 -->
+                    <div class="space-y-4">
+                        <!-- 已添加的食材 -->
+                        <div v-if="ingredients.length > 0" class="flex flex-wrap gap-2">
+                            <div
+                                v-for="ingredient in ingredients"
+                                :key="ingredient"
+                                class="inline-flex items-center gap-2 bg-yellow-400 text-dark-800 px-3 py-2 rounded-full text-sm font-medium border-2 border-black"
+                            >
+                                {{ ingredient }}
+                                <button @click="removeIngredient(ingredient)" class="hover:bg-yellow-500 rounded-full p-1 transition-colors">
+                                    <span class="text-xs">✕</span>
+                                </button>
+                            </div>
+                        </div>
 
-                <!-- 食材输入框 -->
-                <div class="relative mb-8">
-                    <div
-                        class="flex flex-wrap gap-4 min-h-[120px] p-10 border-4 border-dashed border-accent-300 rounded-3xl bg-gradient-to-r from-accent-50 to-primary-50 hover:border-accent-400 transition-all duration-300 focus-within:border-accent-500 focus-within:shadow-xl"
-                    >
-                        <div
-                            v-for="ingredient in ingredients"
-                            :key="ingredient"
-                            class="inline-flex items-center gap-3 bg-accent-500 text-white px-6 py-3 rounded-full text-xl font-medium shadow-lg hover:bg-accent-600 transition-colors"
-                        >
-                            {{ ingredient }}
-                            <button @click="removeIngredient(ingredient)" class="hover:bg-accent-700 rounded-full p-1 transition-colors">
-                                <span class="text-lg">✕</span>
+                        <!-- 输入框 -->
+                        <div class="relative">
+                            <input
+                                v-model="currentIngredient"
+                                @keyup.enter="addIngredient"
+                                placeholder="输入食材名称,按回车添加..."
+                                class="w-full p-4 border-2 border-black rounded-lg text-lg font-medium focus:outline-none focus:ring-2 focus:ring-pink-400"
+                            />
+                        </div>
+
+                        <!-- 生成按钮 -->
+                        <div class="text-center pt-4">
+                            <button
+                                @click="generateRecipes"
+                                :disabled="ingredients.length === 0 || isLoading"
+                                class="bg-dark-800 hover:bg-dark-700 disabled:bg-gray-400 text-white px-8 py-3 rounded-lg font-bold text-lg border-2 border-black transition-all duration-300 transform hover:scale-105 disabled:scale-100 disabled:cursor-not-allowed"
+                            >
+                                <span v-if="!isLoading" class="flex items-center gap-2 justify-center"> ✨ {{ customPrompt.trim() ? '按要求生成' : '交给大师' }} </span>
                             </button>
+                            <p v-if="customPrompt.trim()" class="text-xs text-gray-600 mt-2">将根据您的自定义要求生成菜谱</p>
+                            <p v-else-if="selectedCuisines.length > 0" class="text-xs text-gray-600 mt-2">将生成 {{ selectedCuisines.length }} 个菜系的菜谱</p>
+                            <p v-else class="text-xs text-gray-600 mt-2">将随机选择菜系生成菜谱</p>
                         </div>
-                        <input
-                            v-model="currentIngredient"
-                            @keyup.enter="addIngredient"
-                            placeholder="输入食材名称,按回车添加..."
-                            class="flex-1 min-w-[350px] bg-transparent outline-none text-neutral-800 placeholder-neutral-500 text-2xl font-medium"
-                        />
                     </div>
                 </div>
+            </div>
 
-                <!-- 生成按钮 -->
-                <div class="text-center">
-                    <button
-                        @click="generateRecipes"
-                        :disabled="ingredients.length === 0 || isLoading"
-                        class="bg-gradient-to-r from-accent-600 to-accent-700 hover:from-accent-700 hover:to-accent-800 disabled:from-gray-300 disabled:to-gray-400 text-white px-12 py-4 rounded-2xl font-bold text-xl shadow-xl transition-all duration-300 transform hover:scale-105 disabled:scale-100 disabled:cursor-not-allowed"
-                    >
-                        <span v-if="!isLoading" class="flex items-center gap-3"> 👨‍🍳 交给大师 </span>
-                    </button>
+            <!-- 步骤2和3: 选择风格 OR 自定义提示 -->
+            <div class="grid grid-cols-1 lg:grid-cols-2 gap-4 mb-6">
+                <!-- 选择菜系 -->
+                <div>
+                    <div class="bg-green-400 text-white px-4 py-2 rounded-t-lg border-2 border-black border-b-0 inline-block">
+                        <span class="font-bold">2. 选择菜系</span>
+                    </div>
+                    <div class="bg-white border-2 border-black rounded-lg rounded-tl-none p-6">
+                        <div v-if="customPrompt.trim()" class="text-center py-8 text-gray-500">
+                            <p class="text-sm">已设置自定义要求,将忽略菜系选择</p>
+                            <button @click="customPrompt = ''" class="text-blue-600 hover:text-blue-700 underline text-sm mt-2">清除自定义要求以选择菜系</button>
+                        </div>
+                        <div v-else class="grid grid-cols-2 gap-3">
+                            <button
+                                v-for="cuisine in cuisines.slice(0, 6)"
+                                :key="cuisine.id"
+                                @click="selectCuisine(cuisine)"
+                                :class="[
+                                    'p-3 rounded-lg border-2 border-black font-medium text-sm transition-all duration-200',
+                                    selectedCuisines.includes(cuisine.id) ? 'bg-yellow-400 text-dark-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+                                ]"
+                            >
+                                {{ cuisine.name.replace('大师', '') }}
+                            </button>
+                        </div>
+                    </div>
                 </div>
-            </div>
 
-            <!-- 厨艺大师选择区域 -->
-            <div class="bg-white/70 backdrop-blur-sm rounded-2xl shadow-md p-6 mb-8">
-                <details class="group">
-                    <summary class="cursor-pointer text-center p-4 hover:bg-neutral-50 rounded-xl transition-colors">
-                        <h3 class="text-lg font-medium text-neutral-700 inline-flex items-center gap-2">
-                            👨‍🍳 选择厨艺大师
-                            <span class="text-sm text-neutral-500">(可选,默认随机选择)</span>
-                            <span class="group-open:rotate-180 transition-transform duration-300">▼</span>
-                        </h3>
-                    </summary>
-
-                    <div class="mt-4 grid grid-cols-3 gap-4">
-                        <div
-                            v-for="cuisine in cuisines"
-                            :key="cuisine.id"
-                            @click="selectCuisine(cuisine)"
-                            :class="[
-                                'cursor-pointer p-4 rounded-xl border-2 transition-all duration-200 hover:scale-105',
-                                selectedCuisines.includes(cuisine.id)
-                                    ? 'border-accent-500 bg-accent-100 shadow-lg ring-2 ring-accent-200'
-                                    : 'border-neutral-200 bg-white hover:border-accent-300 hover:shadow-md'
-                            ]"
-                        >
-                            <div class="text-center">
-                                <div class="text-3xl mb-2">{{ cuisine.avatar }}</div>
-                                <h4 class="font-semibold text-neutral-800 text-base">{{ cuisine.name }}</h4>
-                                <p class="text-sm text-neutral-600 mt-1">{{ cuisine.specialty }}</p>
+                <!-- 自定义提示 -->
+                <div>
+                    <div class="bg-blue-400 text-white px-4 py-2 rounded-t-lg border-2 border-black border-b-0 inline-block">
+                        <span class="font-bold">3. 或自定义要求</span>
+                    </div>
+                    <div class="bg-white border-2 border-black rounded-lg rounded-tl-none p-6">
+                        <div class="mb-3">
+                            <label class="block text-sm font-bold text-dark-800 mb-2">描述你的需求:</label>
+                            <textarea
+                                v-model="customPrompt"
+                                placeholder="例如:做一道清淡的汤,适合老人食用..."
+                                class="w-full p-3 border-2 border-gray-300 rounded-lg text-sm resize-none focus:outline-none focus:border-blue-400"
+                                rows="4"
+                            ></textarea>
+                            <div v-if="customPrompt.trim()" class="mt-2 flex justify-between items-center">
+                                <span class="text-xs text-green-600">✓ 已设置自定义要求</span>
+                                <button @click="customPrompt = ''" class="text-xs text-red-600 hover:text-red-700 underline">清除</button>
                             </div>
                         </div>
+                        <p class="text-xs text-gray-500">越具体越好!</p>
                     </div>
-                </details>
+                </div>
             </div>
 
-            <!-- 加载动效 -->
-            <div v-if="isLoading" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
-                <CookingLoader />
+            <!-- 中间的OR -->
+            <div class="text-center mb-6">
+                <span class="bg-yellow-400 text-dark-800 px-4 py-2 rounded-full font-bold text-xl border-2 border-black"> OR </span>
             </div>
 
-            <!-- 菜谱展示区域 -->
-            <div v-if="recipes.length > 0" class="space-y-8">
-                <h2 class="text-4xl font-bold text-neutral-800 text-center mb-8">🍽️ 大师们的杰作</h2>
-
-                <!-- 菜谱网格布局 -->
-                <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
-                    <div
-                        v-for="recipe in recipes"
-                        :key="recipe.id"
-                        class="bg-white/95 backdrop-blur-sm rounded-2xl shadow-xl border border-accent-200 overflow-hidden hover:shadow-2xl transition-all duration-300 transform hover:scale-[1.02] flex flex-col"
-                    >
-                        <RecipeCard :recipe="recipe" />
-                    </div>
+            <!-- 步骤4: 结果 -->
+            <div>
+                <div class="bg-dark-800 text-white px-4 py-2 rounded-t-lg border-2 border-black border-b-0 inline-block">
+                    <span class="font-bold">4. 菜谱结果</span>
                 </div>
+                <div class="bg-white border-2 border-black rounded-lg rounded-tl-none p-8">
+                    <!-- 加载状态 -->
+                    <div v-if="isLoading" class="text-center py-12">
+                        <div class="w-16 h-16 border-4 border-gray-300 border-t-dark-800 rounded-full animate-spin mx-auto mb-4"></div>
+                        <h3 class="text-xl font-bold text-dark-800 mb-2">大师正在创作中...</h3>
+                        <p class="text-gray-600">{{ loadingText }}</p>
+                    </div>
+
+                    <!-- 空状态 -->
+                    <div v-else-if="recipes.length === 0" class="text-center py-12">
+                        <div class="w-16 h-16 bg-gray-200 rounded-lg flex items-center justify-center mx-auto mb-4">
+                            <span class="text-gray-400 text-2xl">⭐</span>
+                        </div>
+                        <h3 class="text-xl font-bold text-gray-400 mb-2">等待魔法发生...</h3>
+                        <p class="text-gray-500">添加食材并选择菜系开始创作</p>
+                    </div>
 
-                <!-- 如果菜谱较多,添加一个回到顶部按钮 -->
-                <div v-if="recipes.length > 3" class="text-center mt-8">
-                    <button
-                        @click="scrollToTop"
-                        class="bg-accent-500 hover:bg-accent-600 text-white px-6 py-3 rounded-full font-medium shadow-lg transition-all duration-300 transform hover:scale-105"
-                    >
-                        ⬆️ 回到顶部
-                    </button>
+                    <!-- 菜谱结果 -->
+                    <div v-else class="grid grid-cols-1 lg:grid-cols-2 gap-6">
+                        <div v-for="recipe in recipes" :key="recipe.id" class="border-2 border-black rounded-lg overflow-hidden">
+                            <RecipeCard :recipe="recipe" />
+                        </div>
+                    </div>
                 </div>
             </div>
-        </main>
+        </div>
+
+        <!-- 底部 -->
+        <footer class="bg-white border-2 border-black mx-4 mb-4 rounded-lg p-4 text-center">
+            <p class="text-sm text-gray-600">© 2025 一饭封神 | Made with ❤️ and 🤖</p>
+        </footer>
     </div>
 </template>
 
 <script setup lang="ts">
-import { ref } from 'vue'
+import { ref, onMounted, onUnmounted } from 'vue'
 import { cuisines } from '@/config/cuisines'
 import RecipeCard from '@/components/RecipeCard.vue'
-import CookingLoader from '@/components/CookingLoader.vue'
-import { generateMultipleRecipes } from '@/services/aiService'
+import { generateMultipleRecipes, generateCustomRecipe } from '@/services/aiService'
 import type { Recipe, CuisineType } from '@/types'
 
 // 响应式数据
 const ingredients = ref<string[]>([])
 const currentIngredient = ref('')
 const selectedCuisines = ref<string[]>([])
+const customPrompt = ref('')
 const recipes = ref<Recipe[]>([])
 const isLoading = ref(false)
+const loadingText = ref('大师正在挑选食材...')
+
+// 加载文字轮播
+const loadingTexts = [
+    '大师正在挑选食材...',
+    '大师正在起火热锅...',
+    '大师正在爆香配料...',
+    '大师正在调制秘制酱料...',
+    '大师正在掌控火候...',
+    '大师正在精心摆盘...',
+    '美味佳肴即将出炉...'
+]
+
+let loadingInterval: NodeJS.Timeout | null = null
 
 // 添加食材
 const addIngredient = () => {
     const ingredient = currentIngredient.value.trim()
-    if (ingredient && !ingredients.value.includes(ingredient)) {
+    if (ingredient && !ingredients.value.includes(ingredient) && ingredients.value.length < 10) {
         ingredients.value.push(ingredient)
         currentIngredient.value = ''
     }
@@ -177,26 +247,44 @@ const generateRecipes = async () => {
     isLoading.value = true
     recipes.value = []
 
+    // 开始加载文字轮播
+    let textIndex = 0
+    loadingInterval = setInterval(() => {
+        loadingText.value = loadingTexts[textIndex]
+        textIndex = (textIndex + 1) % loadingTexts.length
+    }, 2000)
+
     try {
-        // 根据选择的菜系数量决定生成菜谱
-        let selectedCuisineObjects = cuisines.filter(c => selectedCuisines.value.includes(c.id))
+        // 检查是否有自定义提示词
+        if (customPrompt.value.trim()) {
+            // 使用自定义提示词生成菜谱
+            const customRecipe = await generateCustomRecipe(ingredients.value, customPrompt.value.trim())
+            recipes.value = [customRecipe]
+        } else {
+            // 使用菜系生成菜谱
+            let selectedCuisineObjects = cuisines.filter(c => selectedCuisines.value.includes(c.id))
 
-        if (selectedCuisineObjects.length === 0) {
-            // 随机选择2-3个菜系
-            const shuffled = [...cuisines].sort(() => 0.5 - Math.random())
-            selectedCuisineObjects = shuffled.slice(0, Math.floor(Math.random() * 2) + 2)
-        }
+            if (selectedCuisineObjects.length === 0) {
+                // 随机选择2-3个菜系
+                const shuffled = [...cuisines].sort(() => 0.5 - Math.random())
+                selectedCuisineObjects = shuffled.slice(0, Math.floor(Math.random() * 2) + 2)
+            }
 
-        // 调用AI服务生成菜谱
-        const generatedRecipes = await generateMultipleRecipes(ingredients.value, selectedCuisineObjects)
+            // 调用AI服务生成菜谱
+            const generatedRecipes = await generateMultipleRecipes(ingredients.value, selectedCuisineObjects, customPrompt.value.trim() || undefined)
 
-        recipes.value = generatedRecipes
+            recipes.value = generatedRecipes
+        }
     } catch (error) {
         console.error('生成菜谱失败:', error)
         // 如果AI调用失败,使用模拟数据作为后备
         await simulateAICall()
     } finally {
         isLoading.value = false
+        if (loadingInterval) {
+            clearInterval(loadingInterval)
+            loadingInterval = null
+        }
     }
 }
 
@@ -212,25 +300,49 @@ const simulateAICall = async () => {
                 cuisinesToUse = shuffled.slice(0, Math.floor(Math.random() * 2) + 2)
             }
 
-            // 模拟生成菜谱数据
-            const mockRecipes: Recipe[] = cuisinesToUse.map((cuisine, index) => {
-                return {
-                    id: `recipe-${cuisine.id}-${Date.now()}-${index}`,
-                    name: `${cuisine.name}推荐:${ingredients.value.join('')}料理`,
-                    cuisine: cuisine.name,
-                    ingredients: ingredients.value,
-                    steps: [
-                        { step: 1, description: '准备所有食材,清洗干净', time: 5 },
-                        { step: 2, description: '热锅下油,爆香配料', time: 3 },
-                        { step: 3, description: '下主料翻炒至半熟', time: 8 },
-                        { step: 4, description: '调味炒制至熟透', time: 5 },
-                        { step: 5, description: '装盘即可享用', time: 1 }
-                    ],
-                    cookingTime: 22,
-                    difficulty: 'medium',
-                    tips: ['火候要掌握好,避免炒糊', '调料要适量,突出食材本味', '炒制过程中要勤翻动']
-                }
-            })
+            // 检查是否有自定义提示词
+            let mockRecipes: Recipe[] = []
+
+            if (customPrompt.value.trim()) {
+                // 生成自定义菜谱
+                mockRecipes = [
+                    {
+                        id: `recipe-custom-${Date.now()}`,
+                        name: `自定义:${ingredients.value.join('')}料理`,
+                        cuisine: '自定义',
+                        ingredients: ingredients.value,
+                        steps: [
+                            { step: 1, description: '准备所有食材,清洗干净', time: 5 },
+                            { step: 2, description: '根据要求进行烹饪处理', time: 10 },
+                            { step: 3, description: '调味并完成最后的制作', time: 8 },
+                            { step: 4, description: '装盘即可享用', time: 2 }
+                        ],
+                        cookingTime: 25,
+                        difficulty: 'medium',
+                        tips: ['根据个人喜好调整口味', '注意食材的新鲜度', '掌握好火候']
+                    }
+                ]
+            } else {
+                // 生成菜系菜谱
+                mockRecipes = cuisinesToUse.map((cuisine, index) => {
+                    return {
+                        id: `recipe-${cuisine.id}-${Date.now()}-${index}`,
+                        name: `${cuisine.name}推荐:${ingredients.value.join('')}料理`,
+                        cuisine: cuisine.name,
+                        ingredients: ingredients.value,
+                        steps: [
+                            { step: 1, description: '准备所有食材,清洗干净', time: 5 },
+                            { step: 2, description: '热锅下油,爆香配料', time: 3 },
+                            { step: 3, description: '下主料翻炒至半熟', time: 8 },
+                            { step: 4, description: '调味炒制至熟透', time: 5 },
+                            { step: 5, description: '装盘即可享用', time: 1 }
+                        ],
+                        cookingTime: 22,
+                        difficulty: 'medium',
+                        tips: ['火候要掌握好,避免炒糊', '调料要适量,突出食材本味', '炒制过程中要勤翻动']
+                    }
+                })
+            }
 
             recipes.value = mockRecipes
             resolve(mockRecipes)
@@ -238,11 +350,9 @@ const simulateAICall = async () => {
     })
 }
 
-// 回到顶部功能
-const scrollToTop = () => {
-    window.scrollTo({
-        top: 0,
-        behavior: 'smooth'
-    })
-}
+onUnmounted(() => {
+    if (loadingInterval) {
+        clearInterval(loadingInterval)
+    }
+})
 </script>

+ 38 - 22
tailwind.config.js

@@ -7,30 +7,46 @@ export default {
     theme: {
         extend: {
             colors: {
-                primary: {
-                    50: '#fef7ed',
-                    100: '#fdedd3',
-                    200: '#fbd9a5',
-                    300: '#f8c06d',
-                    400: '#f59e33',
-                    500: '#f3830f',
-                    600: '#e4690a',
-                    700: '#bd4f0b',
-                    800: '#973f10',
-                    900: '#7a3511',
+                // 主黄色系 - 参考图片的背景色
+                yellow: {
+                    50: '#fefce8',
+                    100: '#fef3c7',
+                    200: '#fde68a',
+                    300: '#fcd34d',
+                    400: '#fbbf24',
+                    500: '#f59e0b',
+                    600: '#d97706',
+                    700: '#b45309',
+                    800: '#92400e',
+                    900: '#78350f',
                 },
-                accent: {
-                    50: '#fff7ed',
-                    100: '#ffedd5',
-                    200: '#fed7aa',
-                    300: '#fdba74',
-                    400: '#fb923c',
-                    500: '#f97316',
-                    600: '#ea580c',
-                    700: '#c2410c',
-                    800: '#9a3412',
-                    900: '#7c2d12',
+                // 粉色系 - 参考图片的头部色
+                pink: {
+                    50: '#fdf2f8',
+                    100: '#fce7f3',
+                    200: '#fbcfe8',
+                    300: '#f9a8d4',
+                    400: '#f472b6',
+                    500: '#ec4899',
+                    600: '#db2777',
+                    700: '#be185d',
+                    800: '#9d174d',
+                    900: '#831843',
                 },
+                // 黑色系 - 参考图片的标签色
+                dark: {
+                    50: '#f8fafc',
+                    100: '#f1f5f9',
+                    200: '#e2e8f0',
+                    300: '#cbd5e1',
+                    400: '#94a3b8',
+                    500: '#64748b',
+                    600: '#475569',
+                    700: '#334155',
+                    800: '#1e293b',
+                    900: '#0f172a',
+                },
+                // 保留中性色
                 neutral: {
                     50: '#fafaf9',
                     100: '#f5f5f4',