|
@@ -1,62 +1,65 @@
|
|
|
<template>
|
|
<template>
|
|
|
<div class="min-h-screen bg-gradient-to-br from-primary-50 to-primary-200">
|
|
<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">
|
|
<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>
|
|
|
</div>
|
|
</div>
|
|
|
</header>
|
|
</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>
|
|
|
|
|
|
|
|
- <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
|
|
<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>
|
|
</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="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>
|
|
</div>
|
|
|
|
|
|
|
|
- <!-- 厨艺大师选择区域 - 弱化设计 -->
|
|
|
|
|
|
|
+ <!-- 厨艺大师选择区域 -->
|
|
|
<div class="bg-white/70 backdrop-blur-sm rounded-2xl shadow-md p-6 mb-8">
|
|
<div class="bg-white/70 backdrop-blur-sm rounded-2xl shadow-md p-6 mb-8">
|
|
|
<details class="group">
|
|
<details class="group">
|
|
|
<summary class="cursor-pointer text-center p-4 hover:bg-neutral-50 rounded-xl transition-colors">
|
|
<summary class="cursor-pointer text-center p-4 hover:bg-neutral-50 rounded-xl transition-colors">
|
|
@@ -67,39 +70,28 @@
|
|
|
</h3>
|
|
</h3>
|
|
|
</summary>
|
|
</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
|
|
<div
|
|
|
v-for="cuisine in cuisines"
|
|
v-for="cuisine in cuisines"
|
|
|
:key="cuisine.id"
|
|
:key="cuisine.id"
|
|
|
@click="selectCuisine(cuisine)"
|
|
@click="selectCuisine(cuisine)"
|
|
|
:class="[
|
|
: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)
|
|
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-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>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
</details>
|
|
</details>
|
|
|
</div>
|
|
</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">
|
|
<div v-if="isLoading" class="fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50">
|
|
|
<CookingLoader />
|
|
<CookingLoader />
|
|
@@ -109,8 +101,25 @@
|
|
|
<div v-if="recipes.length > 0" class="space-y-8">
|
|
<div v-if="recipes.length > 0" class="space-y-8">
|
|
|
<h2 class="text-4xl font-bold text-neutral-800 text-center mb-8">🍽️ 大师们的杰作</h2>
|
|
<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>
|
|
|
</div>
|
|
</div>
|
|
|
</main>
|
|
</main>
|
|
@@ -118,7 +127,7 @@
|
|
|
</template>
|
|
</template>
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
|
<script setup lang="ts">
|
|
|
-import { ref, reactive } from 'vue'
|
|
|
|
|
|
|
+import { ref } from 'vue'
|
|
|
import { cuisines } from '@/config/cuisines'
|
|
import { cuisines } from '@/config/cuisines'
|
|
|
import RecipeCard from '@/components/RecipeCard.vue'
|
|
import RecipeCard from '@/components/RecipeCard.vue'
|
|
|
import CookingLoader from '@/components/CookingLoader.vue'
|
|
import CookingLoader from '@/components/CookingLoader.vue'
|
|
@@ -169,7 +178,7 @@ const generateRecipes = async () => {
|
|
|
recipes.value = []
|
|
recipes.value = []
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- // 如果没有选择菜系,随机选择2-3个
|
|
|
|
|
|
|
+ // 根据选择的菜系数量决定生成菜谱
|
|
|
let selectedCuisineObjects = cuisines.filter(c => selectedCuisines.value.includes(c.id))
|
|
let selectedCuisineObjects = cuisines.filter(c => selectedCuisines.value.includes(c.id))
|
|
|
|
|
|
|
|
if (selectedCuisineObjects.length === 0) {
|
|
if (selectedCuisineObjects.length === 0) {
|
|
@@ -195,13 +204,20 @@ const generateRecipes = async () => {
|
|
|
const simulateAICall = async () => {
|
|
const simulateAICall = async () => {
|
|
|
return new Promise(resolve => {
|
|
return new Promise(resolve => {
|
|
|
setTimeout(() => {
|
|
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 {
|
|
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,
|
|
ingredients: ingredients.value,
|
|
|
steps: [
|
|
steps: [
|
|
|
{ step: 1, description: '准备所有食材,清洗干净', time: 5 },
|
|
{ step: 1, description: '准备所有食材,清洗干净', time: 5 },
|
|
@@ -218,7 +234,15 @@ const simulateAICall = async () => {
|
|
|
|
|
|
|
|
recipes.value = mockRecipes
|
|
recipes.value = mockRecipes
|
|
|
resolve(mockRecipes)
|
|
resolve(mockRecipes)
|
|
|
- }, 2000)
|
|
|
|
|
|
|
+ }, 3000)
|
|
|
|
|
+ })
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 回到顶部功能
|
|
|
|
|
+const scrollToTop = () => {
|
|
|
|
|
+ window.scrollTo({
|
|
|
|
|
+ top: 0,
|
|
|
|
|
+ behavior: 'smooth'
|
|
|
})
|
|
})
|
|
|
}
|
|
}
|
|
|
</script>
|
|
</script>
|