"use client"; import { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; import { formatDistanceToNow } from "date-fns"; import { zhCN } from "date-fns/locale"; import { Trash2, FileImage, Loader2, Plus, X } from "lucide-react"; import { Button } from "@/components/ui/button"; import { ScrollArea } from "@/components/ui/scroll-area"; import { canvasService } from "@/services/canvas"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import type { CanvasProjectListItem, CanvasWorkspaceType } from "@/types/canvas"; const PROJECT_LIST_PAGE_SIZE = 24; interface CanvasFileListPanelProps { currentId: string; onClose: () => void; workspaceType?: CanvasWorkspaceType; workspaceLabel?: string; createUrl?: string; buildProjectUrl?: (id: string) => string; } export function CanvasFileListPanel({ currentId, onClose, workspaceType = "canvas", workspaceLabel = "画布", createUrl = "/canvas?new=1", buildProjectUrl = (id) => `/canvas/${id}`, }: CanvasFileListPanelProps) { const router = useRouter(); const isVideoWorkspace = workspaceType === "video"; const [projects, setProjects] = useState([]); const [isLoading, setIsLoading] = useState(true); const [isLoadingMore, setIsLoadingMore] = useState(false); const [page, setPage] = useState(1); const [hasMore, setHasMore] = useState(false); // 加载项目列表 const loadProjects = async (nextPage = 1, append = false) => { if (append) { setIsLoadingMore(true); } else { setIsLoading(true); } const result = await canvasService.getProjects(workspaceType, { page: nextPage, limit: PROJECT_LIST_PAGE_SIZE, }); if (result.success && result.data) { // 按更新时间降序排序 const sorted = result.data.sort((a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime() ); setProjects((prev) => { if (!append) return sorted; const seen = new Set(prev.map((project) => project.id)); return [ ...prev, ...sorted.filter((project) => !seen.has(project.id)), ]; }); setPage(nextPage); setHasMore(Boolean(result.pagination?.hasMore)); } if (append) { setIsLoadingMore(false); } else { setIsLoading(false); } }; useEffect(() => { setProjects([]); setPage(1); setHasMore(false); loadProjects(1, false); }, [workspaceType]); // 创建新画布 const handleCreateNew = async () => { router.push(createUrl); }; // 切换画布 const handleSwitch = (id: string) => { if (id === currentId) { onClose(); return; } const targetUrl = buildProjectUrl(id); onClose(); // 关闭文件列表面板 // 强制整页跳转,避免仅更新 URL 不刷新 setTimeout(() => { window.location.assign(targetUrl); }, 100); }; // 删除画布 const handleDelete = async (e: React.MouseEvent, id: string) => { e.stopPropagation(); if (!confirm("确定要删除这个画布吗?")) return; const result = await canvasService.deleteProject(id); if (result.success) { setProjects(prev => prev.filter(p => p.id !== id)); toast.success("删除成功"); // 如果删除的是当前画布,跳转到列表第一个或创建新的 if (id === currentId) { const next = projects.find(p => p.id !== id); if (next) { router.push(buildProjectUrl(next.id)); } else { handleCreateNew(); } } } else { toast.error("删除失败"); } }; return (
{/* Header */}

{workspaceLabel}项目列表

{/* Content */}
{isLoading ? (
) : projects.length === 0 ? (

暂无{workspaceLabel}历史

) : (
{projects.map((project) => (
handleSwitch(project.id)} className={cn( "group flex cursor-pointer items-center gap-3 rounded-2xl p-3 transition-all", isVideoWorkspace ? "border border-border/60 bg-background/72 shadow-sm hover:-translate-y-0.5 hover:border-primary/30 hover:bg-background/88 hover:shadow-md" : "hover:bg-muted/50", project.id === currentId ? isVideoWorkspace ? "border-primary/35 bg-background ring-1 ring-primary/10" : "bg-primary/5 ring-1 ring-primary/20" : isVideoWorkspace ? "" : "bg-card" )} > {/* 列表只展示轻量占位,不拉取未打开项目的云端图片 */}
{/* 信息 */}

{project.title || `未命名${workspaceLabel}`}

{formatDistanceToNow(new Date(project.updatedAt), { addSuffix: true, locale: zhCN })}

{/* 删除按钮 */}
))} {hasMore ? ( ) : (
到底了
)}
)}
); }