perf(*): 创作工作台 - 优化首页按需加载与画布交互并修复历史迁移冲突

This commit is contained in:
ddCat committed 2026-09-05 13:24:10 +08:00
1 parent 165b4443ab
commit 58f01d1eb6
107 files changed
+3025 -1089

No files matched your search

@@ -1,15 +1,17 @@
import { Dropdown, Input } from "antd";
import { App, Dropdown, Input } from "antd";
import { Download, LoaderCircle, MoreHorizontal, Pencil, Trash2 } from "lucide-react";
import type { KeyboardEvent } from "react";
import { ProjectPreview } from "@/components/canvas/canvas-project-card";
import { exportCanvasProjects } from "@/lib/canvas/canvas-export";
import { useCanvasStore, type CanvasProject } from "@/stores/canvas/use-canvas-store";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
import type { CanvasLibrarySummary } from "@/services/api/user-data";
import { loadCanvasProjectForEditing, saveRemoteUserDataNow } from "@/services/user-data-sync";
import { useCanvasUiStore } from "@/stores/canvas/use-canvas-ui-store";
import { cn } from "@/lib/utils";
type CanvasFolderCardProps = {
project: CanvasProject;
project: CanvasLibrarySummary;
projectName?: string;
onClick: () => void;
onPrefetch?: () => void;
@@ -18,6 +20,7 @@ type CanvasFolderCardProps = {
/** 画布库中的文件夹封面:单一卡片表面承载预览和信息,避免相邻卡片互相侵入。 */
export function CanvasFolderCard({ project, projectName, onClick, onPrefetch, opening = false }: CanvasFolderCardProps) {
const { message } = App.useApp();
const renameProject = useCanvasStore((state) => state.renameProject);
const selectedIds = useCanvasUiStore((state) => state.selectedProjectIds);
const editingId = useCanvasUiStore((state) => state.editingProjectId);
@@ -30,9 +33,21 @@ export function CanvasFolderCard({ project, projectName, onClick, onPrefetch, op
const editing = editingId === project.id;
const selected = selectedIds.includes(project.id);
const saveTitle = () => {
renameProject(project.id, editingTitle);
const saveTitle = async () => {
if (!editing) return;
stopEditing();
try {
await loadCanvasProjectForEditing(project.id);
renameProject(project.id, editingTitle);
await saveRemoteUserDataNow();
} catch (error) { message.error(error instanceof Error ? error.message : "重命名失败"); }
};
const exportProject = async () => {
try {
const fullProject = await loadCanvasProjectForEditing(project.id);
if (!fullProject) throw new Error("画布不存在");
await exportCanvasProjects([fullProject], project.title || "画布");
} catch (error) { message.error(error instanceof Error ? error.message : "导出失败"); }
};
const handleOpenKeyDown = (event: KeyboardEvent<HTMLDivElement>) => {
@@ -47,7 +62,7 @@ export function CanvasFolderCard({ project, projectName, onClick, onPrefetch, op
<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 />
<ProjectPreview project={{ id: project.id, nodes: project.previewNodes }} preferLatestImage />
{opening ? <div className="canvas-folder-opening"><LoaderCircle className="size-5 animate-spin" /><span>正在打开</span></div> : null}
</div>
<div className="canvas-folder-body">
@@ -72,7 +87,7 @@ export function CanvasFolderCard({ project, projectName, onClick, onPrefetch, op
<div className="canvas-folder-meta">
<span className="canvas-folder-meta-item">{projectName ? `所属项目:${projectName}` : "自由画布"}</span>
<span className="canvas-folder-meta-separator" aria-hidden="true">·</span>
<span className="canvas-folder-meta-item">{project.nodes.length} 节点</span>
<span className="canvas-folder-meta-item">{project.nodeCount} 节点</span>
</div>
<div className="canvas-folder-dates">
<span><small>创建时间</small><time dateTime={project.createdAt}>{formatCanvasDate(project.createdAt)}</time></span>
@@ -112,7 +127,7 @@ export function CanvasFolderCard({ project, projectName, onClick, onPrefetch, op
menu={{
onClick: ({ domEvent }) => domEvent.stopPropagation(),
items: [
{ key: "export", icon: <Download className="size-3.5" />, label: "导出画布", onClick: () => void exportCanvasProjects([project], project.title || "画布") },
{ key: "export", icon: <Download className="size-3.5" />, label: "导出画布", onClick: () => void exportProject() },
{ type: "divider" },
{ key: "delete", danger: true, icon: <Trash2 className="size-3.5" />, label: "删除", onClick: () => setDeleteIds([project.id]) },
],