mirror of https://github.com/allinssl/allinssl
687 lines
20 KiB
TypeScript
687 lines
20 KiB
TypeScript
import { v4 as uuidv4 } from 'uuid'
|
||
import { WorkflowNode, WorkflowEdge, NodeData } from '../types'
|
||
import { MarkerType } from '@vue-flow/core'
|
||
|
||
// 节点类型映射
|
||
const NODE_TYPE_MAP = {
|
||
start: 'start',
|
||
apply: 'apply',
|
||
branch: 'normal', // 使用普通节点作为分支节点
|
||
condition: 'normal', // 条件节点
|
||
deploy: 'deploy',
|
||
execute_result_branch: 'normal', // 执行结果分支节点
|
||
execute_result_condition: 'normal', // 执行结果条件节点
|
||
notify: 'notify',
|
||
}
|
||
|
||
// 节点标签映射
|
||
const NODE_LABEL_MAP = {
|
||
start: '开始',
|
||
apply: '申请证书',
|
||
branch: '分支节点',
|
||
condition: '条件节点',
|
||
deploy: '部署证书',
|
||
execute_result_branch: '执行结果',
|
||
execute_result_condition: '执行结果条件',
|
||
notify: '通知',
|
||
}
|
||
|
||
// 布局配置
|
||
const LAYOUT_CONFIG = {
|
||
// 节点尺寸
|
||
nodeWidth: 180,
|
||
nodeHeight: 40,
|
||
// 节点间距
|
||
horizontalGap: 220, // 水平间距,增加防止重叠
|
||
verticalGap: 150, // 垂直间距,增加更多空间
|
||
// 分支节点的水平间距
|
||
branchHorizontalGap: 280, // 增加分支节点间距
|
||
// 多分支的间距调整系数
|
||
multiBranchSpacingFactor: 0.9, // 多分支时适当减小间距,确保视野内能容纳
|
||
// 初始位置
|
||
initialX: 600, // 增加初始X坐标,使整个图更居中
|
||
initialY: 80,
|
||
}
|
||
|
||
interface NestedNode {
|
||
id: string
|
||
name: string
|
||
type: string
|
||
config?: any
|
||
childNode?: NestedNode
|
||
conditionNodes?: NestedNode[]
|
||
inputs?: Array<{ name: string; fromNodeId: string }>
|
||
}
|
||
|
||
interface ProcessResult {
|
||
nodes: WorkflowNode[]
|
||
edges: WorkflowEdge[]
|
||
}
|
||
|
||
/**
|
||
* 节点子树的大小信息
|
||
*/
|
||
interface SubtreeSize {
|
||
width: number // 子树宽度
|
||
height: number // 子树高度
|
||
childCount: number // 子节点数量
|
||
}
|
||
|
||
/**
|
||
* 将嵌套节点数据结构转换为VueFlow所需的节点和边
|
||
* @param data 嵌套的节点数据
|
||
* @returns VueFlow的节点和边
|
||
*/
|
||
export function transformNestedFlowData(data: NestedNode): ProcessResult {
|
||
const result: ProcessResult = {
|
||
nodes: [],
|
||
edges: [],
|
||
}
|
||
|
||
if (!data) return result
|
||
|
||
// 垂直对齐布局的实现
|
||
const nodePositions = new Map<string, { x: number; y: number }>()
|
||
const yLevels = new Map<number, number>() // 每个层级的y坐标
|
||
|
||
// 计算层级
|
||
const nodeLevels = new Map<string, number>()
|
||
const nodeParents = new Map<string, string>() // 保存节点的父节点
|
||
const nodeTrees = new Map<string, NestedNode>() // 保存所有节点引用,便于访问
|
||
const nodeSubtreeSize = new Map<string, SubtreeSize>() // 保存每个节点子树的大小信息
|
||
const conditionEndNodes = new Map<string, string[]>() // 保存分支节点的所有结束节点IDs
|
||
|
||
// 第一步:遍历节点树,确定每个节点的层级和父节点,并收集所有节点
|
||
function assignLevels(node: NestedNode | undefined, level: number = 0, parentId: string | null = null) {
|
||
if (!node) return
|
||
|
||
// 保存节点信息
|
||
nodeLevels.set(node.id, level)
|
||
nodeTrees.set(node.id, node)
|
||
|
||
if (parentId) {
|
||
nodeParents.set(node.id, parentId)
|
||
}
|
||
|
||
// 处理条件分支节点
|
||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||
const hasChildNode = node.childNode !== undefined
|
||
|
||
// 条件节点层级 +1
|
||
for (const condNode of node.conditionNodes) {
|
||
assignLevels(condNode, level + 1, node.id)
|
||
|
||
// 递归处理条件节点的子节点
|
||
if (condNode.childNode) {
|
||
// 条件节点的子节点层级 +2
|
||
assignLevels(condNode.childNode, level + 2, condNode.id)
|
||
|
||
// 如果条件分支有子节点,要递归找到所有末端节点
|
||
findAllEndNodes(condNode.childNode, node.id)
|
||
} else {
|
||
// 如果条件节点没有子节点,它自己就是终止节点
|
||
if (!conditionEndNodes.has(node.id)) {
|
||
conditionEndNodes.set(node.id, [])
|
||
}
|
||
conditionEndNodes.get(node.id)?.push(condNode.id)
|
||
}
|
||
}
|
||
|
||
// 处理普通子节点 - 如果同时存在conditionNodes和childNode
|
||
if (hasChildNode && node.childNode) {
|
||
// 如果节点同时有条件分支和子节点,子节点级别设为独立的,不直接关联到父节点
|
||
// 放在比所有条件分支末端节点更低的层级
|
||
const maxConditionLevel = findMaxConditionEndLevel(node)
|
||
assignLevels(node.childNode, maxConditionLevel + 1, null)
|
||
}
|
||
} else if (node.childNode) {
|
||
// 普通子节点,层级+1
|
||
assignLevels(node.childNode, level + 1, node.id)
|
||
}
|
||
}
|
||
|
||
// 查找条件分支的最大末端层级
|
||
function findMaxConditionEndLevel(node: NestedNode): number {
|
||
let maxLevel = nodeLevels.get(node.id) || 0
|
||
|
||
// 获取当前分支的所有末端节点
|
||
const endNodeIds = conditionEndNodes.get(node.id) || []
|
||
|
||
// 找出最大层级
|
||
for (const endId of endNodeIds) {
|
||
const level = nodeLevels.get(endId) || 0
|
||
maxLevel = Math.max(maxLevel, level)
|
||
}
|
||
|
||
// 至少比父节点高2个层级
|
||
return Math.max(maxLevel, nodeLevels.get(node.id)! + 2)
|
||
}
|
||
|
||
// 递归查找分支下所有末端节点
|
||
function findAllEndNodes(node: NestedNode, branchParentId: string) {
|
||
// 初始化终止节点集合
|
||
if (!conditionEndNodes.has(branchParentId)) {
|
||
conditionEndNodes.set(branchParentId, [])
|
||
}
|
||
|
||
// 如果有条件分支,则不是末端节点,需要继续递归
|
||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||
for (const condNode of node.conditionNodes) {
|
||
if (condNode.childNode) {
|
||
findAllEndNodes(condNode.childNode, branchParentId)
|
||
} else {
|
||
// 条件节点没有子节点,它是末端节点
|
||
conditionEndNodes.get(branchParentId)?.push(condNode.id)
|
||
}
|
||
}
|
||
|
||
// 如果还有子节点,继续递归查找
|
||
if (node.childNode) {
|
||
findAllEndNodes(node.childNode, branchParentId)
|
||
}
|
||
}
|
||
// 如果还有子节点,不是末端节点,继续递归
|
||
else if (node.childNode) {
|
||
findAllEndNodes(node.childNode, branchParentId)
|
||
}
|
||
// 没有子节点,也没有条件分支,它是末端节点
|
||
else {
|
||
conditionEndNodes.get(branchParentId)?.push(node.id)
|
||
}
|
||
}
|
||
|
||
// 第二步:计算每个节点子树的大小,自底向上
|
||
function calculateSubtreeSizes(nodeId: string): SubtreeSize {
|
||
const node = nodeTrees.get(nodeId)
|
||
if (!node) {
|
||
return { width: 0, height: 0, childCount: 0 }
|
||
}
|
||
|
||
// 如果已经计算过,直接返回
|
||
if (nodeSubtreeSize.has(nodeId)) {
|
||
return nodeSubtreeSize.get(nodeId)!
|
||
}
|
||
|
||
// 默认大小
|
||
let subtreeWidth = LAYOUT_CONFIG.nodeWidth
|
||
let maxChildWidth = 0
|
||
let totalChildWidth = 0
|
||
let childCount = 0
|
||
|
||
// 计算条件分支的子树大小
|
||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||
for (const condNode of node.conditionNodes) {
|
||
// 计算条件节点子树大小
|
||
const condSize = calculateSubtreeSizes(condNode.id)
|
||
totalChildWidth += condSize.width
|
||
maxChildWidth = Math.max(maxChildWidth, condSize.width)
|
||
childCount += condSize.childCount + 1 // +1 是条件节点本身
|
||
|
||
// 如果条件节点有子节点,也要计算
|
||
if (condNode.childNode) {
|
||
const childSize = calculateSubtreeSizes(condNode.childNode.id)
|
||
totalChildWidth += childSize.width
|
||
maxChildWidth = Math.max(maxChildWidth, childSize.width)
|
||
childCount += childSize.childCount + 1 // +1 是子节点本身
|
||
}
|
||
}
|
||
|
||
// 多个条件分支的总宽度
|
||
if (node.conditionNodes.length > 1) {
|
||
// 条件分支节点之间需要间距
|
||
subtreeWidth = Math.max(
|
||
subtreeWidth,
|
||
totalChildWidth + (node.conditionNodes.length - 1) * LAYOUT_CONFIG.branchHorizontalGap,
|
||
)
|
||
} else {
|
||
subtreeWidth = Math.max(subtreeWidth, maxChildWidth)
|
||
}
|
||
}
|
||
|
||
// 计算普通子节点的子树大小
|
||
if (node.childNode) {
|
||
const childSize = calculateSubtreeSizes(node.childNode.id)
|
||
subtreeWidth = Math.max(subtreeWidth, childSize.width)
|
||
childCount += childSize.childCount + 1 // +1 是子节点本身
|
||
}
|
||
|
||
// 保存并返回结果
|
||
const result = { width: subtreeWidth, height: 0, childCount }
|
||
nodeSubtreeSize.set(nodeId, result)
|
||
return result
|
||
}
|
||
|
||
// 第三步:计算每个层级的Y坐标
|
||
function calculateYCoordinates() {
|
||
const maxLevel = Math.max(...Array.from(nodeLevels.values()))
|
||
|
||
for (let i = 0; i <= maxLevel; i++) {
|
||
yLevels.set(i, LAYOUT_CONFIG.initialY + i * LAYOUT_CONFIG.verticalGap)
|
||
}
|
||
}
|
||
|
||
// 第四步:计算节点的X坐标位置,考虑子树宽度避免重叠
|
||
function positionNodes(nodeId: string, leftBoundary: number = 0): number {
|
||
const node = nodeTrees.get(nodeId)
|
||
if (!node) return leftBoundary
|
||
|
||
const level = nodeLevels.get(nodeId) || 0
|
||
const subtreeSize = nodeSubtreeSize.get(nodeId) || { width: LAYOUT_CONFIG.nodeWidth, height: 0, childCount: 0 }
|
||
|
||
// 计算节点的x坐标 - 居中于其子树
|
||
const x = leftBoundary + subtreeSize.width / 2
|
||
|
||
// 保存节点位置
|
||
nodePositions.set(nodeId, {
|
||
x: x,
|
||
y: yLevels.get(level)!,
|
||
})
|
||
|
||
// 初始左边界,用于子节点布局
|
||
let childLeftBoundary = leftBoundary
|
||
|
||
// 处理条件分支
|
||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||
const conditionCount = node.conditionNodes.length
|
||
|
||
// 计算所有条件分支以及它们子节点的总宽度
|
||
let totalConditionWidth = 0
|
||
const conditionSizes: { nodeId: string; width: number }[] = []
|
||
|
||
// 先收集所有条件分支的宽度信息
|
||
for (const condNode of node.conditionNodes) {
|
||
const condSize = nodeSubtreeSize.get(condNode.id) || {
|
||
width: LAYOUT_CONFIG.nodeWidth,
|
||
height: 0,
|
||
childCount: 0,
|
||
}
|
||
|
||
// 计算这个条件分支的总宽度(包括所有子节点)
|
||
let branchWidth = condSize.width
|
||
|
||
// 如果有子节点,需要考虑子节点的宽度
|
||
if (condNode.childNode) {
|
||
// 递归计算子树的宽度
|
||
const childTreeWidth = calculateBranchWidth(condNode.childNode)
|
||
branchWidth = Math.max(branchWidth, childTreeWidth)
|
||
}
|
||
|
||
conditionSizes.push({ nodeId: condNode.id, width: branchWidth })
|
||
totalConditionWidth += branchWidth
|
||
}
|
||
|
||
// 根据分支数量动态调整间距
|
||
let adjustedGap = LAYOUT_CONFIG.branchHorizontalGap
|
||
// 当分支数量大于2时,适当减小间距
|
||
if (conditionCount > 2) {
|
||
adjustedGap =
|
||
LAYOUT_CONFIG.branchHorizontalGap * Math.pow(LAYOUT_CONFIG.multiBranchSpacingFactor, conditionCount - 2)
|
||
}
|
||
|
||
// 添加分支之间的间距
|
||
totalConditionWidth += (conditionCount - 1) * adjustedGap
|
||
|
||
// 计算条件分支区域的起始位置,确保条件分支居中于父节点
|
||
childLeftBoundary = x - totalConditionWidth / 2
|
||
|
||
// 布局每个条件分支
|
||
for (const condInfo of conditionSizes) {
|
||
const condNode = nodeTrees.get(condInfo.nodeId)
|
||
if (!condNode) continue
|
||
|
||
// 定位条件节点
|
||
const condX = childLeftBoundary + condInfo.width / 2
|
||
|
||
// 保存条件节点位置
|
||
nodePositions.set(condNode.id, {
|
||
x: condX,
|
||
y: yLevels.get(level + 1)!,
|
||
})
|
||
|
||
// 处理条件子节点
|
||
if (condNode.childNode) {
|
||
// 递归布局条件子节点及其子树
|
||
positionBranch(condNode.childNode, condX, level + 2, condInfo.width)
|
||
}
|
||
|
||
// 更新下一个条件分支的位置
|
||
childLeftBoundary += condInfo.width + adjustedGap
|
||
}
|
||
|
||
// 如果节点同时有条件分支和普通子节点,独立处理子节点的位置
|
||
if (node.childNode) {
|
||
const childNodeId = node.childNode.id
|
||
// 获取子节点的层级(已经在assignLevels中计算为条件分支末端之后)
|
||
const childLevel = nodeLevels.get(childNodeId) || level + 3
|
||
|
||
// 保存子节点位置 - 与父节点垂直对齐
|
||
nodePositions.set(childNodeId, {
|
||
x: x,
|
||
y: yLevels.get(childLevel)!,
|
||
})
|
||
|
||
// 递归处理子节点的子节点
|
||
if (node.childNode.childNode || (node.childNode.conditionNodes && node.childNode.conditionNodes.length > 0)) {
|
||
positionNodes(childNodeId, x - subtreeSize.width / 2)
|
||
}
|
||
}
|
||
} else if (node.childNode) {
|
||
// 处理普通子节点 - 垂直对齐于父节点下方
|
||
const childNodeId = node.childNode.id
|
||
const childLevel = level + 1
|
||
|
||
// 保存节点位置
|
||
nodePositions.set(childNodeId, {
|
||
x: x, // 与父节点垂直对齐
|
||
y: yLevels.get(childLevel)!,
|
||
})
|
||
|
||
// 递归处理其子节点
|
||
if (node.childNode.childNode || (node.childNode.conditionNodes && node.childNode.conditionNodes.length > 0)) {
|
||
positionNodes(childNodeId, leftBoundary)
|
||
}
|
||
}
|
||
|
||
return leftBoundary + subtreeSize.width
|
||
}
|
||
|
||
// 计算分支的总宽度(包括所有子节点)
|
||
function calculateBranchWidth(node: NestedNode): number {
|
||
const nodeSize = nodeSubtreeSize.get(node.id) || {
|
||
width: LAYOUT_CONFIG.nodeWidth,
|
||
height: 0,
|
||
childCount: 0,
|
||
}
|
||
|
||
let totalWidth = nodeSize.width
|
||
|
||
// 如果有条件分支,计算所有分支的总宽度
|
||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||
let branchesWidth = 0
|
||
const branchCount = node.conditionNodes.length
|
||
|
||
// 获取每个分支的宽度
|
||
const branchWidths: number[] = []
|
||
for (const condNode of node.conditionNodes) {
|
||
const condWidth = calculateBranchWidth(condNode)
|
||
branchWidths.push(condWidth)
|
||
branchesWidth += condWidth
|
||
}
|
||
|
||
// 根据分支数量动态调整间距
|
||
let adjustedGap = LAYOUT_CONFIG.branchHorizontalGap
|
||
// 当分支数量大于2时,适当减小间距,避免超出视野
|
||
if (branchCount > 2) {
|
||
adjustedGap =
|
||
LAYOUT_CONFIG.branchHorizontalGap * Math.pow(LAYOUT_CONFIG.multiBranchSpacingFactor, branchCount - 2)
|
||
}
|
||
|
||
// 加上分支间距
|
||
if (branchCount > 1) {
|
||
branchesWidth += (branchCount - 1) * adjustedGap
|
||
}
|
||
|
||
totalWidth = Math.max(totalWidth, branchesWidth)
|
||
}
|
||
|
||
// 如果有子节点,递归计算
|
||
if (node.childNode) {
|
||
const childWidth = calculateBranchWidth(node.childNode)
|
||
totalWidth = Math.max(totalWidth, childWidth)
|
||
}
|
||
|
||
return totalWidth
|
||
}
|
||
|
||
// 递归定位分支中的所有节点
|
||
function positionBranch(node: NestedNode, centerX: number, level: number, availableWidth: number): void {
|
||
// 保存节点位置 - 垂直对齐于父节点
|
||
nodePositions.set(node.id, {
|
||
x: centerX,
|
||
y: yLevels.get(level)!,
|
||
})
|
||
|
||
// 如果有条件分支,递归处理
|
||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||
// 类似于positionNodes中的处理逻辑
|
||
const conditionCount = node.conditionNodes.length
|
||
let totalWidth = 0
|
||
const condSizes: { nodeId: string; width: number }[] = []
|
||
|
||
// 计算所有条件分支的宽度
|
||
for (const condNode of node.conditionNodes) {
|
||
const width = calculateBranchWidth(condNode)
|
||
condSizes.push({ nodeId: condNode.id, width })
|
||
totalWidth += width
|
||
}
|
||
|
||
// 根据分支数量动态调整间距
|
||
let adjustedGap = LAYOUT_CONFIG.branchHorizontalGap
|
||
// 当分支数量大于2时,适当减小间距
|
||
if (conditionCount > 2) {
|
||
adjustedGap =
|
||
LAYOUT_CONFIG.branchHorizontalGap * Math.pow(LAYOUT_CONFIG.multiBranchSpacingFactor, conditionCount - 2)
|
||
}
|
||
|
||
// 添加分支间距
|
||
totalWidth += (conditionCount - 1) * adjustedGap
|
||
|
||
// 计算起始位置,确保居中
|
||
let startX = centerX - totalWidth / 2
|
||
|
||
// 处理每个条件分支
|
||
for (const condInfo of condSizes) {
|
||
const condNode = nodeTrees.get(condInfo.nodeId)
|
||
if (!condNode) continue
|
||
|
||
// 分支节点居中
|
||
const condX = startX + condInfo.width / 2
|
||
|
||
// 保存位置
|
||
nodePositions.set(condNode.id, {
|
||
x: condX,
|
||
y: yLevels.get(level + 1)!,
|
||
})
|
||
|
||
// 处理子节点
|
||
if (condNode.childNode) {
|
||
positionBranch(condNode.childNode, condX, level + 2, condInfo.width)
|
||
}
|
||
|
||
// 更新下一个分支的位置
|
||
startX += condInfo.width + adjustedGap
|
||
}
|
||
|
||
// 处理子节点
|
||
if (node.childNode) {
|
||
// 获取子节点层级
|
||
const childLevel =
|
||
Math.max(
|
||
...Array.from(nodeLevels.entries())
|
||
.filter(([id]) => nodeParents.get(id) === node.id)
|
||
.map(([, level]) => level),
|
||
) + 1
|
||
|
||
// 定位子节点
|
||
positionBranch(node.childNode, centerX, childLevel, availableWidth)
|
||
}
|
||
}
|
||
// 如果有子节点但没有条件分支
|
||
else if (node.childNode) {
|
||
positionBranch(node.childNode, centerX, level + 1, availableWidth)
|
||
}
|
||
}
|
||
|
||
// 第五步:创建节点和边
|
||
function createNodesAndEdges(node: NestedNode | undefined, parentId: string | null = null) {
|
||
if (!node) return
|
||
|
||
// 获取节点位置
|
||
const position = nodePositions.get(node.id)
|
||
if (!position) return // 跳过没有位置信息的节点
|
||
|
||
// 节点类型和标签
|
||
const nodeType = NODE_TYPE_MAP[node.type as keyof typeof NODE_TYPE_MAP] || 'normal'
|
||
const nodeLabel = node.name || NODE_LABEL_MAP[node.type as keyof typeof NODE_LABEL_MAP] || '未知节点'
|
||
|
||
// 创建节点数据
|
||
const nodeData: NodeData = {
|
||
id: node.id,
|
||
type: nodeType as any,
|
||
label: nodeLabel,
|
||
canMove: true,
|
||
canDelete: true,
|
||
canChangeType: true,
|
||
}
|
||
|
||
// 添加配置信息
|
||
if (node.config) {
|
||
Object.assign(nodeData, { config: node.config })
|
||
}
|
||
|
||
// 创建节点
|
||
const flowNode: WorkflowNode = {
|
||
id: node.id,
|
||
type: nodeType,
|
||
position: { x: position.x, y: position.y },
|
||
data: nodeData,
|
||
}
|
||
|
||
// 添加节点
|
||
result.nodes.push(flowNode)
|
||
|
||
// 添加从父节点到当前节点的边(如果有父节点)
|
||
if (parentId) {
|
||
const edgeId = `${parentId}-${node.id}`
|
||
const parentPosition = nodePositions.get(parentId)
|
||
if (parentPosition) {
|
||
// 计算边缘路径的偏移量,避免与节点重叠
|
||
const offset = LAYOUT_CONFIG.nodeHeight / 2 + 10 // 节点高度的一半加上额外间距
|
||
const sourceY = parentPosition.y + offset
|
||
const targetY = position.y - offset
|
||
|
||
const edge: WorkflowEdge = {
|
||
id: edgeId,
|
||
source: parentId,
|
||
target: node.id,
|
||
type: 'step',
|
||
style: {
|
||
strokeWidth: 2,
|
||
strokeDasharray: 5,
|
||
},
|
||
animated: true,
|
||
// 添加箭头标记
|
||
markerEnd: {
|
||
type: MarkerType.ArrowClosed,
|
||
width: 15,
|
||
height: 15,
|
||
color: '#b1b1b7',
|
||
},
|
||
// 添加路径偏移
|
||
sourceY,
|
||
targetY,
|
||
}
|
||
result.edges.push(edge)
|
||
}
|
||
}
|
||
|
||
// 特殊处理 - 同时有条件分支和子节点的情况
|
||
const hasConditionNodesAndChildNode = node.conditionNodes && node.conditionNodes.length > 0 && node.childNode
|
||
|
||
// 处理条件分支节点
|
||
if (node.conditionNodes && node.conditionNodes.length > 0) {
|
||
// 处理每个条件分支
|
||
for (const condNode of node.conditionNodes) {
|
||
// 创建条件节点及其子节点
|
||
createNodesAndEdges(condNode, node.id)
|
||
|
||
if (condNode.childNode) {
|
||
createNodesAndEdges(condNode.childNode, condNode.id)
|
||
}
|
||
}
|
||
|
||
// 如果同时存在子节点,将所有条件分支末端节点连接到该子节点
|
||
if (hasConditionNodesAndChildNode) {
|
||
const childNodeId = node.childNode!.id
|
||
const endNodeIds = conditionEndNodes.get(node.id) || []
|
||
|
||
// 为每个条件分支的末端节点创建到子节点的连接
|
||
for (const endNodeId of endNodeIds) {
|
||
// 避免重复创建边
|
||
const edgeId = `${endNodeId}-${childNodeId}`
|
||
|
||
// 检查是否已经存在此边
|
||
const edgeExists = result.edges.some((edge) => edge.id === edgeId)
|
||
|
||
if (!edgeExists) {
|
||
const endNodePosition = nodePositions.get(endNodeId)
|
||
const childNodePosition = nodePositions.get(childNodeId)
|
||
|
||
if (endNodePosition && childNodePosition) {
|
||
// 计算边缘路径的偏移量
|
||
const offset = LAYOUT_CONFIG.nodeHeight / 2 + 10
|
||
const sourceY = endNodePosition.y + offset
|
||
const targetY = childNodePosition.y - offset
|
||
|
||
const edge: WorkflowEdge = {
|
||
id: edgeId,
|
||
source: endNodeId,
|
||
target: childNodeId,
|
||
type: 'step',
|
||
style: {
|
||
strokeWidth: 2,
|
||
strokeDasharray: 5,
|
||
},
|
||
animated: true,
|
||
markerEnd: {
|
||
type: MarkerType.ArrowClosed,
|
||
width: 15,
|
||
height: 15,
|
||
color: '#b1b1b7',
|
||
},
|
||
// 添加路径偏移
|
||
sourceY,
|
||
targetY,
|
||
}
|
||
result.edges.push(edge)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 独立创建子节点,不从父节点连接
|
||
createNodesAndEdges(node.childNode, null)
|
||
}
|
||
}
|
||
|
||
// 处理常规子节点(如果不是与条件分支共存的情况)
|
||
if (node.childNode && !hasConditionNodesAndChildNode) {
|
||
createNodesAndEdges(node.childNode, node.id)
|
||
}
|
||
}
|
||
|
||
// 执行布局算法
|
||
assignLevels(data)
|
||
// 计算子树大小(从根节点开始)
|
||
calculateSubtreeSizes(data.id)
|
||
calculateYCoordinates()
|
||
// 计算节点位置,从根节点开始,初始左边界为0
|
||
positionNodes(data.id, 0)
|
||
createNodesAndEdges(data)
|
||
|
||
return result
|
||
}
|
||
|
||
/**
|
||
* 处理工作流数据,从 JSON 结构转换为 VueFlow 所需的节点和边
|
||
* @param workflowData 工作流 JSON 数据
|
||
* @returns VueFlow的节点和边
|
||
*/
|
||
export function processWorkflowData(workflowData: any): ProcessResult {
|
||
if (!workflowData) {
|
||
return { nodes: [], edges: [] }
|
||
}
|
||
|
||
// 处理嵌套数据结构
|
||
return transformNestedFlowData(workflowData)
|
||
}
|