feat(canvas): 画布工作台 - 新增生成副本并修复大型画布性能、素材同步与删除校验

统一图片与视频副本的生成落点并保留参考连接。\n优化大型画布节点虚拟化、媒体懒加载、拖拽与详情进入路径。\n修复素材主版本 ID 同步及删除时的画布独立引用校验。
This commit is contained in:
ddCat committed 2026-09-01 14:57:05 +08:00
1 parent 434f63e153
commit 3f4a9ea3d0
45 files changed
+1348 -286

No files matched your search

@@ -1,5 +1,5 @@
import { Dropdown, Input } from "antd";
import { Download, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
import { Download, LoaderCircle, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
import type { KeyboardEvent } from "react";
import { ProjectPreview } from "@/components/canvas/canvas-project-card";
@@ -12,10 +12,12 @@ type CanvasFolderCardProps = {
project: CanvasProject;
projectName?: string;
onClick: () => void;
onPrefetch?: () => void;
opening?: boolean;
};
/** 画布库中的文件夹封面:单一卡片表面承载预览和信息,避免相邻卡片互相侵入。 */
export function CanvasFolderCard({ project, projectName, onClick }: CanvasFolderCardProps) {
export function CanvasFolderCard({ project, projectName, onClick, onPrefetch, opening = false }: CanvasFolderCardProps) {
const renameProject = useCanvasStore((state) => state.renameProject);
const selectedIds = useCanvasUiStore((state) => state.selectedProjectIds);
const editingId = useCanvasUiStore((state) => state.editingProjectId);
@@ -42,10 +44,11 @@ export function CanvasFolderCard({ project, projectName, onClick }: CanvasFolder
};
return (
<article className={cn("canvas-folder-card", selected && "is-selected", editing && "is-editing")}>
<div className="canvas-folder-open" role="button" tabIndex={0} aria-label={`打开画布 ${project.title}`} onClick={() => !editing && onClick()} onKeyDown={handleOpenKeyDown}>
<article className={cn("canvas-folder-card", selected && "is-selected", editing && "is-editing", opening && "is-opening")} onPointerEnter={onPrefetch} onPointerDown={onPrefetch} onFocusCapture={onPrefetch}>
<div className="canvas-folder-open" role="button" tabIndex={0} aria-label={`打开画布 ${project.title}`} aria-busy={opening} onClick={() => !editing && !opening && onClick()} onKeyDown={handleOpenKeyDown}>
<div className="canvas-folder-preview" aria-hidden="true">
<ProjectPreview project={project} preferLatestImage />
{opening ? <div className="canvas-folder-opening"><LoaderCircle className="size-5 animate-spin" /><span>正在打开</span></div> : null}
</div>
<div className="canvas-folder-body">
<div className="canvas-folder-heading-row">