diff --git a/app/page.tsx b/app/page.tsx index 9e8143a..efe7cf0 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -13,7 +13,7 @@ import { Loader2, X, AlertCircle, - Download, Settings, + Download, Settings, Sparkles, } from "lucide-react"; import Link from 'next/link' import {Button} from "@/components/ui/button"; @@ -126,6 +126,24 @@ export default function RecipeTranslationPage() { orientation: "landscape", }); + // 菜品浮框状态 + const [contextMenu, setContextMenu] = useState<{ + visible: boolean; + x: number; + y: number; + dish: Dish | null; + dayCode: number; + mealCode: MealTypeCode; + }>({ visible: false, x: 0, y: 0, dish: null, dayCode: 0, mealCode: MealTypeCode.BREAKFAST }); + const [aiTranslating, setAiTranslating] = useState(false); + + // 关闭浮框 + useEffect(() => { + const handleClick = () => setContextMenu((prev) => ({ ...prev, visible: false })); + document.addEventListener("click", handleClick); + return () => document.removeEventListener("click", handleClick); + }, []); + // 加载导出配置 useEffect(() => { fetch("/api/config") @@ -142,6 +160,53 @@ export default function RecipeTranslationPage() { .catch(console.error); }, []); + // 单个菜品 AI 翻译 + const handleAiTranslateSingle = useCallback(async () => { + if (!contextMenu.dish || !contextMenu.dish.name.zh || !data) return; + + setAiTranslating(true); + try { + const response = await fetch(`/api/ai/translate?word=${encodeURIComponent(contextMenu.dish.name.zh)}`); + const result = await response.json(); + + if (result.english) { + // 更新 data 中的翻译结果 + setData((prevData) => { + if (!prevData) return null; + return { + ...prevData, + meals: prevData.meals.map((meal) => { + if (meal.dayCode === contextMenu.dayCode && meal.mealCode === contextMenu.mealCode) { + return { + ...meal, + dishList: meal.dishList.map((dish) => { + if (dish.id === contextMenu.dish?.id) { + return { + ...dish, + name: { ...dish.name, en: result.english }, + translated: true, + }; + } + return dish; + }), + }; + } + return meal; + }), + }; + }); + setContextMenu((prev) => ({ ...prev, visible: false })); + } else { + alert(result.error || "翻译失败"); + } + } catch (error) { + console.error("AI translation error:", error); + alert("翻译失败,请检查 AI 配置"); + } finally { + setAiTranslating(false); + } + }, [contextMenu.dish, contextMenu.dayCode, contextMenu.mealCode, data]); + // 收集所有未翻译的菜品名称 const getAllDishNames = useCallback((menu: WeeklyMenu): string[] => { const names: string[] = []; @@ -747,23 +812,34 @@ export default function RecipeTranslationPage() { (m) => m.dayCode === day.code && m.mealCode === meal.code ); return ( - +
{mealData?.dishList.length ? ( mealData.dishList.map((dish) => (
{ + e.stopPropagation(); + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + dish, + dayCode: day.code, + mealCode: meal.code, + }); + }} > - - {dish.name.zh} - - {dish.name.en && ( - - {dish.name.en} - - )} + + {dish.name.zh} + + {dish.name.en && ( + + {dish.name.en} + + )}
)) ) : ( @@ -781,20 +857,31 @@ export default function RecipeTranslationPage() { m.dayCode === day.code && m.mealCode === selectedMealType ); - return mealData?.dishList.length ? ( +return mealData?.dishList.length ? (
{mealData.dishList.map((dish) => (
{ + e.stopPropagation(); + setContextMenu({ + visible: true, + x: e.clientX, + y: e.clientY, + dish, + dayCode: day.code, + mealCode: selectedMealType as MealTypeCode, + }); + }} > - - {dish.name.zh} - + + {dish.name.zh} + {dish.name.en && ( - {dish.name.en} - + {dish.name.en} + )}
))} @@ -813,6 +900,28 @@ export default function RecipeTranslationPage() {
)} + + {/* 菜品浮框 */} + {contextMenu.visible && contextMenu.dish && ( +
e.stopPropagation()} + > + +
+ )}
); }