// 根据传入的elements、node节点、edge节点对elements元素进行再计算 import { ArrowHeadType, isEdge, isNode, Position } from 'react-flow-renderer' import { buildUUID } from '@/utils/uuid' import type { Edge, Elements, Node, XYPosition } from 'react-flow-renderer' import { ParticipantMode } from './enum' export enum genreateElementEnum { ADD = 'add', DEL = 'del' } /** 通过传入的新的node节点以及当前操作的edge构建新的elements */ function buildWithNewNode(elements: Elements, newNode?: Node | null, oldEdge?: Edge | null) { if (!newNode || !oldEdge) return elements // 取出当前操作edge的数据 const { id: edgeId, source: edgeSid, target: edgeTid } = oldEdge // 原来node节点中的sort const sort = elements.find(item => item.id === edgeTid)?.data?.sort // console.log(sort) let oldIdx const newElements: Elements = [...elements].map((item, idx) => { if (isNode(item)) { if (item.data?.sort >= sort) { return { ...item, data: { ...item.data, sort: item.data.sort + 1 } } } if (item.id === edgeTid) { oldIdx = idx } return item } else if (isEdge(item) && item.id === edgeId) { // 修改原来edge的source const edge = { ...item, source: newNode.id } return edge } return item }) // 添加新的node和edge // TODO: 在当前oldEdge target指向的节点前插入node newElements.splice(oldIdx + 1, 0, { ...newNode, position: { x: 500, y: sort * 200 }, type: 'common', data: { sort } }) newElements.push({ id: buildUUID(), source: edgeSid, target: newNode.id, type: 'common', arrowHeadType: ArrowHeadType.Arrow }) return newElements.map(item => { if (isNode(item)) { return { ...item, position: { ...item.position, y: item.data?.sort * 200 } } } return item }) } /** 传入需要删除的node构建新的elements */ function buildWithDeletedNode(elements: Elements, deletedNode?: Node | null) { if (!deletedNode) return elements const { id, data: { sort } } = deletedNode // 1.先找到node关联的的那两条线 const upEdge: Edge = elements.find(item => isEdge(item) && item.target === id) const downEdge: Edge = elements.find(item => isEdge(item) && item.source === id) // 2. 删除对应的edge以及node以及重写sort return elements .filter(item => item.id !== id && item.id !== downEdge.id) .map(item => { if (isNode(item) && item.data?.sort > sort) return { ...item, data: { sort: item.data?.sort - 1 }, position: { ...item.position, y: (item.data?.sort - 1) * 200 } } if (isEdge(item) && item.id === upEdge.id) { return { ...item, target: downEdge.target } } return item }) } export const generateElements = (opreate: genreateElementEnum, elements: Elements) => opreate === genreateElementEnum.ADD ? (newNode?: Node | null, oldEdge?: Edge | null) => buildWithNewNode(elements, newNode, oldEdge) : (deletedNode?: Node | null) => buildWithDeletedNode(elements, deletedNode) // export function generateElements(elements: Elements, newNode?: Node | null, oldEdge?: Edge | null) { // } // this helper function returns the intersection point // of the line between the center of the intersectionNode and the target node function getNodeIntersection(intersectionNode: Node, targetNode: Node): XYPosition { // https://math.stackexchange.com/questions/1724792/an-algorithm-for-finding-the-intersection-point-between-a-center-of-vision-and-a const { width: intersectionNodeWidth, height: intersectionNodeHeight, position: intersectionNodePosition } = intersectionNode.__rf const targetPosition = targetNode.__rf.position const w = intersectionNodeWidth / 2 const h = intersectionNodeHeight / 2 const x2 = intersectionNodePosition.x + w const y2 = intersectionNodePosition.y + h const x1 = targetPosition.x + w const y1 = targetPosition.y + h const xx1 = (x1 - x2) / (2 * w) - (y1 - y2) / (2 * h) const yy1 = (x1 - x2) / (2 * w) + (y1 - y2) / (2 * h) const a = 1 / (Math.abs(xx1) + Math.abs(yy1)) const xx3 = a * xx1 const yy3 = a * yy1 const x = w * (xx3 + yy3) + x2 const y = h * (-xx3 + yy3) + y2 return { x, y } } // returns the position (top,right,bottom or right) passed node compared to the intersection point function getEdgePosition(node: Node, intersectionPoint: XYPosition) { const n = { ...node.__rf.position, ...node.__rf } const nx = Math.round(n.x) const ny = Math.round(n.y) const px = Math.round(intersectionPoint.x) const py = Math.round(intersectionPoint.y) if (px <= nx + 1) { return Position.Left } if (px >= nx + n.width - 1) { return Position.Right } if (py <= ny + 1) { return Position.Top } if (py >= n.y + n.height - 1) { return Position.Bottom } return Position.Top } // returns the parameters (sx, sy, tx, ty, sourcePos, targetPos) you need to create an edge export function getEdgeParams(source: Node, target: Node) { const sourceIntersectionPoint = getNodeIntersection(source, target) const targetIntersectionPoint = getNodeIntersection(target, source) const sourcePos = getEdgePosition(source, sourceIntersectionPoint) const targetPos = getEdgePosition(target, targetIntersectionPoint) return { sx: sourceIntersectionPoint.x, sy: sourceIntersectionPoint.y, tx: targetIntersectionPoint.x, ty: targetIntersectionPoint.y, sourcePos, targetPos } } export const participantSchema = { type: 'object', properties: { participantMode: { type: 'string', title: '', required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', default: ParticipantMode.ACCOUNT, enum: [ { label: '仅用户模式', value: ParticipantMode.ACCOUNT } // { label: '角色模式', value: ParticipantMode.ROLE } ], 'x-index': 1 }, name: { type: 'string', title: '', required: true, 'x-decorator': 'FormItem', 'x-component': 'Input', 'x-display': 'none', 'x-index': 4 }, institutionID: { type: 'string', title: '', required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { placeholder: '请选择单位', showSearch: true, virtual: true, optionFilterProp: 'label' }, 'x-reactions': ['{{useAsyncDataSource(loadInstitutionList)}}'], 'x-index': 3 }, ID: { type: 'string', title: '', required: true, 'x-decorator': 'FormItem', 'x-component': 'Select', 'x-component-props': { placeholder: '请选择用户', showSearch: true, optionFilterProp: 'label' }, 'x-index': 4 } } }