From 697e1fc1ebfc92a921cd72ba5af9523e0c78285b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E9=91=AB=E5=87=AF?= Date: Sat, 23 May 2026 15:32:44 +0800 Subject: [PATCH] =?UTF-8?q?feat(canvas):=20=E6=B7=BB=E5=8A=A0=E5=9B=BE?= =?UTF-8?q?=E7=89=87=E9=A2=84=E8=A7=88=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 previewNodeId 状态用于管理图片预览弹窗 - 在 CanvasNodeHoverToolbar 中添加查看大图按钮 - 实现图片详情模态框组件 - 添加图片预览功能的回调处理逻辑 --- .../(user)/canvas/[id]/canvas-client-page.tsx | 23 +++++++++++++++++++ .../components/canvas-node-hover-toolbar.tsx | 5 +++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx b/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx index 593c094..0e8dd24 100644 --- a/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx +++ b/web/src/app/(user)/canvas/[id]/canvas-client-page.tsx @@ -251,6 +251,7 @@ function InfiniteCanvasPage() { const [infoNodeId, setInfoNodeId] = useState(null); const [cropNodeId, setCropNodeId] = useState(null); const [angleNodeId, setAngleNodeId] = useState(null); + const [previewNodeId, setPreviewNodeId] = useState(null); const [assistantCollapsed, setAssistantCollapsed] = useState(true); const [assistantMounted, setAssistantMounted] = useState(false); const [titleEditing, setTitleEditing] = useState(false); @@ -525,6 +526,7 @@ function InfiniteCanvasPage() { const infoNode = infoNodeId ? nodeById.get(infoNodeId) || null : null; const cropNode = cropNodeId ? nodeById.get(cropNodeId) || null : null; const angleNode = angleNodeId ? nodeById.get(angleNodeId) || null : null; + const previewNode = previewNodeId ? nodeById.get(previewNodeId) || null : null; const hasMultipleSelectedNodes = selectedNodeIds.size > 1; const activeNodeId = hasMultipleSelectedNodes ? null : hoveredNodeId || (selectedNodeIds.size === 1 ? Array.from(selectedNodeIds)[0] : null); const batchChildCountById = useMemo(() => { @@ -631,6 +633,7 @@ function InfiniteCanvasPage() { setInfoNodeId((current) => (current && allIds.has(current) ? null : current)); setCropNodeId((current) => (current && allIds.has(current) ? null : current)); setAngleNodeId((current) => (current && allIds.has(current) ? null : current)); + setPreviewNodeId((current) => (current && allIds.has(current) ? null : current)); setRunningNodeId((current) => (current && allIds.has(current) ? null : current)); setContextMenu((current) => (current && allIds.has(current.nodeId) ? null : current)); cleanupAssetImages({ projectId, nodes: nodesRef.current.filter((node) => !allIds.has(node.id)), chatSessions }); @@ -656,6 +659,7 @@ function InfiniteCanvasPage() { setInfoNodeId(null); setCropNodeId(null); setAngleNodeId(null); + setPreviewNodeId(null); setRunningNodeId(null); deselectCanvas(); setClearConfirmOpen(false); @@ -2084,6 +2088,7 @@ function InfiniteCanvasPage() { onSaveAsset={(node) => void saveNodeAsset(node)} onCrop={(node) => setCropNodeId(node.id)} onAngle={(node) => setAngleNodeId(node.id)} + onViewImage={(node) => setPreviewNodeId(node.id)} onRetry={(node) => void handleRetryNode(node)} onToggleFreeResize={(node) => toggleNodeFreeResize(node.id)} onDelete={(node) => deleteNodes(new Set([node.id]))} @@ -2141,6 +2146,24 @@ function InfiniteCanvasPage() { {angleNode?.metadata?.content ? setAngleNodeId(null)} onConfirm={(params) => void generateAngleNode(angleNode!, params)} /> : null} + setPreviewNodeId(null)} + footer={null} + width="auto" + styles={{ body: { padding: 0, display: "flex", justifyContent: "center", alignItems: "center", maxHeight: "80vh" } }} + > + {previewNode?.metadata?.content ? ( + {previewNode.title + ) : null} + + void; onCrop: (node: CanvasNodeData) => void; onAngle: (node: CanvasNodeData) => void; + onViewImage: (node: CanvasNodeData) => void; onRetry: (node: CanvasNodeData) => void; onToggleFreeResize: (node: CanvasNodeData) => void; onDelete: (node: CanvasNodeData) => void; @@ -46,6 +47,7 @@ export function CanvasNodeHoverToolbar({ onSaveAsset, onCrop, onAngle, + onViewImage, onRetry, onToggleFreeResize, onDelete, @@ -95,6 +97,7 @@ export function CanvasNodeHoverToolbar({ ) : null} {hasImage ? } onClick={() => onCrop(node)} /> : null} {hasImage ? } onClick={() => onAngle(node)} /> : null} + {hasImage ? } onClick={() => onViewImage(node)} /> : null} ); }