feat: add project delete action on home canvas cards
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-03-04 01:14:11 +08:00
parent 26b5e55ee8
commit babfeb5714

View File

@ -28,6 +28,7 @@ import {
Settings,
Sparkles,
Star,
Trash2,
Upload,
User,
Users,
@ -361,6 +362,7 @@ export function LovartHome({
const [tutorialDialogOpen, setTutorialDialogOpen] = useState(false);
const [currentTutorialVideo, setCurrentTutorialVideo] = useState<TutorialVideo | null>(null);
const [contactDialogOpen, setContactDialogOpen] = useState(false);
const [deletingProjectId, setDeletingProjectId] = useState<string | null>(null);
const [typedPreviewText, setTypedPreviewText] = useState("");
const [previewCursorVisible, setPreviewCursorVisible] = useState(true);
const projectSectionRef = useRef<HTMLDivElement | null>(null);
@ -1086,6 +1088,39 @@ export function LovartHome({
router.push("/canvas?new=1");
};
const handleDeleteProject = async (projectId: string, projectTitle?: string) => {
if (deletingProjectId === projectId) return;
const normalizedTitle = String(projectTitle || "").trim() || "未命名画布";
const confirmed = window.confirm(`确定删除「${normalizedTitle}」吗?删除后不可恢复。`);
if (!confirmed) return;
setDeletingProjectId(projectId);
try {
const response = await fetch(`/api/canvas/${projectId}`, {
method: "DELETE",
});
const result = await response.json().catch(() => null);
if (!response.ok || result?.success === false) {
throw new Error(result?.error || "删除画布失败");
}
await mutateCanvas((current) => {
if (!current) return current;
return {
...current,
data: (current.data || []).filter((project) => project.id !== projectId),
};
}, false);
toast.success("画布已删除");
} catch (error) {
toast.error(error instanceof Error ? error.message : "删除画布失败");
await mutateCanvas();
} finally {
setDeletingProjectId(null);
}
};
const openAllProjectsInPage = () => {
if (!isAuthenticated) {
openLoginDialog();
@ -1749,26 +1784,43 @@ export function LovartHome({
</button>
{recentProjects.map((item) => (
<button
<div
key={item.id}
type="button"
onClick={() => router.push(`/canvas/${item.id}`)}
className="group cursor-pointer rounded-[22px] border border-black/8 bg-white p-3 text-left shadow-sm transition hover:-translate-y-0.5"
className="group relative rounded-[22px] border border-black/8 bg-white p-3 text-left shadow-sm transition hover:-translate-y-0.5"
>
<div className="aspect-[4/3] overflow-hidden rounded-2xl bg-[#efefef]">
{item.thumbnail ? (
<img
src={item.thumbnail}
alt={item.title}
className="h-full w-full object-cover transition-transform duration-300 ease-out will-change-transform group-hover:scale-[1.045]"
/>
<button
type="button"
onClick={() => router.push(`/canvas/${item.id}`)}
className="block w-full cursor-pointer text-left"
>
<div className="aspect-[4/3] overflow-hidden rounded-2xl bg-[#efefef]">
{item.thumbnail ? (
<img
src={item.thumbnail}
alt={item.title}
className="h-full w-full object-cover transition-transform duration-300 ease-out will-change-transform group-hover:scale-[1.045]"
/>
) : (
<div className="flex h-full w-full items-center justify-center text-black/35">Untitled</div>
)}
</div>
<div className="mt-3 text-[14px] font-medium">{item.title || "Untitled"}</div>
<div className="mt-1 text-xs text-black/35">{formatRelativeTime(item.updatedAt || item.createdAt)}</div>
</button>
<button
type="button"
onClick={() => handleDeleteProject(item.id, item.title)}
disabled={deletingProjectId === item.id}
className="absolute right-5 top-5 z-20 inline-flex h-7 w-7 items-center justify-center rounded-full border border-black/10 bg-white/90 text-black/45 shadow-sm transition hover:border-[#ffb4b4] hover:text-[#ef4444] disabled:cursor-not-allowed disabled:opacity-80"
aria-label={`删除${item.title || "未命名画布"}`}
>
{deletingProjectId === item.id ? (
<Loader2 className="h-3.5 w-3.5 animate-spin" />
) : (
<div className="flex h-full w-full items-center justify-center text-black/35">Untitled</div>
<Trash2 className="h-3.5 w-3.5" />
)}
</div>
<div className="mt-3 text-[14px] font-medium">{item.title || "Untitled"}</div>
<div className="mt-1 text-xs text-black/35">{formatRelativeTime(item.updatedAt || item.createdAt)}</div>
</button>
</button>
</div>
))}
{recentProjects.length === 0 && (