|
|
@@ -1,208 +1,275 @@
|
|
|
import { createUid } from '@/utils/util'
|
|
|
-import { max, omit } from 'lodash'
|
|
|
-import { ArrowHeadType, Edge, Elements, FlowElement, Node, XYPosition } from 'react-flow-renderer'
|
|
|
-import { NodeType, EdgeType } from '../enum'
|
|
|
+import { omit } from 'lodash'
|
|
|
+import {
|
|
|
+ ConnectionLineType,
|
|
|
+ Edge,
|
|
|
+ Elements,
|
|
|
+ FlowElement,
|
|
|
+ isEdge,
|
|
|
+ Node,
|
|
|
+ XYPosition
|
|
|
+} from 'react-flow-renderer'
|
|
|
+import { NodeType } from '../enum'
|
|
|
import { FlowNode, TransformOptions, ComposeOptions } from '../type'
|
|
|
+import { nodeReat } from '../types/node'
|
|
|
|
|
|
-/** 返回condition节点中最长的那一条的长度 */
|
|
|
-const calcMaxConditionLen = (condition: FlowNow): number => {
|
|
|
- const stack = []
|
|
|
- condition.children?.forEach(item => {
|
|
|
- if (item.type === NodeType.CONDITION) {
|
|
|
- stack.push(calcMaxConditionLen())
|
|
|
- } else {
|
|
|
- stack.push(item.children?.length || 0)
|
|
|
- }
|
|
|
- })
|
|
|
- return max(stack)
|
|
|
-}
|
|
|
+const normalNodeWidth = 200
|
|
|
+const normalNodeHeight = 100
|
|
|
|
|
|
/**
|
|
|
* 传入两个node组合生成新的elments
|
|
|
- * @param connectedNode 上一个node节点
|
|
|
+ * @param sourceNode 上一个node节点
|
|
|
* @param composeNode 需要组合的节点
|
|
|
* @param options 额外配置参数
|
|
|
*/
|
|
|
-const composeElement = (connectedNode: Node, composeNode: Node, options: ComposeOptions): FlowElement[] => {
|
|
|
- const { space, order, offset = 0, direction } = options
|
|
|
+const composeElement = (sourceNode: Node, composeNode: Node, options: ComposeOptions): FlowElement[] => {
|
|
|
+ const { space, order = 0, offset = 0, direction } = options
|
|
|
// 组合好的node
|
|
|
- const composedNode: Node = { ...omit(composeNode, ['children', 'order']) }
|
|
|
- // 根据level,order计算postition
|
|
|
+ const composedNode: Node = {
|
|
|
+ ...omit(composeNode, ['children', 'order'])
|
|
|
+ }
|
|
|
+ // 根据offset计算postition
|
|
|
const position: XYPosition = {}
|
|
|
+ const realHeight = nodeReat[composeNode.type]?.height || normalNodeHeight
|
|
|
+ const realWidth = nodeReat[composeNode.type]?.width || normalNodeWidth
|
|
|
+
|
|
|
if (direction === 'x') {
|
|
|
- position.x = (order + 1) * space
|
|
|
- position.y = 400 + offset
|
|
|
+ position.x = order * space - realWidth / 2
|
|
|
+ position.y = realHeight === normalNodeHeight ? 0 : normalNodeHeight / 2 - realHeight / 2
|
|
|
+ // if (nodeReat[sourceNode.type]?.width && nodeReat[sourceNode.type]?.width < realWidth) {
|
|
|
+ // position.x = position.x + (realWidth - nodeReat[sourceNode.type]?.width)
|
|
|
+ // }
|
|
|
} else {
|
|
|
- position.x = 400 + offset
|
|
|
- position.y = (order + 1) * space
|
|
|
- // TODO: 这里要考虑不同level的x的位置
|
|
|
+ position.x = realWidth === normalNodeWidth ? 0 : normalNodeWidth / 2 - realWidth / 2
|
|
|
+ // 当前的位移量等于上一个的坐标+上一个node节点的高度+偏移量
|
|
|
+ // console.log(
|
|
|
+ // '当前id:',
|
|
|
+ // composeNode.id,
|
|
|
+ // '上一个的坐标:',
|
|
|
+ // sourceNode?.position?.y || 0,
|
|
|
+ // '上一个的高度:',
|
|
|
+ // nodeReat[sourceNode?.type]?.height || 0
|
|
|
+ // )
|
|
|
+ position.y =
|
|
|
+ ((sourceNode?.position?.y && sourceNode?.position?.y + space) || nodeReat[sourceNode.type]?.height) +
|
|
|
+ nodeReat[sourceNode.type]?.height
|
|
|
+ if (sourceNode.type === NodeType.INPUT && composeNode.type !== NodeType.INPUT) {
|
|
|
+ position.y = position.y + nodeReat[sourceNode.type]?.height
|
|
|
+ }
|
|
|
+ // console.log('计算后位置:', position.y)
|
|
|
+ // console.log('=======================')
|
|
|
+ }
|
|
|
+
|
|
|
+ if (offset !== 0) {
|
|
|
+ if (direction === 'x') {
|
|
|
+ position.y = offset + position.y + (offset < 0 ? -realHeight : realHeight)
|
|
|
+ } else {
|
|
|
+ position.x = offset + position.x + (offset < 0 ? -realWidth : realWidth)
|
|
|
+ }
|
|
|
}
|
|
|
composedNode.position = position
|
|
|
// 组合好的edge
|
|
|
const edge: Edge = {
|
|
|
id: createUid(),
|
|
|
- source: options?.reverse ? composeNode.id : connectedNode?.id,
|
|
|
- target: options?.reverse ? connectedNode.id : composeNode.id,
|
|
|
- type: options.type ?? EdgeType.COMMON,
|
|
|
- arrowHeadType: ArrowHeadType.Arrow
|
|
|
+ source: options?.reverse ? composeNode.id : sourceNode?.id,
|
|
|
+ target: options?.reverse ? sourceNode.id : composeNode.id,
|
|
|
+ type: options.type ?? ConnectionLineType.SmoothStep
|
|
|
}
|
|
|
return [composedNode, edge]
|
|
|
}
|
|
|
-
|
|
|
/** 通过传入nodes,构建edges */
|
|
|
export function transformElements<T>(
|
|
|
nodes: FlowNode<T>[],
|
|
|
- options: TransformOptions = { space: 200, direction: 'y' }
|
|
|
+ options: TransformOptions = { space: 50, direction: 'y' }
|
|
|
): Elements<T> {
|
|
|
- const firstNode = { id: 'start', type: 'start', position: { x: 400, y: 0 } }
|
|
|
+ const firstNode = { id: 'start', type: 'start' }
|
|
|
const lastNode = { id: 'end', type: 'end' }
|
|
|
const elements: Elements<T> = []
|
|
|
- let lastOrder = 0
|
|
|
// 遍历节点
|
|
|
function generateElements(
|
|
|
nodes: FlowNode<T>[],
|
|
|
- offset?: number | null,
|
|
|
- start?: FlowNode<T>,
|
|
|
- end?: FlowNode<T>
|
|
|
+ opts: { offset?: number; start?: FlowNode<T>; end?: FlowNode<T> }
|
|
|
) {
|
|
|
- nodes.forEach((item, idx) => {
|
|
|
- let baseOrder = 0
|
|
|
-
|
|
|
- if (nodes[idx - 1]?.type === NodeType.OPERATION) {
|
|
|
- // 它的上一个可能是[添加条件按钮]操作节点, 则需要用最大长度的块为基座进行order累加
|
|
|
- // TODO: 将来要考虑多条件节点,目前这里的逻辑是单条件
|
|
|
- baseOrder = calcMaxConditionLen(nodes[idx - 1])
|
|
|
- baseOrder > lastOrder && (lastOrder = baseOrder)
|
|
|
- }
|
|
|
+ const { offset, start, end } = opts || {}
|
|
|
+ nodes.forEach((node, idx) => {
|
|
|
// 通用类型
|
|
|
- if (item.type === NodeType.COMMON) {
|
|
|
+ if (
|
|
|
+ [
|
|
|
+ NodeType.COMMON,
|
|
|
+ NodeType.CONDITION,
|
|
|
+ NodeType.OPERATION_BTN,
|
|
|
+ NodeType.INPUT,
|
|
|
+ NodeType.OUTPUT
|
|
|
+ ].includes(node.type)
|
|
|
+ ) {
|
|
|
// 1. 是否开头
|
|
|
if (idx === 0) {
|
|
|
- const [element, edge] = composeElement(start ?? firstNode, item, {
|
|
|
- offset,
|
|
|
- order: baseOrder + (item.order || idx),
|
|
|
- space: options.space,
|
|
|
- direction: options.direction
|
|
|
- })
|
|
|
+ if (!start) {
|
|
|
+ const [element] = composeElement(firstNode, node, {
|
|
|
+ offset,
|
|
|
+ space: options.space,
|
|
|
+ direction: options.direction
|
|
|
+ })
|
|
|
+ elements.push(element)
|
|
|
+ return
|
|
|
+ }
|
|
|
+ const [element, edge] = composeElement(
|
|
|
+ elements.find(item => item.id === start.id),
|
|
|
+ node,
|
|
|
+ {
|
|
|
+ offset,
|
|
|
+ space: options.space,
|
|
|
+ direction: options.direction
|
|
|
+ }
|
|
|
+ )
|
|
|
elements.push(element, edge)
|
|
|
} else if (idx === nodes.length - 1) {
|
|
|
- // 2. 是否结尾
|
|
|
- if (!end) {
|
|
|
- // TODO: 是否考虑多条件节点下的归属问题
|
|
|
- // 真的结尾了
|
|
|
- lastOrder += idx
|
|
|
- }
|
|
|
- const [element, edge] = composeElement(end ?? lastNode, item, {
|
|
|
- reverse: true,
|
|
|
- offset,
|
|
|
- order: baseOrder + (item.order || idx),
|
|
|
- space: options.space,
|
|
|
- direction: options.direction
|
|
|
- })
|
|
|
+ const [element, edge] = composeElement(
|
|
|
+ elements.find(item => item.id === nodes[idx - 1].id),
|
|
|
+ node,
|
|
|
+ {
|
|
|
+ offset,
|
|
|
+ space: options.space,
|
|
|
+ direction: options.direction
|
|
|
+ }
|
|
|
+ )
|
|
|
elements.push(element, edge)
|
|
|
+
|
|
|
+ // end存在说明是条件节点的结束,则要生成指向end的edges
|
|
|
+ if (end) {
|
|
|
+ const [, lastEdge] = composeElement(end, node, {
|
|
|
+ reverse: true,
|
|
|
+ offset,
|
|
|
+ space: options.space,
|
|
|
+ direction: options.direction
|
|
|
+ })
|
|
|
+ elements.push(lastEdge)
|
|
|
+ }
|
|
|
} else {
|
|
|
// 3. 中间
|
|
|
- const [element, edge] = composeElement(nodes[nodes.length - 1 - idx], item, {
|
|
|
- offset,
|
|
|
- order: baseOrder + (item.order || idx),
|
|
|
+ if (nodes[idx - 1].type === NodeType.OPERATION) {
|
|
|
+ // source指向该节点的所有的node节点
|
|
|
+ const srouceNodeIds = elements
|
|
|
+ .filter(isEdge)
|
|
|
+ .filter(edge => edge.target === node.id)
|
|
|
+ .map(edge => edge.source)
|
|
|
+ // 主轴数值最大的source节点
|
|
|
+ const sourcePositionNode = srouceNodeIds.reduce((prev, curr) => {
|
|
|
+ const currNode: Node = elements.find(item => item.id === curr)
|
|
|
+ // 一开始返回该元素本身
|
|
|
+ if (!prev) return currNode
|
|
|
+ if (currNode?.position?.y > prev?.position.y) {
|
|
|
+ return currNode
|
|
|
+ }
|
|
|
+ return prev
|
|
|
+ }, null)
|
|
|
+
|
|
|
+ const [element] = composeElement(sourcePositionNode, node, {
|
|
|
+ reverse: true,
|
|
|
+ offset,
|
|
|
+ space: options.space,
|
|
|
+ direction: options.direction
|
|
|
+ })
|
|
|
+ elements.push(element)
|
|
|
+ } else {
|
|
|
+ const [element, edge] = composeElement(
|
|
|
+ elements.find(item => item.id === nodes[idx - 1].id),
|
|
|
+ node,
|
|
|
+ {
|
|
|
+ offset,
|
|
|
+ space: options.space,
|
|
|
+ direction: options.direction
|
|
|
+ }
|
|
|
+ )
|
|
|
+ elements.push(element, edge)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (node.type === NodeType.OPERATION) {
|
|
|
+ const [element, edge] = composeElement(
|
|
|
+ elements.find(item => item.id === nodes[idx - 1].id),
|
|
|
+ node,
|
|
|
+ {
|
|
|
+ order: idx,
|
|
|
space: options.space,
|
|
|
direction: options.direction
|
|
|
- })
|
|
|
- elements.push(element, edge)
|
|
|
- }
|
|
|
- } else if (item.type === NodeType.OPERATION) {
|
|
|
- const [element, edge] = composeElement(nodes[idx - 1], item, {
|
|
|
- order: idx,
|
|
|
- space: options.space,
|
|
|
- direction: options.direction
|
|
|
- })
|
|
|
+ }
|
|
|
+ )
|
|
|
|
|
|
elements.push(element, edge)
|
|
|
- generateElements(
|
|
|
- item?.children.map(item => ({ ...item, order: idx })) || [],
|
|
|
- null,
|
|
|
- item,
|
|
|
- idx === nodes.length - 1 ? lastNode : nodes[idx + 1]
|
|
|
- )
|
|
|
- } else if (item.type === NodeType.BLOCK) {
|
|
|
+ return generateElements(node?.children || [], { start: element, end: nodes[idx + 1] })
|
|
|
+ } else {
|
|
|
+ // node.type === NodeType.BLOCK
|
|
|
// 区域节点, 该节点只是占位,直接遍历子节点
|
|
|
const len = nodes.length // 一共有几个条件分支
|
|
|
const intermediation = len / 2
|
|
|
// 一次算出offset,因为该区域节点的offset都是一致的
|
|
|
- let offset
|
|
|
+ let offset = 0
|
|
|
if (len % 2 === 0) {
|
|
|
- if (idx < intermediation) {
|
|
|
- // 算出的当前offset,传入直接算出偏移的x/y
|
|
|
- offset = (idx - intermediation) * 50 + 25
|
|
|
- } else {
|
|
|
- offset = (idx - intermediation) * 50 - 25
|
|
|
- }
|
|
|
+ // 算出的当前offset,传入直接算出偏移的x/y
|
|
|
+ offset = (idx - intermediation) * 50 + 25
|
|
|
} else {
|
|
|
offset = (idx - intermediation) * 50
|
|
|
}
|
|
|
-
|
|
|
- return generateElements(
|
|
|
- item.children.map((child, idx) => ({ ...child, order: item.order + 1 + idx })),
|
|
|
- offset,
|
|
|
- start,
|
|
|
- end
|
|
|
- )
|
|
|
+ return generateElements(node.children || [], { offset, start, end })
|
|
|
}
|
|
|
})
|
|
|
}
|
|
|
- generateElements(nodes)
|
|
|
+ generateElements([firstNode, ...nodes, lastNode])
|
|
|
|
|
|
- return [firstNode, ...elements, { ...lastNode, position: { x: 400, y: (lastOrder + 1) * 200 } }]
|
|
|
+ return elements
|
|
|
}
|
|
|
|
|
|
-// 点击添加条件
|
|
|
-// const node = [
|
|
|
-// {
|
|
|
-// id: 'y-1',
|
|
|
-// type: 'common'
|
|
|
-// },
|
|
|
-// {
|
|
|
-// id: 'xxx',
|
|
|
-// name: '添加条件', // 用type可替代
|
|
|
-// type: 'operation',
|
|
|
-// children: [
|
|
|
-// {
|
|
|
-// id: 'xxx-1',
|
|
|
-// type: 'block',
|
|
|
-// children: [
|
|
|
-// {
|
|
|
-// id: 'xxx-1-1',
|
|
|
-// type: 'condition'
|
|
|
-// },
|
|
|
-// // 正常的节点数据
|
|
|
-// {
|
|
|
-// id: 'xxx-1-2',
|
|
|
-// type: 'common'
|
|
|
-// }
|
|
|
-// ]
|
|
|
-// },
|
|
|
-// {
|
|
|
-// id: 'xxx-2',
|
|
|
-// type: 'block',
|
|
|
-// children: [
|
|
|
-// {
|
|
|
-// id: 'xxx-2-1',
|
|
|
-// type: 'condition'
|
|
|
-// },
|
|
|
-// {
|
|
|
-// id: 'xxx-2-2',
|
|
|
-// type: 'common'
|
|
|
-// },
|
|
|
-// {
|
|
|
-// id: 'xxx-2-3',
|
|
|
-// type: 'common'
|
|
|
-// }
|
|
|
-// ]
|
|
|
-// }
|
|
|
-// ]
|
|
|
-// },
|
|
|
-// {
|
|
|
-// id: 'y-2',
|
|
|
-// type: 'common'
|
|
|
-// }
|
|
|
-// ]
|
|
|
+export const mockNodes = [
|
|
|
+ {
|
|
|
+ id: 'a',
|
|
|
+ type: 'common'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'b',
|
|
|
+ name: '添加条件', // 用type可替代
|
|
|
+ type: 'operation',
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ id: 'b-1',
|
|
|
+ type: 'block',
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ id: 'b-1-1',
|
|
|
+ type: 'condition'
|
|
|
+ },
|
|
|
+ // 正常的节点数据
|
|
|
+ {
|
|
|
+ id: 'b-1-2',
|
|
|
+ type: 'common'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'b-2',
|
|
|
+ type: 'block',
|
|
|
+ children: [
|
|
|
+ {
|
|
|
+ id: 'b-2-1',
|
|
|
+ type: 'condition'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'b-2-2',
|
|
|
+ type: 'common'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 'b-2-3',
|
|
|
+ type: 'common'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ // {
|
|
|
+ // id: 'c',
|
|
|
+ // type: 'operation_btn'
|
|
|
+ // },
|
|
|
+ {
|
|
|
+ id: 'd',
|
|
|
+ type: 'common'
|
|
|
+ }
|
|
|
+]
|