Ver código fonte

新增酱料设计

liuziting 7 meses atrás
pai
commit
d6a30d1dc6

+ 113 - 26
src/components/GlobalNavigation.vue

@@ -20,10 +20,11 @@
                 </router-link>
 
                 <!-- 导航菜单 -->
-                <div class="flex items-center gap-3">
+                <div class="flex items-center gap-2">
+                    <!-- 主要功能 -->
                     <router-link
                         to="/"
-                        class="flex items-center gap-2 px-4 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
+                        class="flex items-center gap-1 px-3 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
                         :class="$route.path === '/' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
                     >
                         <span>🏠</span>
@@ -31,7 +32,7 @@
                     </router-link>
                     <router-link
                         to="/today-eat"
-                        class="flex items-center gap-2 px-4 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
+                        class="flex items-center gap-1 px-3 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
                         :class="$route.path === '/today-eat' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
                     >
                         <span>🎲</span>
@@ -39,7 +40,7 @@
                     </router-link>
                     <router-link
                         to="/table-design"
-                        class="flex items-center gap-2 px-4 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
+                        class="flex items-center gap-1 px-3 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
                         :class="$route.path === '/table-design' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
                     >
                         <span>🍽️</span>
@@ -47,36 +48,70 @@
                     </router-link>
                     <router-link
                         to="/how-to-cook"
-                        class="flex items-center gap-2 px-4 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
+                        class="flex items-center gap-1 px-3 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
                         :class="$route.path === '/how-to-cook' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
                     >
                         <span>🍳</span>
                         <span>菜谱指南</span>
                     </router-link>
                     <router-link
-                        to="/favorites"
-                        class="flex items-center gap-2 px-4 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
-                        :class="$route.path === '/favorites' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
-                    >
-                        <span>❤️</span>
-                        <span>我的收藏</span>
-                    </router-link>
-                    <router-link
-                        to="/gallery"
-                        class="flex items-center gap-2 px-4 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
-                        :class="$route.path === '/gallery' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
-                    >
-                        <span>🖼️</span>
-                        <span>封神图鉴</span>
-                    </router-link>
-                    <router-link
-                        to="/about"
-                        class="flex items-center gap-2 px-4 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
-                        :class="$route.path === '/about' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
+                        to="/sauce-design"
+                        class="flex items-center gap-1 px-3 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm"
+                        :class="$route.path === '/sauce-design' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
                     >
-                        <span>📖</span>
-                        <span>关于我们</span>
+                        <span>🥄</span>
+                        <span>酱料设计</span>
                     </router-link>
+                    
+                    <!-- 更多菜单下拉 -->
+                    <div class="relative" @mouseleave="handleMouseLeave">
+                        <button
+                            @mouseenter="handleMouseEnter"
+                            @click="showMoreMenu = !showMoreMenu"
+                            :class="[
+                                'flex items-center gap-1 px-3 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 transform hover:scale-105 text-sm',
+                                isMoreMenuActive ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+                            ]"
+                        >
+                            <span>⋯</span>
+                            <span>更多</span>
+                        </button>
+                        
+                        <!-- 下拉菜单 -->
+                        <div
+                            v-if="showMoreMenu"
+                            @mouseenter="handleMouseEnter"
+                            class="absolute right-0 top-full mt-0.5 w-40 bg-white border-2 border-[#0A0910] rounded-lg shadow-lg z-50 overflow-hidden"
+                        >
+                            <router-link
+                                to="/favorites"
+                                @click="showMoreMenu = false"
+                                class="flex items-center gap-2 px-4 py-3 text-sm font-bold transition-colors duration-200 hover:bg-gray-100"
+                                :class="$route.path === '/favorites' ? 'bg-yellow-100 text-gray-800' : 'text-gray-700'"
+                            >
+                                <span>❤️</span>
+                                <span>我的收藏</span>
+                            </router-link>
+                            <router-link
+                                to="/gallery"
+                                @click="showMoreMenu = false"
+                                class="flex items-center gap-2 px-4 py-3 text-sm font-bold transition-colors duration-200 hover:bg-gray-100"
+                                :class="$route.path === '/gallery' ? 'bg-yellow-100 text-gray-800' : 'text-gray-700'"
+                            >
+                                <span>🖼️</span>
+                                <span>封神图鉴</span>
+                            </router-link>
+                            <router-link
+                                to="/about"
+                                @click="showMoreMenu = false"
+                                class="flex items-center gap-2 px-4 py-3 text-sm font-bold transition-colors duration-200 hover:bg-gray-100"
+                                :class="$route.path === '/about' ? 'bg-yellow-100 text-gray-800' : 'text-gray-700'"
+                            >
+                                <span>📖</span>
+                                <span>关于我们</span>
+                            </router-link>
+                        </div>
+                    </div>
                 </div>
             </div>
 
@@ -143,6 +178,15 @@
                         <span>🍳</span>
                         <span>菜谱指南</span>
                     </router-link>
+                    <router-link
+                        to="/sauce-design"
+                        @click="showMobileMenu = false"
+                        class="flex items-center gap-2 w-full px-3 py-2 rounded-lg font-bold border-2 border-[#0A0910] transition-all duration-200 text-sm"
+                        :class="$route.path === '/sauce-design' ? 'bg-yellow-400 text-gray-800' : 'bg-gray-100 text-gray-700 hover:bg-gray-200'"
+                    >
+                        <span>🥄</span>
+                        <span>酱料设计</span>
+                    </router-link>
                     <router-link
                         to="/favorites"
                         @click="showMobileMenu = false"
@@ -181,7 +225,9 @@ import { ref, computed } from 'vue'
 import { useRoute } from 'vue-router'
 
 const showMobileMenu = ref(false)
+const showMoreMenu = ref(false)
 const isLogoRotating = ref(false)
+let hideMenuTimer: NodeJS.Timeout | null = null
 
 const rotateLogo = () => {
     isLogoRotating.value = true
@@ -202,6 +248,8 @@ const pageTitle = computed(() => {
             return '一桌好菜师'
         case '/how-to-cook':
             return '菜谱指南'
+        case '/sauce-design':
+            return '酱料设计大师'
         case '/favorites':
             return '我的收藏'
         case '/gallery':
@@ -223,6 +271,8 @@ const pageSubtitle = computed(() => {
             return '让每顿饭,都有剧本!'
         case '/how-to-cook':
             return 'AI大师手把手教学!'
+        case '/sauce-design':
+            return '专业酱料制作,调味灵魂升华!'
         case '/favorites':
             return '珍藏美味,随时回味!'
         case '/gallery':
@@ -233,6 +283,27 @@ const pageSubtitle = computed(() => {
             return 'LEGENDARY STATUS FROM A SINGLE MEAL!'
     }
 })
+
+// 检查更多菜单中的页面是否处于活跃状态
+const isMoreMenuActive = computed(() => {
+    return ['/favorites', '/gallery', '/about'].includes(route.path)
+})
+
+// 处理鼠标进入事件
+const handleMouseEnter = () => {
+    if (hideMenuTimer) {
+        clearTimeout(hideMenuTimer)
+        hideMenuTimer = null
+    }
+    showMoreMenu.value = true
+}
+
+// 处理鼠标离开事件
+const handleMouseLeave = () => {
+    hideMenuTimer = setTimeout(() => {
+        showMoreMenu.value = false
+    }, 150) // 150ms延迟,给用户足够时间移动鼠标
+}
 </script>
 
 <style scoped>
@@ -270,4 +341,20 @@ const pageSubtitle = computed(() => {
 .space-y-2 {
     animation: slideDown 0.3s ease-out;
 }
+
+/* 下拉菜单动画 */
+@keyframes dropDown {
+    from {
+        opacity: 0;
+        transform: translateY(-10px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+.absolute {
+    animation: dropDown 0.2s ease-out;
+}
 </style>

+ 212 - 0
src/components/SauceRecipe.vue

@@ -0,0 +1,212 @@
+<template>
+    <div class="bg-white rounded-lg border-2 border-[#333333] overflow-hidden">
+        <!-- 酱料标题区域 -->
+        <div class="bg-gradient-to-r from-orange-500 to-red-500 text-white p-4 md:p-6">
+            <div class="flex items-center justify-between">
+                <div>
+                    <h2 class="text-2xl font-bold mb-2">{{ sauce.name }}</h2>
+                    <div class="flex items-center gap-4 text-sm">
+                        <span class="flex items-center gap-1">
+                            <span>{{ getCategoryIcon(sauce.category) }}</span>
+                            <span>{{ getCategoryName(sauce.category) }}</span>
+                        </span>
+                        <span class="flex items-center gap-1">
+                            <span>⏱️</span>
+                            <span>{{ sauce.makingTime }}分钟</span>
+                        </span>
+                        <span :class="[
+                            'px-2 py-1 rounded-full text-xs font-medium',
+                            getDifficultyStyle(sauce.difficulty)
+                        ]">
+                            {{ getDifficultyName(sauce.difficulty) }}
+                        </span>
+                    </div>
+                </div>
+
+            </div>
+            
+            <!-- 口味特征 -->
+            <div class="mt-4 grid grid-cols-4 gap-2 md:gap-4">
+                <div class="text-center">
+                    <div class="text-xs opacity-80 mb-1">🌶️ 辣度</div>
+                    <div class="text-xl font-bold text-red-200">
+                        {{ sauce.spiceLevel || 0 }}
+                    </div>
+                </div>
+                <div class="text-center">
+                    <div class="text-xs opacity-80 mb-1">🍯 甜度</div>
+                    <div class="text-xl font-bold text-yellow-200">
+                        {{ sauce.sweetLevel || 0 }}
+                    </div>
+                </div>
+                <div class="text-center">
+                    <div class="text-xs opacity-80 mb-1">🧂 咸度</div>
+                    <div class="text-xl font-bold text-blue-200">
+                        {{ sauce.saltLevel || 0 }}
+                    </div>
+                </div>
+                <div class="text-center">
+                    <div class="text-xs opacity-80 mb-1">🍋 酸度</div>
+                    <div class="text-xl font-bold text-green-200">
+                        {{ sauce.sourLevel || 0 }}
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <div class="p-4 md:p-6">
+            <!-- 酱料描述 -->
+            <div v-if="sauce.description" class="mb-6 p-4 bg-orange-50 rounded-lg border border-orange-200">
+                <h3 class="font-bold text-gray-800 mb-2 flex items-center gap-2">
+                    <span>📝</span>
+                    <span>酱料特色</span>
+                </h3>
+                <p class="text-gray-700">{{ sauce.description }}</p>
+            </div>
+
+            <!-- 食材清单 -->
+            <div class="mb-6">
+                <h3 class="font-bold text-lg text-gray-800 mb-3 flex items-center gap-2">
+                    <span>🛒</span>
+                    <span>食材清单</span>
+                </h3>
+                <div class="grid md:grid-cols-2 gap-2">
+                    <div
+                        v-for="(ingredient, index) in sauce.ingredients"
+                        :key="index"
+                        class="flex items-center gap-2 p-2 bg-gray-50 rounded-lg"
+                    >
+                        <span class="w-2 h-2 bg-orange-400 rounded-full"></span>
+                        <span class="text-gray-700">{{ ingredient }}</span>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 制作步骤 -->
+            <div class="mb-6">
+                <h3 class="font-bold text-lg text-gray-800 mb-3 flex items-center gap-2">
+                    <span>👨‍🍳</span>
+                    <span>制作步骤</span>
+                </h3>
+                <div class="space-y-4">
+                    <div
+                        v-for="step in sauce.steps"
+                        :key="step.step"
+                        class="flex gap-4 p-4 bg-gray-50 rounded-lg border-l-4 border-orange-400"
+                    >
+                        <div class="flex-shrink-0">
+                            <div class="w-8 h-8 bg-orange-500 text-white rounded-full flex items-center justify-center font-bold text-sm">
+                                {{ step.step }}
+                            </div>
+                        </div>
+                        <div class="flex-1">
+                            <p class="text-gray-800 mb-2">{{ step.description }}</p>
+                            <div class="flex gap-4 text-sm text-gray-600">
+                                <span v-if="step.time" class="flex items-center gap-1">
+                                    <span>⏱️</span>
+                                    <span>{{ step.time }}分钟</span>
+                                </span>
+                                <span v-if="step.temperature" class="flex items-center gap-1">
+                                    <span>🔥</span>
+                                    <span>{{ step.temperature }}</span>
+                                </span>
+                                <span v-if="step.technique" class="flex items-center gap-1">
+                                    <span>🥄</span>
+                                    <span>{{ step.technique }}</span>
+                                </span>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 制作技巧 -->
+            <div v-if="sauce.tips.length > 0" class="mb-6">
+                <h3 class="font-bold text-lg text-gray-800 mb-3 flex items-center gap-2">
+                    <span>💡</span>
+                    <span>制作技巧</span>
+                </h3>
+                <div class="space-y-2">
+                    <div
+                        v-for="(tip, index) in sauce.tips"
+                        :key="index"
+                        class="flex items-start gap-2 p-3 bg-yellow-50 rounded-lg border border-yellow-200"
+                    >
+                        <span class="text-yellow-600 mt-0.5">💡</span>
+                        <span class="text-gray-700">{{ tip }}</span>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 保存方法 -->
+            <div class="mb-6">
+                <h3 class="font-bold text-lg text-gray-800 mb-3 flex items-center gap-2">
+                    <span>📦</span>
+                    <span>保存方法</span>
+                </h3>
+                <div class="p-4 bg-blue-50 rounded-lg border border-blue-200">
+                    <div class="grid md:grid-cols-3 gap-4 text-sm">
+                        <div class="flex items-center gap-2">
+                            <span class="text-blue-600">📦</span>
+                            <span class="text-gray-700">{{ sauce.storage.method }}</span>
+                        </div>
+                        <div class="flex items-center gap-2">
+                            <span class="text-blue-600">⏰</span>
+                            <span class="text-gray-700">{{ sauce.storage.duration }}</span>
+                        </div>
+                        <div class="flex items-center gap-2">
+                            <span class="text-blue-600">🌡️</span>
+                            <span class="text-gray-700">{{ sauce.storage.temperature }}</span>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 搭配建议 -->
+            <div v-if="sauce.pairings.length > 0" class="mb-6">
+                <h3 class="font-bold text-lg text-gray-800 mb-3 flex items-center gap-2">
+                    <span>🍽️</span>
+                    <span>搭配建议</span>
+                </h3>
+                <div class="flex flex-wrap gap-2">
+                    <span
+                        v-for="pairing in sauce.pairings"
+                        :key="pairing"
+                        class="px-3 py-2 bg-green-100 text-green-800 rounded-full text-sm border border-green-200"
+                    >
+                        {{ pairing }}
+                    </span>
+                </div>
+            </div>
+
+            <!-- 标签 -->
+            <div v-if="sauce.tags.length > 0" class="mb-4">
+                <div class="flex flex-wrap gap-2">
+                    <span
+                        v-for="tag in sauce.tags"
+                        :key="tag"
+                        class="px-2 py-1 bg-gray-200 text-gray-700 rounded-full text-xs"
+                    >
+                        #{{ tag }}
+                    </span>
+                </div>
+            </div>
+
+
+        </div>
+    </div>
+</template>
+
+<script setup lang="ts">
+import type { SauceRecipe } from '@/types'
+import { getCategoryIcon, getCategoryName, getDifficultyStyle, getDifficultyName } from '@/utils/sauceHelpers'
+
+interface Props {
+    sauce: SauceRecipe
+    showActions?: boolean
+}
+
+const props = withDefaults(defineProps<Props>(), {
+    showActions: false
+})
+</script>

+ 152 - 0
src/config/sauces.ts

@@ -0,0 +1,152 @@
+import type { SauceCategoryConfig, SauceCategory } from '@/types'
+
+// 酱料分类配置
+export const sauceCategories: SauceCategoryConfig[] = [
+    {
+        id: 'spicy',
+        name: '辣味酱料',
+        description: '各种辣椒酱、辣油、麻辣酱料',
+        icon: '🌶️',
+        color: 'bg-red-500',
+        examples: ['辣椒酱', '蒜蓉辣椒酱', '麻辣油', '韩式辣椒酱', '泰式甜辣酱']
+    },
+    {
+        id: 'garlic',
+        name: '蒜香酱料',
+        description: '以大蒜为主的香味酱料',
+        icon: '🧄',
+        color: 'bg-green-500',
+        examples: ['蒜蓉酱', '蒜泥白肉酱', '蒜香辣椒油', '黑蒜酱', '蒜蓉豆豉酱']
+    },
+    {
+        id: 'sweet',
+        name: '甜味酱料',
+        description: '甜口酱料和果酱类',
+        icon: '🍯',
+        color: 'bg-yellow-500',
+        examples: ['甜面酱', '海鲜酱', '糖醋酱', '蜂蜜芥末酱', '果酱']
+    },
+    {
+        id: 'complex',
+        name: '复合调味酱',
+        description: '多种调料混合的复合酱料',
+        icon: '🥄',
+        color: 'bg-purple-500',
+        examples: ['XO酱', '沙茶酱', '黑椒酱', '蘑菇酱', '咖喱酱']
+    },
+    {
+        id: 'regional',
+        name: '地方特色酱',
+        description: '各地传统特色酱料',
+        icon: '🏮',
+        color: 'bg-orange-500',
+        examples: ['郫县豆瓣酱', '老干妈', '柱候酱', '海天黄豆酱', '东北大酱']
+    },
+    {
+        id: 'fusion',
+        name: '创新融合酱',
+        description: '现代创新和中西融合酱料',
+        icon: '✨',
+        color: 'bg-pink-500',
+        examples: ['芝士酱', '牛油果酱', '芝麻酱', '花生酱', '创意调味酱']
+    }
+]
+
+// 使用场景配置
+export const useCaseOptions = [
+    { id: 'noodles', name: '拌面', icon: '🍜' },
+    { id: 'dipping', name: '蘸菜', icon: '🥢' },
+    { id: 'cooking', name: '炒菜', icon: '🍳' },
+    { id: 'bbq', name: '烧烤', icon: '🔥' },
+    { id: 'hotpot', name: '火锅', icon: '🍲' }
+]
+
+// 预设酱料模板
+export const sauceTemplates = [
+    {
+        name: '蒜蓉辣椒酱',
+        category: 'spicy' as SauceCategory,
+        tags: ['经典', '下饭', '万能'],
+        spiceLevel: 4,
+        sweetLevel: 1,
+        saltLevel: 3,
+        sourLevel: 1
+    },
+    {
+        name: '甜面酱',
+        category: 'sweet' as SauceCategory,
+        tags: ['传统', '烤鸭', '甜口'],
+        spiceLevel: 0,
+        sweetLevel: 4,
+        saltLevel: 2,
+        sourLevel: 0
+    },
+    {
+        name: 'XO酱',
+        category: 'complex' as SauceCategory,
+        tags: ['高级', '海鲜', '港式'],
+        spiceLevel: 2,
+        sweetLevel: 2,
+        saltLevel: 4,
+        sourLevel: 1
+    },
+    {
+        name: '郫县豆瓣酱',
+        category: 'regional' as SauceCategory,
+        tags: ['川菜', '传统', '发酵'],
+        spiceLevel: 3,
+        sweetLevel: 1,
+        saltLevel: 4,
+        sourLevel: 2
+    },
+    {
+        name: '蒜泥白肉酱',
+        category: 'garlic' as SauceCategory,
+        tags: ['川菜', '蒜香', '清爽'],
+        spiceLevel: 1,
+        sweetLevel: 1,
+        saltLevel: 3,
+        sourLevel: 3
+    },
+    {
+        name: '芝麻花生酱',
+        category: 'fusion' as SauceCategory,
+        tags: ['创新', '香浓', '营养'],
+        spiceLevel: 0,
+        sweetLevel: 2,
+        saltLevel: 2,
+        sourLevel: 0
+    }
+]
+
+// 口味等级描述
+export const tasteDescriptions = {
+    spice: ['不辣', '微辣', '中辣', '很辣', '超辣'],
+    sweet: ['不甜', '微甜', '中甜', '很甜', '超甜'],
+    salt: ['不咸', '微咸', '中咸', '很咸', '超咸'],
+    sour: ['不酸', '微酸', '中酸', '很酸', '超酸']
+}
+
+// 难度等级描述
+export const difficultyDescriptions = {
+    easy: { name: '简单', color: 'text-green-600', description: '新手友好,步骤简单' },
+    medium: { name: '中等', color: 'text-yellow-600', description: '需要一定经验' },
+    hard: { name: '困难', color: 'text-red-600', description: '需要丰富经验和技巧' }
+}
+
+// 获取分类配置
+export const getSauceCategoryById = (id: SauceCategory): SauceCategoryConfig | undefined => {
+    return sauceCategories.find(category => category.id === id)
+}
+
+// 获取分类颜色
+export const getCategoryColor = (category: SauceCategory): string => {
+    const config = getSauceCategoryById(category)
+    return config?.color || 'bg-gray-500'
+}
+
+// 获取分类图标
+export const getCategoryIcon = (category: SauceCategory): string => {
+    const config = getSauceCategoryById(category)
+    return config?.icon || '🥄'
+}

+ 3 - 1
src/main.ts

@@ -8,6 +8,7 @@ import TableDesign from './views/TableDesign.vue'
 import Favorites from './views/Favorites.vue'
 import Gallery from './views/Gallery.vue'
 import HowToCook from './views/HowToCook.vue'
+import SauceDesign from './views/SauceDesign.vue'
 import './style.css'
 
 const routes = [
@@ -17,7 +18,8 @@ const routes = [
     { path: '/table-design', component: TableDesign },
     { path: '/favorites', component: Favorites },
     { path: '/gallery', component: Gallery },
-    { path: '/how-to-cook', component: HowToCook }
+    { path: '/how-to-cook', component: HowToCook },
+    { path: '/sauce-design', component: SauceDesign }
 ]
 
 const router = createRouter({

+ 371 - 13
src/services/aiService.ts

@@ -1,19 +1,19 @@
 import axios from 'axios'
-import type { Recipe, CuisineType, NutritionAnalysis, WinePairing } from '@/types'
+import type { Recipe, CuisineType, NutritionAnalysis, WinePairing, SauceRecipe, SaucePreference, CustomSauceRequest } from '@/types'
 
 // AI服务配置 - 从环境变量读取
 const AI_CONFIG = {
-    // baseURL: 'https://api.deepseek.com/v1/',
-    // apiKey: import.meta.env.VITE_TEXT_GENERATION_API_KEY,
-    // model: 'deepseek-chat',
-    // temperature: 0.7,
-    // timeout: 300000
-
-    baseURL: 'https://open.bigmodel.cn/api/paas/v4/',
-    apiKey: import.meta.env.VITE_IMAGE_GENERATION_API_KEY,
-    model: 'glm-4-flash-250414',
+    baseURL: 'https://api.deepseek.com/v1/',
+    apiKey: import.meta.env.VITE_TEXT_GENERATION_API_KEY,
+    model: 'deepseek-chat',
     temperature: 0.7,
     timeout: 300000
+
+    // baseURL: 'https://open.bigmodel.cn/api/paas/v4/',
+    // apiKey: import.meta.env.VITE_IMAGE_GENERATION_API_KEY,
+    // model: 'glm-4-flash-250414',
+    // temperature: 0.7,
+    // timeout: 300000
 }
 
 // 创建axios实例
@@ -177,9 +177,8 @@ export const generateTableMenu = async (config: {
 - 营养搭配:${nutritionMap[config.nutritionFocus] || '营养均衡'}`
 
         if (config.customDishes.length > 0) {
-            prompt += `\n- ${config.flexibleCount ? '优先考虑的菜品' : '必须包含的菜品'}:${config.customDishes.join('、')}${
-                config.flexibleCount ? '(可以作为参考,根据搭配需要决定是否全部包含)' : '(请确保这些菜品都包含在菜单中)'
-            }`
+            prompt += `\n- ${config.flexibleCount ? '优先考虑的菜品' : '必须包含的菜品'}:${config.customDishes.join('、')}${config.flexibleCount ? '(可以作为参考,根据搭配需要决定是否全部包含)' : '(请确保这些菜品都包含在菜单中)'
+                }`
         }
 
         if (config.customRequirement) {
@@ -819,5 +818,364 @@ export const generateDishRecipeByName = async (dishName: string): Promise<Recipe
     }
 }
 
+/**
+ * 根据酱料名称生成详细制作方法
+ * @param sauceName 酱料名称
+ * @returns Promise<SauceRecipe>
+ */
+export const generateSauceRecipe = async (sauceName: string): Promise<SauceRecipe> => {
+    try {
+        const prompt = `请为"${sauceName}"这种酱料生成详细的制作教程。
+
+要求:
+1. 提供完整的食材清单(主料、辅料、调料)
+2. 详细的制作步骤,包含具体操作方法、时间、温度控制
+3. 实用的制作技巧和注意事项
+4. 保存方法和保质期信息
+5. 推荐的搭配菜品
+6. 口味特点描述
+
+请按照以下JSON格式返回酱料配方:
+{
+  "name": "酱料名称",
+  "category": "spicy/garlic/sweet/complex/regional/fusion",
+  "ingredients": ["主料1 200g", "调料1 适量", "调料2 1勺"],
+  "steps": [
+    {
+      "step": 1,
+      "description": "详细的步骤描述",
+      "time": 5,
+      "temperature": "中火",
+      "technique": "炒制"
+    }
+  ],
+  "makingTime": 30,
+  "difficulty": "easy/medium/hard",
+  "tips": ["制作技巧1", "注意事项2"],
+  "storage": {
+    "method": "密封冷藏",
+    "duration": "1个月",
+    "temperature": "4°C"
+  },
+  "pairings": ["搭配菜品1", "搭配菜品2"],
+  "tags": ["标签1", "标签2"],
+  "spiceLevel": 3,
+  "sweetLevel": 1,
+  "saltLevel": 4,
+  "sourLevel": 2,
+  "description": "酱料特色描述"
+}`
+
+        const response = await aiClient.post('/chat/completions', {
+            model: AI_CONFIG.model,
+            messages: [
+                {
+                    role: 'system',
+                    content: '你是一位专业的酱料制作大师,精通各种传统和创新酱料的制作方法。请根据用户提供的酱料名称,生成详细、实用的制作教程。请严格按照JSON格式返回,不要包含任何其他文字。请务必用中文回答。'
+                },
+                {
+                    role: 'user',
+                    content: prompt
+                }
+            ],
+            temperature: 0.7,
+            stream: false
+        })
+
+        const aiResponse = response.data.choices[0].message.content
+        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 sauceData = JSON.parse(cleanResponse)
+
+        const sauceRecipe: SauceRecipe = {
+            id: `sauce-${Date.now()}`,
+            name: sauceData.name || sauceName,
+            category: sauceData.category || 'complex',
+            ingredients: sauceData.ingredients || ['主要食材', '调料'],
+            steps: sauceData.steps || [
+                { step: 1, description: '准备所有食材', time: 5 },
+                { step: 2, description: '按照传统方法制作', time: 20 }
+            ],
+            makingTime: sauceData.makingTime || 25,
+            difficulty: sauceData.difficulty || 'medium',
+            tips: sauceData.tips || ['注意火候控制', '调味要适中'],
+            storage: sauceData.storage || {
+                method: '密封保存',
+                duration: '1周',
+                temperature: '常温'
+            },
+            pairings: sauceData.pairings || ['面条', '蔬菜'],
+            tags: sauceData.tags || ['家常', '经典'],
+            spiceLevel: sauceData.spiceLevel || 2,
+            sweetLevel: sauceData.sweetLevel || 2,
+            saltLevel: sauceData.saltLevel || 3,
+            sourLevel: sauceData.sourLevel || 2,
+            description: sauceData.description || '经典酱料配方'
+        }
+
+        return sauceRecipe
+    } catch (error) {
+        console.error(`生成"${sauceName}"酱料配方失败:`, error)
+        throw new Error(`AI生成"${sauceName}"酱料配方失败,请稍后重试`)
+    }
+}
+
+/**
+ * 根据用户偏好推荐酱料
+ * @param preferences 用户偏好配置
+ * @returns Promise<string[]>
+ */
+export const recommendSauces = async (preferences: SaucePreference): Promise<string[]> => {
+    try {
+        const useCaseMap = {
+            noodles: '拌面',
+            dipping: '蘸菜',
+            cooking: '炒菜',
+            bbq: '烧烤',
+            hotpot: '火锅'
+        }
+
+        const useCaseText = preferences.useCase.map(uc => useCaseMap[uc] || uc).join('、')
+        const ingredientsText = preferences.availableIngredients.length > 0
+            ? preferences.availableIngredients.join('、')
+            : '无特殊要求'
+
+        const prompt = `请根据以下用户偏好推荐合适的酱料:
+
+用户偏好:
+- 辣度偏好:${preferences.spiceLevel}/5
+- 甜度偏好:${preferences.sweetLevel}/5  
+- 咸度偏好:${preferences.saltLevel}/5
+- 酸度偏好:${preferences.sourLevel}/5
+- 使用场景:${useCaseText}
+- 现有食材:${ingredientsText}
+
+请推荐5-8种最适合的酱料,按匹配度排序。
+
+请按照以下JSON格式返回推荐结果:
+{
+  "recommendations": [
+    "酱料名称1",
+    "酱料名称2",
+    "酱料名称3"
+  ]
+}`
+
+        const response = await aiClient.post('/chat/completions', {
+            model: AI_CONFIG.model,
+            messages: [
+                {
+                    role: 'system',
+                    content: '你是一位专业的酱料推荐专家,能够根据用户的口味偏好和使用场景推荐最合适的酱料。请严格按照JSON格式返回,不要包含任何其他文字。请务必用中文回答。'
+                },
+                {
+                    role: 'user',
+                    content: prompt
+                }
+            ],
+            temperature: 0.8,
+            stream: false
+        })
+
+        const aiResponse = response.data.choices[0].message.content
+        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 recommendationData = JSON.parse(cleanResponse)
+        return recommendationData.recommendations || []
+    } catch (error) {
+        console.error('获取酱料推荐失败:', error)
+        throw new Error('AI推荐酱料失败,请稍后重试')
+    }
+}
+
+/**
+ * 创建自定义酱料配方
+ * @param request 自定义酱料创作请求
+ * @returns Promise<SauceRecipe>
+ */
+export const createCustomSauce = async (request: CustomSauceRequest): Promise<SauceRecipe> => {
+    try {
+        const baseTypeMap = {
+            oil: '油性酱料',
+            water: '水性酱料',
+            paste: '膏状酱料',
+            granular: '颗粒状酱料'
+        }
+
+        const flavorMap = {
+            spicy: '辣味',
+            sweet: '甜味',
+            sour: '酸味',
+            umami: '鲜味',
+            aromatic: '香味'
+        }
+
+        const prompt = `请根据以下要求创作一个独特的酱料配方:
+
+创作要求:
+- 基础类型:${baseTypeMap[request.baseType]}
+- 主要风味:${flavorMap[request.flavorDirection]}
+- 特殊食材:${request.specialIngredients.join('、')}
+- 期望口感:${request.expectedTexture}
+- 用途:${request.intendedUse}
+${request.customRequirements ? `- 特殊要求:${request.customRequirements}` : ''}
+
+请创作一个创新的酱料配方,要有独特性和实用性。
+
+请按照以下JSON格式返回酱料配方:
+{
+  "name": "创新酱料名称",
+  "category": "fusion",
+  "ingredients": ["主料1 200g", "调料1 适量"],
+  "steps": [
+    {
+      "step": 1,
+      "description": "详细步骤描述",
+      "time": 5,
+      "temperature": "中火",
+      "technique": "制作技法"
+    }
+  ],
+  "makingTime": 30,
+  "difficulty": "medium",
+  "tips": ["创作技巧1", "注意事项2"],
+  "storage": {
+    "method": "保存方法",
+    "duration": "保质期",
+    "temperature": "保存温度"
+  },
+  "pairings": ["搭配建议1", "搭配建议2"],
+  "tags": ["创新", "自制"],
+  "spiceLevel": 3,
+  "sweetLevel": 2,
+  "saltLevel": 3,
+  "sourLevel": 2,
+  "description": "创新酱料特色描述"
+}`
+
+        const response = await aiClient.post('/chat/completions', {
+            model: AI_CONFIG.model,
+            messages: [
+                {
+                    role: 'system',
+                    content: '你是一位富有创意的酱料创作大师,擅长根据用户需求创作独特的酱料配方。请严格按照JSON格式返回,不要包含任何其他文字。请务必用中文回答。'
+                },
+                {
+                    role: 'user',
+                    content: prompt
+                }
+            ],
+            temperature: 0.9,
+            stream: false
+        })
+
+        const aiResponse = response.data.choices[0].message.content
+        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 sauceData = JSON.parse(cleanResponse)
+
+        const customSauce: SauceRecipe = {
+            id: `custom-sauce-${Date.now()}`,
+            name: sauceData.name || '自创酱料',
+            category: 'fusion',
+            ingredients: sauceData.ingredients || ['创新食材'],
+            steps: sauceData.steps || [
+                { step: 1, description: '准备创新食材', time: 5 },
+                { step: 2, description: '按照创意方法制作', time: 20 }
+            ],
+            makingTime: sauceData.makingTime || 25,
+            difficulty: sauceData.difficulty || 'medium',
+            tips: sauceData.tips || ['发挥创意', '调整口味'],
+            storage: sauceData.storage || {
+                method: '密封保存',
+                duration: '1周',
+                temperature: '冷藏'
+            },
+            pairings: sauceData.pairings || ['创意搭配'],
+            tags: sauceData.tags || ['创新', '自制'],
+            spiceLevel: sauceData.spiceLevel || 2,
+            sweetLevel: sauceData.sweetLevel || 2,
+            saltLevel: sauceData.saltLevel || 3,
+            sourLevel: sauceData.sourLevel || 2,
+            description: sauceData.description || '独特的自创酱料'
+        }
+
+        return customSauce
+    } catch (error) {
+        console.error('创建自定义酱料失败:', error)
+        throw new Error('AI创建自定义酱料失败,请稍后重试')
+    }
+}
+
+/**
+ * 获取酱料搭配建议
+ * @param sauceName 酱料名称
+ * @returns Promise<string[]>
+ */
+export const getSaucePairings = async (sauceName: string): Promise<string[]> => {
+    try {
+        const prompt = `请为"${sauceName}"这种酱料推荐最佳的搭配菜品和使用方法。
+
+要求:
+1. 推荐5-8种最适合的搭配菜品
+2. 说明具体的使用方法
+3. 考虑不同的烹饪场景
+
+请按照以下JSON格式返回搭配建议:
+{
+  "pairings": [
+    "搭配菜品1 - 使用方法",
+    "搭配菜品2 - 使用方法",
+    "搭配菜品3 - 使用方法"
+  ]
+}`
+
+        const response = await aiClient.post('/chat/completions', {
+            model: AI_CONFIG.model,
+            messages: [
+                {
+                    role: 'system',
+                    content: '你是一位专业的美食搭配专家,精通各种酱料与菜品的最佳搭配方法。请严格按照JSON格式返回,不要包含任何其他文字。请务必用中文回答。'
+                },
+                {
+                    role: 'user',
+                    content: prompt
+                }
+            ],
+            temperature: 0.7,
+            stream: false
+        })
+
+        const aiResponse = response.data.choices[0].message.content
+        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 pairingData = JSON.parse(cleanResponse)
+        return pairingData.pairings || []
+    } catch (error) {
+        console.error('获取酱料搭配建议失败:', error)
+        return ['面条 - 拌面使用', '蔬菜 - 蘸食使用', '肉类 - 调味使用']
+    }
+}
+
 // 导出配置更新函数,供外部使用
 export { AI_CONFIG }

+ 77 - 0
src/types/index.ts

@@ -100,3 +100,80 @@ export interface AIResponse {
     data: Recipe
     message?: string
 }
+
+// 酱料分类类型
+export type SauceCategory = 'spicy' | 'garlic' | 'sweet' | 'complex' | 'regional' | 'fusion'
+
+// 酱料制作步骤
+export interface SauceStep {
+    step: number
+    description: string
+    time?: number
+    temperature?: string
+    technique?: string
+}
+
+// 酱料保存信息
+export interface StorageInfo {
+    method: string
+    duration: string
+    temperature: string
+}
+
+// 酱料配方类型
+export interface SauceRecipe {
+    id: string
+    name: string
+    category: SauceCategory
+    ingredients: string[]
+    steps: SauceStep[]
+    makingTime: number
+    difficulty: 'easy' | 'medium' | 'hard'
+    tips: string[]
+    storage: StorageInfo
+    pairings: string[]
+    tags: string[]
+    spiceLevel?: number
+    sweetLevel?: number
+    saltLevel?: number
+    sourLevel?: number
+    description?: string
+}
+
+// 酱料偏好配置
+export interface SaucePreference {
+    spiceLevel: number // 1-5
+    sweetLevel: number // 1-5
+    saltLevel: number // 1-5
+    sourLevel: number // 1-5
+    useCase: string[] // ['noodles', 'dipping', 'cooking', 'bbq', 'hotpot']
+    availableIngredients: string[]
+}
+
+// 自定义酱料创作请求
+export interface CustomSauceRequest {
+    baseType: 'oil' | 'water' | 'paste' | 'granular'
+    flavorDirection: 'spicy' | 'sweet' | 'sour' | 'umami' | 'aromatic'
+    specialIngredients: string[]
+    expectedTexture: string
+    intendedUse: string
+    customRequirements?: string
+}
+
+// 酱料分类配置
+export interface SauceCategoryConfig {
+    id: SauceCategory
+    name: string
+    description: string
+    icon: string
+    color: string
+    examples: string[]
+}
+
+// 收藏酱料类型
+export interface FavoriteSauce {
+    id: string
+    sauce: SauceRecipe
+    favoriteDate: string
+    notes?: string
+}

+ 117 - 0
src/utils/sauceHelpers.ts

@@ -0,0 +1,117 @@
+import type { SauceCategory } from '@/types'
+import { sauceCategories } from '@/config/sauces'
+
+// 难度等级描述
+const difficultyDescriptions = {
+    easy: { name: '简单', color: 'text-green-600', description: '新手友好,步骤简单' },
+    medium: { name: '中等', color: 'text-yellow-600', description: '需要一定经验' },
+    hard: { name: '困难', color: 'text-red-600', description: '需要丰富经验和技巧' }
+}
+
+// 获取分类图标
+export const getCategoryIcon = (category: SauceCategory): string => {
+    const config = sauceCategories.find(c => c.id === category)
+    return config?.icon || '🥄'
+}
+
+// 获取分类名称
+export const getCategoryName = (category: SauceCategory): string => {
+    const config = sauceCategories.find(c => c.id === category)
+    return config?.name || '其他酱料'
+}
+
+// 获取分类颜色
+export const getCategoryColor = (category: SauceCategory): string => {
+    const config = sauceCategories.find(c => c.id === category)
+    return config?.color || 'bg-gray-500'
+}
+
+// 获取难度样式
+export const getDifficultyStyle = (difficulty: 'easy' | 'medium' | 'hard'): string => {
+    const styles = {
+        easy: 'bg-green-100 text-green-800 border-green-200',
+        medium: 'bg-yellow-100 text-yellow-800 border-yellow-200',
+        hard: 'bg-red-100 text-red-800 border-red-200'
+    }
+    return `border ${styles[difficulty]}`
+}
+
+// 获取难度名称
+export const getDifficultyName = (difficulty: 'easy' | 'medium' | 'hard'): string => {
+    return difficultyDescriptions[difficulty]?.name || '中等'
+}
+
+// 获取难度描述
+export const getDifficultyDescription = (difficulty: 'easy' | 'medium' | 'hard'): string => {
+    return difficultyDescriptions[difficulty]?.description || '需要一定经验'
+}
+
+// 格式化制作时间
+export const formatMakingTime = (minutes: number): string => {
+    if (minutes < 60) {
+        return `${minutes}分钟`
+    } else {
+        const hours = Math.floor(minutes / 60)
+        const remainingMinutes = minutes % 60
+        return remainingMinutes > 0 ? `${hours}小时${remainingMinutes}分钟` : `${hours}小时`
+    }
+}
+
+// 获取口味等级描述
+export const getTasteLevelDescription = (level: number, type: 'spice' | 'sweet' | 'salt' | 'sour'): string => {
+    const descriptions = {
+        spice: ['不辣', '微辣', '中辣', '很辣', '超辣'],
+        sweet: ['不甜', '微甜', '中甜', '很甜', '超甜'],
+        salt: ['不咸', '微咸', '中咸', '很咸', '超咸'],
+        sour: ['不酸', '微酸', '中酸', '很酸', '超酸']
+    }
+    return descriptions[type][Math.max(0, Math.min(4, level - 1))] || '适中'
+}
+
+// 生成酱料标签
+export const generateSauceTags = (sauce: any): string[] => {
+    const tags: string[] = []
+    
+    // 根据口味特征添加标签
+    if (sauce.spiceLevel && sauce.spiceLevel >= 4) tags.push('超辣')
+    if (sauce.spiceLevel && sauce.spiceLevel <= 1) tags.push('不辣')
+    if (sauce.sweetLevel && sauce.sweetLevel >= 4) tags.push('甜口')
+    if (sauce.saltLevel && sauce.saltLevel >= 4) tags.push('重口味')
+    if (sauce.sourLevel && sauce.sourLevel >= 4) tags.push('酸爽')
+    
+    // 根据制作时间添加标签
+    if (sauce.makingTime <= 15) tags.push('快手')
+    if (sauce.makingTime >= 60) tags.push('慢工细活')
+    
+    // 根据难度添加标签
+    if (sauce.difficulty === 'easy') tags.push('新手友好')
+    if (sauce.difficulty === 'hard') tags.push('高难度')
+    
+    return tags
+}
+
+// 检查是否为素食酱料
+export const isVegetarian = (ingredients: string[]): boolean => {
+    const meatKeywords = ['肉', '鸡', '鱼', '虾', '蟹', '贝', '蛋', '奶', '黄油', '猪油', '牛油']
+    return !ingredients.some(ingredient => 
+        meatKeywords.some(keyword => ingredient.includes(keyword))
+    )
+}
+
+// 估算酱料营养等级
+export const estimateNutritionLevel = (ingredients: string[]): 'low' | 'medium' | 'high' => {
+    const healthyKeywords = ['蔬菜', '水果', '坚果', '豆', '菌', '藻', '醋', '柠檬']
+    const unhealthyKeywords = ['糖', '盐', '油', '味精', '防腐剂']
+    
+    const healthyCount = ingredients.filter(ingredient => 
+        healthyKeywords.some(keyword => ingredient.includes(keyword))
+    ).length
+    
+    const unhealthyCount = ingredients.filter(ingredient => 
+        unhealthyKeywords.some(keyword => ingredient.includes(keyword))
+    ).length
+    
+    if (healthyCount > unhealthyCount) return 'high'
+    if (healthyCount === unhealthyCount) return 'medium'
+    return 'low'
+}

+ 565 - 0
src/views/SauceDesign.vue

@@ -0,0 +1,565 @@
+<template>
+    <div class="min-h-screen bg-orange-400 px-2 md:px-4 py-6">
+        <!-- 全局导航 -->
+        <GlobalNavigation />
+
+        <div class="max-w-7xl mx-auto">
+
+
+
+            <!-- 智能推荐配置区域 -->
+            <div class="mb-8">
+                <div class="bg-blue-500 text-white px-4 py-2 rounded-t-lg border-2 border-[#0A0910] border-b-0 inline-block">
+                    <span class="font-bold">🤖 智能推荐</span>
+                </div>
+                <div class="bg-white border-2 border-[#0A0910] rounded-lg rounded-tl-none p-4 md:p-6">
+                    <div class="grid md:grid-cols-2 gap-6">
+                        <!-- 口味偏好 -->
+                        <div>
+                            <h3 class="font-bold text-lg mb-4 text-gray-800">口味偏好</h3>
+                            <div class="space-y-4">
+                                <div>
+                                    <label class="block text-sm font-medium text-gray-700 mb-2 flex items-center justify-between">
+                                        <span>🌶️ 辣度</span>
+                                        <span class="text-red-600 font-bold">{{ preferences.spiceLevel }}</span>
+                                    </label>
+                                    <input
+                                        v-model="preferences.spiceLevel"
+                                        type="range"
+                                        min="1"
+                                        max="5"
+                                        class="w-full h-2 bg-red-200 rounded-lg appearance-none cursor-pointer slider-red"
+                                    />
+                                </div>
+                                <div>
+                                    <label class="block text-sm font-medium text-gray-700 mb-2 flex items-center justify-between">
+                                        <span>🍯 甜度</span>
+                                        <span class="text-yellow-600 font-bold">{{ preferences.sweetLevel }}</span>
+                                    </label>
+                                    <input
+                                        v-model="preferences.sweetLevel"
+                                        type="range"
+                                        min="1"
+                                        max="5"
+                                        class="w-full h-2 bg-yellow-200 rounded-lg appearance-none cursor-pointer slider-yellow"
+                                    />
+                                </div>
+                                <div>
+                                    <label class="block text-sm font-medium text-gray-700 mb-2 flex items-center justify-between">
+                                        <span>🧂 咸度</span>
+                                        <span class="text-blue-600 font-bold">{{ preferences.saltLevel }}</span>
+                                    </label>
+                                    <input
+                                        v-model="preferences.saltLevel"
+                                        type="range"
+                                        min="1"
+                                        max="5"
+                                        class="w-full h-2 bg-blue-200 rounded-lg appearance-none cursor-pointer slider-blue"
+                                    />
+                                </div>
+                                <div>
+                                    <label class="block text-sm font-medium text-gray-700 mb-2 flex items-center justify-between">
+                                        <span>🍋 酸度</span>
+                                        <span class="text-green-600 font-bold">{{ preferences.sourLevel }}</span>
+                                    </label>
+                                    <input
+                                        v-model="preferences.sourLevel"
+                                        type="range"
+                                        min="1"
+                                        max="5"
+                                        class="w-full h-2 bg-green-200 rounded-lg appearance-none cursor-pointer slider-green"
+                                    />
+                                </div>
+                            </div>
+                        </div>
+
+                        <!-- 使用场景和食材 -->
+                        <div>
+                            <h3 class="font-bold text-lg mb-4 text-gray-800">使用场景</h3>
+                            <div class="grid grid-cols-2 gap-2 mb-4">
+                                <button
+                                    v-for="useCase in useCaseOptions"
+                                    :key="useCase.id"
+                                    @click="toggleUseCase(useCase.id)"
+                                    :class="[
+                                        'p-3 rounded-lg border-2 border-[#0A0910] text-sm font-medium transition-all duration-200',
+                                        preferences.useCase.includes(useCase.id)
+                                            ? 'bg-blue-500 text-white'
+                                            : 'bg-gray-100 text-gray-700 hover:bg-gray-200'
+                                    ]"
+                                >
+                                    <span class="mr-1">{{ useCase.icon }}</span>
+                                    {{ useCase.name }}
+                                </button>
+                            </div>
+
+                            <h3 class="font-bold text-lg mb-2 text-gray-800">现有食材</h3>
+                            <div class="relative">
+                                <input
+                                    v-model="ingredientInput"
+                                    @keyup.enter="addIngredient"
+                                    placeholder="输入食材名称,回车添加..."
+                                    class="w-full p-3 border-2 border-[#0A0910] rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
+                                />
+                            </div>
+                            <div class="flex flex-wrap gap-2 mt-2">
+                                <span
+                                    v-for="ingredient in preferences.availableIngredients"
+                                    :key="ingredient"
+                                    class="inline-flex items-center px-2 py-1 bg-orange-100 text-orange-800 text-xs rounded-full"
+                                >
+                                    {{ ingredient }}
+                                    <button
+                                        @click="removeIngredient(ingredient)"
+                                        class="ml-1 text-orange-600 hover:text-orange-800"
+                                    >
+                                        ×
+                                    </button>
+                                </span>
+                            </div>
+
+                            <button
+                                @click="getRecommendations"
+                                :disabled="isLoadingRecommendations"
+                                class="w-full mt-4 bg-gradient-to-r from-blue-500 to-purple-500 hover:from-blue-600 hover:to-purple-600 disabled:from-gray-400 disabled:to-gray-400 text-white px-4 py-3 rounded-lg font-bold text-sm border-2 border-[#0A0910] transition-all duration-300 disabled:cursor-not-allowed"
+                            >
+                                <span class="flex items-center gap-2 justify-center">
+                                    <template v-if="isLoadingRecommendations">
+                                        <div class="animate-spin w-4 h-4 border-2 border-white border-t-transparent rounded-full"></div>
+                                        <span>AI推荐中...</span>
+                                    </template>
+                                    <template v-else>
+                                        <span>🤖</span>
+                                        <span>获取智能推荐</span>
+                                    </template>
+                                </span>
+                            </button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 推荐结果区域 -->
+            <div v-if="recommendations.length > 0 || isLoadingRecommendations" class="mb-8" data-recommendations>
+                <div class="bg-purple-500 text-white px-4 py-2 rounded-t-lg border-2 border-[#0A0910] border-b-0 inline-block">
+                    <span class="font-bold">✨ 为您推荐</span>
+                </div>
+                <div class="bg-white border-2 border-[#0A0910] rounded-lg rounded-tl-none p-4 md:p-6">
+                    <!-- 推荐加载状态 -->
+                    <div v-if="isLoadingRecommendations" class="text-center py-8">
+                        <div class="w-12 h-12 bg-gradient-to-br from-purple-400 to-pink-500 rounded-lg flex items-center justify-center mx-auto mb-3 animate-pulse">
+                            <span class="text-white text-xl">🤖</span>
+                        </div>
+                        <h3 class="text-lg font-bold text-gray-700 mb-2">AI正在为您精选酱料...</h3>
+                        <div class="animate-spin w-6 h-6 border-3 border-purple-400 border-t-transparent rounded-full mx-auto"></div>
+                    </div>
+
+                    <!-- 推荐列表 -->
+                    <div v-else-if="recommendations.length > 0" class="space-y-4">
+                        <div class="flex items-center justify-between mb-4">
+                            <h3 class="text-lg font-bold text-gray-800">🎯 根据您的偏好,推荐以下酱料:</h3>
+                            <span class="text-sm text-gray-500">共{{ recommendations.length }}种</span>
+                        </div>
+                        <div class="grid md:grid-cols-2 lg:grid-cols-3 gap-4">
+                            <button
+                                v-for="(sauceName, index) in recommendations"
+                                :key="sauceName"
+                                @click="selectRecommendedSauce(sauceName)"
+                                class="group p-4 bg-gradient-to-br from-purple-50 to-pink-50 border-2 border-[#0A0910] rounded-lg hover:from-purple-100 hover:to-pink-100 transition-all duration-200 transform hover:scale-105 text-left relative overflow-hidden"
+                            >
+                                <div class="absolute top-2 right-2 w-6 h-6 bg-purple-500 text-white rounded-full flex items-center justify-center text-xs font-bold">
+                                    {{ index + 1 }}
+                                </div>
+                                <div class="font-bold text-gray-800 mb-2 pr-8">{{ sauceName }}</div>
+                                <div class="text-sm text-gray-600 mb-2">点击查看详细制作方法</div>
+                                <div class="flex items-center text-xs text-purple-600 group-hover:text-purple-700">
+                                    <span class="mr-1">👨‍🍳</span>
+                                    <span>AI推荐</span>
+                                </div>
+                            </button>
+                        </div>
+                        <div class="text-center pt-4 border-t border-gray-200">
+                            <p class="text-sm text-gray-500">
+                                💡 不满意推荐结果?试试调整上方的口味偏好或使用场景
+                            </p>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 酱料搜索区域 -->
+            <div class="mb-8">
+                <div class="bg-green-500 text-white px-4 py-2 rounded-t-lg border-2 border-[#0A0910] border-b-0 inline-block">
+                    <span class="font-bold">🔍 酱料搜索</span>
+                </div>
+                <div class="bg-white border-2 border-[#0A0910] rounded-lg rounded-tl-none p-4 md:p-6">
+                    <div class="flex gap-4">
+                        <div class="flex-1 relative">
+                            <input
+                                v-model="searchQuery"
+                                @keyup.enter="searchSauce"
+                                placeholder="输入酱料名称"
+                                class="w-full p-4 border-2 border-[#0A0910] rounded-lg text-md font-medium focus:outline-none focus:ring-2 focus:ring-green-400"
+                            />
+                            <div class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400">
+                                <span class="text-2xl">🔍</span>
+                            </div>
+                        </div>
+                        <button
+                            @click="searchSauce"
+                            :disabled="!searchQuery.trim() || isLoadingSearch"
+                            class="bg-gradient-to-r from-green-500 to-teal-500 hover:from-green-600 hover:to-teal-600 disabled:from-gray-400 disabled:to-gray-400 text-white px-6 py-4 rounded-lg font-bold text-md border-2 border-[#0A0910] transition-all duration-300 disabled:cursor-not-allowed"
+                        >
+                            <span class="flex items-center gap-2">
+                                <template v-if="isLoadingSearch">
+                                    <div class="animate-spin w-5 h-5 border-2 border-white border-t-transparent rounded-full"></div>
+                                </template>
+                                <template v-else>
+                                    <span>搜索</span>
+                                </template>
+                            </span>
+                        </button>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 制作教程区域 -->
+            <div class="mb-8" data-results>
+                <div class="bg-teal-500 text-white px-4 py-2 rounded-t-lg border-2 border-[#0A0910] border-b-0 inline-block">
+                    <span class="font-bold">📋 制作教程</span>
+                </div>
+                <div class="bg-white border-2 border-[#0A0910] rounded-lg rounded-tl-none p-4 md:p-6">
+                    <!-- 空状态 -->
+                    <div v-if="!currentSauce && !isLoadingSearch" class="text-center py-12">
+                        <div class="w-16 h-16 bg-gradient-to-br from-teal-400 to-green-500 rounded-lg flex items-center justify-center mx-auto mb-4">
+                            <span class="text-white text-2xl">🥄</span>
+                        </div>
+                        <h3 class="text-lg font-bold text-gray-600 mb-3">开始您的酱料制作之旅</h3>
+                        <div class="space-y-2 text-sm text-gray-500 max-w-md mx-auto">
+                            <p class="flex items-center justify-center gap-2">
+                                <span>🤖</span>
+                                <span>配置口味偏好,获取个性化推荐</span>
+                            </p>
+                            <p class="flex items-center justify-center gap-2">
+                                <span>🔍</span>
+                                <span>直接搜索想要制作的酱料</span>
+                            </p>
+                            
+                        </div>
+                    </div>
+
+                    <!-- 搜索加载状态 -->
+                    <div v-if="isLoadingSearch" class="text-center py-12">
+                        <div class="w-16 h-16 bg-gradient-to-br from-teal-400 to-green-500 rounded-lg flex items-center justify-center mx-auto mb-4 animate-pulse">
+                            <span class="text-white text-2xl">👨‍🍳</span>
+                        </div>
+                        <h3 class="text-xl font-bold text-gray-700 mb-2">AI大师正在准备酱料配方...</h3>
+                        <p class="text-gray-500">请稍等片刻,美味秘方即将呈现</p>
+                        <div class="mt-4">
+                            <div class="animate-spin w-8 h-8 border-4 border-teal-400 border-t-transparent rounded-full mx-auto"></div>
+                        </div>
+                    </div>
+
+                    <!-- 酱料详情 -->
+                    <div v-if="currentSauce" class="max-w-4xl mx-auto">
+                        <SauceRecipeComponent :sauce="currentSauce" />
+                    </div>
+                </div>
+            </div>
+
+            <!-- 历史记录 -->
+            <div v-if="searchHistory.length > 0" class="mb-8">
+                <div class="bg-gray-600 text-white px-4 py-2 rounded-t-lg border-2 border-[#0A0910] border-b-0 inline-block">
+                    <span class="font-bold">📚 最近查看</span>
+                </div>
+                <div class="bg-white border-2 border-[#0A0910] rounded-lg rounded-tl-none p-4">
+                    <div class="flex flex-wrap gap-2">
+                        <button
+                            v-for="historyItem in searchHistory.slice(0, 8)"
+                            :key="historyItem"
+                            @click="selectHistoryItem(historyItem)"
+                            class="px-3 py-2 text-sm bg-orange-100 text-orange-700 rounded-full border border-orange-300 hover:bg-orange-200 hover:border-orange-400 transition-all duration-200"
+                        >
+                            {{ historyItem }}
+                        </button>
+                        <button
+                            @click="clearHistory"
+                            class="px-3 py-2 text-sm text-red-600 hover:text-red-700 underline"
+                        >
+                            清除历史
+                        </button>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 全局底部 -->
+        <GlobalFooter />
+    </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { generateSauceRecipe, recommendSauces } from '@/services/aiService'
+import type { SauceRecipe, SaucePreference } from '@/types'
+import { useCaseOptions, tasteDescriptions } from '@/config/sauces'
+import SauceRecipeComponent from '@/components/SauceRecipe.vue'
+import GlobalNavigation from '@/components/GlobalNavigation.vue'
+import GlobalFooter from '@/components/GlobalFooter.vue'
+
+// 响应式数据
+const searchQuery = ref('')
+const ingredientInput = ref('')
+const currentSauce = ref<SauceRecipe | null>(null)
+const recommendations = ref<string[]>([])
+const searchHistory = ref<string[]>([])
+const isLoadingSearch = ref(false)
+const isLoadingRecommendations = ref(false)
+
+// 用户偏好配置
+const preferences = ref<SaucePreference>({
+    spiceLevel: 3,
+    sweetLevel: 2,
+    saltLevel: 3,
+    sourLevel: 2,
+    useCase: [],
+    availableIngredients: []
+})
+
+// 页面加载时恢复历史记录
+onMounted(() => {
+    const saved = localStorage.getItem('sauceDesign_history')
+    if (saved) {
+        try {
+            searchHistory.value = JSON.parse(saved)
+        } catch (e) {
+            console.error('恢复搜索历史失败:', e)
+        }
+    }
+})
+
+
+
+// 切换使用场景
+const toggleUseCase = (useCaseId: string) => {
+    const index = preferences.value.useCase.indexOf(useCaseId)
+    if (index > -1) {
+        preferences.value.useCase.splice(index, 1)
+    } else {
+        preferences.value.useCase.push(useCaseId)
+    }
+}
+
+// 添加食材
+const addIngredient = () => {
+    const ingredient = ingredientInput.value.trim()
+    if (ingredient && !preferences.value.availableIngredients.includes(ingredient)) {
+        preferences.value.availableIngredients.push(ingredient)
+        ingredientInput.value = ''
+    }
+}
+
+// 移除食材
+const removeIngredient = (ingredient: string) => {
+    const index = preferences.value.availableIngredients.indexOf(ingredient)
+    if (index > -1) {
+        preferences.value.availableIngredients.splice(index, 1)
+    }
+}
+
+// 获取智能推荐
+const getRecommendations = async () => {
+    isLoadingRecommendations.value = true
+    currentSauce.value = null
+    
+    try {
+        const result = await recommendSauces(preferences.value)
+        recommendations.value = result
+        
+        if (result.length === 0) {
+            showErrorMessage('暂时没有找到合适的推荐,请尝试调整偏好设置')
+        } else {
+            // 滚动到推荐区域
+            setTimeout(() => {
+                const recommendationsElement = document.querySelector('[data-recommendations]')
+                if (recommendationsElement) {
+                    recommendationsElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
+                }
+            }, 100)
+        }
+    } catch (error) {
+        console.error('获取推荐失败:', error)
+        showErrorMessage('获取推荐失败,请检查网络连接后重试')
+    } finally {
+        isLoadingRecommendations.value = false
+    }
+}
+
+// 搜索酱料
+const searchSauce = async () => {
+    if (!searchQuery.value.trim() || isLoadingSearch.value) return
+
+    const sauceName = searchQuery.value.trim()
+    addToHistory(sauceName)
+    
+    isLoadingSearch.value = true
+    recommendations.value = []
+    currentSauce.value = null
+
+    try {
+        const result = await generateSauceRecipe(sauceName)
+        currentSauce.value = result
+        
+        // 滚动到结果区域
+        setTimeout(() => {
+            const resultsElement = document.querySelector('[data-results]')
+            if (resultsElement) {
+                resultsElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
+            }
+        }, 100)
+    } catch (error) {
+        console.error('搜索酱料失败:', error)
+        showErrorMessage('生成酱料配方失败,请稍后重试')
+    } finally {
+        isLoadingSearch.value = false
+    }
+}
+
+// 选择推荐的酱料
+const selectRecommendedSauce = async (sauceName: string) => {
+    addToHistory(sauceName)
+    
+    isLoadingSearch.value = true
+    currentSauce.value = null
+
+    try {
+        const result = await generateSauceRecipe(sauceName)
+        currentSauce.value = result
+        
+        // 滚动到结果区域
+        setTimeout(() => {
+            const resultsElement = document.querySelector('[data-results]')
+            if (resultsElement) {
+                resultsElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
+            }
+        }, 100)
+    } catch (error) {
+        console.error('获取酱料配方失败:', error)
+        showErrorMessage('生成酱料配方失败,请稍后重试')
+    } finally {
+        isLoadingSearch.value = false
+    }
+}
+
+// 选择历史记录项
+const selectHistoryItem = (sauceName: string) => {
+    searchQuery.value = sauceName
+    searchSauce()
+}
+
+// 添加到历史记录
+const addToHistory = (sauceName: string) => {
+    if (!searchHistory.value.includes(sauceName)) {
+        searchHistory.value.unshift(sauceName)
+        if (searchHistory.value.length > 20) {
+            searchHistory.value = searchHistory.value.slice(0, 20)
+        }
+        localStorage.setItem('sauceDesign_history', JSON.stringify(searchHistory.value))
+    }
+}
+
+// 清除历史记录
+const clearHistory = () => {
+    searchHistory.value = []
+    localStorage.removeItem('sauceDesign_history')
+}
+
+// 显示错误消息
+const showErrorMessage = (message: string) => {
+    // 创建临时错误提示元素
+    const errorToast = document.createElement('div')
+    errorToast.className = 'fixed top-4 right-4 bg-red-500 text-white px-4 py-3 rounded-lg shadow-lg z-50 transition-all duration-300 transform translate-x-full max-w-sm'
+    errorToast.innerHTML = `
+        <div class="flex items-center gap-2">
+            <span>⚠️</span>
+            <span class="text-sm font-medium">${message}</span>
+        </div>
+    `
+
+    document.body.appendChild(errorToast)
+
+    // 显示动画
+    setTimeout(() => {
+        errorToast.style.transform = 'translateX(0)'
+    }, 10)
+
+    // 自动隐藏
+    setTimeout(() => {
+        errorToast.style.transform = 'translateX(100%)'
+        setTimeout(() => {
+            if (document.body.contains(errorToast)) {
+                document.body.removeChild(errorToast)
+            }
+        }, 300)
+    }, 4000)
+}
+</script>
+
+<style scoped>
+/* 自定义滑块样式 */
+.slider-red::-webkit-slider-thumb {
+    appearance: none;
+    height: 20px;
+    width: 20px;
+    border-radius: 50%;
+    background: #ef4444;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+.slider-yellow::-webkit-slider-thumb {
+    appearance: none;
+    height: 20px;
+    width: 20px;
+    border-radius: 50%;
+    background: #eab308;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+.slider-blue::-webkit-slider-thumb {
+    appearance: none;
+    height: 20px;
+    width: 20px;
+    border-radius: 50%;
+    background: #3b82f6;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+.slider-green::-webkit-slider-thumb {
+    appearance: none;
+    height: 20px;
+    width: 20px;
+    border-radius: 50%;
+    background: #22c55e;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+
+.slider-red::-moz-range-thumb,
+.slider-yellow::-moz-range-thumb,
+.slider-blue::-moz-range-thumb,
+.slider-green::-moz-range-thumb {
+    height: 20px;
+    width: 20px;
+    border-radius: 50%;
+    cursor: pointer;
+    border: 2px solid #fff;
+    box-shadow: 0 2px 4px rgba(0,0,0,0.2);
+}
+</style>