import { useEffect, useState } from "react"; import { useQuery, useQueryClient } from "@tanstack/react-query"; import { open } from "@tauri-apps/plugin-dialog"; import { createProject, updateProject, getSpaces, getTags, getProjectTags, setProjectTags, type Project } from "../../lib/commands"; import { useUIStore } from "../../store/ui"; import { Dialog } from "../ui/Dialog"; const EMPTY: Partial = { id: "", name: "", description: "", type: "", status: "active", priority: "mid", wsl_path: "", win_path: "", tech_stack: "", tags: "", recent_focus: "", startup_commands: "", platform: "windows", }; const inputCls = "w-full border border-gray-200 rounded-lg px-3 py-2 text-sm outline-none focus:ring-2 focus:ring-blue-200 focus:border-blue-400"; const labelCls = "block text-xs font-medium text-gray-600 mb-1"; const sectionCls = "text-xs font-semibold text-gray-400 uppercase tracking-wide mb-3"; export function ProjectModal() { const editTarget = useUIStore((s) => s.editTarget); const closeEdit = useUIStore((s) => s.closeEdit); const showToast = useUIStore((s) => s.showToast); const qc = useQueryClient(); const { data: spaces = [] } = useQuery({ queryKey: ["spaces"], queryFn: getSpaces }); const currentSpace = spaces.find((s) => s.is_current); const [form, setForm] = useState>(EMPTY); const [saving, setSaving] = useState(false); const [tab, setTab] = useState<"info" | "deploy">("info"); const isNew = editTarget === null; // 标签多选 const [selectedTagIds, setSelectedTagIds] = useState([]); const { data: allTags = [] } = useQuery({ queryKey: ["tags"], queryFn: getTags }); useEffect(() => { setForm(editTarget ?? EMPTY); if (editTarget && editTarget.id) { getProjectTags(editTarget.id).then((tags) => { setSelectedTagIds(tags.map((t) => t.id)); }).catch(() => {}); } else { setSelectedTagIds([]); } }, [editTarget]); if (editTarget === undefined) return null; const set = (key: keyof Project, value: string) => setForm((f) => ({ ...f, [key]: value })); const pickFolder = async (field: "wsl_path" | "win_path") => { const selected = await open({ directory: true, multiple: false }); if (!selected) return; // 直接存原始路径(WSL 路径为 \\wsl.localhost\distro\... 格式,Windows 文件管理器可直接访问) set(field, selected as string); }; // 新建项目顶部快速选择:选文件夹后自动填充路径、名称、ID // WSL 路径直接存 UNC 格式(\\wsl.localhost\Ubuntu-22.04\home\...),不再转换为 Linux 路径 const quickPickFolder = async () => { const selected = await open({ directory: true, multiple: false }); if (!selected) return; const raw = selected as string; const isWsl = form.platform === "wsl"; // UNC 路径兼容提取文件夹名 const folderName = raw.replace(/\\/g, "/").split("/").filter(Boolean).pop() ?? ""; setForm((f) => ({ ...f, [isWsl ? "wsl_path" : "win_path"]: raw, name: folderName, id: crypto.randomUUID(), })); }; const handleSave = async () => { if (!form.name?.trim()) { showToast("❌ 项目名称不能为空"); return; } if (isNew && !form.id?.trim()) { showToast("❌ 项目 ID 不能为空"); return; } setSaving(true); try { if (isNew) { await createProject({ ...form, space_id: currentSpace?.id } as Project); // 新建时用前端生成的 workspace id(即 form.id)设置标签 if (form.id && selectedTagIds.length > 0) { await setProjectTags(form.id, selectedTagIds).catch(() => {}); } showToast("项目已添加 ✓"); } else { await updateProject(editTarget!.id, form); await setProjectTags(editTarget!.id, selectedTagIds); showToast("已保存 ✓"); } qc.invalidateQueries({ queryKey: ["projects"] }); qc.invalidateQueries({ queryKey: ["tagMaps"] }); closeEdit(); } catch (e) { showToast(`❌ ${e}`); } finally { setSaving(false); } }; return ( {/* ── 运行平台 + 项目文件夹(新建/编辑均显示)── */}

{isNew ? "选择项目文件夹" : "运行平台 / 项目路径"}

{/* 运行平台 */}
{(["windows", "wsl"] as const).map((p) => ( ))}
{/* 文件夹选择 / 路径输入 */}
{isNew && (form.platform === "wsl" ? form.wsl_path : form.win_path) && (

✓ 已自动填充项目名称与 ID,可在下方继续编辑

)}
{/* ── 标签页导航 ── */}
{([ { id: "info" as const, label: "基本信息" }, { id: "deploy" as const, label: "部署信息" }, ]).map(({ id, label }) => ( ))}
{/* ── Tab 内容(可滚动区)── */}
{/* ── Tab: 基本信息 ── */} {tab === "info" && (
{/* 项目标识 */}

项目标识

set("id", e.target.value)} disabled={!isNew} /> {isNew && ( )}
set("name", e.target.value)} />
set("description", e.target.value)} />
{/* 分类与状态 */}

分类与状态

set("tech_stack", e.target.value)} />
{allTags.length === 0 && (

请先在「管理-标签」里创建标签

)} {allTags.map((tag) => { const selected = selectedTagIds.includes(tag.id); return ( ); })}
{/* 工作区配置 */}

工作区配置

set("recent_focus", e.target.value)} />