lanjianrong 3 lat temu
rodzic
commit
8a736dd621

+ 25 - 14
src/components/Flow/src/components/Edge/index.tsx

@@ -1,10 +1,10 @@
 import { buildUUID } from '@/utils/uuid'
 import { ApartmentOutlined, ClusterOutlined, PlusOutlined } from '@ant-design/icons'
 import { Button, Dropdown, Menu, Popover } from 'antd'
-import React, { useState, useContext } from 'react'
-import { getBezierPath, getEdgeCenter, getMarkerEnd } from 'react-flow-renderer'
+import React, { useMemo, useState, useContext } from 'react'
+import { getBezierPath, getEdgeCenter, getMarkerEnd, useStoreState } from 'react-flow-renderer'
 import { Actions, FlowContext } from '../../context'
-import { generateElements } from '../../util'
+import { generateElements, getEdgeParams } from '../../utils'
 import styles from './index.less'
 
 const foreignObjectSize = 40
@@ -68,8 +68,8 @@ export function CommonEdge(props) {
     sourceY,
     targetX,
     targetY,
-    sourcePosition,
-    targetPosition,
+    source,
+    target,
     style = {},
     arrowHeadType,
     markerEndId
@@ -79,15 +79,26 @@ export function CommonEdge(props) {
   const [visible, setVisible] = useState(false)
   const { state, dispatch } = useContext(FlowContext)
   const { elements } = state
-  const edgePath = getBezierPath({
-    sourceX,
-    sourceY,
-    sourcePosition,
-    targetX,
-    targetY,
-    targetPosition
-  })
+  const nodes = useStoreState(store => store.nodes)
   const markerEnd = getMarkerEnd(arrowHeadType, markerEndId)
+
+  const sourceNode = useMemo(() => nodes.find(n => n.id === source), [source, nodes])
+  const targetNode = useMemo(() => nodes.find(n => n.id === target), [target, nodes])
+
+  if (!sourceNode || !targetNode) {
+    return null
+  }
+
+  const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode)
+
+  const d = getBezierPath({
+    sourceX: sx,
+    sourceY: sy,
+    sourcePosition: sourcePos,
+    targetPosition: targetPos,
+    targetX: tx,
+    targetY: ty
+  })
   const [edgeCenterX, edgeCenterY] = getEdgeCenter({
     sourceX,
     sourceY,
@@ -107,7 +118,7 @@ export function CommonEdge(props) {
 
   return (
     <>
-      <path id={id} style={style} className={styles.flowPath} d={edgePath} markerEnd={markerEnd} />
+      <path id={id} style={style} className={styles.flowPath} d={d} markerEnd={markerEnd} />
       <foreignObject
         width={foreignObjectSize}
         height={foreignObjectSize}

+ 41 - 0
src/components/Flow/src/components/Graph/FloatingConnectionLine.tsx

@@ -0,0 +1,41 @@
+import { getBezierPath } from 'react-flow-renderer'
+import type { FC } from 'react'
+import type { ConnectionLineComponentProps, Node } from 'react-flow-renderer'
+
+import { getEdgeParams } from '../../utils'
+
+const FloatingConnectionLine: FC<ConnectionLineComponentProps> = ({
+  targetX,
+  targetY,
+  sourcePosition,
+  targetPosition,
+  sourceNode
+}) => {
+  if (!sourceNode) {
+    return null
+  }
+
+  const targetNode = {
+    id: 'connection-target',
+    __rf: { width: 1, height: 1, position: { x: targetX, y: targetY } }
+  } as Node
+
+  const { sx, sy } = getEdgeParams(sourceNode, targetNode)
+  const d = getBezierPath({
+    sourceX: sx,
+    sourceY: sy,
+    sourcePosition,
+    targetPosition,
+    targetX,
+    targetY
+  })
+
+  return (
+    <g>
+      <path fill="none" stroke="#222" strokeWidth={1.5} className="animated" d={d} />
+      <circle cx={targetX} cy={targetY} fill="#fff" r={3} stroke="#222" strokeWidth={1.5} />
+    </g>
+  )
+}
+
+export default FloatingConnectionLine

+ 35 - 0
src/components/Flow/src/components/Graph/FloatingEdge.tsx

@@ -0,0 +1,35 @@
+import { FC, useMemo, CSSProperties } from 'react';
+import { EdgeProps, getMarkerEnd, useStoreState, getBezierPath } from 'react-flow-renderer';
+
+import { getEdgeParams } from './utils';
+
+const FloatingEdge: FC<EdgeProps> = ({ id, source, target, arrowHeadType, markerEndId, style }) => {
+  const nodes = useStoreState((state) => state.nodes);
+  const markerEnd = getMarkerEnd(arrowHeadType, markerEndId);
+
+  const sourceNode = useMemo(() => nodes.find((n) => n.id === source), [source, nodes]);
+  const targetNode = useMemo(() => nodes.find((n) => n.id === target), [target, nodes]);
+
+  if (!sourceNode || !targetNode) {
+    return null;
+  }
+
+  const { sx, sy, tx, ty, sourcePos, targetPos } = getEdgeParams(sourceNode, targetNode);
+
+  const d = getBezierPath({
+    sourceX: sx,
+    sourceY: sy,
+    sourcePosition: sourcePos,
+    targetPosition: targetPos,
+    targetX: tx,
+    targetY: ty,
+  });
+
+  return (
+    <g className="react-flow__connection">
+      <path id={id} className="react-flow__edge-path" d={d} markerEnd={markerEnd} style={style as CSSProperties} />
+    </g>
+  );
+};
+
+export default FloatingEdge;

+ 7 - 1
src/components/Flow/src/components/Graph/index.tsx

@@ -5,6 +5,7 @@ import { FlowContext } from '../../context'
 import type { NodeTypesType, EdgeTypesType } from 'react-flow-renderer'
 import { CommonNode, InputNode, OutputNode } from '../Node'
 import { CommonEdge } from '../Edge'
+import FloatingConnectionLine from './FloatingConnectionLine'
 
 const nodeTypes: NodeTypesType = {
   start: InputNode,
@@ -23,7 +24,12 @@ export default function FlowGroph() {
   const defaultOptions = {
     // nodesDraggable: false, // 不可拖拽
     zoomOnScroll: false, // 使用鼠标滚轮或触控板放大和缩小图形
-    zoomOnPinch: false // 使用捏合放大和缩小图形
+    zoomOnPinch: false, // 使用捏合放大和缩小图形
+    // snapToGrid: true,
+    onLoad: reactFlowInstance => {
+      reactFlowInstance?.fitView()
+    }
+    // connectionLineComponent: FloatingConnectionLine
   }
   return (
     <ReactFlow {...defaultOptions} elements={elements} nodeTypes={nodeTypes} edgeTypes={edgeTypes}>

+ 5 - 3
src/components/Flow/src/context/index.tsx

@@ -3,6 +3,7 @@ import reducer from './reducer'
 import { Actions } from './reducer'
 
 import type { Elements } from 'react-flow-renderer'
+import { ArrowHeadType } from 'react-flow-renderer'
 
 const FlowContext = createContext()
 
@@ -21,14 +22,14 @@ const initialState: InitialState = {
     {
       id: '89e43a46-88da-4243-ad0b-3c701819ff63',
       type: 'start',
-      position: { x: 250, y: 0 },
+      position: { x: 500, y: 0 },
       data: { sort: 0 }
     },
 
     {
       id: '83282d24-22d9-4bd8-a6bf-71d6df94f29e',
       type: 'end',
-      position: { x: 250, y: 200 },
+      position: { x: 500, y: 200 },
       data: { sort: 1 }
     },
 
@@ -36,7 +37,8 @@ const initialState: InitialState = {
       id: '29b4469f-9522-4688-8a35-a57ca4875a7a',
       source: '89e43a46-88da-4243-ad0b-3c701819ff63',
       target: '83282d24-22d9-4bd8-a6bf-71d6df94f29e',
-      type: 'common'
+      type: 'common',
+      arrowHeadType: ArrowHeadType.Arrow
     }
   ],
   flowData: new Map(),

+ 3 - 0
src/components/Flow/src/index.less

@@ -0,0 +1,3 @@
+.flow-container .react-flow__handle {
+  opacity: 0;
+}

+ 0 - 53
src/components/Flow/src/util.ts

@@ -1,53 +0,0 @@
-// 根据传入的elements、node节点、edge节点对elements元素进行再计算
-
-import { isEdge, isNode } from 'react-flow-renderer'
-import type { Edge, Elements, Node } from 'react-flow-renderer'
-import { buildUUID } from '@/utils/uuid'
-
-export function generateElements(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: 250, y: sort * 200 },
-    type: 'common',
-    data: { sort }
-  })
-  console.log(newElements)
-
-  newElements.push({ id: buildUUID(), source: edgeSid, target: newNode.id, type: 'common' })
-  return newElements.map(item => {
-    if (isNode(item)) {
-      if (item.id === '83282d24-22d9-4bd8-a6bf-71d6df94f29e') {
-        console.log(item.data?.sort * 200)
-      }
-      return { ...item, position: { ...item.position, y: item.data?.sort * 200 } }
-    }
-    return item
-  })
-}

+ 157 - 0
src/components/Flow/src/utils.ts

@@ -0,0 +1,157 @@
+// 根据传入的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'
+
+export function generateElements(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)) {
+      if (item.id === '83282d24-22d9-4bd8-a6bf-71d6df94f29e') {
+        console.log(item.data?.sort * 200)
+      }
+      return { ...item, position: { ...item.position, y: item.data?.sort * 200 } }
+    }
+    return item
+  })
+}
+
+// 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 function createElements() {
+  const elements = []
+  const center = { x: window.innerWidth / 2, y: window.innerHeight / 2 }
+
+  elements.push({ id: 'target', data: { label: 'Target' }, position: center })
+
+  for (let i = 0; i < 8; i++) {
+    const degrees = i * (360 / 8)
+    const radians = degrees * (Math.PI / 180)
+    const x = 250 * Math.cos(radians) + center.x
+    const y = 250 * Math.sin(radians) + center.y
+
+    elements.push({ id: `${i}`, data: { label: 'Source' }, position: { x, y } })
+
+    elements.push({
+      id: `edge-${i}`,
+      target: 'target',
+      source: `${i}`,
+      type: 'floating',
+      arrowHeadType: ArrowHeadType.Arrow
+    })
+  }
+
+  return elements
+}

+ 2 - 1
src/pages/Project/Management/index.tsx

@@ -50,7 +50,8 @@ const List: React.FC<ListProps> = ({ schema, dispatch, pTypeList }) => {
   })
   const columns: ProColumnType<API.ProjectListItem>[] = [
     {
-      dataIndex: 'ID'
+      dataIndex: 'ID',
+      hideInTable: true
     },
     {
       dataIndex: 'name',