Add in-canvas module delete actions
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled

This commit is contained in:
Your Name 2026-04-05 03:37:04 +08:00
parent 3876a0c403
commit 20d0529365
3 changed files with 71 additions and 9 deletions

View File

@ -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) {
)}
</Button>
) : null}
<Button
type="button"
variant="destructive"
onClick={handleDeleteSelectedWorkflowNode}
disabled={isAnyWorkflowExecutionRunning}
className="w-full rounded-2xl shadow-sm"
>
<Trash2 className="mr-2 h-4 w-4" />
删除当前模块
</Button>
</div>
</CardContent>
</Card>

View File

@ -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<HTMLTextAreaElement>(null);
const updateNode = useCanvasStore((state) => state.updateNode);
const deleteNode = useCanvasStore((state) => state.deleteNode);
// 进入编辑模式时聚焦
useEffect(() => {
@ -119,12 +121,33 @@ export function TextNode({ node, isSelected }: TextNodeProps) {
</div>
)}
{/* 编辑提示 */}
{!isEditing && isSelected && (
<div className="absolute bottom-1 right-1 text-[10px] text-muted-foreground">
双击编辑
</div>
)}
{!isEditing && isSelected ? (
<>
<button
type="button"
onPointerDown={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onClick={(e) => {
e.stopPropagation();
deleteNode(node.id);
}}
className={cn(
"absolute right-2 top-2 flex h-8 w-8 items-center justify-center rounded-full border text-muted-foreground transition-colors hover:text-destructive",
data.transparent
? "border-slate-200 bg-white/95 shadow-sm hover:border-destructive/30 hover:bg-red-50"
: "border-slate-200 bg-white hover:border-destructive/30 hover:bg-red-50"
)}
aria-label="删除文本模块"
title="删除文本模块"
>
<Trash2 className="h-4 w-4" />
</button>
<div className="absolute bottom-1 right-2 text-[10px] text-muted-foreground">
双击编辑
</div>
</>
) : null}
</div>
);
}

View File

@ -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<string, any>;
@ -183,9 +186,27 @@ export function WorkflowNode({ node, isSelected }: WorkflowNodeProps) {
</p>
</div>
</div>
<Badge variant="outline" className={badgeClassName}>
{STATUS_LABELS[data.status] || STATUS_LABELS.pending}
</Badge>
<div className="flex items-start gap-2">
{isSelected ? (
<button
type="button"
onPointerDown={(event) => event.stopPropagation()}
onMouseDown={(event) => event.stopPropagation()}
onClick={(event) => {
event.stopPropagation();
deleteNode(node.id);
}}
className="flex h-9 w-9 items-center justify-center rounded-full border border-slate-200 bg-white text-muted-foreground transition-colors hover:border-destructive/30 hover:bg-red-50 hover:text-destructive dark:border-zinc-800 dark:bg-zinc-950 dark:hover:border-destructive/40 dark:hover:bg-red-950/30"
aria-label="删除模块"
title="删除模块"
>
<Trash2 className="h-4 w-4" />
</button>
) : null}
<Badge variant="outline" className={badgeClassName}>
{STATUS_LABELS[data.status] || STATUS_LABELS.pending}
</Badge>
</div>
</div>
<div className={sectionClassName}>