Files
recipe_tool/app/settings/database/page.tsx
T
hanhan a249fb038c feat: add recipe translation tool with AI translation and settings management
- Add Word document parsing and translation export
- Add database configuration management
- Add AI/LLM translation configuration
- Add translation vocabulary management
- Add export settings (colors, page orientation, headers)
- Separate database and AI configurations
- Add debounced search to prevent database overload
2026-04-12 23:50:54 +08:00

153 lines
5.9 KiB
TypeScript

"use client";
import {useState, useEffect} from "react";
import {useToast} from "@/components/ui/toast";
import {Loader2} from "lucide-react";
import {Button} from "@/components/ui/button";
interface DBConfig {
host: string;
port: number;
user: string;
password: string;
database: string;
}
const defaultDBConfig: DBConfig = {
host: "127.0.0.1",
port: 3306,
user: "root",
password: "",
database: "recipe_tools",
};
export default function DatabasePage() {
const [config, setConfig] = useState<DBConfig>(defaultDBConfig);
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const {addToast} = useToast();
useEffect(() => {
fetch("/api/config")
.then((res) => res.json())
.then((result) => {
if (result.success && result.data) {
setConfig({
host: result.data.db?.host || defaultDBConfig.host,
port: result.data.db?.port || defaultDBConfig.port,
user: result.data.db?.user || defaultDBConfig.user,
password: result.data.db?.password || defaultDBConfig.password,
database: result.data.db?.database || defaultDBConfig.database,
});
}
})
.catch(console.error)
.finally(() => setLoading(false));
}, []);
const handleSave = async () => {
setSaving(true);
try {
const response = await fetch("/api/config", {
method: "POST",
headers: {"Content-Type": "application/json"},
body: JSON.stringify({db: config}),
});
const result = await response.json();
if (result.success) {
addToast({type: "success", title: "数据库配置已保存"});
} else {
addToast({type: "error", title: "保存失败", description: result.error});
}
} catch {
addToast({type: "error", title: "保存失败"});
} finally {
setSaving(false);
}
};
if (loading) {
return (
<div className="space-y-6">
<div>
<h2 className="text-lg font-semibold">数据库配置</h2>
<p className="text-sm text-muted-foreground">配置 MySQL 数据库连接信息</p>
</div>
<div className="h-40 animate-pulse rounded-lg bg-muted"/>
</div>
);
}
return (
<div className="space-y-6">
<div className={"flex items-center justify-between"}>
<div>
<h2 className="text-lg font-semibold">数据库配置</h2>
<p className="text-sm text-muted-foreground">配置 MySQL 数据库连接信息</p>
</div>
<div className="flex gap-3">
<Button onClick={handleSave} disabled={saving}>
{saving && <Loader2 className="mr-2 h-4 w-4 animate-spin"/>}
{saving ? "保存中..." : "保存配置"}
</Button>
</div>
</div>
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<label className="text-sm font-medium">主机地址</label>
<input
type="text"
value={config.host}
onChange={(e) => setConfig({...config, host: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="127.0.0.1"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">端口</label>
<input
type="text"
value={config.port}
onChange={(e) => setConfig({...config, port: parseInt(e.target.value) || 3306})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="3306"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">用户名</label>
<input
type="text"
value={config.user}
onChange={(e) => setConfig({...config, user: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="root"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium">密码</label>
<input
type="password"
value={config.password}
onChange={(e) => setConfig({...config, password: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="******"
/>
</div>
<div className="space-y-2 md:col-span-2">
<label className="text-sm font-medium">数据库名称</label>
<input
type="text"
value={config.database}
onChange={(e) => setConfig({...config, database: e.target.value})}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm"
placeholder="recipe_tools"
/>
</div>
</div>
</div>
);
}