feat:执行过程编排新建分支单节点删除和整个分支节点删除

This commit is contained in:
liailing1026
2026-03-06 11:22:56 +08:00
parent 8cd3152c29
commit 73232babdc
4 changed files with 475 additions and 30 deletions

View File

@@ -39,6 +39,10 @@ const branchLoading = ref(false)
const deleteDialogVisible = ref(false)
const branchIdToDelete = ref<string | null>(null)
// 删除单个节点弹窗相关状态
const deleteNodeDialogVisible = ref(false)
const nodeIdToDelete = ref<string | null>(null)
// 节点和边数据
const nodes = ref<Node[]>([])
const edges = ref<Edge[]>([])
@@ -387,29 +391,75 @@ const getBranchParentChain = (targetNodeId: string): string[] => {
}
// 判断节点是否可删除(分支从底部出发连接的节点)
const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: string | null } => {
// 找到指向该节点的边
const incomingEdges = edges.value.filter(edge => edge.target === nodeId)
const getNodeDeletable = (
nodeId: string
): { isDeletable: boolean; branchId: string | null; parentNodeId: string | null } => {
// 直接从 store 的分支数据里用节点 id 查找它属于哪个分支
const taskStepId = currentTask.value?.Id
const currentAgents = currentTask.value?.AgentSelection || []
// 检查是否有从底部sourceHandle='bottom')连接过来的边
const bottomEdge = incomingEdges.find(edge => edge.sourceHandle === 'bottom')
if (bottomEdge && bottomEdge.data?.branchId) {
if (!taskStepId) {
return {
isDeletable: true,
branchId: bottomEdge.data.branchId
isDeletable: false,
branchId: null,
parentNodeId: null
}
}
// 获取所有分支数据
const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
// 遍历所有分支,用节点 id 在 nodes 数组中查找
for (const branch of savedBranches) {
// 检查该节点是否是该分支的第一个节点
const firstNode = branch.nodes?.[0]
if (firstNode && firstNode.id === nodeId) {
// 只有该分支的第一个节点才显示删除分支按钮
return {
isDeletable: true,
branchId: branch.id,
parentNodeId: branch.parentNodeId || null
}
}
}
return {
isDeletable: false,
branchId: null
branchId: null,
parentNodeId: null
}
}
// 删除分支(显示确认对话框)
const handleDeleteBranch = (branchId: string) => {
branchIdToDelete.value = branchId
const handleDeleteBranch = (nodeId: string) => {
// 获取节点的删除信息(包括 parentNodeId
const nodeInfo = getNodeDeletable(nodeId)
if (!nodeInfo.isDeletable || !nodeInfo.parentNodeId) {
ElMessage.error('无法删除该分支')
return
}
const taskStepId = currentTask.value?.Id
const currentAgents = currentTask.value?.AgentSelection || []
if (!taskStepId) return
// 通过 branchId 查找正确的分支(使用 branchId 精确匹配,而不是 parentNodeId
const savedBranches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
const targetBranch = savedBranches.find(branch => branch.id === nodeInfo.branchId)
if (!targetBranch) {
ElMessage.error('未找到该分支')
return
}
console.log(
'[删除分支] 找到的 branchId:',
targetBranch.id,
'parentNodeId:',
nodeInfo.parentNodeId
)
branchIdToDelete.value = targetBranch.id
deleteDialogVisible.value = true
}
@@ -464,6 +514,137 @@ const confirmDeleteBranch = async () => {
ElMessage.success('分支删除成功')
}
// 删除单个节点(显示确认对话框)
const handleDeleteNode = (nodeId: string) => {
nodeIdToDelete.value = nodeId
deleteNodeDialogVisible.value = true
}
// 确认删除单个节点
const confirmDeleteNode = async () => {
if (!nodeIdToDelete.value) return
const nodeId = nodeIdToDelete.value
const taskStepId = currentTask.value?.Id
const currentAgents = currentTask.value?.AgentSelection || []
// 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)
// 2. 将子节点重新连接到父节点(使用固定的 left/right handle
const newEdges: Edge[] = []
outgoingEdges.forEach(outEdge => {
incomingEdges.forEach(inEdge => {
// 创建新边,连接父节点(右侧)到子节点(左侧)
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
}
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 如果删除的是分支的第一个节点,需要更新新第一个节点的边信息
if (deletedBranchId && childNodeIds.length > 0) {
// 遍历所有子节点,更新它们的入边信息
childNodeIds.forEach(childNodeId => {
// 找到连接子节点的入边
const childIncomingEdge = edges.value.find(
e => e.target === childNodeId && e.sourceHandle === 'right'
)
if (childIncomingEdge) {
// 更新边的 sourceHandle 为 'bottom' 并添加 branchId使新节点成为可删除分支的入口
childIncomingEdge.sourceHandle = 'bottom'
childIncomingEdge.data = {
...childIncomingEdge.data,
branchId: deletedBranchId
}
console.log('[删除节点] 已更新新节点的边信息:', childNodeId, deletedBranchId)
}
})
}
// 5. 精准删除 store 中对应分支的节点数据,并调用后端接口删除
if (taskStepId && currentAgents.length > 0) {
// 获取所有分支数据
const branches = selectionStore.getTaskProcessBranches(taskStepId, currentAgents)
// 找到被删除节点所属的分支
const targetBranch = branches.find(branch => branch.nodes?.some((n: any) => n.id === nodeId))
if (targetBranch) {
// 使用精准删除方法(前端 store
const success = selectionStore.removeNodeFromBranch(
taskStepId,
currentAgents,
targetBranch.id,
nodeId,
incomingEdges,
outgoingEdges,
newEdges
)
if (success) {
console.log('[删除节点] 前端精准删除成功')
// 调用后端接口删除节点,传递更新后的 edges 数据
const TaskID = (window as any).__CURRENT_TASK_ID__
if (TaskID) {
const result = await selectionStore.deleteTaskProcessNodeFromDB(
TaskID,
taskStepId,
targetBranch.id,
nodeId,
edges.value
)
console.log('[删除节点] 后端删除结果:', result)
}
} else {
console.error('[删除节点] 前端精准删除失败')
}
} else {
console.error('[删除节点] 未找到节点所属的分支')
}
} else {
console.error('[删除节点] 缺少 taskStepId 或 currentAgents')
}
// 6. 刷新视图
nextTick(() => {
fit({ padding: 0.15, duration: 300 })
})
// 7. 清理状态
deleteNodeDialogVisible.value = false
nodeIdToDelete.value = null
ElMessage.success('节点删除成功')
}
// 初始化流程图
const initializeFlow = () => {
if (!currentTask.value) {
@@ -1043,7 +1224,7 @@ const submitBranch = async (branchContent: string) => {
// 基于同一父节点下的分支数量计算位置
const currentAgents = currentTask.value?.AgentSelection || []
const sameParentBranches = selectionStore
.getTaskProcessBranches(currentTask.value.Id, currentAgents)
.getTaskProcessBranches(currentTask.value?.Id || '', currentAgents)
.filter(b => b.parentNodeId === parentNodeId)
// 新分支的索引 = 现有分支数量(放在最后)
const newBranchIndex = sameParentBranches.length
@@ -1153,7 +1334,7 @@ const submitBranch = async (branchContent: string) => {
// 基于同一父节点下的分支数量计算位置
const currentAgents = currentTask.value?.AgentSelection || []
const sameParentBranches = selectionStore
.getTaskProcessBranches(currentTask.value!.Id, currentAgents)
.getTaskProcessBranches(currentTask.value?.Id || '', currentAgents)
.filter(b => b.parentNodeId === parentNodeId)
const branchIndex = sameParentBranches.length
@@ -1257,7 +1438,7 @@ defineExpose({
v-model:edges="styledEdges"
:delete-key-code="null"
:default-viewport="{ zoom: 0.5, x: 0, y: 0 }"
:min-zoom="0.5"
:min-zoom="0.2"
:max-zoom="4"
@node-click="onNodeClick"
class="vue-flow-container"
@@ -1278,16 +1459,16 @@ defineExpose({
<div
v-if="addingBranchNodeId !== 'root'"
class="external-add-btn"
@click="startAddBranch('root')"
@click.stop="startAddBranch('root')"
>
<el-icon :size="40" color="#000000">
<el-icon :size="12" color="#000000">
<CirclePlus />
</el-icon>
</div>
<!-- 取消按钮 -->
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
<el-icon :size="40" color="#f56c6c">
<div v-else class="external-add-btn cancel-btn" @click.stop="cancelAddBranch">
<el-icon :size="12" color="#f56c6c">
<Remove />
</el-icon>
</div>
@@ -1308,7 +1489,16 @@ defineExpose({
<div
v-if="getNodeDeletable(nodeProps.id).isDeletable"
class="node-delete-btn left"
@click="handleDeleteBranch(getNodeDeletable(nodeProps.id).branchId!)"
@click.stop="handleDeleteBranch(nodeProps.id)"
>
<svg-icon icon-class="close" size="14px" color="#d8d8d8" />
</div>
<!-- 删除单个节点按钮 - 非root节点显示在右上角 -->
<div
v-if="nodeProps.id !== 'root'"
class="node-delete-btn-single"
@click.stop="handleDeleteNode(nodeProps.id)"
>
<svg-icon icon-class="close" size="14px" color="#d8d8d8" />
</div>
@@ -1365,16 +1555,16 @@ defineExpose({
<div
v-if="addingBranchNodeId !== nodeProps.id"
class="external-add-btn"
@click="startAddBranch(nodeProps.id)"
@click.stop="startAddBranch(nodeProps.id)"
>
<el-icon :size="40" color="#000000">
<el-icon :size="12" color="#000000">
<CirclePlus />
</el-icon>
</div>
<!-- 取消按钮 -->
<div v-else class="external-add-btn cancel-btn" @click="cancelAddBranch">
<el-icon :size="40" color="#f56c6c">
<div v-else class="external-add-btn cancel-btn" @click.stop="cancelAddBranch">
<el-icon :size="12" color="#f56c6c">
<Remove />
</el-icon>
</div>
@@ -1397,6 +1587,14 @@ defineExpose({
content="删除后,该分支无法恢复 !"
@confirm="confirmDeleteBranch"
/>
<!-- 删除单个节点确认对话框 -->
<DeleteConfirmDialog
v-model="deleteNodeDialogVisible"
title="确认删除该节点 ?"
content="删除后,该节点无法恢复 !"
@confirm="confirmDeleteNode"
/>
</div>
</div>
</template>
@@ -1520,14 +1718,13 @@ defineExpose({
// 外部添加按钮
.external-add-btn {
position: absolute;
bottom: -20px;
bottom: -5px;
left: 50%;
transform: translateX(-50%);
width: 32px;
height: 32px;
width: 12px;
height: 12px;
border-radius: 50%;
background: #fff;
border: 2px solid #409eff;
display: flex;
align-items: center;
justify-content: center;
@@ -1718,7 +1915,28 @@ defineExpose({
}
&:hover {
background-color: #ff4d4f;
background-color: #0c0c0c;
}
}
// 单节点删除按钮(右上角)- 与左侧分支删除按钮样式一致
.node-delete-btn-single {
position: absolute;
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #2d2d2d;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: background-color 0.2s ease;
z-index: 100;
top: -12px;
right: -12px;
&:hover {
background-color: #0c0c0c;
}
}