|
@@ -0,0 +1,208 @@
|
|
|
+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 { FlowNode, TransformOptions, ComposeOptions } from '../type'
|
|
|
+
|
|
|
+/** 返回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)
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 传入两个node组合生成新的elments
|
|
|
+ * @param connectedNode 上一个node节点
|
|
|
+ * @param composeNode 需要组合的节点
|
|
|
+ * @param options 额外配置参数
|
|
|
+ */
|
|
|
+const composeElement = (connectedNode: Node, composeNode: Node, options: ComposeOptions): FlowElement[] => {
|
|
|
+ const { space, order, offset = 0, direction } = options
|
|
|
+ // 组合好的node
|
|
|
+ const composedNode: Node = { ...omit(composeNode, ['children', 'order']) }
|
|
|
+ // 根据level,order计算postition
|
|
|
+ const position: XYPosition = {}
|
|
|
+ if (direction === 'x') {
|
|
|
+ position.x = (order + 1) * space
|
|
|
+ position.y = 400 + offset
|
|
|
+ } else {
|
|
|
+ position.x = 400 + offset
|
|
|
+ position.y = (order + 1) * space
|
|
|
+ // TODO: 这里要考虑不同level的x的位置
|
|
|
+ }
|
|
|
+ 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
|
|
|
+ }
|
|
|
+ return [composedNode, edge]
|
|
|
+}
|
|
|
+
|
|
|
+/** 通过传入nodes,构建edges */
|
|
|
+export function transformElements<T>(
|
|
|
+ nodes: FlowNode<T>[],
|
|
|
+ options: TransformOptions = { space: 200, direction: 'y' }
|
|
|
+): Elements<T> {
|
|
|
+ const firstNode = { id: 'start', type: 'start', position: { x: 400, y: 0 } }
|
|
|
+ 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>
|
|
|
+ ) {
|
|
|
+ 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)
|
|
|
+ }
|
|
|
+ // 通用类型
|
|
|
+ if (item.type === NodeType.COMMON) {
|
|
|
+ // 1. 是否开头
|
|
|
+ if (idx === 0) {
|
|
|
+ const [element, edge] = composeElement(start ?? firstNode, item, {
|
|
|
+ offset,
|
|
|
+ order: baseOrder + (item.order || idx),
|
|
|
+ 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
|
|
|
+ })
|
|
|
+ elements.push(element, edge)
|
|
|
+ } else {
|
|
|
+ // 3. 中间
|
|
|
+ const [element, edge] = composeElement(nodes[nodes.length - 1 - idx], item, {
|
|
|
+ offset,
|
|
|
+ order: baseOrder + (item.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) {
|
|
|
+ // 区域节点, 该节点只是占位,直接遍历子节点
|
|
|
+ const len = nodes.length // 一共有几个条件分支
|
|
|
+ const intermediation = len / 2
|
|
|
+ // 一次算出offset,因为该区域节点的offset都是一致的
|
|
|
+ let offset
|
|
|
+ if (len % 2 === 0) {
|
|
|
+ if (idx < intermediation) {
|
|
|
+ // 算出的当前offset,传入直接算出偏移的x/y
|
|
|
+ offset = (idx - intermediation) * 50 + 25
|
|
|
+ } else {
|
|
|
+ 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
|
|
|
+ )
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ generateElements(nodes)
|
|
|
+
|
|
|
+ return [firstNode, ...elements, { ...lastNode, position: { x: 400, y: (lastOrder + 1) * 200 } }]
|
|
|
+}
|
|
|
+
|
|
|
+// 点击添加条件
|
|
|
+// 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'
|
|
|
+// }
|
|
|
+// ]
|