feat:任务大纲编排删除分支和删除单个节点供实现
This commit is contained in:
@@ -39,6 +39,7 @@
|
|||||||
@start-add-branch="handleStartAddBranch"
|
@start-add-branch="handleStartAddBranch"
|
||||||
@cancel-add-branch="handleCancelAddBranch"
|
@cancel-add-branch="handleCancelAddBranch"
|
||||||
@delete-branch="handleDeleteBranch"
|
@delete-branch="handleDeleteBranch"
|
||||||
|
@delete-node="handleDeleteNode"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</VueFlow>
|
</VueFlow>
|
||||||
@@ -50,6 +51,14 @@
|
|||||||
content="删除后,该分支无法恢复 !"
|
content="删除后,该分支无法恢复 !"
|
||||||
@confirm="confirmDeleteBranch"
|
@confirm="confirmDeleteBranch"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 删除单个节点确认对话框 -->
|
||||||
|
<DeleteConfirmDialog
|
||||||
|
v-model="deleteNodeDialogVisible"
|
||||||
|
title="确认删除该节点 ?"
|
||||||
|
content="删除后,该节点无法恢复 !"
|
||||||
|
@confirm="confirmDeleteNode"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -711,16 +720,20 @@ const handleCancelAddBranch = () => {
|
|||||||
|
|
||||||
// 判断节点是否可删除(分支从底部出发连接的节点)
|
// 判断节点是否可删除(分支从底部出发连接的节点)
|
||||||
const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: string | null } => {
|
const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: string | null } => {
|
||||||
// 找到指向该节点的边
|
// 直接从 store 的分支数据里用节点 id 查找它属于哪个分支
|
||||||
const incomingEdges = edges.value.filter(edge => edge.target === nodeId)
|
// 获取所有分支数据
|
||||||
|
const allBranches = selectionStore.getAllFlowBranches()
|
||||||
|
|
||||||
// 检查是否有从底部(sourceHandle='bottom')连接过来的边
|
// 遍历所有分支,用节点 id 在 nodes 数组中查找
|
||||||
const bottomEdge = incomingEdges.find(edge => edge.sourceHandle === 'bottom')
|
for (const branch of allBranches) {
|
||||||
|
// 检查该节点是否是该分支的第一个节点
|
||||||
if (bottomEdge && bottomEdge.data?.branchId) {
|
const firstNode = branch.nodes?.[0]
|
||||||
|
if (firstNode && firstNode.id === nodeId) {
|
||||||
|
// 只有该分支的第一个节点才显示删除分支按钮
|
||||||
return {
|
return {
|
||||||
isDeletable: true,
|
isDeletable: true,
|
||||||
branchId: bottomEdge.data.branchId
|
branchId: branch.id || branch.branchContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -734,8 +747,19 @@ const getNodeDeletable = (nodeId: string): { isDeletable: boolean; branchId: str
|
|||||||
const deleteDialogVisible = ref(false)
|
const deleteDialogVisible = ref(false)
|
||||||
const branchIdToDelete = ref<string | null>(null)
|
const branchIdToDelete = ref<string | null>(null)
|
||||||
|
|
||||||
|
// 删除单个节点弹窗相关状态
|
||||||
|
const deleteNodeDialogVisible = ref(false)
|
||||||
|
const nodeIdToDelete = ref<string | null>(null)
|
||||||
|
|
||||||
// 删除分支(显示确认对话框)
|
// 删除分支(显示确认对话框)
|
||||||
const handleDeleteBranch = (branchId: string) => {
|
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
|
branchIdToDelete.value = branchId
|
||||||
deleteDialogVisible.value = true
|
deleteDialogVisible.value = true
|
||||||
}
|
}
|
||||||
@@ -748,10 +772,8 @@ const confirmDeleteBranch = async () => {
|
|||||||
|
|
||||||
// 1. 从store中获取该分支的信息
|
// 1. 从store中获取该分支的信息
|
||||||
const allBranches = selectionStore.getAllFlowBranches()
|
const allBranches = selectionStore.getAllFlowBranches()
|
||||||
// branchId是timestamp格式,需要通过edges中的data.branchId来匹配
|
// 直接使用 branchId 精确匹配分支(而不是通过边的 data.branchId)
|
||||||
const branchToDelete = allBranches.find(b =>
|
const branchToDelete = allBranches.find(b => b.id === branchId)
|
||||||
b.edges.some(e => e.data?.branchId === branchId)
|
|
||||||
)
|
|
||||||
|
|
||||||
if (!branchToDelete) {
|
if (!branchToDelete) {
|
||||||
ElMessage.error('未找到该分支')
|
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<string, string> = {}
|
||||||
|
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) => {
|
const handleEditTask = (nodeId: string) => {
|
||||||
// 更新节点的编辑状态
|
// 更新节点的编辑状态
|
||||||
updateNode(nodeId, {
|
updateNode(nodeId, {
|
||||||
|
|||||||
@@ -12,6 +12,11 @@
|
|||||||
<SvgIcon icon-class="close" size="14px" color="#d8d8d8" />
|
<SvgIcon icon-class="close" size="14px" color="#d8d8d8" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 删除单个节点按钮 - 在右上角,非根节点显示 -->
|
||||||
|
<div v-if="!isRoot" class="node-delete-btn right" @click.stop="handleDeleteNode">
|
||||||
|
<SvgIcon icon-class="close" size="14px" color="#d8d8d8" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 根节点内容 -->
|
<!-- 根节点内容 -->
|
||||||
<el-card v-if="isRoot" class="node-card root-style" :shadow="true">
|
<el-card v-if="isRoot" class="node-card root-style" :shadow="true">
|
||||||
<div class="goal-content">
|
<div class="goal-content">
|
||||||
@@ -114,6 +119,7 @@ const emit = defineEmits<{
|
|||||||
(e: 'start-add-branch', nodeId: string): void
|
(e: 'start-add-branch', nodeId: string): void
|
||||||
(e: 'cancel-add-branch'): void
|
(e: 'cancel-add-branch'): void
|
||||||
(e: 'delete-branch', branchId: string): 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)
|
const isDeletable = computed(() => props.isDeletable || false)
|
||||||
</script>
|
</script>
|
||||||
@@ -389,6 +400,12 @@ const isDeletable = computed(() => props.isDeletable || false)
|
|||||||
transform: translateY(-50%);
|
transform: translateY(-50%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 右侧位置
|
||||||
|
&.right {
|
||||||
|
right: -12px;
|
||||||
|
top: -12px;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: #0c0c0c;
|
background-color: #0c0c0c;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user