feat: 模板配置页(上传文件、自动识别占位符、字段映射)
This commit is contained in:
parent
58f639c113
commit
a37f17b3b6
@ -1 +1,147 @@
|
||||
export default function TemplateConfig() { return <div>TemplateConfig</div>; }
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import FieldMappingEditor from "../components/FieldMappingEditor";
|
||||
|
||||
export default function TemplateConfig() {
|
||||
const navigate = useNavigate();
|
||||
const { id } = useParams();
|
||||
const isEdit = Boolean(id);
|
||||
|
||||
const [name, setName] = useState("");
|
||||
const [grp, setGrp] = useState("");
|
||||
const [filePath, setFilePath] = useState("");
|
||||
const [fields, setFields] = useState([]);
|
||||
const [autoDetected, setAutoDetected] = useState([]);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (isEdit) {
|
||||
window.api.listTemplates().then((templates) => {
|
||||
const t = templates.find((t) => t.id === id);
|
||||
if (t) {
|
||||
setName(t.name);
|
||||
setGrp(t.grp || "");
|
||||
setFilePath(t.file_path);
|
||||
setFields(t.fields || []);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [id]);
|
||||
|
||||
async function handleSelectFile() {
|
||||
const path = await window.api.selectFile([
|
||||
{ name: "Excel 文件", extensions: ["xlsx"] },
|
||||
]);
|
||||
if (!path) return;
|
||||
setFilePath(path);
|
||||
const result = await window.api.parseTemplate(path);
|
||||
if (result.placeholders?.length > 0) {
|
||||
setAutoDetected(result.placeholders);
|
||||
const detected = result.placeholders.map((p) => ({
|
||||
name: p.name,
|
||||
type: "text",
|
||||
sheet: p.sheet,
|
||||
cell: p.cell,
|
||||
}));
|
||||
setFields((prev) => {
|
||||
const existing = prev.map((f) => f.name);
|
||||
const newOnes = detected.filter((d) => !existing.includes(d.name));
|
||||
return [...prev, ...newOnes];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSave() {
|
||||
if (!name.trim() || !filePath) return alert("请填写模板名称并选择文件");
|
||||
setSaving(true);
|
||||
try {
|
||||
if (!isEdit) {
|
||||
const tempId = Date.now().toString();
|
||||
const storedPath = await window.api.copyFileToAppData(filePath, tempId);
|
||||
await window.api.saveTemplate({ name, grp, file_path: storedPath, fields });
|
||||
} else {
|
||||
await window.api.updateTemplate(id, { name, grp, fields });
|
||||
}
|
||||
navigate("/templates");
|
||||
} finally {
|
||||
setSaving(false);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 p-8">
|
||||
<div className="max-w-2xl mx-auto bg-white rounded-lg border p-6 space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<h1 className="text-xl font-bold">{isEdit ? "编辑模板" : "新建模板"}</h1>
|
||||
<button onClick={() => navigate("/templates")} className="text-gray-400 hover:text-gray-600 text-sm">
|
||||
← 返回
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">模板名称</label>
|
||||
<input
|
||||
value={name}
|
||||
onChange={(e) => setName(e.target.value)}
|
||||
className="w-full border rounded px-3 py-2 text-sm"
|
||||
placeholder="如:月度报告模板"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">分组(可选)</label>
|
||||
<input
|
||||
value={grp}
|
||||
onChange={(e) => setGrp(e.target.value)}
|
||||
className="w-full border rounded px-3 py-2 text-sm"
|
||||
placeholder="如:财务部"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">Excel 模板文件</label>
|
||||
<div className="flex gap-2">
|
||||
<input
|
||||
readOnly
|
||||
value={filePath}
|
||||
className="flex-1 border rounded px-3 py-2 text-sm bg-gray-50"
|
||||
placeholder="点击右侧按钮选择文件..."
|
||||
/>
|
||||
<button
|
||||
onClick={handleSelectFile}
|
||||
className="border rounded px-4 py-2 text-sm hover:bg-gray-100"
|
||||
>
|
||||
选择文件
|
||||
</button>
|
||||
</div>
|
||||
{autoDetected.length > 0 && (
|
||||
<p className="text-xs text-green-600 mt-1">
|
||||
已自动识别 {autoDetected.length} 个占位符
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">字段映射</label>
|
||||
<FieldMappingEditor fields={fields} onChange={setFields} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end gap-2 pt-4 border-t">
|
||||
<button
|
||||
onClick={() => navigate("/templates")}
|
||||
className="border rounded px-4 py-2 text-sm hover:bg-gray-100"
|
||||
>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
onClick={handleSave}
|
||||
disabled={saving}
|
||||
className="bg-blue-600 text-white rounded px-4 py-2 text-sm hover:bg-blue-700 disabled:opacity-50"
|
||||
>
|
||||
{saving ? "保存中..." : "保存模板"}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user