浏览代码

feat: 步骤配置-抽屉更新节点信息

fix: 1
lanjianrong 3 年之前
父节点
当前提交
590e8f8fd3
共有 38 个文件被更改,包括 504 次插入206 次删除
  1. 18 12
      src/components/AnimateContent/index.tsx
  2. 70 29
      src/components/Drawer/index.tsx
  3. 10 1
      src/components/Flow/src/shared/transformer.ts
  4. 3 3
      src/components/Modal/index.tsx
  5. 9 0
      src/enums/audit.ts
  6. 1 1
      src/enums/gc.ts
  7. 4 0
      src/enums/index.ts
  8. 6 0
      src/enums/permission.ts
  9. 1 0
      src/pages/Business/Process/components/MemberItem.tsx
  10. 38 11
      src/pages/Business/Process/hooks/useScripts.tsx
  11. 3 3
      src/pages/Business/Process/index.tsx
  12. 5 0
      src/pages/Business/Step/components/Flow/components/Drawer/Auditor/MatterPermission.tsx
  13. 5 0
      src/pages/Business/Step/components/Flow/components/Drawer/Auditor/RelatedMatter.tsx
  14. 83 0
      src/pages/Business/Step/components/Flow/components/Drawer/Auditor/StepSetting.tsx
  15. 103 0
      src/pages/Business/Step/components/Flow/components/Drawer/Auditor/index.tsx
  16. 3 0
      src/pages/Business/Step/components/Flow/components/Drawer/Condition.tsx
  17. 2 0
      src/pages/Business/Step/components/Flow/components/Drawer/index.ts
  18. 0 0
      src/pages/Business/Step/components/Flow/components/Drawer_old/ParticipantCard.tsx
  19. 0 0
      src/pages/Business/Step/components/Flow/components/Drawer_old/index.less
  20. 0 0
      src/pages/Business/Step/components/Flow/components/Drawer_old/index.tsx
  21. 14 1
      src/pages/Business/Step/components/Flow/components/Edge/BaseEdge.tsx
  22. 17 0
      src/pages/Business/Step/components/Flow/components/Edge/utils.ts
  23. 0 42
      src/pages/Business/Step/components/Flow/components/Graph/FloatingEdge.tsx
  24. 5 6
      src/pages/Business/Step/components/Flow/components/Graph/index.tsx
  25. 36 8
      src/pages/Business/Step/components/Flow/components/Node/BaseNode.tsx
  26. 2 0
      src/pages/Business/Step/components/Flow/components/Node/ConditionNode.tsx
  27. 2 3
      src/pages/Business/Step/components/Flow/components/Toolbar/index.tsx
  28. 9 2
      src/pages/Business/Step/components/Flow/context/index.tsx
  29. 8 6
      src/pages/Business/Step/components/Flow/context/reducer.ts
  30. 6 3
      src/pages/Business/Step/components/Flow/index.tsx
  31. 1 1
      src/pages/Business/Step/components/Flow/shared/transformer.ts
  32. 22 6
      src/pages/Business/Step/index.tsx
  33. 4 2
      src/pages/Business/model.ts
  34. 2 10
      src/pages/Institutions/Company/Detail/components/Staff.tsx
  35. 2 0
      src/services/api/business.ts
  36. 7 0
      src/services/api/subject.ts
  37. 3 20
      src/services/api/typings.d.ts
  38. 0 36
      src/utils/util.ts

+ 18 - 12
src/components/AnimateContent/index.tsx

@@ -4,39 +4,45 @@ import type { PropsWithChildren, FC } from 'react'
 import styles from './index.less'
 import { useModel } from '@umijs/max'
 type AnimateContentProps = {
+  mode?: 'default' | 'amimate'
   visible: boolean
   onVisibleChange: (visible: boolean) => void
   disableBreadcrumb?: boolean
 } & Omit<DrawerProps, 'visible' | 'onVisibleChange'>
 
 const AnimateContent: FC<PropsWithChildren<AnimateContentProps>> = ({
+  mode = 'amimate',
   visible,
   onVisibleChange,
   children,
   title,
-  ...others
+  ...reset
 }) => {
   const wrapHeight = document.querySelector('.ant-pro-page-container-warp')?.clientHeight || 0
   const {
     initialState: { setting: { collapsed } = { collapsed: false } }
   } = useModel('@@initialState')
+
   return (
     <div className={styles.pageContainer}>
       <Drawer
-        {...others}
-        title={<PageHeader title={title} onBack={() => onVisibleChange(false)} />}
-        getContainer={false}
+        {...reset}
+        title={
+          mode === 'amimate' ? <PageHeader title={title} onBack={() => onVisibleChange(false)} /> : title
+        }
         visible={visible}
-        push={false}
         onClose={() => onVisibleChange(false)}
         mask={false}
-        width={`calc(100vw - ${collapsed ? 48 : 256}px - 48px)`}
-        style={{ right: '24px', top: `${48 + wrapHeight}px` }}
-        contentWrapperStyle={{
-          height: `calc(100vh - 72px - ${wrapHeight}px)`
-        }}
-        // closeIcon={<Button onClick={() => onVisibleChange(false)}>返回</Button>}>
-      >
+        push={false}
+        width={mode === 'amimate' ? `calc(100vw - ${collapsed ? 48 : 256}px - 48px)` : undefined}
+        style={mode === 'amimate' ? { right: '24px', top: `${48 + wrapHeight}px` } : undefined}
+        contentWrapperStyle={
+          mode === 'amimate'
+            ? {
+                height: `calc(100vh - 72px - ${wrapHeight}px)`
+              }
+            : undefined
+        }>
         {visible ? children : null}
       </Drawer>
     </div>

+ 70 - 29
src/components/Drawer/index.tsx

@@ -13,7 +13,10 @@ import { useModel } from '@umijs/max'
 import type { DrawerProps } from 'antd'
 
 import styles from './index.less'
-
+import classNames from 'classnames'
+type DrawerHocProps = {
+  mode?: 'default' | 'innerEdge'
+} & DrawerProps
 const DrawerHoc = memo(
   forwardRef((prop: any, ref) => {
     const {
@@ -21,7 +24,7 @@ const DrawerHoc = memo(
     } = useModel('@@initialState')
 
     const [chidlren, setDrawerChildren] = useState<React.ReactElement>(null)
-    const [drawerProps, setDrawerProps] = useState<DrawerProps>({
+    const [drawerProps, setDrawerProps] = useState<DrawerHocProps>({
       open: false
     })
 
@@ -58,32 +61,76 @@ const DrawerHoc = memo(
           })
         },
         // 打开Modal
-        open: () => {
-          onOpen()
-        },
+        open: () => onOpen(),
         // 关闭Modal
-        close: () => {
-          onClose()
-        }
+        close: () => onClose()
       }),
       []
     )
     const wrapHeight = document.querySelector('.ant-pro-page-container-warp')?.clientHeight || 0
 
-    // 这里的Modal是ant.design中的Modal
+    const {
+      mode = 'innerEdge',
+      className,
+      width,
+      style,
+      closeIcon,
+      contentWrapperStyle,
+      title,
+      mask,
+      push = false,
+      destroyOnClose = true,
+      ...reset
+    } = drawerProps
+
+    const mergedTitle = useMemo(
+      () => (mode === 'innerEdge' ? <PageHeader title={title} onBack={onClose} /> : title),
+      [mode, title]
+    )
+    const mergedClassName = useMemo(
+      () => classNames(className, mode === 'innerEdge' ? styles.pageContainer : null),
+      [className, mode]
+    )
+
+    const mergedWidth = useMemo(
+      () => (mode === 'innerEdge' ? `calc(100vw - ${collapsed ? 48 : 256}px - 48px)` : width),
+      [mode, width, collapsed]
+    )
+
+    const mergedStyle = useMemo(
+      () => (mode === 'innerEdge' ? { right: '24px', top: `${48 + wrapHeight}px` } : style),
+      [style, mode, wrapHeight]
+    )
+
+    const mergedcontentWrapperStyle = useMemo(
+      () =>
+        mode === 'innerEdge'
+          ? {
+              height: `calc(100vh - 72px - ${wrapHeight}px)`
+            }
+          : contentWrapperStyle,
+      [contentWrapperStyle, mode, wrapHeight]
+    )
+
+    const mergedCloseIcon = useMemo(
+      () => (mode === 'innerEdge' ? <Button onClick={onClose}>返回</Button> : closeIcon),
+      [mode, closeIcon]
+    )
+
+    const mergedMask = useMemo(() => (mode === 'innerEdge' ? false : mask), [mode, mask])
     return (
       <Drawer
-        {...drawerProps}
-        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={{
-          height: `calc(100vh - 72px - ${wrapHeight}px)`
-        }}
-        closeIcon={<Button onClick={onClose}>返回</Button>}>
+        {...reset}
+        className={mergedClassName}
+        title={mergedTitle}
+        mask={mergedMask}
+        destroyOnClose={destroyOnClose}
+        push={push}
+        width={mergedWidth}
+        style={mergedStyle}
+        contentWrapperStyle={mergedcontentWrapperStyle}
+        closeIcon={mergedCloseIcon}
+        onClose={onClose}>
         {chidlren}
       </Drawer>
     )
@@ -94,16 +141,10 @@ interface DrawerRefType {
   open: () => void
   close: () => void
   injectChildren: (child: React.ReactElement) => void
-  injectDrawerProps: (
-    props: Omit<DrawerProps, 'mask' | 'width' | 'style' | 'contentWrapperStyle' | 'closeIcon'>
-  ) => void
+  injectDrawerProps: (props: DrawerHocProps) => void
 }
 
-interface Options
-  extends Omit<
-    DrawerProps,
-    'destroyOnClose' | 'mask' | 'width' | 'style' | 'contentWrapperStyle' | 'closeIcon'
-  > {
+interface Options extends DrawerHocProps {
   children?: React.ReactElement
 }
 
@@ -120,7 +161,7 @@ const useDrawer = () => {
         drawerRef.current.open()
       },
       close: () => {
-        modalRef.current.close()
+        drawerRef.current.close()
       }
     }
   }, [])

+ 10 - 1
src/components/Flow/src/shared/transformer.ts

@@ -218,7 +218,16 @@ export function transformElements<T>(
           offset = (idx - intermediation) * 50
         }
         return generateElements(
-          node?.children?.map(item => ({ ...item, data: { ...item.data, parentId: node.id } })) || [],
+          node?.children?.map((item, i) => {
+            const newItem = { ...item, data: { ...item.data, parentId: node.id } }
+            if (item.type === NodeType.CONDITION) {
+              newItem.data.priority = idx
+              if (i === node.children.length - 1) {
+                newItem.data.priority = 'default'
+              }
+            }
+            return newItem
+          }) || [],
           {
             offset,
             start,

+ 3 - 3
src/components/Modal/index.tsx

@@ -19,7 +19,7 @@ const MyModal = memo(
     const [modalChildren, setModalChildren] = useState<React.ReactElement>(null)
     const [loading, setLoading] = useState(false)
     const [modalProps, setModalProps] = useState<ModalProps>({
-      visible: false
+      open: false
     })
     const typeRef = useRef<string>()
 
@@ -49,7 +49,7 @@ const MyModal = memo(
     const onClose = useCallback(() => {
       setModalProps(source => ({
         ...source,
-        visible: false
+        open: false
       }))
       startTransition(() => {
         if (typeRef.current === 'form') {
@@ -62,7 +62,7 @@ const MyModal = memo(
     const onOpen = useCallback(() => {
       setModalProps(source => ({
         ...source,
-        visible: true
+        open: true
       }))
     }, [form])
 

+ 9 - 0
src/enums/audit.ts

@@ -0,0 +1,9 @@
+/** 配置信息 */
+export enum ConfigureType {
+  // SKIP = 'skip', // 跳过
+  RETURN = 'return' // 退回
+  // REVOKE = 'revoke', // 撤回
+  // ASSISTAUDIT = 'assistAudit', // 协审
+  // ADDSIGN = 'addSign', // 加签
+  // NEXTSECTOR = 'nextSector' // 下个环节
+}

+ 1 - 1
src/enums/gc.ts

@@ -10,7 +10,7 @@ export enum BusinessType {
 /** 执行者配置方式 */
 export enum ExecutorSetType {
   /** 预置成员 */
-  PERSET = 'perset',
+  PERSET = 'preset',
   /** 步骤设置 */
   DYNAMIC = 'dynamic'
 }

+ 4 - 0
src/enums/index.ts

@@ -0,0 +1,4 @@
+export * from './permission'
+export * from './access'
+export * from './gc'
+export * from './audit'

+ 6 - 0
src/enums/permission.ts

@@ -0,0 +1,6 @@
+export enum ExecutorPermission {
+  /** 查看计价 */
+  VIEW_COST = 'viewCost',
+  /** 编辑计价 */
+  EDIT_COST = 'editCost'
+}

+ 1 - 0
src/pages/Business/Process/components/MemberItem.tsx

@@ -120,6 +120,7 @@ const MemberItem: React.FC<MemberItemProps> = ({ value, onChange }) => {
             if (isString(item)) return item
             return item.ID
           })}
+          placeholder="请选择"
           treeData={state.memberOptions}
           onChange={toggleChange}
           multiple

+ 38 - 11
src/pages/Business/Process/hooks/useScripts.tsx

@@ -2,8 +2,14 @@ import MemberItem from '../components/MemberItem'
 import consts from '@/utils/consts'
 import { useState } from 'react'
 import { useRequest } from 'ahooks'
-import { addExecutor, delExecutor, queryExecutorList, updateExecutor } from '@/services/api/business'
-import { ApprovalType, ExecutorSetType } from '@/enums/gc'
+import {
+  addExecutor,
+  delExecutor,
+  queryExecutorList,
+  queryMatterList,
+  updateExecutor
+} from '@/services/api/business'
+import { ExecutorSetType } from '@/enums/gc'
 import { QuestionCircleFilled } from '@ant-design/icons'
 import { Form, message, Tooltip } from 'antd'
 import ProForm, { ProFormDependency, ProFormRadio, ProFormSelect, ProFormText } from '@ant-design/pro-form'
@@ -21,7 +27,7 @@ const setTypeOptions = [
         </Tooltip>
       </div>
     ),
-    value: ExecutorSetType.PRESET_MEMBER
+    value: ExecutorSetType.PERSET
   },
   {
     label: (
@@ -32,7 +38,7 @@ const setTypeOptions = [
         </Tooltip>
       </div>
     ),
-    value: ExecutorSetType.STEP_SETTING
+    value: ExecutorSetType.DYNAMIC
   }
 ]
 
@@ -40,7 +46,7 @@ type iState = {
   total: number
 }
 
-export default function useScripts(modal: ModalAction) {
+export default function useScripts(modal: ModalAction, subject: string) {
   const dispatch = useDispatch()
   const [state, seState] = useState<iState>({
     total: 0
@@ -64,6 +70,16 @@ export default function useScripts(modal: ModalAction) {
     }
   })
 
+  const { data: { data: { items: matters = [] } = {} } = {} } = useRequest(
+    () => {
+      if (!subject) return
+      const [subjectID, businessType] = subject.split('_')
+
+      return queryMatterList({ subjectID, businessType, pageSize: 214000, matterType: 'matter' })
+    },
+    { refreshDeps: [subject] }
+  )
+
   const addOrEdit = (mode: 'add' | 'edit', initialValues?: API.ExecutorItem) => {
     const text = mode === 'add' ? '新增执行者' : '编辑执行者'
     modal.open({
@@ -72,8 +88,7 @@ export default function useScripts(modal: ModalAction) {
       cancelText: '取消',
       type: 'form',
       initialValues: initialValues ?? {
-        setType: ExecutorSetType.PRESET_MEMBER,
-        approvalType: ApprovalType.TARGET
+        setType: ExecutorSetType.PERSET
       },
       children: (
         <ProForm submitter={false} layout="horizontal" labelCol={{ span: 5 }} isKeyPressSubmit>
@@ -91,23 +106,35 @@ export default function useScripts(modal: ModalAction) {
             rules={[{ required: true, message: '请选择' }]}
           />
           <ProFormDependency name={['setType']}>
-            {({ name }) => {
-              if (name === ExecutorSetType.PRESET_MEMBER)
+            {({ setType }) => {
+              if (setType === ExecutorSetType.PERSET)
                 return (
                   <Form.Item label="成员" name="members" rules={[{ required: true, message: '请选择' }]}>
                     <MemberItem />
                   </Form.Item>
                 )
-              return <ProFormSelect name="" label="范围选择" />
+              return (
+                <ProFormSelect name="scope" label="范围选择" options={[{ label: '全部', value: 'all' }]} />
+              )
             }}
           </ProFormDependency>
+          <ProFormSelect
+            name="approvaledPermission"
+            label="事项权限"
+            placeholder="请选择"
+            rules={[{ required: true, message: '请选择' }]}
+            fieldProps={{ mode: 'multiple' }}
+            options={matters.map(item => ({ label: item.name, value: item.ID }))}
+          />
         </ProForm>
       ),
       onOk: async (values: API.ExecutorItem) => {
+        const [subjectID, businessType] = subject.split('_')
+        values.subjectID = subjectID
+        values.businessType = businessType
         let requestFn: Nullable<() => Promise<viod>> = null
         if (mode === 'add') {
           requestFn = addExecutor
-          values.businessType = business
         } else {
           requestFn = updateExecutor
         }

+ 3 - 3
src/pages/Business/Process/index.tsx

@@ -23,7 +23,7 @@ const Process: React.FC<ProcessProps> = ({ processMap }) => {
   })
 
   const [modal, ModalDOM] = useModal()
-  const { total, query, loading, addOrEdit, del } = useScripts(modal)
+  const { total, query, loading, addOrEdit, del } = useScripts(modal, state.activeKey)
 
   const list = useMemo(() => {
     if (!state.activeKey) return []
@@ -44,8 +44,8 @@ const Process: React.FC<ProcessProps> = ({ processMap }) => {
       align: 'center',
       width: 100,
       valueEnum: {
-        [ExecutorSetType.PRESET_MEMBER]: { text: '预置成员' },
-        [ExecutorSetType.STEP_SETTING]: { text: '步骤配置' }
+        [ExecutorSetType.PERSET]: { text: '预置成员' },
+        [ExecutorSetType.DYNAMIC]: { text: '步骤配置' }
       }
     },
     {

+ 5 - 0
src/pages/Business/Step/components/Flow/components/Drawer/Auditor/MatterPermission.tsx

@@ -0,0 +1,5 @@
+const MatterPermission = () => {
+  return <div>步骤设置内容</div>
+}
+
+export default MatterPermission

+ 5 - 0
src/pages/Business/Step/components/Flow/components/Drawer/Auditor/RelatedMatter.tsx

@@ -0,0 +1,5 @@
+const RelatedMatter = () => {
+  return <div>步骤设置内容</div>
+}
+
+export default RelatedMatter

+ 83 - 0
src/pages/Business/Step/components/Flow/components/Drawer/Auditor/StepSetting.tsx

@@ -0,0 +1,83 @@
+import { QuestionCircleFilled } from '@ant-design/icons'
+import ProForm, { ProFormList, ProFormSelect, ProFormText } from '@ant-design/pro-form'
+import { FormInstance, FormListOperation } from 'antd'
+import { useRef } from 'react'
+
+type StepSettingProps = {
+  dataID: string
+  form: FormInstance
+  disabled: boolean
+  stepOptions: { label: string; value: string }[]
+  executorOptions: { label: string; value: string }[]
+}
+const StepSetting: React.FC<StepSettingProps> = ({ form, disabled, stepOptions, executorOptions }) => {
+  const actionRef = useRef<FormListOperation>()
+
+  return (
+    <ProForm submitter={false} layout="horizontal" labelCol={{ span: 4 }} labelAlign="left" form={form}>
+      <div className="divide-y divide-dashed">
+        <div>
+          <div className="font-medium mb-3">
+            <span>本步骤配置</span>
+            <QuestionCircleFilled style={{ color: '#FEA100' }} className="ml-1" />
+          </div>
+          <ProFormText
+            name="name"
+            label="步骤名称"
+            placeholder="请输入"
+            rules={[{ required: true, message: '请输入步骤名称' }]}
+          />
+          <ProFormSelect
+            name="executorID"
+            label="步骤执行者"
+            placeholder="请选择"
+            disabled={disabled}
+            options={executorOptions}
+            rules={[{ required: true, message: '请选择步骤执行者' }]}
+          />
+        </div>
+        <div className="">
+          <div className="font-medium py-3">
+            <span>动态步骤配置</span>
+            <QuestionCircleFilled style={{ color: '#FEA100' }} className="ml-1" />
+          </div>
+          <ProFormList
+            name="dynamicSteps"
+            actionRef={actionRef}
+            creatorButtonProps={{ position: 'top' }}
+            copyIconProps={false}
+            deleteIconProps={false}
+            itemRender={({ listDom }, { index }) => (
+              <div className="shadow flex flex-col w-full p-4 mb-4">
+                <div className="text-right -translate-y-2 ">
+                  <span
+                    className="text-red hover:text-red-600 hover:cursor-pointer transition-colors duration-300"
+                    onClick={() => actionRef.current.remove(index)}>
+                    删除
+                  </span>
+                </div>
+                {listDom}
+              </div>
+            )}>
+            <ProFormSelect
+              label="步骤名称"
+              name="stepID"
+              labelCol={{ span: 4 }}
+              options={stepOptions}
+              rules={[{ required: true, message: '请选择步骤' }]}
+            />
+            <ProFormSelect
+              label="步骤执行者"
+              name="executorID"
+              labelCol={{ span: 4 }}
+              options={executorOptions}
+              rules={[{ required: true, message: '请选择步骤执行者' }]}
+            />
+          </ProFormList>
+        </div>
+      </div>
+    </ProForm>
+  )
+}
+
+export default StepSetting

+ 103 - 0
src/pages/Business/Step/components/Flow/components/Drawer/Auditor/index.tsx

@@ -0,0 +1,103 @@
+import StepSetting from './StepSetting'
+import RelatedMatter from './RelatedMatter'
+import MatterPermission from './MatterPermission'
+import { Button, Form, Tabs } from 'antd'
+import { useContext, useEffect } from 'react'
+import { FlowContext } from '../../../context'
+import { Edge, isNode } from 'react-flow-renderer'
+import { updateNodeData } from '../../Edge/utils'
+
+type AuditorProps = {
+  nodeId: string
+  relativeEdge?: Edge
+  nodeData: {
+    name: string
+    participantInfo: API.ParticipantInfo
+  }
+}
+
+const Auditor: React.FC<AuditorProps> = ({ nodeId, nodeData: { name, participantInfo } }) => {
+  const [form] = Form.useForm()
+
+  const {
+    flowStore: { executorList, process, drawer, elements },
+    dispatch
+  } = useContext(FlowContext)
+
+  useEffect(() => {
+    const initalValues = {
+      name,
+      executorID: participantInfo?.executor?.ID,
+      dynamicSteps: participantInfo?.executor?.dynamicSteps
+    }
+    form.setFieldsValue(initalValues)
+  }, [])
+
+  // 动态配置的步骤列表
+  const currStepIdx = elements.filter(isNode).findIndex(item => item.id === nodeId)
+  const stepOptions = elements
+    .filter(isNode)
+    .filter((item, idx) => item.data?.name && item.id !== nodeId && idx > currStepIdx)
+    .map(item => ({ label: item.data.name, value: item.id }))
+
+  const items = [
+    {
+      label: '步骤设置',
+      key: 'stepSetting',
+      children: (
+        <StepSetting
+          executorOptions={executorList.map(item => ({ label: item.name, value: item.ID }))}
+          disabled={participantInfo?.executor?.ID === '业务发起人'}
+          form={form}
+          stepOptions={stepOptions}
+        />
+      )
+    },
+    { label: '关联事项', key: 'relatedMatter', children: <RelatedMatter /> },
+    { label: '事项权限', key: 'matterPermission', children: <MatterPermission /> }
+  ]
+
+  const onConfirm = () => {
+    form
+      .validateFields()
+      .then(values => {
+        const needUpdatedNode = {
+          id: nodeId,
+          data: {
+            name: values.name,
+            participantInfo: {
+              approvalWay: 'account',
+              executor: {
+                ID: values.executorID,
+                configure: ['return']
+              },
+              dynamicSteps: values.dynamicSteps || []
+            }
+          }
+        }
+        dispatch({
+          type: 'set_flow_process',
+          payload: updateNodeData(process, needUpdatedNode)
+        })
+        drawer.close()
+      })
+      .catch()
+  }
+
+  return (
+    <div className="flex flex-col justify-between h-full">
+      <div className="p-24px">
+        <Tabs items={items} />
+      </div>
+
+      <div className="flex justify-end items-center py-10px px-16px border-t">
+        <Button onClick={() => drawer.close()}>取消</Button>
+        <Button type="primary" onClick={() => onConfirm()} className="ml-2">
+          确定
+        </Button>
+      </div>
+    </div>
+  )
+}
+
+export default Auditor

+ 3 - 0
src/pages/Business/Step/components/Flow/components/Drawer/Condition.tsx

@@ -0,0 +1,3 @@
+const Condition = () => {}
+
+export default Condition

+ 2 - 0
src/pages/Business/Step/components/Flow/components/Drawer/index.ts

@@ -0,0 +1,2 @@
+export * from './Condition'
+export * from './Auditor'

src/pages/Business/Step/components/Flow/components/Drawer/ParticipantCard.tsx → src/pages/Business/Step/components/Flow/components/Drawer_old/ParticipantCard.tsx


src/pages/Business/Step/components/Flow/components/Drawer/index.less → src/pages/Business/Step/components/Flow/components/Drawer_old/index.less


src/pages/Business/Step/components/Flow/components/Drawer/index.tsx → src/pages/Business/Step/components/Flow/components/Drawer_old/index.tsx


+ 14 - 1
src/pages/Business/Step/components/Flow/components/Edge/BaseEdge.tsx

@@ -7,6 +7,7 @@ import { FlowContext } from '../../context'
 import { NodeType } from '@/components/Flow/src/enum'
 import { addNodes } from './utils'
 import { createUid } from '@/utils/util'
+import { ConfigureType } from '@/enums'
 export type BaseEdgeProps = Pick<EdgeProps, 'style' | 'markerEnd'> & {
   centerX: number
   centerY: number
@@ -43,7 +44,19 @@ export default ({ id, source, target, path, centerX, centerY, style }: BaseEdgeP
   const addAuditorOrCondition = async (mode: 'auditor' | 'condition') => {
     let insertNodes = []
     if (mode === 'auditor') {
-      insertNodes.push({ id: createUid(), type: NodeType.COMMON })
+      const node = { id: createUid(), type: NodeType.COMMON }
+      if (source === NodeType.INPUT) {
+        node.data = {
+          participantInfo: {
+            approvalWay: 'account',
+            executor: {
+              ID: '业务发起人',
+              configure: [ConfigureType.RETURN]
+            }
+          }
+        }
+      }
+      insertNodes.push(node)
     } else {
       insertNodes = [
         {

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

@@ -136,3 +136,20 @@ export function findNodeById(nodes: FlowTreeNode[], id: string): FlowTreeNode {
   }
   return targetNode
 }
+
+/**
+ * 更新节点数据
+ * @param nodes 原始节点数据
+ * @param node 要更新的数据
+ */
+export function updateNodeData(nodes: FlowTreeNode[], updatedNode: FlowTreeNode) {
+  return nodes.map(item => {
+    if (item.id === updatedNode.id) {
+      item.data = updatedNode.data
+    }
+    if (item.children?.length) {
+      item.children = updateNodeData(item.children, updatedNode)
+    }
+    return item
+  })
+}

+ 0 - 42
src/pages/Business/Step/components/Flow/components/Graph/FloatingEdge.tsx

@@ -1,42 +0,0 @@
-import { getMarkerEnd, useStoreState, getBezierPath } from 'react-flow-renderer'
-import { getEdgeParams } from './utils'
-import { useMemo } from 'react'
-import type { FC, CSSProperties } from 'react'
-import type { EdgeProps } from 'react-flow-renderer'
-
-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

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

@@ -11,10 +11,11 @@ import type { NodeTypesType, EdgeTypesType } from 'react-flow-renderer'
 import { BaseNode, ConditionNode, ConditionButton, ConditionHanderNode, InputNode, OutputNode } from '../Node'
 import { SmoothStepEdge } from '../Edge'
 import { NodeType } from '@/components/Flow/src/enum/index'
+import useDrawer from '@/components/Drawer'
 
 function FlowGroph() {
   const { flowStore, dispatch } = useContext(FlowContext)
-
+  const [drawer, DrawerDom] = useDrawer()
   const elements = useMemo(() => flowStore.elements, [flowStore.process, flowStore.elements])
 
   const nodeTypes: NodeTypesType = useMemo(
@@ -42,11 +43,8 @@ function FlowGroph() {
     zoomOnPinch: false, // 使用捏合放大和缩小图形
     // snapToGrid: true,
     onLoad: (flowInstance: OnLoadParams) => {
-      const payload = {}
-      // if (defaultElements?.length || elements?.length) {
-      //   payload.elements = defaultElements || elements
-      // }
-      payload.flowInstance = flowInstance
+      const payload = { flowInstance, drawer }
+
       dispatch({
         type: Actions.INIT_FLOW_CONTEXT,
         payload
@@ -73,6 +71,7 @@ function FlowGroph() {
       <MiniMap />
       <Background />
       <Controls {...controlsOptions} className="text-xl" />
+      {DrawerDom}
     </ReactFlow>
   )
 }

+ 36 - 8
src/pages/Business/Step/components/Flow/components/Node/BaseNode.tsx

@@ -1,19 +1,26 @@
 import { CloseOutlined, RightOutlined } from '@ant-design/icons'
+import classNames from 'classnames'
 import { memo, useContext } from 'react'
 import { Handle, isEdge, NodeProps } from 'react-flow-renderer'
 import { FlowContext } from '../../context'
+import Auditor from '../Drawer/Auditor'
 import { delNode, findNodeById } from '../Edge/utils'
 
-export const BaseNode = memo(({ id, data = {} }: NodeProps) => {
-  const { parentId } = data
+type BaseNodeProps = {
+  data: {
+    parentId: string
+    name: string
+    participantInfo?: API.ParticipantInfo
+  }
+} & Omit<NodeProps, 'data'>
+export const BaseNode = memo(({ id, data = {} }: BaseNodeProps) => {
+  const { parentId, participantInfo } = data
   const { flowStore, dispatch } = useContext(FlowContext)
   const removeNode = () => {
     let updatedProcess = null
     if (parentId) {
       // 判断当前区块共有几个节点,小于等于2时要销毁整个节点
       const parentNode = findNodeById(flowStore.process, parentId)
-      console.log('parentNode', parentNode)
-
       if (parentNode.children?.length <= 2) {
         // 要查找它👴👵🈶️多少个👦👧,如果小于等于2则删除👴👵节点,否则删除当前的👨👩节点
 
@@ -42,20 +49,41 @@ export const BaseNode = memo(({ id, data = {} }: NodeProps) => {
     flowStore.flowInstance?.fitView({ duration: 80 })
   }
 
+  const auditorTrigger = () => {
+    flowStore.drawer.open({
+      mode: 'default',
+      width: 720,
+      title: '配置步骤',
+      mask: true,
+      bodyStyle: { padding: 'unset' },
+      destroyOnClose: true,
+      children: <Auditor nodeId={id} nodeData={data} />
+    })
+  }
+
+  const auditorText =
+    participantInfo?.executor?.ID &&
+    [...flowStore.executorList, { ID: '业务发起人', name: '业务发起人' }].find(
+      item => item.ID === participantInfo?.executor?.ID
+    )?.name
   return (
     <>
       <Handle type="target" position="top" />
       <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>
+        <div className="relative text-light-500 p-1 flex justify-between items-center">
+          <div className="leading-5 h-5 text-12px">{data?.name ?? '审批人'}</div>
           <div className="remove-node w-24px h-24px bg-hex-ff4d4f align-middle" onClick={removeNode}>
             <CloseOutlined size={12} />
           </div>
         </div>
-        <div className="text-14px px-4px py-5px leading-22px text-black text-opacity-85 bg-white rounded-4px h-32px flex justify-between">
+        <div
+          className="text-14px px-4px py-5px leading-22px text-black text-opacity-85 bg-white rounded-4px h-32px flex justify-between hover:cursor-pointer"
+          onClick={auditorTrigger}>
           <div>
-            <span className="text-hex-999999">发起人自选</span>
+            <span className={classNames({ 'text-hex-999999': !auditorText })}>
+              {auditorText ?? '发起人自选'}
+            </span>
           </div>
           <div>
             <RightOutlined />

+ 2 - 0
src/pages/Business/Step/components/Flow/components/Node/ConditionNode.tsx

@@ -5,6 +5,8 @@ import { FlowContext } from '../../context'
 import { delNode, findNodeById } from '../Edge/utils'
 
 export const ConditionNode = memo(({ id, data }: NodeProps) => {
+  console.log(data)
+
   const { parentId } = data
   const { flowStore, dispatch } = useContext(FlowContext)
 

+ 2 - 3
src/pages/Business/Step/components/Flow/components/Toolbar/index.tsx

@@ -7,8 +7,8 @@ import consts from '@/utils/consts'
 import { isNode } from 'react-flow-renderer'
 import type { Elements } from 'react-flow-renderer'
 
-export default function ToolBar(props: { dataId: string; closeAnimateContent: (visible: boolean) => void }) {
-  const { dataId, closeAnimateContent } = props
+export default function ToolBar(props: { dataId: string }) {
+  const { dataId } = props
   const { flowState } = useContext(FlowContext)
 
   const validate = (elements: Elements) => {
@@ -34,7 +34,6 @@ export default function ToolBar(props: { dataId: string; closeAnimateContent: (v
         })
         if (code === consts.RET_CODE.SUCCESS) {
           message.success('发布成功')
-          closeAnimateContent(false)
         }
       }
     })

+ 9 - 2
src/pages/Business/Step/components/Flow/context/index.tsx

@@ -4,15 +4,20 @@ 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'
+import { DrawerAction } from '@/components/Drawer'
 
 export type FlowContextState = {
+  dataID: string
   process: FlowTreeNode[]
   elements: Elements
+  executorList: API.ExecutorItem[]
   flowInstance?: OnLoadParams
+  drawer: DrawerAction
 }
 type FlowContextProviderProps = {
   children: React.ReactNode
   initialProcess: Elements
+  dataID: string
 }
 
 export type DispatchAction = Dispatch<{
@@ -23,10 +28,12 @@ export type DispatchAction = Dispatch<{
 const FlowContext = createContext<{ flowStore: FlowContextState; dispatch: DispatchAction }>({})
 
 const FlowContextProvider: React.FC<FlowContextProviderProps> = props => {
-  const { children, initialProcess = [] } = props
+  const { children, initialProcess = [], dataID, executorList = [] } = props
   const [flowStore, dispatch] = useReducer(reducer, {
     process: initialProcess,
-    elements: transformElements(initialProcess)
+    elements: transformElements(initialProcess),
+    dataID,
+    executorList
   })
   return <FlowContext.Provider value={{ flowStore, dispatch }}>{children}</FlowContext.Provider>
 }

+ 8 - 6
src/pages/Business/Step/components/Flow/context/reducer.ts

@@ -18,14 +18,16 @@ const setFlowProcess = (state, process: FlowTreeNode[]) => {
 
 const setFlowNode = (state, payload) => {
   const res = { ...state }
-  if (payload instanceof Map) {
-    res.flowData = payload
-    return res
-  }
-  const { id, node } = payload
+  const { id, data } = payload
   if (!id) return state
+  res.elements = res.elements.map(item => {
+    if (item.id === id) {
+      console.log('data', data)
 
-  res.flowData.set(id, node)
+      item.data = data
+    }
+    return item
+  })
   return res
 }
 

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

@@ -3,18 +3,21 @@ import { FlowContextProvider } from './context'
 import FlowGroph from './components/Graph'
 import { FlowTreeNode } from '@/components/Flow/src/type'
 import './index.less'
+import ToolBar from './components/Toolbar'
 
 type ApprovalFlowProps<T> = {
   defaultValue?: FlowTreeNode<T>[]
+  dataID: string
+  executorList: API.ExecutorItem[]
 }
 
-function ApprovalFlow<T>({ defaultValue }: ApprovalFlowProps<T>) {
+function ApprovalFlow<T>({ defaultValue, dataID, executorList }: ApprovalFlowProps<T>) {
   return (
     <div className="flow-container h-full w-full">
-      <FlowContextProvider initialProcess={defaultValue}>
+      <FlowContextProvider initialProcess={defaultValue} dataID={dataID} executorList={executorList}>
         <ReactFlowProvider>
           {/** 顶部工具栏 */}
-          {/* <ToolBar dataId={props.dataID} closeAnimateContent={props.closeAnimateContent} /> */}
+          <ToolBar />
           <div className="h-full w-full">
             <FlowGroph<T> />
           </div>

+ 1 - 1
src/pages/Business/Step/components/Flow/shared/transformer.ts

@@ -37,7 +37,7 @@ export const transformToApprovalProcess = (flowData: FlowTreeNode[]): API.Approv
   flowData
     .filter(item => ![NodeType.START, NodeType.END].includes(item.type))
     .forEach(item => {
-      const processItem: API.ApprovalProcess = { ID: item.id }
+      const processItem: API.ApprovalProcess = { ID: item.id, name: item.data?.name }
       if (item.type === NodeType.COMMON) {
         processItem.sectorType = SectorType.APPROVAL
         processItem.participantInfo = item.data?.participantInfo || {}

+ 22 - 6
src/pages/Business/Step/index.tsx

@@ -1,4 +1,5 @@
 import useDrawer from '@/components/Drawer'
+import { queryExecutorList } from '@/services/api/business'
 import { queryApprovalDetail } from '@/services/api/project'
 import consts from '@/utils/consts'
 import { PageContainer } from '@ant-design/pro-layout'
@@ -27,7 +28,9 @@ const Step: React.FC = () => {
     return { subjectID, businessType }
   }, [state.activeKey])
 
-  const handleMenuChange = (key: string) => {
+  const handleMenuChange = (key: string, option) => {
+    console.log(option)
+
     setState({ ...state, activeKey: key })
     actionRef.current?.reload()
     // const [subjectID, businessType] = key.split('_')
@@ -51,11 +54,24 @@ const Step: React.FC = () => {
     }
   ]
 
-  const handleBtnClick = () => {
-    drawer.open({
-      title: state.subjectName,
-      children: <ApprovalFlow defaultElements={transformToFlowNodes(state.process)} />
-    })
+  const handleBtnClick = async () => {
+    const [subjectID, businessType] = state.activeKey?.split('_')
+    const {
+      code = -1,
+      data: { items }
+    } = await queryExecutorList({ subjectID, businessType, pageSize: 214000 })
+    if (code === consts.RET_CODE.SUCCESS) {
+      drawer.open({
+        title: state.subjectName,
+        children: (
+          <ApprovalFlow
+            executorList={items}
+            dataID={state.activeKey}
+            defaultElements={transformToFlowNodes(state.process)}
+          />
+        )
+      })
+    }
   }
   return (
     <PageContainer title={false}>

+ 4 - 2
src/pages/Business/model.ts

@@ -62,10 +62,12 @@ const BusinessModel: BusinessModelType = {
     *queryExecutor({ payload }, { call, put }) {
       const response = yield call(queryExecutorList, payload)
       if (response?.code === consts.RET_CODE.SUCCESS) {
+        const { subjectID, businessType } = payload
         yield put({
-          type: 'save',
+          type: 'saveProcessMap',
           payload: {
-            processList: response.data.items
+            ID: `${subjectID}_${businessType}`,
+            process: response.data.items
           }
         })
       }

+ 2 - 10
src/pages/Institutions/Company/Detail/components/Staff.tsx

@@ -7,7 +7,6 @@ import dayjs from 'dayjs'
 import { queryAccountList } from '@/services/api/institution'
 import StaffDetail from '@/pages/Institutions/Staff/components/StaffDetail'
 import { BaseMenuEnum } from '@/pages/Schema/Base'
-import { generateColumns } from '@/utils/util'
 import { ModalType } from '@/utils/enum'
 import { BackstagePermission } from '@/enums/access'
 import { UserOutlined } from '@ant-design/icons'
@@ -24,14 +23,7 @@ type ListProps = ConnectProps & {
   companyName: string
   managerID: string
 }
-const Staff: React.FC<ListProps> = ({
-  schema,
-  dataID,
-  companyName,
-  dispatch,
-  memberTotal = 3,
-  managerID
-}) => {
+const Staff: React.FC<ListProps> = ({ dataID, companyName, dispatch, memberTotal = 3, managerID }) => {
   const tRef = useRef<ActionType>(null)
   const { validatePerm } = useAccess()
   const [drawer, drawerDom] = useDrawer()
@@ -214,7 +206,7 @@ const Staff: React.FC<ListProps> = ({
           y: document.body.clientHeight - (263 + wrapHeight)
         }}
         columnsState={columnsState}
-        columns={generateColumns(columns, schema)}
+        columns={columns}
         search={false}
         request={async (params, filter, sorter) => {
           const { code = -1, data: { items = [], total = 0, iCount = 0 } = {} } = await queryAccountList({

+ 2 - 0
src/services/api/business.ts

@@ -112,6 +112,8 @@ export async function queryMatterList(params: {
   subjectID: string
   businessType: BusinessType
   pageSize: number
+  /** category分类  matter事项 不传获得全部  */
+  matterType?: 'category' | 'matter'
 }) {
   return request('/Matter/list', {
     params

+ 7 - 0
src/services/api/subject.ts

@@ -23,3 +23,10 @@ export async function delSubject(params: API.DelSubjectParams) {
     params
   })
 }
+
+/** 获取步骤详情 */
+export async function queryParticipant(params: { subjectID: string; stepID: string }) {
+  return request('/approval/participant', {
+    params
+  })
+}

+ 3 - 20
src/services/api/typings.d.ts

@@ -285,24 +285,6 @@ declare namespace API {
     backstageLogo: string
   }
 
-  type ParticipantInfo = {
-    approvalWay: string
-    accounts: {
-      participantMode: string
-      ID: string
-      name: string
-      institutionID: string
-      configure: string[]
-    }[]
-  }
-
-  type ApprovalProcessItem = {
-    ID: string
-    name: string
-    participantInfo: ParticipantInfo
-    sectorType: 'approval' | 'condition'
-  }
-
   type MenuRoleItem = {
     id: string
     name: string
@@ -455,9 +437,10 @@ declare namespace API {
   type Executor = {
     ID?: string
     name?: string
-    executorType?: 'preset' | 'dynamic'
-    permission?: string[]
     configure?: string[]
+    /** 表示该执行者由动态配置出来的 */
+    dynamicID?: string
+    dynamicSteps?: { executorID: string; stepID: string }[]
   }
   type ParticipantInfo = {
     approvalWay?: string

+ 0 - 36
src/utils/util.ts

@@ -1,41 +1,5 @@
-import type { TableColumnType } from 'antd'
 import dayjs from 'dayjs'
 
-// eslint-disable-next-line no-promise-executor-return
-export const delay = (ms?: number | undefined) => new Promise(res => setTimeout(res, ms))
-
-/**
- *
- * @param c 表格columns
- * @param s schema
- * @param f 需要过滤掉的colmun
- * @returns
- */
-export function generateColumns(c: TableColumnType, s: any, f?: string | string[]) {
-  if (!s) return c
-  // 新的列
-  const nC = [...c]
-  if (s) {
-    const { properties } = s
-    const keys = Object.keys(properties)
-    keys.forEach(item => {
-      const isExist = c.some(column => column.dataIndex === item)
-      // 该列在columns中未定义且不需要过滤
-      if ((Array.isArray(f) && !f.includes(item)) || f !== item) {
-        return
-      }
-      if (!isExist) {
-        nC.splice(-2, 0, {
-          dataIndex: item,
-          title: properties[item].title,
-          hideInTable: properties[item].hidden
-        })
-      }
-    })
-  }
-  return nC
-}
-
 /**
  * 日期格式化
  * @param format - 格式