From 12f7114f7d6e50e3a821cfde1116c224f1208fd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AF=92=E5=AF=92?= <2596194220@qq.com> Date: Mon, 13 Apr 2026 00:09:41 +0800 Subject: [PATCH] feat: add context menu for individual dish AI translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add right-click/click context menu on dishes in the main table - Menu includes 'AI 翻译 (Beta)' option - Translates single dish and updates the display immediately - Clicking outside closes the context menu --- app/page.tsx | 147 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 128 insertions(+), 19 deletions(-) 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 ( -