import { useState, useEffect } from "react"; import { useQueryClient } from "@tanstack/react-query"; import { getTemplate, saveTemplate, type ProjectTemplate } from "../../lib/commands"; import { useUIStore } from "../../store/ui"; import { Dialog } from "../ui/Dialog"; interface Props { templateId: string | null; // null = 新建 onClose: () => void; } interface FileEntry { _key: string; path: string; content: string; } const PLATFORM_OPTIONS = [ { value: "both", label: "通用(Win + WSL)" }, { value: "win", label: "仅 Windows" }, { value: "wsl", label: "仅 WSL / Linux" }, ]; export function TemplateEditor({ templateId, onClose }: Props) { const showToast = useUIStore((s) => s.showToast); const qc = useQueryClient(); const [loading, setLoading] = useState(!!templateId); const [saving, setSaving] = useState(false); const [isBuiltin, setIsBuiltin] = useState(false); const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [platform, setPlatform] = useState("both"); const [techStack, setTechStack] = useState(""); const [initCommands, setInitCommands] = useState(""); const [files, setFiles] = useState([]); const [selectedKey, setSelectedKey] = useState(null); useEffect(() => { if (!templateId) { setLoading(false); return; } getTemplate(templateId).then((t: ProjectTemplate) => { setName(t.name); setDescription(t.description ?? ""); setPlatform(t.platform); setTechStack(t.techStack ?? ""); setInitCommands(t.initCommands ?? ""); setIsBuiltin(t.isBuiltin); const entries = t.files.map((f) => ({ _key: crypto.randomUUID(), path: f.path, content: f.content, })); setFiles(entries); if (entries.length > 0) setSelectedKey(entries[0]._key); }).catch((e) => showToast(`❌ 加载失败: ${e}`)) .finally(() => setLoading(false)); }, [templateId]); const addFile = () => { const entry: FileEntry = { _key: crypto.randomUUID(), path: "", content: "" }; setFiles((prev) => [...prev, entry]); setSelectedKey(entry._key); }; const removeFile = (key: string) => { setFiles((prev) => prev.filter((f) => f._key !== key)); setSelectedKey((prev) => { if (prev !== key) return prev; const remaining = files.filter((f) => f._key !== key); return remaining[0]?._key ?? null; }); }; const updateFile = (key: string, field: "path" | "content", value: string) => { setFiles((prev) => prev.map((f) => f._key === key ? { ...f, [field]: value } : f)); }; const selectedFile = files.find((f) => f._key === selectedKey) ?? null; const handleSave = async () => { if (!name.trim()) { showToast("❌ 模板名称不能为空"); return; } setSaving(true); try { await saveTemplate({ id: templateId ?? undefined, name: name.trim(), description: description.trim() || undefined, platform, techStack: techStack.trim() || undefined, initCommands: initCommands.trim() || undefined, files: files .filter((f) => f.path.trim()) .map((f) => ({ path: f.path.trim(), content: f.content })), }); qc.invalidateQueries({ queryKey: ["templates"] }); showToast(templateId ? "已更新" : "已创建"); onClose(); } catch (e) { showToast(`❌ 保存失败: ${e}`); } finally { setSaving(false); } }; return ( {loading ? (
加载中…
) : (
{/* ── 元信息表单 ── */}
setName(e.target.value)} disabled={isBuiltin} placeholder="如:pnpm Monorepo" />
setDescription(e.target.value)} disabled={isBuiltin} placeholder="一句话说明用途" />
setTechStack(e.target.value)} disabled={isBuiltin} placeholder="Node,pnpm,TypeScript" />