From 61119f9012f7d2da86774b5e0b953c653c3f9ac4 Mon Sep 17 00:00:00 2001 From: liailing1026 <1815388873@qq.com> Date: Fri, 6 Mar 2026 16:28:59 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E4=BB=BB=E5=8A=A1=E5=A4=A7=E7=BA=B2?= =?UTF-8?q?=E7=BC=96=E6=8E=92=E5=88=A0=E9=99=A4=E5=88=86=E6=94=AF=E5=92=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E5=8D=95=E4=B8=AA=E8=8A=82=E7=82=B9=E4=BE=9B?= =?UTF-8?q?=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskSyllabus/Branch/PlanModification.vue | 180 ++++++++++++++++-- .../Branch/components/TaskNode.vue | 17 ++ 2 files changed, 184 insertions(+), 13 deletions(-) diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue index 1c7bd2d..176a4aa 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/PlanModification.vue @@ -39,6 +39,7 @@ @start-add-branch="handleStartAddBranch" @cancel-add-branch="handleCancelAddBranch" @delete-branch="handleDeleteBranch" + @delete-node="handleDeleteNode" /> @@ -50,6 +51,14 @@ content="删除后,该分支无法恢复 !" @confirm="confirmDeleteBranch" /> + + + @@ -711,16 +720,20 @@ const handleCancelAddBranch = () => { // 判断节点是否可删除(分支从底部出发连接的节点) const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: string | null } => { - // 找到指向该节点的边 - const incomingEdges = edges.value.filter(edge => edge.target === nodeId) + // 直接从 store 的分支数据里用节点 id 查找它属于哪个分支 + // 获取所有分支数据 + const allBranches = selectionStore.getAllFlowBranches() - // 检查是否有从底部(sourceHandle='bottom')连接过来的边 - const bottomEdge = incomingEdges.find(edge => edge.sourceHandle === 'bottom') - - if (bottomEdge && bottomEdge.data?.branchId) { - return { - isDeletable: true, - branchId: bottomEdge.data.branchId + // 遍历所有分支,用节点 id 在 nodes 数组中查找 + for (const branch of allBranches) { + // 检查该节点是否是该分支的第一个节点 + const firstNode = branch.nodes?.[0] + if (firstNode && firstNode.id === nodeId) { + // 只有该分支的第一个节点才显示删除分支按钮 + return { + isDeletable: true, + branchId: branch.id || branch.branchContent + } } } @@ -734,8 +747,19 @@ const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: str const deleteDialogVisible = ref(false) const branchIdToDelete = ref(null) +// 删除单个节点弹窗相关状态 +const deleteNodeDialogVisible = ref(false) +const nodeIdToDelete = ref(null) + // 删除分支(显示确认对话框) const handleDeleteBranch = (branchId: string) => { + // 调试信息:输出要删除的分支ID + const allBranches = selectionStore.getAllFlowBranches() + const targetBranch = allBranches.find( + b => b.id === branchId || b.edges.some(e => e.data?.branchId === branchId) + ) + console.log('[删除分支] 找到的 branchId:', branchId, '分支信息:', targetBranch) + branchIdToDelete.value = branchId deleteDialogVisible.value = true } @@ -748,10 +772,8 @@ const confirmDeleteBranch = async () => { // 1. 从store中获取该分支的信息 const allBranches = selectionStore.getAllFlowBranches() - // branchId是timestamp格式,需要通过edges中的data.branchId来匹配 - const branchToDelete = allBranches.find(b => - b.edges.some(e => e.data?.branchId === branchId) - ) + // 直接使用 branchId 精确匹配分支(而不是通过边的 data.branchId) + const branchToDelete = allBranches.find(b => b.id === branchId) if (!branchToDelete) { ElMessage.error('未找到该分支') @@ -789,6 +811,138 @@ const confirmDeleteBranch = async () => { } // 开始编辑任务 +const handleDeleteNode = (nodeId: string) => { + nodeIdToDelete.value = nodeId + deleteNodeDialogVisible.value = true +} + +const confirmDeleteNode = async () => { + if (!nodeIdToDelete.value) return + + const nodeId = nodeIdToDelete.value + + // 0. 检查被删除的节点是否是分支的第一个节点(带删除分支按钮) + const nodeDeletable = getNodeDeletable(nodeId) + const deletedBranchId = nodeDeletable.isDeletable ? nodeDeletable.branchId : null + + // 1. 找到该节点的入边和出边 + const incomingEdges = edges.value.filter(e => e.target === nodeId) + const outgoingEdges = edges.value.filter(e => e.source === nodeId) + + // 1.1 保存删除前的子节点信息(用于后续更新 branchId) + const childNodeIds = outgoingEdges.map(e => e.target) + + // 1.2 提前获取每个子节点所属分支的 branchId(在删除节点之前获取) + const allBranches = selectionStore.getAllFlowBranches() + const childBranchIdMap: Record = {} + outgoingEdges.forEach(outEdge => { + const childBranch = allBranches.find(branch => + branch.nodes.some(n => n.id === outEdge.target) + ) + if (childBranch) { + childBranchIdMap[outEdge.target] = childBranch.id + } + }) + + // 2. 将子节点重新连接到父节点(使用固定的 left/right handle) + // 修复:为每个子节点使用它自己所属分支的 branchId,而不是父节点的 branchId + const newEdges: Edge[] = [] + outgoingEdges.forEach(outEdge => { + incomingEdges.forEach(inEdge => { + // 使用提前获取的子节点所属分支的 branchId + const targetBranchId = childBranchIdMap[outEdge.target] || inEdge.data?.branchId + + // 创建新边,连接父节点(右侧)到子节点(左侧) + const newEdge = { + id: `e-${inEdge.source}-${outEdge.target}-${Date.now()}`, + source: inEdge.source, + target: outEdge.target, + sourceHandle: 'right', // 父节点右侧 + targetHandle: 'left', // 子节点左侧 + type: 'smoothstep', + animated: true, + style: outEdge.style, + markerEnd: outEdge.markerEnd, + data: { ...inEdge.data, branchId: targetBranchId } // 使用子节点所属分支的 branchId + } + edges.value.push(newEdge) + newEdges.push(newEdge) + }) + }) + + // 3. 删除该节点的入边和出边 + const relatedEdgeIds = [...incomingEdges.map(e => e.id), ...outgoingEdges.map(e => e.id)] + edges.value = edges.value.filter(e => !relatedEdgeIds.includes(e.id)) + + // 4. 删除该节点 + nodes.value = nodes.value.filter(n => n.id !== nodeId) + + // 4.1 如果删除的是分支的第一个节点,需要更新新第一个节点的边信息 + // 修复:每个子节点使用它自己所属分支的 branchId,而不是统一使用被删除节点的 branchId + if (deletedBranchId && childNodeIds.length > 0) { + // 遍历所有子节点,更新它们的入边信息 + childNodeIds.forEach(childNodeId => { + // 找到连接子节点的入边(删除后新创建的边,sourceHandle 应该是 'right') + const childIncomingEdge = edges.value.find( + e => e.target === childNodeId && e.sourceHandle === 'right' + ) + if (childIncomingEdge) { + // 使用预先保存的子节点所属分支的 branchId(删除节点之前就保存好了) + const targetBranchId = childBranchIdMap[childNodeId] || deletedBranchId + + // 更新边的 sourceHandle 为 'bottom' 并添加 branchId,使新节点成为可删除分支的入口 + childIncomingEdge.sourceHandle = 'bottom' + childIncomingEdge.data = { + ...childIncomingEdge.data, + branchId: targetBranchId + } + console.log('[删除节点] 已更新新节点的边信息:', childNodeId, targetBranchId) + } + }) + } + + // 5. 找到被删除节点所属的分支,并同步更新 store 中的分支数据 + for (const branch of allBranches) { + const nodeIndex = branch.nodes.findIndex(n => n.id === nodeId) + if (nodeIndex !== -1) { + branch.nodes.splice(nodeIndex, 1) + branch.edges = branch.edges.filter(e => e.source !== nodeId && e.target !== nodeId) + if (nodeIndex < branch.tasks.length) { + branch.tasks.splice(nodeIndex, 1) + } + + // 5.1 如果删除的是分支的第一个节点,需要更新新第一个节点的 branch 信息 + if (deletedBranchId && childNodeIds.length > 0 && nodeIndex === 0) { + // 更新 store 中分支的 parentNodeId 指向新的第一个节点 + if (branch.nodes.length > 0) { + branch.parentNodeId = branch.nodes[0]!.id + } + } + + // 5.2 将新的边信息更新到 store 中(如果有新创建的边) + if (newEdges.length > 0) { + branch.edges.push(...newEdges) + } + + break + } + } + + // 6. 保存到数据库 + await saveBranchesToDB() + + // 7. 关闭对话框并清理状态 + deleteNodeDialogVisible.value = false + nodeIdToDelete.value = null + + // 8. 刷新视图 + nextTick(() => { + fit({ padding: 0.15, duration: 300 }) + }) + + ElMessage.success('节点删除成功') +} + const handleEditTask = (nodeId: string) => { // 更新节点的编辑状态 updateNode(nodeId, { diff --git a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue index f7043e3..8a5e18e 100644 --- a/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue +++ b/frontend/src/layout/components/Main/TaskTemplate/TaskSyllabus/Branch/components/TaskNode.vue @@ -12,6 +12,11 @@ + +
+ +
+
@@ -114,6 +119,7 @@ const emit = defineEmits<{ (e: 'start-add-branch', nodeId: string): void (e: 'cancel-add-branch'): void (e: 'delete-branch', branchId: string): void + (e: 'delete-node', nodeId: string): void }>() // 判断是否为根节点 @@ -184,6 +190,11 @@ const handleDeleteBranch = () => { } } +// 删除单个节点 +const handleDeleteNode = () => { + emit('delete-node', props.id) +} + // 判断是否显示删除按钮 const isDeletable = computed(() => props.isDeletable || false) @@ -389,6 +400,12 @@ const isDeletable = computed(() => props.isDeletable || false) transform: translateY(-50%); } + // 右侧位置 + &.right { + right: -12px; + top: -12px; + } + &:hover { background-color: #0c0c0c; }