diff --git a/Dockerfile b/Dockerfile
index 11cdb94..d71a70f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -4,16 +4,20 @@ FROM node:20-alpine AS builder
WORKDIR /app
-# Copy package files
+# 国内镜像加速
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
+
COPY package.json pnpm-lock.yaml ./
-# Install dependencies
-RUN corepack enable && pnpm install --frozen-lockfile
+# 国内源 + 快速启用 pnpm
+RUN corepack enable && \
+ npm config set registry https://registry.npmmirror.com && \
+ pnpm config set registry https://registry.npmmirror.com
+
+RUN pnpm install --frozen-lockfile
-# Copy source code
COPY . .
-# Build the application
RUN pnpm build
# Stage 2: Runner
@@ -23,21 +27,30 @@ WORKDIR /app
ENV NODE_ENV=production
-# Create non-root user
+# 国内镜像
+RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
+
+# 创建用户
RUN addgroup --system --gid 1001 nodejs && \
adduser --system --uid 1001 nextjs
-# Copy built application
+# 复制构建产物
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
-# Set ownership
+# ===================== 修复权限错误 =====================
+# 复制项目根目录的配置文件
+COPY --from=builder /app/config.json ./
+
+# 给 nextjs 用户授权整个目录
+RUN chown -R nextjs:nodejs /app
+# ========================================================
+
USER nextjs
EXPOSE 3000
-
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
-CMD ["node", "server.js"]
+CMD ["node", "server.js"]
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index efe7cf0..9e8143a 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -13,7 +13,7 @@ import {
Loader2,
X,
AlertCircle,
- Download, Settings, Sparkles,
+ Download, Settings,
} from "lucide-react";
import Link from 'next/link'
import {Button} from "@/components/ui/button";
@@ -126,24 +126,6 @@ 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")
@@ -160,53 +142,6 @@ 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[] = [];
@@ -812,34 +747,23 @@ 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,
- });
- }}
+ className="flex flex-col gap-0.5 text-sm"
>
-
- {dish.name.zh}
-
- {dish.name.en && (
-
- {dish.name.en}
-
- )}
+
+ {dish.name.zh}
+
+ {dish.name.en && (
+
+ {dish.name.en}
+
+ )}
))
) : (
@@ -857,31 +781,20 @@ 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,
- });
- }}
+ className="flex flex-col gap-0.5"
>
-
- {dish.name.zh}
-
+
+ {dish.name.zh}
+
{dish.name.en && (
- {dish.name.en}
-
+ {dish.name.en}
+
)}
))}
@@ -900,28 +813,6 @@ return mealData?.dishList.length ? (
)}
-
- {/* 菜品浮框 */}
- {contextMenu.visible && contextMenu.dish && (
- e.stopPropagation()}
- >
-
-
- )}
);
}
diff --git a/lib/database.ts b/lib/database.ts
index 883faa6..5e539c3 100644
--- a/lib/database.ts
+++ b/lib/database.ts
@@ -1,16 +1,29 @@
import mysql from "mysql2/promise";
+import {getConfig} from "@/lib/config";
+
+// 获取配置文件中的数据链接
+const dataBaseConfig = getConfig().db
const pool = mysql.createPool({
- host: process.env.DB_HOST || "127.0.0.1",
- port: parseInt(process.env.DB_PORT || "3306"),
- user: process.env.DB_USER || "root",
- password: process.env.DB_PASSWORD || "",
- database: process.env.DB_NAME || "recipe_tools",
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0,
+ host: dataBaseConfig.host || "127.0.0.1",
+ port: dataBaseConfig.port || 3306,
+ user: dataBaseConfig.user || "root",
+ password: dataBaseConfig.password || "123456",
+ database: dataBaseConfig.database || "recipe_tools",
+ waitForConnections: true,
+ connectionLimit: 10,
+ queueLimit: 0,
});
-
+console.log({
+ host: dataBaseConfig.host || "127.0.0.1",
+ port: dataBaseConfig.port || 3306,
+ user: dataBaseConfig.user || "root",
+ password: dataBaseConfig.password || "123456",
+ database: dataBaseConfig.database || "recipe_tools",
+ waitForConnections: true,
+ connectionLimit: 10,
+ queueLimit: 0,
+})
export default pool;
/**
@@ -19,20 +32,20 @@ export default pool;
* @returns 翻译结果或 null
*/
export async function getTranslation(chineseName: string): Promise {
- try {
- const [rows] = await pool.execute(
- "SELECT english FROM words WHERE word = ? LIMIT 1",
- [chineseName]
- );
+ try {
+ const [rows] = await pool.execute(
+ "SELECT english FROM words WHERE word = ? LIMIT 1",
+ [chineseName]
+ );
- if (rows.length > 0 && rows[0].english) {
- return rows[0].english as string;
+ if (rows.length > 0 && rows[0].english) {
+ return rows[0].english as string;
+ }
+ return null;
+ } catch (error) {
+ console.error("Database query error:", error);
+ return null;
}
- return null;
- } catch (error) {
- console.error("Database query error:", error);
- return null;
- }
}
/**
@@ -41,27 +54,29 @@ export async function getTranslation(chineseName: string): Promise> {
- const result = new Map();
+ const result = new Map();
- if (chineseNames.length === 0) return result;
+ if (chineseNames.length === 0) return result;
- try {
- const placeholders = chineseNames.map(() => "?").join(",");
- const [rows] = await pool.execute(
- `SELECT word, english FROM words WHERE word IN (${placeholders})`,
- chineseNames
- );
+ try {
+ const placeholders = chineseNames.map(() => "?").join(",");
+ const [rows] = await pool.execute(
+ `SELECT word, english
+ FROM words
+ WHERE word IN (${placeholders})`,
+ chineseNames
+ );
- for (const row of rows) {
- if (row.word && row.english) {
- result.set(row.word as string, row.english as string);
- }
+ for (const row of rows) {
+ if (row.word && row.english) {
+ result.set(row.word as string, row.english as string);
+ }
+ }
+ } catch (error) {
+ console.error("Database batch query error:", error);
}
- } catch (error) {
- console.error("Database batch query error:", error);
- }
- return result;
+ return result;
}
|