diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx index 8986bddeb..549cffead 100644 --- a/app/canvas/[id]/page.tsx +++ b/app/canvas/[id]/page.tsx @@ -133,7 +133,7 @@ export default function CanvasEditorPage({ params }: PageProps) { const [negativePrompt, setNegativePrompt] = useState(""); const [aspectRatio, setAspectRatio] = useState("1:1"); const [imageCount, setImageCount] = useState(1); - const previousNodeCountRef = useRef(0); + const previousNodeIdsRef = useRef>(new Set()); // 获取选中的图片节点 const selectedImageNode = selectedNodeIds.length === 1 @@ -174,15 +174,26 @@ export default function CanvasEditorPage({ params }: PageProps) { loadProject(); }, [id]); // 只依赖 id,使用 getState() 直接调用 store actions + const persistCanvasState = useCallback( + async (reason: "autosave" | "delete" | "beforeunload") => { + const canvasData = getCanvasState(); + const result = await canvasService.saveCanvasState(id, canvasData); + if (!result.success) { + console.error(`[Canvas] ${reason} save failed:`, result.error); + } + return result.success; + }, + [getCanvasState, id] + ); + // 自动保存逻辑 useEffect(() => { if (isLoading) return; const saveTimer = setTimeout(async () => { setIsSaving(true); - const canvasData = getCanvasState(); try { - await canvasService.saveCanvasState(id, canvasData); + await persistCanvasState("autosave"); } catch (error) { console.error("Auto save failed:", error); } @@ -190,24 +201,30 @@ export default function CanvasEditorPage({ params }: PageProps) { }, 3000); // 3秒自动保存 return () => clearTimeout(saveTimer); - }, [nodes, id, isLoading, getCanvasState]); + }, [nodes, isLoading, persistCanvasState]); - // 删除节点时立即保存,避免用户立刻刷新后看到被删除节点又出现 + // 删除节点时立即保存(按节点ID差集检测),避免删除后快速刷新被回滚 useEffect(() => { if (isLoading) { - previousNodeCountRef.current = nodes.length; + previousNodeIdsRef.current = new Set(nodes.map((node) => node.id)); return; } - const previousCount = previousNodeCountRef.current; - const currentCount = nodes.length; - previousNodeCountRef.current = currentCount; + const previousIds = previousNodeIdsRef.current; + const currentIds = new Set(nodes.map((node) => node.id)); + const removedIds: string[] = []; + previousIds.forEach((nodeId) => { + if (!currentIds.has(nodeId)) { + removedIds.push(nodeId); + } + }); + previousNodeIdsRef.current = currentIds; - if (previousCount > currentCount) { + if (removedIds.length > 0) { const persistAfterDelete = async () => { setIsSaving(true); try { - await canvasService.saveCanvasState(id, getCanvasState()); + await persistCanvasState("delete"); } catch (error) { console.error("Delete immediate save failed:", error); } @@ -215,7 +232,33 @@ export default function CanvasEditorPage({ params }: PageProps) { }; void persistAfterDelete(); } - }, [nodes.length, id, isLoading, getCanvasState]); + }, [nodes, isLoading, persistCanvasState]); + + // 页面关闭/刷新前尝试做一次 keepalive 保存,降低“刚删就刷新”回滚概率 + useEffect(() => { + if (isLoading) return; + + const handleBeforeUnload = () => { + try { + const canvasData = getCanvasState(); + void fetch(`/api/canvas/${id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ canvasData }), + keepalive: true, + }); + } catch (error) { + console.error("[Canvas] beforeunload save failed:", error); + } + }; + + window.addEventListener("beforeunload", handleBeforeUnload); + window.addEventListener("pagehide", handleBeforeUnload); + return () => { + window.removeEventListener("beforeunload", handleBeforeUnload); + window.removeEventListener("pagehide", handleBeforeUnload); + }; + }, [getCanvasState, id, isLoading]); // 智能命名与缩略图逻辑 useEffect(() => {