初始化项目;更新至 v0.1.0_20251209 版本

This commit is contained in:
2025-12-09 22:18:14 +08:00
parent 051d9603b1
commit 2c7cbac954
15 changed files with 584 additions and 2 deletions

228
App.tsx Normal file
View File

@@ -0,0 +1,228 @@
import React, { useState } from 'react';
import { generateSql } from './services/gemini';
import { Button } from './components/Button';
import { TextArea } from './components/TextArea';
import { Select } from './components/Select';
import { LoadingState, DatabaseType } from './types';
// Default placeholders to help the user understand what to input
const PLACEHOLDER_STRUCTURE = `例如:
CREATE TABLE student (
id INT PRIMARY KEY,
name VARCHAR(50) COMMENT '姓名',
school_code VARCHAR(20) COMMENT '学校代码',
nation_code VARCHAR(10) COMMENT '国籍代码',
politics_code VARCHAR(10) COMMENT '政治面貌代码',
year VARCHAR(4) COMMENT '年度',
address VARCHAR(200) COMMENT '家庭住址'
);
CREATE TABLE school (
code VARCHAR(20),
name VARCHAR(100)
);`;
const PLACEHOLDER_DICT = `例如:
字典表 dict_common (
type_code VARCHAR(50), -- 字典类型,如 'NATION', 'POLITICS'
item_code VARCHAR(50), -- 实际值,如 '01', 'CN'
item_name VARCHAR(100) -- 显示名,如 '汉族', '中国'
)
关联关系:
- student.nation_code -> dict_common (type_code='NATION')
- student.politics_code -> dict_common (type_code='POLITICS')`;
const PLACEHOLDER_REQ = `例如:
我需要查询学校基本信息。
输出:学校名称,学校代码,学生姓名,手机号,家庭住址,年度,政治面貌(需要字典翻译),国籍(需要字典翻译)。`;
const DB_OPTIONS = [
{ value: 'MySQL', label: 'MySQL / MariaDB' },
{ value: 'PostgreSQL', label: 'PostgreSQL' },
{ value: 'Oracle', label: 'Oracle Database' },
{ value: 'SQL Server', label: 'SQL Server (MSSQL)' },
{ value: 'Hive', label: 'Hive / SparkSQL' },
{ value: 'Dm', label: '达梦数据库 (Dameng)' },
{ value: 'SQLite', label: 'SQLite' },
];
const App: React.FC = () => {
const [databaseType, setDatabaseType] = useState<DatabaseType>('MySQL');
const [tableStructure, setTableStructure] = useState<string>('');
const [dictionaryData, setDictionaryData] = useState<string>('');
const [requirement, setRequirement] = useState<string>('');
const [generatedSql, setGeneratedSql] = useState<string>('');
const [status, setStatus] = useState<LoadingState>(LoadingState.IDLE);
const [errorMsg, setErrorMsg] = useState<string | null>(null);
const handleGenerate = async () => {
if (!tableStructure.trim() || !requirement.trim()) {
setErrorMsg("请至少填写表结构和查询需求。");
return;
}
setStatus(LoadingState.LOADING);
setErrorMsg(null);
setGeneratedSql('');
try {
const sql = await generateSql({
tableStructure,
dictionaryData,
requirement,
databaseType
});
setGeneratedSql(sql);
setStatus(LoadingState.SUCCESS);
} catch (err: any) {
setErrorMsg(err.message || "未知错误");
setStatus(LoadingState.ERROR);
}
};
const copyToClipboard = () => {
if (generatedSql) {
navigator.clipboard.writeText(generatedSql);
// Optional: Show a toast here, but for now we'll just rely on user action
}
};
return (
<div className="flex flex-col h-screen bg-slate-50">
{/* Header */}
<header className="bg-white border-b border-gray-200 px-6 py-4 flex items-center justify-between shrink-0">
<div className="flex items-center gap-3">
<div className="w-8 h-8 bg-indigo-600 rounded-lg flex items-center justify-center">
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
</svg>
</div>
<div>
<h1 className="text-xl font-bold text-slate-800">SQL Translate Pro</h1>
<p className="text-xs text-slate-500"> SQL & </p>
</div>
</div>
<div>
{/* Placeholder for future user settings or profile */}
</div>
</header>
{/* Main Content - Two Columns Layout */}
<main className="flex-1 overflow-hidden flex flex-col md:flex-row">
{/* Left Panel: Inputs */}
<div className="w-full md:w-1/2 lg:w-5/12 p-6 flex flex-col gap-6 overflow-y-auto border-r border-gray-200 bg-white">
<div>
<Select
label="目标数据库类型"
options={DB_OPTIONS}
value={databaseType}
onChange={(e) => setDatabaseType(e.target.value as DatabaseType)}
helperText="生成结果将适配所选数据库的方言"
/>
</div>
<div className="flex-1 min-h-[200px]">
<TextArea
label="1. 表结构与字段说明"
helperText="粘贴 DDL 或字段描述"
placeholder={PLACEHOLDER_STRUCTURE}
value={tableStructure}
onChange={(e) => setTableStructure(e.target.value)}
/>
</div>
<div className="flex-1 min-h-[150px]">
<TextArea
label="2. 字典表信息"
helperText="说明字典表结构及关联方式"
placeholder={PLACEHOLDER_DICT}
value={dictionaryData}
onChange={(e) => setDictionaryData(e.target.value)}
/>
</div>
<div className="flex-1 min-h-[120px]">
<TextArea
label="3. 最终需求"
helperText="你想要查询哪些字段?"
placeholder={PLACEHOLDER_REQ}
value={requirement}
onChange={(e) => setRequirement(e.target.value)}
/>
</div>
<div className="pt-2 sticky bottom-0 bg-white pb-2">
<Button
onClick={handleGenerate}
isLoading={status === LoadingState.LOADING}
className="w-full shadow-lg"
>
SQL
</Button>
{errorMsg && (
<div className="mt-2 text-red-500 text-sm bg-red-50 p-2 rounded border border-red-100">
{errorMsg}
</div>
)}
</div>
</div>
{/* Right Panel: Output */}
<div className="w-full md:w-1/2 lg:w-7/12 bg-slate-50 flex flex-col overflow-hidden relative">
<div className="px-6 py-4 border-b border-gray-200 bg-white flex justify-between items-center shrink-0">
<h2 className="text-lg font-semibold text-slate-700"> <span className="text-xs font-normal text-slate-400 ml-2">({databaseType})</span></h2>
{status === LoadingState.SUCCESS && (
<Button variant="outline" onClick={copyToClipboard} className="text-xs py-1.5 h-8">
SQL
</Button>
)}
</div>
<div className="flex-1 p-6 overflow-y-auto">
{status === LoadingState.IDLE && (
<div className="h-full flex flex-col items-center justify-center text-slate-400">
<svg className="w-16 h-16 mb-4 opacity-50" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5} d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" />
</svg>
<p></p>
</div>
)}
{status === LoadingState.LOADING && (
<div className="h-full flex flex-col items-center justify-center text-indigo-500">
<div className="animate-pulse flex flex-col items-center">
<div className="h-2.5 bg-indigo-200 rounded-full w-48 mb-4"></div>
<div className="h-2 bg-indigo-100 rounded-full w-32 mb-2.5"></div>
<div className="h-2 bg-indigo-100 rounded-full w-40"></div>
<span className="mt-6 text-sm font-medium text-slate-500"> {databaseType} ...</span>
</div>
</div>
)}
{status === LoadingState.SUCCESS && (
<div className="relative group">
<pre className="block p-4 rounded-lg bg-slate-900 text-slate-50 font-mono text-sm leading-relaxed whitespace-pre-wrap shadow-inner border border-slate-700">
<code>{generatedSql}</code>
</pre>
</div>
)}
{status === LoadingState.ERROR && (
<div className="h-full flex items-center justify-center text-red-400">
<p></p>
</div>
)}
</div>
</div>
</main>
</div>
);
};
export default App;