Merge pull request #8 from 1847123733/main

feat(canvas): 添加图片预览功能
This commit is contained in:
kunkun
2026-05-23 15:43:22 +08:00
committed by GitHub
2 changed files with 27 additions and 1 deletions
@@ -251,6 +251,7 @@ function InfiniteCanvasPage() {
const [infoNodeId, setInfoNodeId] = useState<string | null>(null);
const [cropNodeId, setCropNodeId] = useState<string | null>(null);
const [angleNodeId, setAngleNodeId] = useState<string | null>(null);
const [previewNodeId, setPreviewNodeId] = useState<string | null>(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 ? <CanvasNodeAngleDialog dataUrl={angleNode.metadata.content} open={Boolean(angleNode)} onClose={() => setAngleNodeId(null)} onConfirm={(params) => void generateAngleNode(angleNode!, params)} /> : null}
<Modal
title="图片详情"
open={Boolean(previewNode?.metadata?.content)}
centered
onCancel={() => setPreviewNodeId(null)}
footer={null}
width="auto"
styles={{ body: { padding: 0, display: "flex", justifyContent: "center", alignItems: "center", maxHeight: "80vh" } }}
>
{previewNode?.metadata?.content ? (
<img
src={previewNode.metadata.content}
alt={previewNode.title || "图片"}
style={{ maxWidth: "100%", maxHeight: "80vh", objectFit: "contain" }}
/>
) : null}
</Modal>
<Modal
title="清空画布?"
open={clearConfirmOpen}
@@ -2,7 +2,7 @@
import { useEffect, useMemo, useState, type ReactNode } from "react";
import { Modal, Segmented, Tooltip } from "antd";
import { Camera, Download, FolderPlus, Image as ImageIcon, Info, Lock, LockOpen, MessageSquare, Minus, Pencil, Plus, RefreshCw, Scissors, Settings2, Trash2, Upload } from "lucide-react";
import { Camera, Download, FolderPlus, Image as ImageIcon, Info, Lock, LockOpen, Maximize2, MessageSquare, Minus, Pencil, Plus, RefreshCw, Scissors, Settings2, Trash2, Upload } from "lucide-react";
import { canvasThemes } from "@/lib/canvas-theme";
import { formatBytes, getDataUrlByteSize } from "@/lib/image-utils";
@@ -25,6 +25,7 @@ type CanvasNodeHoverToolbarProps = {
onSaveAsset: (node: CanvasNodeData) => 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 ? <ToolbarAction title="裁剪并生成新节点" label="裁剪" icon={<Scissors className="size-4" />} onClick={() => onCrop(node)} /> : null}
{hasImage ? <ToolbarAction title="生成角度" label="多角度" icon={<Camera className="size-4" />} onClick={() => onAngle(node)} /> : null}
{hasImage ? <ToolbarAction title="查看图片详情" label="查看大图" icon={<Maximize2 className="size-4" />} onClick={() => onViewImage(node)} /> : null}
</div>
);
}