liuziting 7 ヶ月 前
コミット
9b19a288d0
4 ファイル変更184 行追加109 行削除
  1. 11 0
      netlify.toml
  2. 66 41
      src/components/RecipeCard.vue
  3. 15 0
      src/style.css
  4. 92 68
      src/views/Home.vue

+ 11 - 0
netlify.toml

@@ -0,0 +1,11 @@
+[build]
+  command = "npm run build"
+  publish = "dist"
+
+[build.environment]
+  NODE_VERSION = "18"
+
+[[redirects]]
+  from = "/*"
+  to = "/index.html"
+  status = 200

+ 66 - 41
src/components/RecipeCard.vue

@@ -1,49 +1,66 @@
 <template>
-    <div class="recipe-card">
+    <div class="recipe-card p-6">
         <!-- 菜谱头部 -->
-        <div class="flex items-start justify-between mb-6">
-            <div>
-                <h3 class="text-3xl font-bold text-neutral-800 mb-3">{{ recipe.name }}</h3>
-                <div class="flex items-center gap-6 text-base text-neutral-600">
-                    <span class="flex items-center gap-2 bg-accent-100 px-3 py-1 rounded-full"> 👨‍🍳 {{ recipe.cuisine }} </span>
-                    <span class="flex items-center gap-2"> ⏱️ {{ recipe.cookingTime }}分钟 </span>
-                    <span class="flex items-center gap-2"> 📊 {{ difficultyText }} </span>
+        <div class="mb-5">
+            <div class="flex items-start justify-between mb-3">
+                <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>
+                    </div>
                 </div>
-            </div>
-            <div class="text-right">
-                <div class="text-4xl mb-2">🍽️</div>
+                <div class="text-4xl ml-3">🍽️</div>
             </div>
         </div>
 
         <!-- 食材列表 -->
-        <div class="mb-6">
-            <h4 class="text-xl font-semibold text-neutral-800 mb-4 flex items-center gap-2">🥬 所需食材</h4>
-            <div class="flex flex-wrap gap-3">
-                <span
-                    v-for="ingredient in recipe.ingredients"
-                    :key="ingredient"
-                    class="bg-accent-100 text-accent-800 px-4 py-2 rounded-full text-base font-medium border border-accent-200"
-                >
+        <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>
         </div>
 
-        <!-- 制作步骤 -->
-        <div class="mb-6">
-            <h4 class="text-xl font-semibold text-neutral-800 mb-4 flex items-center gap-2">📝 制作步骤</h4>
-            <div class="space-y-4">
-                <div v-for="step in recipe.steps" :key="step.step" class="flex gap-4 p-5 bg-gradient-to-r from-neutral-50 to-primary-50 rounded-xl border border-neutral-200">
-                    <div
-                        class="flex-shrink-0 w-10 h-10 bg-gradient-to-r from-accent-500 to-accent-600 text-white rounded-full flex items-center justify-center font-bold text-base shadow-md"
-                    >
+        <!-- 制作步骤预览 -->
+        <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 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>
+                    <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-2 text-base leading-relaxed">{{ step.description }}</p>
-                        <div v-if="step.time || step.temperature" class="flex gap-4 text-sm text-neutral-600">
-                            <span v-if="step.time" class="flex items-center gap-1 bg-white px-2 py-1 rounded-md"> ⏱️ {{ step.time }}分钟 </span>
-                            <span v-if="step.temperature" class="flex items-center gap-1 bg-white px-2 py-1 rounded-md"> 🌡️ {{ step.temperature }} </span>
+                        <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>
                     </div>
                 </div>
@@ -51,13 +68,13 @@
         </div>
 
         <!-- 烹饪技巧 -->
-        <div v-if="recipe.tips && recipe.tips.length > 0">
-            <h4 class="text-xl font-semibold text-neutral-800 mb-4 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-5 rounded-r-xl">
-                <ul class="space-y-3">
+        <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-lg">•</span>
-                        <span class="text-base leading-relaxed">{{ tip }}</span>
+                        <span class="text-accent-500 mt-1 text-base">•</span>
+                        <span class="text-sm leading-relaxed">{{ tip }}</span>
                     </li>
                 </ul>
             </div>
@@ -66,7 +83,7 @@
 </template>
 
 <script setup lang="ts">
-import { computed } from 'vue'
+import { computed, ref } from 'vue'
 import type { Recipe } from '@/types'
 
 interface Props {
@@ -74,6 +91,7 @@ interface Props {
 }
 
 const props = defineProps<Props>()
+const isExpanded = ref(false)
 
 const difficultyText = computed(() => {
     const difficultyMap = {
@@ -83,14 +101,21 @@ const difficultyText = computed(() => {
     }
     return difficultyMap[props.recipe.difficulty] || '中等'
 })
+
+const toggleExpanded = () => {
+    isExpanded.value = !isExpanded.value
+}
 </script>
 
 <style scoped>
 .recipe-card {
-    @apply transition-all duration-300;
+    @apply transition-all duration-300 h-full;
 }
 
-.recipe-card:hover {
-    @apply transform scale-[1.02];
+.line-clamp-2 {
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
 }
 </style>

+ 15 - 0
src/style.css

@@ -73,3 +73,18 @@ body {
 .pulse-glow {
     animation: pulse-glow 2s ease-in-out infinite;
 }
+
+/* 文本截断工具类 */
+.line-clamp-2 {
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+}
+
+.line-clamp-3 {
+    display: -webkit-box;
+    -webkit-line-clamp: 3;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+}

+ 92 - 68
src/views/Home.vue

@@ -1,62 +1,65 @@
 <template>
     <div class="min-h-screen bg-gradient-to-br from-primary-50 to-primary-200">
         <!-- 头部 -->
-        <header class="bg-white/90 backdrop-blur-sm shadow-lg border-b border-primary-200">
-            <div class="max-w-4xl mx-auto px-4 py-8">
+        <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">
-                    <h1 class="text-5xl font-bold text-neutral-800 mb-3">🍳 一饭封神</h1>
-                    <p class="text-neutral-600 text-xl font-medium">AI厨艺大师为您量身定制美味佳肴</p>
+                    <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>
+                    <p class="text-white/80 text-lg font-medium">让AI厨艺大师为您的食材创造美味</p>
                 </div>
             </div>
         </header>
 
-        <main class="max-w-4xl mx-auto px-4 py-8">
+        <main class="max-w-6xl mx-auto px-4 py-8">
             <!-- 主要输入区域 -->
-            <div class="bg-white/95 backdrop-blur-sm rounded-3xl shadow-2xl p-10 mb-8 pulse-glow">
-                <div class="text-center mb-8">
-                    <h2 class="text-3xl font-bold text-neutral-800 mb-3">🥬 告诉我你有什么食材</h2>
-                    <p class="text-neutral-600 text-lg">输入你现有的食材,让AI大师为你创造美味</p>
+            <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>
 
-                <div class="space-y-6">
-                    <!-- 食材输入框 -->
-                    <div class="relative">
+                <!-- 食材输入框 -->
+                <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
-                            class="flex flex-wrap gap-3 min-h-[80px] p-6 border-3 border-dashed border-accent-300 rounded-2xl bg-gradient-to-r from-accent-50 to-primary-50 hover:border-accent-400 transition-all duration-300"
+                            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"
                         >
-                            <div
-                                v-for="ingredient in ingredients"
-                                :key="ingredient"
-                                class="inline-flex items-center gap-2 bg-accent-500 text-white px-4 py-2 rounded-full text-base font-medium shadow-md 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-sm">✕</span>
-                                </button>
-                            </div>
-                            <input
-                                v-model="currentIngredient"
-                                @keyup.enter="addIngredient"
-                                placeholder="输入食材名称,按回车添加..."
-                                class="flex-1 min-w-[250px] bg-transparent outline-none text-neutral-800 placeholder-neutral-500 text-lg font-medium"
-                            />
+                            {{ ingredient }}
+                            <button @click="removeIngredient(ingredient)" class="hover:bg-accent-700 rounded-full p-1 transition-colors">
+                                <span class="text-lg">✕</span>
+                            </button>
                         </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 class="text-center">
-                        <button
-                            @click="addIngredient"
-                            :disabled="!currentIngredient.trim()"
-                            class="bg-gradient-to-r from-accent-500 to-accent-600 hover:from-accent-600 hover:to-accent-700 disabled:from-gray-300 disabled:to-gray-400 text-white px-8 py-3 rounded-xl font-bold text-lg shadow-lg transition-all duration-300 transform hover:scale-105 disabled:scale-100"
-                        >
-                            ➕ 添加食材
-                        </button>
-                    </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>
                 </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">
@@ -67,39 +70,28 @@
                         </h3>
                     </summary>
 
-                    <div class="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3">
+                    <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-3 rounded-lg border transition-all duration-200 hover:scale-102',
+                                'cursor-pointer p-4 rounded-xl border-2 transition-all duration-200 hover:scale-105',
                                 selectedCuisines.includes(cuisine.id)
-                                    ? 'border-accent-400 bg-accent-100 shadow-md'
-                                    : 'border-neutral-200 bg-white hover:border-accent-300 hover:shadow-sm'
+                                    ? '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-2xl mb-1">{{ cuisine.avatar }}</div>
-                                <h4 class="font-medium text-neutral-800 text-sm">{{ cuisine.name }}</h4>
-                                <p class="text-xs text-neutral-500 mt-1">{{ cuisine.specialty }}</p>
+                                <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>
                     </div>
                 </details>
             </div>
 
-            <!-- 生成按钮 -->
-            <div class="text-center mb-8">
-                <button
-                    @click="generateRecipes"
-                    :disabled="ingredients.length === 0 || isLoading"
-                    class="bg-gradient-to-r from-accent-500 to-accent-600 hover:from-accent-600 hover:to-accent-700 disabled:from-gray-300 disabled:to-gray-400 text-white px-16 py-5 rounded-2xl font-bold text-xl shadow-2xl 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>
-            </div>
-
             <!-- 加载动效 -->
             <div v-if="isLoading" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
                 <CookingLoader />
@@ -109,8 +101,25 @@
             <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 v-for="recipe in recipes" :key="recipe.id" class="bg-white/95 backdrop-blur-sm rounded-3xl shadow-2xl p-8 border border-accent-200">
-                    <RecipeCard :recipe="recipe" />
+                <!-- 菜谱网格布局 -->
+                <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>
+                </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>
             </div>
         </main>
@@ -118,7 +127,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive } from 'vue'
+import { ref } from 'vue'
 import { cuisines } from '@/config/cuisines'
 import RecipeCard from '@/components/RecipeCard.vue'
 import CookingLoader from '@/components/CookingLoader.vue'
@@ -169,7 +178,7 @@ const generateRecipes = async () => {
     recipes.value = []
 
     try {
-        // 如果没有选择菜系,随机选择2-3个
+        // 根据选择的菜系数量决定生成菜谱
         let selectedCuisineObjects = cuisines.filter(c => selectedCuisines.value.includes(c.id))
 
         if (selectedCuisineObjects.length === 0) {
@@ -195,13 +204,20 @@ const generateRecipes = async () => {
 const simulateAICall = async () => {
     return new Promise(resolve => {
         setTimeout(() => {
+            // 获取要使用的菜系
+            let cuisinesToUse = cuisines.filter(c => selectedCuisines.value.includes(c.id))
+            if (cuisinesToUse.length === 0) {
+                // 随机选择2-3个菜系
+                const shuffled = [...cuisines].sort(() => 0.5 - Math.random())
+                cuisinesToUse = shuffled.slice(0, Math.floor(Math.random() * 2) + 2)
+            }
+
             // 模拟生成菜谱数据
-            const mockRecipes: Recipe[] = selectedCuisines.value.map((cuisineId, index) => {
-                const cuisine = cuisines.find(c => c.id === cuisineId)
+            const mockRecipes: Recipe[] = cuisinesToUse.map((cuisine, index) => {
                 return {
-                    id: `recipe-${cuisineId}-${Date.now()}`,
-                    name: `${cuisine?.name}推荐:${ingredients.value.join('')}炒制`,
-                    cuisine: cuisine?.name || '',
+                    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 },
@@ -218,7 +234,15 @@ const simulateAICall = async () => {
 
             recipes.value = mockRecipes
             resolve(mockRecipes)
-        }, 2000)
+        }, 3000)
+    })
+}
+
+// 回到顶部功能
+const scrollToTop = () => {
+    window.scrollTo({
+        top: 0,
+        behavior: 'smooth'
     })
 }
 </script>