a249fb038c
- 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
153 lines
5.9 KiB
TypeScript
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>
|
|
);
|
|
}
|