From 6cae8bfb8689a90f17035cc757c01b14ed00e8d5 Mon Sep 17 00:00:00 2001 From: YoLin02 Date: Tue, 23 Jun 2026 15:57:13 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BF=9D=E5=AD=98=E9=80=89?= =?UTF-8?q?=E5=AE=9A=E8=8A=82=E7=82=B9=E4=B8=BA=E6=A8=A1=E6=9D=BF=E7=9A=84?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/features/canvas/CanvasOverlays.tsx | 2 ++ src/features/canvas/FlowCanvas.tsx | 1 + .../components/CanvasPropertiesPanel.tsx | 34 ++++++++++++++++++- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/src/features/canvas/CanvasOverlays.tsx b/src/features/canvas/CanvasOverlays.tsx index 9f239bd..7a6f031 100644 --- a/src/features/canvas/CanvasOverlays.tsx +++ b/src/features/canvas/CanvasOverlays.tsx @@ -44,6 +44,7 @@ interface CanvasOverlaysProps { onUpdateNodes: (nodeIds: string[], patch: Partial) => void; onResizeNodes: (nodeIds: string[], width?: number, height?: number) => void; onUpdateEdge: (edgeId: string, patch: Partial) => void; + onSaveSelectionAsTemplate?: () => void; }; contextMenu: { state: CanvasContextMenuState | null; @@ -134,6 +135,7 @@ export default function CanvasOverlays({ onUpdateNodes={properties.onUpdateNodes} onResizeNodes={properties.onResizeNodes} onUpdateEdge={properties.onUpdateEdge} + onSaveSelectionAsTemplate={properties.onSaveSelectionAsTemplate} /> )} diff --git a/src/features/canvas/FlowCanvas.tsx b/src/features/canvas/FlowCanvas.tsx index db49035..57d04da 100644 --- a/src/features/canvas/FlowCanvas.tsx +++ b/src/features/canvas/FlowCanvas.tsx @@ -385,6 +385,7 @@ export default function FlowCanvas({ onUpdateNodes: nodeCommands.updateNodesFromPanel, onResizeNodes: nodeCommands.resizeNodesFromPanel, onUpdateEdge: edgeCommands.updateEdgeFromPanel, + onSaveSelectionAsTemplate: templates.saveSelectionAsTemplate, }} contextMenu={{ state: contextMenu.contextMenu, diff --git a/src/features/canvas/components/CanvasPropertiesPanel.tsx b/src/features/canvas/components/CanvasPropertiesPanel.tsx index 516be7a..5b03cb3 100644 --- a/src/features/canvas/components/CanvasPropertiesPanel.tsx +++ b/src/features/canvas/components/CanvasPropertiesPanel.tsx @@ -1,5 +1,5 @@ import { Edge, MarkerType } from '@xyflow/react'; -import { AlignCenter, AlignLeft, AlignRight, CornerDownRight, Link2, MousePointer2, Route, SquareDashedMousePointer, Upload } from 'lucide-react'; +import { AlignCenter, AlignLeft, AlignRight, Boxes, CornerDownRight, Link2, MousePointer2, Route, SquareDashedMousePointer, Upload } from 'lucide-react'; import { useRef, useState } from 'react'; import type { ChangeEvent, ReactNode } from 'react'; import { TableCellSelection, TableNodeDataValue, TableTextAlign, WorkspaceNode } from '../../../types'; @@ -11,6 +11,7 @@ interface CanvasPropertiesPanelProps { onUpdateNodes: (nodeIds: string[], patch: Partial) => void; onResizeNodes: (nodeIds: string[], width?: number, height?: number) => void; onUpdateEdge: (edgeId: string, patch: Partial) => void; + onSaveSelectionAsTemplate?: () => void; } const STATUS_OPTIONS = ['', '草稿', '待补充', '已确认', '重点', '废弃']; @@ -41,6 +42,7 @@ export default function CanvasPropertiesPanel({ onUpdateNodes, onResizeNodes, onUpdateEdge, + onSaveSelectionAsTemplate, }: CanvasPropertiesPanelProps) { if (selectedNodes.length === 0 && !selectedEdge) return null; @@ -71,6 +73,13 @@ export default function CanvasPropertiesPanel({ onUpdateNodes={onUpdateNodes} /> + {!isSingle && onSaveSelectionAsTemplate && ( + + )} + {isSingle && firstNode.type !== 'timeline' && ( @@ -80,6 +89,29 @@ export default function CanvasPropertiesPanel({ ); } +function BatchTemplateAction({ + selectedCount, + onSaveSelectionAsTemplate, +}: { + selectedCount: number; + onSaveSelectionAsTemplate: () => void; +}) { + return ( +
+ +
+ ); +} + function EdgePropertiesPanel({ selectedEdge, onUpdateEdge,