Pārlūkot izejas kodu

refactor: 流程图构建新增、删除,画布重新渲染

lanjianrong 3 gadi atpakaļ
vecāks
revīzija
4e19629083

+ 1 - 0
.env

@@ -3,3 +3,4 @@
 PORT=8082
 # host
 HOST=localhost
+DID_YOU_KNOW=none

+ 5 - 1
src/components/Drawer/index.tsx

@@ -77,6 +77,7 @@ const DrawerHoc = memo(
         className={styles.pageContainer}
         title={<PageHeader title={drawerProps.title} onBack={onClose} />}
         mask={false}
+        destroyOnClose
         width={`calc(100vw - ${collapsed ? 48 : 256}px - 48px)`}
         style={{ right: '24px', top: `${48 + wrapHeight}px` }}
         contentWrapperStyle={{
@@ -99,7 +100,10 @@ interface DrawerRefType {
 }
 
 interface Options
-  extends Omit<DrawerProps, 'mask' | 'width' | 'style' | 'contentWrapperStyle' | 'closeIcon'> {
+  extends Omit<
+    DrawerProps,
+    'destroyOnClose' | 'mask' | 'width' | 'style' | 'contentWrapperStyle' | 'closeIcon'
+  > {
   children?: React.ReactElement
 }
 

+ 21 - 28
src/components/Flow/src/shared/transformer.ts

@@ -26,7 +26,7 @@ const composeElement = (sourceNode: Node, composeNode: Node, options: ComposeOpt
   const { space, order = 0, offset = 0, direction } = options
   // 组合好的node
   const composedNode: Node = {
-    ...omit(composeNode, ['children', 'order'])
+    ...omit(composeNode, ['children'])
   }
   // 根据offset计算postition
   const position: XYPosition = {}
@@ -50,6 +50,7 @@ const composeElement = (sourceNode: Node, composeNode: Node, options: ComposeOpt
     //   '上一个的高度:',
     //   nodeReat[sourceNode?.type]?.height || 0
     // )
+
     position.y =
       ((sourceNode?.position?.y && sourceNode?.position?.y + space) || nodeReat[sourceNode.type]?.height) +
       nodeReat[sourceNode.type]?.height
@@ -102,8 +103,8 @@ export function transformElements<T>(
           NodeType.OUTPUT
         ].includes(node.type)
       ) {
-        // 1. 是否开头
-        if (idx === 0) {
+        // 1. 是否开头, 条件下只有一个condition节点
+        if (idx === 0 && nodes.length > 1) {
           if (!start) {
             const [element] = composeElement(firstNode, node, {
               offset,
@@ -123,9 +124,9 @@ export function transformElements<T>(
             }
           )
           elements.push(element, edge)
-        } else if (idx === nodes.length - 1) {
+        } else if (idx === nodes.length - 1 || nodes.length - 1 === 0) {
           const [element, edge] = composeElement(
-            elements.find(item => item.id === nodes[idx - 1].id),
+            elements.find(item => item.id === nodes[idx - 1]?.id) || start,
             node,
             {
               offset,
@@ -196,7 +197,13 @@ export function transformElements<T>(
         )
 
         elements.push(element, edge)
-        return generateElements(node?.children || [], { start: element, end: nodes[idx + 1] })
+        return generateElements(
+          node?.children.map(item => ({ ...item, data: { parentId: node.id } })) || [],
+          {
+            start: element,
+            end: nodes[idx + 1]
+          }
+        )
       } else {
         // node.type === NodeType.BLOCK
         // 区域节点, 该节点只是占位,直接遍历子节点
@@ -210,7 +217,14 @@ export function transformElements<T>(
         } else {
           offset = (idx - intermediation) * 50
         }
-        return generateElements(node.children || [], { offset, start, end })
+        return generateElements(
+          node?.children?.map(item => ({ ...item, data: { ...item.data, parentId: node.id } })) || [],
+          {
+            offset,
+            start,
+            end
+          }
+        )
       }
     })
   }
@@ -236,11 +250,6 @@ export const mockNodes = [
           {
             id: 'b-1-1',
             type: 'condition'
-          },
-          // 正常的节点数据
-          {
-            id: 'b-1-2',
-            type: 'common'
           }
         ]
       },
@@ -251,25 +260,9 @@ export const mockNodes = [
           {
             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'
   }
 ]

+ 1 - 0
src/components/Flow/src/type.ts

@@ -31,4 +31,5 @@ export type FlowTreeNode = {
   type?: NodeType
   data?: Record<string, any>
   children?: FlowTreeNode[]
+  parentId?: string
 }

+ 2 - 2
src/components/Flow/src/types/node.ts

@@ -14,8 +14,8 @@ export const nodeReat = {
     height: 32
   },
   [NodeType.OPERATION_BTN]: {
-    width: 32,
-    height: 32
+    width: 24,
+    height: 24
   },
   [NodeType.INPUT]: {
     width: 200,

+ 85 - 16
src/pages/Business/Step/components/Flow/components/Edge/BaseEdge.tsx

@@ -1,10 +1,12 @@
-import React, { useContext, useState } from 'react'
+import React, { useContext, useMemo, useState } from 'react'
 import { EdgeProps } from 'react-flow-renderer'
 import { Button, Popover } from 'antd'
 import { BranchesOutlined, PlusOutlined, SolutionOutlined } from '@ant-design/icons'
 import styles from './index.less'
 import { FlowContext } from '../../context'
 import { NodeType } from '@/components/Flow/src/enum'
+import { addNodes } from './utils'
+import { createUid } from '@/utils/util'
 export type BaseEdgeProps = Pick<EdgeProps, 'style' | 'markerEnd'> & {
   centerX: number
   centerY: number
@@ -12,11 +14,22 @@ export type BaseEdgeProps = Pick<EdgeProps, 'style' | 'markerEnd'> & {
 }
 const foreignObjectSize = 48
 
-export default ({ id, source, path, centerX, centerY, style }: BaseEdgeProps) => {
+export default ({ id, source, target, path, centerX, centerY, style }: BaseEdgeProps) => {
   const [open, setOpen] = useState(false)
-  const { flowStore } = useContext(FlowContext)
+  const { flowStore, dispatch } = useContext(FlowContext)
+
+  const showForeignObject = ![NodeType.OPERATION, NodeType.OPERATION_BTN].includes(
+    flowStore.elements.find(item => item.id === source)?.type
+  )
+
+  const showConditionItem = useMemo(() => {
+    return (
+      flowStore.elements.find(item => item.id === source)?.type === NodeType.INPUT ||
+      flowStore.elements.find(item => item.id === target)?.type === NodeType.OPERATION ||
+      flowStore.elements.find(item => item.id === source)?.data?.parentId
+    )
+  }, [flowStore.elements])
 
-  const showForeignObject = flowStore.elements.find(item => item.id === source)?.type !== NodeType.OPERATION
   const togglePopver = (open: boolean) => {
     setOpen(open)
   }
@@ -27,11 +40,59 @@ export default ({ id, source, path, centerX, centerY, style }: BaseEdgeProps) =>
     // alert(`remove ${id}`)
   }
 
-  const addAuditor = async () => {
-    togglePopver(false)
-    setTimeout(() => {
-      flowInstance?.fitView()
-    }, 80)
+  const addAuditorOrCondition = async (mode: 'auditor' | 'condition') => {
+    let insertNodes = []
+    if (mode === 'auditor') {
+      insertNodes.push({ id: createUid(), type: NodeType.COMMON })
+    } else {
+      insertNodes = [
+        {
+          id: createUid(),
+          type: NodeType.OPERATION,
+          children: [
+            {
+              id: createUid(),
+              type: NodeType.BLOCK,
+              children: [
+                {
+                  id: createUid(),
+                  type: NodeType.CONDITION
+                },
+                {
+                  id: createUid(),
+                  type: NodeType.COMMON
+                }
+              ]
+            },
+            {
+              id: createUid(),
+              type: NodeType.BLOCK,
+              children: [
+                {
+                  id: createUid(),
+                  type: NodeType.CONDITION
+                },
+                {
+                  id: createUid(),
+                  type: NodeType.COMMON
+                }
+              ]
+            }
+          ]
+        },
+        {
+          id: createUid(),
+          type: NodeType.OPERATION_BTN
+        }
+      ]
+    }
+
+    dispatch({
+      type: 'set_flow_process',
+      payload: addNodes(flowStore.process, source, insertNodes)
+    })
+    // togglePopver(false)
+    flowStore.flowInstance?.fitView({ duration: 280 })
   }
   return (
     <>
@@ -49,22 +110,30 @@ export default ({ id, source, path, centerX, centerY, style }: BaseEdgeProps) =>
               onBlur={e => {
                 if (!e.currentTarget.contains(e.relatedTarget)) {
                   // Not triggered when swapping focus between children
-                  togglePopver(false)
+                  // togglePopver(false)
                 }
               }}>
               <Popover
                 content={
                   <ul className="m-0 p-0">
                     <li>
-                      <Button type="dashed" onClick={addAuditor} icon={<SolutionOutlined />}>
+                      <Button
+                        type="dashed"
+                        onClick={() => addAuditorOrCondition('auditor')}
+                        icon={<SolutionOutlined />}>
                         审批人
                       </Button>
                     </li>
-                    <li className="mt-2 condition">
-                      <Button type="dashed" onClick={addAuditor} icon={<BranchesOutlined />}>
-                        条件分支
-                      </Button>
-                    </li>
+                    {!showConditionItem && (
+                      <li className="mt-2 condition">
+                        <Button
+                          type="dashed"
+                          onClick={() => addAuditorOrCondition('condition')}
+                          icon={<BranchesOutlined />}>
+                          条件分支
+                        </Button>
+                      </li>
+                    )}
                   </ul>
                 }
                 trigger="click"

+ 2 - 0
src/pages/Business/Step/components/Flow/components/Edge/SmoothStepEdge.tsx

@@ -145,6 +145,7 @@ export default memo(
     source,
     sourceX,
     sourceY,
+    target,
     targetX,
     targetY,
     label,
@@ -206,6 +207,7 @@ export default memo(
       <BaseEdge
         id={id}
         source={source}
+        target={target}
         path={path}
         centerX={centerX}
         centerY={centerY}

+ 83 - 0
src/pages/Business/Step/components/Flow/components/Edge/utils.ts

@@ -1,3 +1,5 @@
+import { NodeType } from '@/components/Flow/src/enum'
+import { FlowTreeNode } from '@/components/Flow/src/type'
 import { ArrowHeadType, Position } from 'react-flow-renderer'
 
 export const getMarkerEnd = (markerType?: ArrowHeadType, markerEndId?: string): string => {
@@ -53,3 +55,84 @@ export const getCenter = ({
 
   return [centerX, centerY, xOffset, yOffset]
 }
+
+/**
+ * 插入节点
+ * @param nodes 原始节点数据
+ * @param sourceId 当前需要操作的id
+ * @param insertNodes 插入的数据
+ */
+export function addNodes(
+  nodes: FlowTreeNode[],
+  sourceId: string,
+  insertNodes: FlowTreeNode[]
+): FlowTreeNode[] {
+  if (sourceId === NodeType.INPUT) return insertNodes.concat(nodes)
+  if (!nodes.length) return insertNodes
+  return nodes.reduce((prev, curr) => {
+    if (curr.id === sourceId) {
+      return [...prev, curr, ...insertNodes]
+    }
+    if (curr.children?.length) {
+      curr.children = addNodes(curr.children, sourceId, insertNodes)
+    }
+    return [...prev, curr]
+  }, [])
+}
+
+/**
+ * 删除节点
+ * @param nodes 原始节点数据
+ * @param deleteNodeId 需要删除的节点
+ * @returns
+ */
+export function delNode(nodes: FlowTreeNode[], deleteNodeId: string): FlowTreeNode[] {
+  const deleteIds = [deleteNodeId]
+  return nodes.reduce((prev, curr, idx) => {
+    if (deleteIds.includes(curr.id)) {
+      if (curr.type === NodeType.OPERATION) {
+        // 要顺便把它的下一个[OPERATION_BTN]节点也push进deleteIds,方便删除
+        deleteIds.push(nodes[idx + 1].id)
+      }
+      return prev
+    }
+    if (curr.children?.length) {
+      curr.children = delNode(curr.children, deleteNodeId)
+    }
+    return prev.concat(curr)
+  }, [])
+}
+
+/**
+ * 通过id查找node
+ * @param nodes 原始节点数据
+ * @param id 目标id
+ */
+export function findNodeById(nodes: FlowTreeNode[], id: string): FlowTreeNode {
+  // 利用队列进行查找
+  const stack: FlowTreeNode[][] = []
+  let targetNode = null
+  for (const node of nodes) {
+    if (node.id === id) {
+      targetNode = node
+      break
+    }
+    if (node.children?.length) {
+      stack.push([...node.children])
+    }
+  }
+
+  while (!targetNode && stack.length) {
+    const currNodes = stack.shift()
+    for (const node of currNodes) {
+      if (node.id === id) {
+        targetNode = node
+        break
+      }
+      if (node.children?.length) {
+        stack.push([...node.children])
+      }
+    }
+  }
+  return targetNode
+}

+ 1 - 1
src/pages/Business/Step/components/Flow/components/Graph/index.tsx

@@ -15,7 +15,7 @@ import { NodeType } from '@/components/Flow/src/enum/index'
 function FlowGroph() {
   const { flowStore, dispatch } = useContext(FlowContext)
 
-  const elements = useMemo(() => flowStore.elements, [flowStore.elements])
+  const elements = useMemo(() => flowStore.elements, [flowStore.process, flowStore.elements])
 
   const nodeTypes: NodeTypesType = useMemo(
     () => ({

+ 37 - 23
src/pages/Business/Step/components/Flow/components/Node/BaseNode.tsx

@@ -1,37 +1,51 @@
 import { CloseOutlined, RightOutlined } from '@ant-design/icons'
 import { memo, useContext } from 'react'
-import { Handle } from 'react-flow-renderer'
+import { Handle, isEdge, NodeProps } from 'react-flow-renderer'
 import { FlowContext } from '../../context'
+import { delNode, findNodeById } from '../Edge/utils'
 
-export const BaseNode = memo(() => {
+export const BaseNode = memo(({ id, data = {} }: NodeProps) => {
+  const { parentId } = data
   const { flowStore, dispatch } = useContext(FlowContext)
-  console.log(flowStore, dispatch)
+  const removeNode = () => {
+    let updatedProcess = null
+    if (parentId) {
+      // 判断当前区块共有几个节点,小于等于2时要销毁整个节点
+      const parentNode = findNodeById(flowStore.process, parentId)
+      console.log('parentNode', parentNode)
 
-  const removeNode = async () => {
-    // const node = { id, data }
-    // const newElements = generateElements(genreateElementEnum.DEL, flowState.elements)(node)
-    // const { code = -1 } = await removeApprovalNode({
-    //   ID: flowState.dataID,
-    //   segmentID: id,
-    //   flowProcess: newElements
-    // })
-    // if (code === consts.RET_CODE.SUCCESS) {
-    //   dispatch({
-    //     type: Actions.REMOVE_FLOW_NODE,
-    //     payload: { ...node, newElements }
-    //   })
-    //   setTimeout(() => {
-    //     flowInstance?.fitView()
-    //   }, 80)
-    // }
+      if (parentNode.children?.length <= 2) {
+        // 要查找它👴👵🈶️多少个👦👧,如果小于等于2则删除👴👵节点,否则删除当前的👨👩节点
+
+        const grandparentNode = findNodeById(
+          flowStore.process,
+          flowStore.elements.filter(isEdge).find(element => element.target === parentNode.children[0].id)
+            ?.source
+        )
+
+        if (grandparentNode?.children?.length <= 2) {
+          updatedProcess = delNode(flowStore.process, grandparentNode.id)
+        } else {
+          updatedProcess = delNode(flowStore.process, parentNode?.parentId)
+        }
+      } else {
+        updatedProcess = delNode(flowStore.process, id)
+      }
+    } else {
+      updatedProcess = delNode(flowStore.process, id)
+    }
+
+    dispatch({
+      type: 'set_flow_process',
+      payload: updatedProcess
+    })
+    flowStore.flowInstance?.fitView({ duration: 80 })
   }
 
   return (
     <>
-      <Handle type="source" position="bottom" />
       <Handle type="target" position="top" />
-      <Handle type="source" position="right" />
-      <Handle type="source" position="left" />
+      <Handle type="source" position="bottom" />
       <div className="shadow-card cursor-default p-1 bg-hex-1890ff rounded-lg node-content w-200px max-h-86px">
         <div className="relative text-light-500  p-1 flex justify-between items-center">
           <div className="leading-5 h-5 text-12px">审批人</div>

+ 19 - 51
src/pages/Business/Step/components/Flow/components/Node/ConditionButton.tsx

@@ -1,58 +1,26 @@
-import { BranchesOutlined, PlusOutlined, SolutionOutlined } from '@ant-design/icons'
-import { Button, Popover } from 'antd'
-import { memo, useState } from 'react'
-import { Handle } from 'react-flow-renderer'
-import styles from '../Edge/index.less'
-export const ConditionButton = memo(() => {
-  const [open, setOpen] = useState(false)
-
-  const togglePopver = (open: boolean) => {
-    setOpen(open)
+import { NodeType } from '@/components/Flow/src/enum'
+import { createUid } from '@/utils/util'
+import { PlusOutlined } from '@ant-design/icons'
+import { Button } from 'antd'
+import { memo, useContext } from 'react'
+import { Handle, NodeProps } from 'react-flow-renderer'
+import { FlowContext } from '../../context'
+import { addNodes } from '../Edge/utils'
+export const ConditionButton = memo(({ id }: NodeProps) => {
+  const { flowStore, dispatch } = useContext(FlowContext)
+  const addAuditor = () => {
+    dispatch({
+      type: 'set_flow_process',
+      payload: addNodes(flowStore.process, id, [{ id: createUid(), type: NodeType.COMMON }])
+    })
+    flowStore.flowInstance?.fitView({ duration: 280 })
   }
-
-  const addAuditor = () => {}
   return (
     <>
       <Handle type="target" position="top" />
-      <div
-        className={styles.addIcon}
-        onBlur={e => {
-          if (!e.currentTarget.contains(e.relatedTarget)) {
-            // Not triggered when swapping focus between children
-            togglePopver(false)
-          }
-        }}>
-        <Popover
-          content={
-            <ul className="m-0 p-0">
-              <li>
-                <Button type="dashed" onClick={addAuditor} icon={<SolutionOutlined />}>
-                  审批人
-                </Button>
-              </li>
-              <li className="mt-2 condition">
-                <Button type="dashed" onClick={addAuditor} icon={<BranchesOutlined />}>
-                  条件分支
-                </Button>
-              </li>
-            </ul>
-          }
-          trigger="click"
-          placement="right"
-          open={open}
-          destroyTooltipOnHide
-          onOpenChange={togglePopver}
-          overlayClassName="flow-popover">
-          <Button
-            icon={<PlusOutlined />}
-            shape="circle"
-            size="small"
-            onClick={event => onEdgeClick(event, id)}
-          />
-        </Popover>
-      </div>
-      <Handle type="source" position="left" />
-      <Handle type="source" position="right" />
+      {/* <Handle type="target" position="right" /> */}
+      <Button shape="circle" size="small" onClick={addAuditor} icon={<PlusOutlined />} />
+      <Handle type="source" position="bottom" />
     </>
   )
 })

+ 36 - 22
src/pages/Business/Step/components/Flow/components/Node/ConditionNode.tsx

@@ -1,38 +1,52 @@
 import { CloseOutlined, RightOutlined } from '@ant-design/icons'
 import { memo, useContext } from 'react'
-import { Handle } from 'react-flow-renderer'
+import { Handle, isEdge, NodeProps } from 'react-flow-renderer'
 import { FlowContext } from '../../context'
+import { delNode, findNodeById } from '../Edge/utils'
 
-export const ConditionNode = memo(() => {
+export const ConditionNode = memo(({ id, data }: NodeProps) => {
+  const { parentId } = data
   const { flowStore, dispatch } = useContext(FlowContext)
-  console.log(flowStore, dispatch)
 
-  const removeNode = async () => {
-    // const node = { id, data }
-    // const newElements = generateElements(genreateElementEnum.DEL, flowState.elements)(node)
-    // const { code = -1 } = await removeApprovalNode({
-    //   ID: flowState.dataID,
-    //   segmentID: id,
-    //   flowProcess: newElements
-    // })
-    // if (code === consts.RET_CODE.SUCCESS) {
-    //   dispatch({
-    //     type: Actions.REMOVE_FLOW_NODE,
-    //     payload: { ...node, newElements }
-    //   })
-    //   setTimeout(() => {
-    //     flowInstance?.fitView()
-    //   }, 80)
-    // }
+  const removeNode = () => {
+    let updatedProcess = null
+    if (parentId) {
+      // 判断当前区块共有几个节点,小于等于2时要销毁整个节点
+      const parentNode = findNodeById(flowStore.process, parentId)
+      if (parentNode.children?.length <= 2) {
+        // 要查找它👴👵🈶️多少个👦👧,如果小于等于2则删除👴👵节点,否则删除当前的👨👩节点
+        const grandparentNode = findNodeById(
+          flowStore.process,
+          flowStore.elements.filter(isEdge).find(element => element.target === id)?.source
+        )
+        if (grandparentNode?.children?.length <= 2) {
+          updatedProcess = delNode(flowStore.process, grandparentNode.id)
+        } else {
+          updatedProcess = delNode(flowStore.process, parentNode?.parentId)
+        }
+      } else {
+        updatedProcess = delNode(flowStore.process, id)
+      }
+    } else {
+      updatedProcess = delNode(flowStore.process, id)
+    }
+
+    dispatch({
+      type: 'set_flow_process',
+      payload: updatedProcess
+    })
+    flowStore.flowInstance?.fitView({ duration: 80 })
   }
 
   return (
     <>
       <Handle type="target" position="top" />
       <div className="shadow-card cursor-default p-1 bg-white rounded-lg node-content w-200px max-h-86px">
-        <div className="relative text-hex-52C41A  p-1 flex justify-between items-center">
+        <div className="relative text-hex-52C41A p-1 flex justify-between items-center">
           <div className="leading-5 h-5 text-12px">审批人</div>
-          <div className="remove-node w-24px h-24px bg-hex-ff4d4f align-middle" onClick={removeNode}>
+          <div
+            className="remove-node w-24px h-24px bg-hex-ff4d4f align-middle text-white"
+            onClick={removeNode}>
             <CloseOutlined size={12} />
           </div>
         </div>

+ 1 - 1
src/pages/Business/Step/components/Flow/components/Node/DefaultNodes.tsx

@@ -4,7 +4,7 @@ export const InputNode = memo(() => {
   return (
     <>
       <div className="shadow-card cursor-default p-1 bg-hex-bfbfbf rounded-lg text-center w-200px h-36px">
-        <div className=" text-hex-fff leading-7 h-7 text-12px ml-1">流程发起</div>
+        <div className=" text-hex-fff leading-7 h-7 text-12px ml-1">流程发起</div>
         {/* <div className="text-14px px-4px py-5px leading-22px text-black text-opacity-85 bg-white rounded-4px h-32px">
           具有新建权限的员工
         </div> */}

+ 16 - 4
src/pages/Business/Step/components/Flow/context/index.tsx

@@ -2,20 +2,32 @@ import React, { createContext, useReducer, Dispatch } from 'react'
 import reducer from './reducer'
 import { Actions } from '../enum'
 import type { Elements, OnLoadParams } from 'react-flow-renderer'
+import { transformElements } from '@/components/Flow/src/shared/transformer'
+import { FlowTreeNode } from '@/components/Flow/src/type'
 
 export type FlowContextState = {
+  process: FlowTreeNode[]
   elements: Elements
   flowInstance?: OnLoadParams
 }
 type FlowContextProviderProps = {
   children: React.ReactNode
-  initialElements: Elements
+  initialProcess: Elements
 }
-const FlowContext = createContext<{ flowStore: FlowContextState; dispatch: Dispatch<Actions> }>({})
+
+export type DispatchAction = Dispatch<{
+  type: `${Actions}`
+  payload: any
+}>
+
+const FlowContext = createContext<{ flowStore: FlowContextState; dispatch: DispatchAction }>({})
 
 const FlowContextProvider: React.FC<FlowContextProviderProps> = props => {
-  const { children, initialElements = [] } = props
-  const [flowStore, dispatch] = useReducer(reducer, { elements: initialElements })
+  const { children, initialProcess = [] } = props
+  const [flowStore, dispatch] = useReducer(reducer, {
+    process: initialProcess,
+    elements: transformElements(initialProcess)
+  })
   return <FlowContext.Provider value={{ flowStore, dispatch }}>{children}</FlowContext.Provider>
 }
 

+ 12 - 7
src/pages/Business/Step/components/Flow/context/reducer.ts

@@ -1,15 +1,20 @@
 import { isNullOrUnDef } from '@/utils/is'
-import type { Elements } from 'react-flow-renderer'
 import { Actions } from '../enum'
 import type { InitialState } from '.'
+import { FlowTreeNode } from '@/components/Flow/src/type'
+import { transformElements } from '@/components/Flow/src/shared/transformer'
 
 export enum ProcessType {
   NODE = '1'
 }
-const setElements = (state, elements: Elements) => ({
-  ...state,
-  elements: Array.isArray(elements) ? elements : []
-})
+
+const setFlowProcess = (state, process: FlowTreeNode[]) => {
+  return {
+    ...state,
+    process: Array.isArray(process) ? process : [],
+    elements: transformElements(process)
+  }
+}
 
 const setFlowNode = (state, payload) => {
   const res = { ...state }
@@ -80,13 +85,13 @@ const handlerMap = {
   [Actions.REMOVE_FLOW_NODE]: removeFlowNode,
   [Actions.OPEN_MODAL]: openModal,
   [Actions.CLOSE_MODAL]: closeModal,
-  [Actions.SET_ELEMENTS]: setElements,
+  [Actions.SET_FLOW_PROCESS]: setFlowProcess,
   [Actions.SET_FLOW_INSTANCE]: setFlowInstance,
   [Actions.SET_FOLW_PROPS]: setFlowProps,
   [Actions.INIT_FLOW_CONTEXT]: onStoreLoad
 }
 
-const reducer = (state: InitialState, action: Actions) => {
+const reducer = (state: InitialState, action: DispatchAction) => {
   const { type, payload } = action
   const handler = handlerMap[type]
   const res = typeof handler === 'function' && handler(state, payload)

+ 1 - 1
src/pages/Business/Step/components/Flow/enum/index.ts

@@ -1,6 +1,6 @@
 export enum Actions {
   INIT_FLOW_CONTEXT = 'init_flow_context',
-  SET_ELEMENTS = 'set_elements',
+  SET_FLOW_PROCESS = 'set_flow_process',
   SET_FLOW_NODE = 'set_flow_node',
   SET_FLOW_INSTANCE = 'set_flow_instance',
   SET_FOLW_PROPS = 'set_flow_props',

+ 4 - 4
src/pages/Business/Step/components/Flow/index.tsx

@@ -1,17 +1,17 @@
 import { ReactFlowProvider } from 'react-flow-renderer'
 import { FlowContextProvider } from './context'
 import FlowGroph from './components/Graph'
-import { FlowElements } from '@/components/Flow/src/type'
+import { FlowTreeNode } from '@/components/Flow/src/type'
 import './index.less'
 
 type ApprovalFlowProps<T> = {
-  defaultElements?: FlowElements<T>
+  defaultValue?: FlowTreeNode<T>[]
 }
 
-function ApprovalFlow<T>({ defaultElements }: ApprovalFlowProps<T>) {
+function ApprovalFlow<T>({ defaultValue }: ApprovalFlowProps<T>) {
   return (
     <div className="flow-container h-full w-full">
-      <FlowContextProvider initialElements={defaultElements}>
+      <FlowContextProvider initialProcess={defaultValue}>
         <ReactFlowProvider>
           {/** 顶部工具栏 */}
           {/* <ToolBar dataId={props.dataID} closeAnimateContent={props.closeAnimateContent} /> */}

+ 2 - 7
src/pages/Business/Step/index.tsx

@@ -1,14 +1,13 @@
 import useDrawer from '@/components/Drawer'
-import { mockNodes, transformElements } from '@/components/Flow/src/shared/transformer'
 import { queryApprovalDetail } from '@/services/api/project'
 import consts from '@/utils/consts'
 import { PageContainer } from '@ant-design/pro-layout'
 import ProTable, { ActionType, ProColumns } from '@ant-design/pro-table'
 import { Button } from 'antd'
 import { useMemo, useRef, useState } from 'react'
-import { isNode } from 'react-flow-renderer'
 import LeftMenu from '../RuleCode/components/LeftMenu'
 import ApprovalFlow from './components/Flow'
+import { transformToFlowNodes } from './components/Flow/shared/transformer'
 type iState = {
   activeKey: string | null
   process: API.ApprovalProcess[]
@@ -29,8 +28,6 @@ const Step: React.FC = () => {
   }, [state.activeKey])
 
   const handleMenuChange = (key: string) => {
-    console.log(key)
-
     setState({ ...state, activeKey: key })
     actionRef.current?.reload()
     // const [subjectID, businessType] = key.split('_')
@@ -55,11 +52,9 @@ const Step: React.FC = () => {
   ]
 
   const handleBtnClick = () => {
-    console.log(transformElements(mockNodes).filter(isNode))
-
     drawer.open({
       title: state.subjectName,
-      children: <ApprovalFlow defaultElements={transformElements(mockNodes)} />
+      children: <ApprovalFlow defaultElements={transformToFlowNodes(state.process)} />
     })
   }
   return (

+ 26 - 21
src/pages/Permission/FrontRole/components/RoleLeftMenu/index.tsx

@@ -31,8 +31,8 @@ const RoleLeftMenu: React.FC<RoleLeftMenuProps> = ({
   onReloadStaff
 }) => {
   const formRef = useRef<ProFormInstance>(null)
-  const [activeID, setActiveID] = useState<Nullable<string>>(null)
-
+  const [inputFocusID, setInputFocusID] = useState<Nullable<string>>(null)
+  const [activeKey, setActiveKey] = useState([menuRoles[0]?.key])
   const { run: tryAddRole } = useRequest((params: API.CreateRoleParams) => addRoleMenu(params), {
     manual: true,
     onSuccess: () => {
@@ -67,7 +67,7 @@ const RoleLeftMenu: React.FC<RoleLeftMenuProps> = ({
     if (val !== oldTitle) {
       await tryUpdateRole({ ID, name: val })
     }
-    setActiveID(null)
+    setInputFocusID(null)
   }
 
   function renderTreeNode(tree) {
@@ -79,7 +79,7 @@ const RoleLeftMenu: React.FC<RoleLeftMenuProps> = ({
           <span className="department-node py-1">{item.title}</span>
         ) : (
           <div className="department-node py-1">
-            {item.key === activeID ? (
+            {item.key === inputFocusID ? (
               <Input
                 autoFocus
                 defaultValue={item.title}
@@ -93,26 +93,28 @@ const RoleLeftMenu: React.FC<RoleLeftMenuProps> = ({
               <div className="title">{item.title}</div>
             )}
             <div className="extra">
-              <FormOutlined className="pr-2" onClick={() => setActiveID(item.key)} />
-              <Popconfirm
-                disabled={!showDelIcon}
-                title="确认删除吗?"
-                onText="确认"
-                cancelText="取消"
-                onConfirm={() => tryDelRole(item.key)}
-                icon={<QuestionCircleOutlined style={{ color: 'red' }} />}>
-                <DeleteOutlined
-                  onClick={() => {
-                    !showDelIcon && message.warning('请先移除该角色下的所有用户')
-                  }}
-                />
-              </Popconfirm>
+              <FormOutlined className="pr-2" onClick={() => setInputFocusID(item.key)} />
+              {activeKey === item.key && (
+                <Popconfirm
+                  disabled={!showDelIcon}
+                  title="确认删除吗?"
+                  onText="确认"
+                  cancelText="取消"
+                  onConfirm={() => tryDelRole(item.key)}
+                  icon={<QuestionCircleOutlined style={{ color: 'red' }} />}>
+                  <DeleteOutlined
+                    onClick={() => {
+                      !showDelIcon && message.warning('请先移除该角色下的所有用户')
+                    }}
+                  />
+                </Popconfirm>
+              )}
             </div>
           </div>
         )
       }
       if (newItem.children?.length) {
-        newItem.children = renderTreeNode(newItem.children)
+        newItem.children = renderTreeNode(newItem.children, activeKey)
       }
       return newItem
     })
@@ -153,8 +155,11 @@ const RoleLeftMenu: React.FC<RoleLeftMenuProps> = ({
           <DirectoryTree
             itemHeight={32}
             height={virtualHeight - 32}
-            defaultSelectedKeys={[menuRoles[0]?.key]}
-            onSelect={(keys, node) => onSelect(keys[0], node.node.roleType)}
+            selectedKeys={[activeKey]}
+            onSelect={(keys, node) => {
+              setActiveKey(keys[0])
+              onSelect(keys[0], node.node.roleType)
+            }}
             showIcon={false}
             treeData={renderTreeNode(menuRoles)}
           />