fix(canvas): 3D 导演台 - 完成 Issue #305 P0 稳定性治理 (#324)

* fix(canvas): 3D 导演台 - 修复状态回弹与保存恢复生命周期

* style(canvas): 3D 导演台 - 对齐 CI 格式门禁
This commit is contained in:
echoD886 authored and GitHub committed 2026-08-27 10:32:45 +08:00
1 parent 2a28ababf1
commit c2766544d7
42 files changed
+8124 -188

No files matched your search

+2 -1
View File
@@ -1784,7 +1784,7 @@ function InfiniteCanvasPage() {
<CanvasDirectorNodePanel
node={contentNode}
scene={currentProject?.directorScenes?.find((scene) => scene.id === contentNode.metadata?.directorSceneId) || null}
previewUrl={nodesRef.current.find((item) => item.id === contentNode.metadata?.directorPreviewNodeId)?.metadata?.content}
readNodeContent={(nodeId) => (nodeId ? nodesRef.current.find((item) => item.id === nodeId)?.metadata?.content : undefined)}
professional={workspaceMode === "professional"}
onOpen={() => openDirectorWorkbench(contentNode.id)}
/>
@@ -2598,6 +2598,7 @@ function InfiniteCanvasPage() {
onChange={saveDirectorScene}
onApply={applyDirectorOutput}
onDeleteImageNode={(nodeId) => deleteNodes(new Set([nodeId]))}
onFlush={() => flushCanvasStorePersistence()}
/>
</Suspense>
) : null}
+15 -4
View File
@@ -6,8 +6,10 @@ import { imageMetadata, videoMetadata } from "@/lib/canvas/canvas-generation-tas
import { fitNodeSize } from "@/lib/canvas/canvas-node-size";
import { createCanvasNode } from "@/lib/canvas/canvas-project-domain";
import { createDirectorScene } from "@/lib/canvas/director/director-scene";
import { upsertDirectorSceneById } from "@/lib/canvas/director/director-session";
import { uploadImage } from "@/services/image-storage";
import { uploadMediaFile } from "@/services/file-storage";
import { useCanvasStore } from "@/stores/canvas/use-canvas-store";
import { CanvasNodeType, type CanvasConnection, type CanvasNodeData, type CanvasNodeMetadata, type Position } from "@/types/canvas";
import type { DirectorScene, DirectorSceneOutput } from "@/types/director";
@@ -28,6 +30,14 @@ type UseCanvasDirectorOptions = {
const NODE_STATUS_IDLE = "idle" as const;
/**
* 项目真实内存权威在 useCanvasStore.getState().projects;
* 闭包里的 directorScenes 只作为 store 尚未就绪时的兜底。
*/
function currentDirectorScenes(projectId: string, fallback: DirectorScene[]) {
const project = useCanvasStore.getState().projects.find((item) => item.id === projectId);
return project?.directorScenes ?? fallback;
}
export function useCanvasDirector({
projectId,
directorNodeId,
@@ -68,14 +78,14 @@ export function useCanvasDirector({
setNodes(nextNodes);
setSelectedNodeIds(new Set([node.id]));
setSelectedConnectionId(null);
updateProject(projectId, { directorScenes: [...directorScenes, scene] });
updateProject(projectId, { directorScenes: upsertDirectorSceneById(currentDirectorScenes(projectId, directorScenes), scene) });
message.success("已创建导演台节点,点击缩略图进入编辑");
}, [directorScenes, getCanvasCenter, message, nodesRef, projectId, setNodes, setSelectedConnectionId, setSelectedNodeIds, updateProject]);
const openDirectorWorkbench = useCallback((nodeId: string) => {
const node = nodesRef.current.find((item) => item.id === nodeId);
if (!node || node.metadata?.workflowKind !== "shot") return;
let scene = directorScenes.find((item) => item.id === node.metadata?.directorSceneId);
let scene = currentDirectorScenes(projectId, directorScenes).find((item) => item.id === node.metadata?.directorSceneId);
if (!scene) {
scene = createDirectorScene(node.metadata?.workflowTitle || node.title || "镜头场景");
const shot = scene.shots[0];
@@ -83,13 +93,14 @@ export function useCanvasDirector({
const directorSceneId = scene.id;
const directorShotId = shot.id;
setNodes((current) => current.map((item) => item.id === nodeId ? { ...item, metadata: { ...item.metadata, directorSceneId, directorShotId } } : item));
updateProject(projectId, { directorScenes: [...directorScenes, scene] });
updateProject(projectId, { directorScenes: upsertDirectorSceneById(currentDirectorScenes(projectId, directorScenes), scene) });
}
setDirectorNodeId(nodeId);
}, [directorScenes, nodesRef, projectId, setDirectorNodeId, setNodes, updateProject]);
/** 每次保存都基于 store 中最新 directorScenes upsert,避免旧闭包数组覆盖并发保存。 */
const saveDirectorScene = useCallback((scene: DirectorScene) => {
updateProject(projectId, { directorScenes: directorScenes.some((item) => item.id === scene.id) ? directorScenes.map((item) => item.id === scene.id ? scene : item) : [...directorScenes, scene] });
updateProject(projectId, { directorScenes: upsertDirectorSceneById(currentDirectorScenes(projectId, directorScenes), scene) });
}, [directorScenes, projectId, updateProject]);
const applyDirectorOutput = useCallback(async (output: DirectorSceneOutput) => {