From 20d05293653d13c72dfa000bb4d7ed5a6f1f3346 Mon Sep 17 00:00:00 2001
From: Your Name
Date: Sun, 5 Apr 2026 03:37:04 +0800
Subject: [PATCH] Add in-canvas module delete actions
---
app/canvas/[id]/page.tsx | 18 ++++++++++++
components/canvas/nodes/TextNode.tsx | 35 ++++++++++++++++++++----
components/canvas/nodes/WorkflowNode.tsx | 27 ++++++++++++++++--
3 files changed, 71 insertions(+), 9 deletions(-)
diff --git a/app/canvas/[id]/page.tsx b/app/canvas/[id]/page.tsx
index ca5411f19..a047d4efc 100644
--- a/app/canvas/[id]/page.tsx
+++ b/app/canvas/[id]/page.tsx
@@ -10,6 +10,7 @@ import {
ChevronDown,
ChevronLeft,
ChevronRight,
+ Trash2,
Save,
Undo2,
Redo2,
@@ -941,6 +942,7 @@ export default function CanvasEditorPage({ params }: PageProps) {
const clearCanvas = useCanvasStore((state) => state.clearCanvas);
const getCanvasState = useCanvasStore((state) => state.getCanvasState);
const addNode = useCanvasStore((state) => state.addNode);
+ const deleteNode = useCanvasStore((state) => state.deleteNode);
const addEdge = useCanvasStore((state) => state.addEdge);
const selectNodes = useCanvasStore((state) => state.selectNodes);
const updateNode = useCanvasStore((state) => state.updateNode);
@@ -1187,6 +1189,11 @@ export default function CanvasEditorPage({ params }: PageProps) {
: 296
: 0;
const selectedWorkflowPanelLeft = isVideoMode ? videoModuleSidebarWidth + 28 : 16;
+ const handleDeleteSelectedWorkflowNode = useCallback(() => {
+ if (!selectedVideoWorkflowNode) return;
+ deleteNode(selectedVideoWorkflowNode.id);
+ toast.success(`已删除模块:${selectedVideoWorkflowDefinition?.label || "当前模块"}`);
+ }, [deleteNode, selectedVideoWorkflowDefinition?.label, selectedVideoWorkflowNode]);
const workflowGraph = useMemo(
() => createWorkflowGraphContext(nodes, edges),
[edges, nodes]
@@ -7941,6 +7948,17 @@ export default function CanvasEditorPage({ params }: PageProps) {
)}
) : null}
+
+
diff --git a/components/canvas/nodes/TextNode.tsx b/components/canvas/nodes/TextNode.tsx
index 73eeeb7c2..e98166d6e 100644
--- a/components/canvas/nodes/TextNode.tsx
+++ b/components/canvas/nodes/TextNode.tsx
@@ -1,6 +1,7 @@
"use client";
import React, { useState, useRef, useEffect } from "react";
+import { Trash2 } from "lucide-react";
import { useCanvasStore } from "@/store/canvas";
import type { CanvasNode, TextNodeData } from "@/types/canvas";
import { cn } from "@/lib/utils";
@@ -17,6 +18,7 @@ export function TextNode({ node, isSelected }: TextNodeProps) {
const textareaRef = useRef(null);
const updateNode = useCanvasStore((state) => state.updateNode);
+ const deleteNode = useCanvasStore((state) => state.deleteNode);
// 进入编辑模式时聚焦
useEffect(() => {
@@ -119,12 +121,33 @@ export function TextNode({ node, isSelected }: TextNodeProps) {
)}
- {/* 编辑提示 */}
- {!isEditing && isSelected && (
-
- 双击编辑
-
- )}
+ {!isEditing && isSelected ? (
+ <>
+
+
+
+ 双击编辑
+
+ >
+ ) : null}
);
}
diff --git a/components/canvas/nodes/WorkflowNode.tsx b/components/canvas/nodes/WorkflowNode.tsx
index 8fa6dc0a6..f8ed0eda5 100644
--- a/components/canvas/nodes/WorkflowNode.tsx
+++ b/components/canvas/nodes/WorkflowNode.tsx
@@ -8,8 +8,10 @@ import {
LayoutPanelTop,
ScanSearch,
Sparkles,
+ Trash2,
} from "lucide-react";
import { Badge } from "@/components/ui/badge";
+import { useCanvasStore } from "@/store/canvas";
import { cn } from "@/lib/utils";
import {
getVideoWorkflowNodeDefinition,
@@ -78,6 +80,7 @@ const STATUS_LABELS = {
} as const;
export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
+ const deleteNode = useCanvasStore((state) => state.deleteNode);
const data = node.data as WorkflowNodeData;
const definition = getVideoWorkflowNodeDefinition(data.templateType);
const metadata = (data.metadata || {}) as Record;
@@ -183,9 +186,27 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
-
- {STATUS_LABELS[data.status] || STATUS_LABELS.pending}
-
+
+ {isSelected ? (
+
+ ) : null}
+
+ {STATUS_LABELS[data.status] || STATUS_LABELS.pending}
+
+