Преглед изворни кода

编辑作业流程调用检测节点接口与修改部分设计

pm пре 4 месеци
родитељ
комит
186adca831
3 измењених фајлова са 1156 додато и 256 уклоњено
  1. 55 0
      src/api/WorkJob.ts
  2. 950 223
      src/components/IsolationWork.tsx
  3. 151 33
      src/components/ProcessDesigner.tsx

+ 55 - 0
src/api/WorkJob.ts

@@ -117,10 +117,65 @@ export const workJobApi = {
     return axiosInstance.get<WorkJobVO>(`/iscs/workflow-work/selectWorkflowWorkById?id=${id}`);
   },
   
+  // 检查作业(完成流程设置时调用)
+  checkWorkById: (id: number) => {
+    return axiosInstance.get(`/iscs/workflow-work/checkWorkById?id=${id}`);
+  },
+  
   // 删除作业列表
   deleteWorkflowWorkList: (ids: number[]) => {
     // 将数组转换为逗号分隔的字符串作为URL参数
     const idsStr = ids.join(',');
     return axiosInstance.delete(`/iscs/workflow-work/deleteWorkflowWorkList?ids=${idsStr}`);
   },
+  
+  // 更新作业节点
+  updateWorkflowWorkNode: (data: UpdateWorkflowWorkNodeParam) => {
+    return axiosInstance.put('/iscs/workflow-work-node/updateWorkflowWorkNode', data);
+  },
 };
+
+// 更新作业节点参数
+export interface UpdateWorkflowWorkNodeParam {
+  nodeId: number; // 节点ID(必填)
+  nodeName: string; // 节点名称(必填)
+  formId?: number; // 业务表单ID
+  workerUserId?: number; // 负责人ID
+  isolationType?: string; // 隔离方式
+  isolationPoints?: string; // 隔离点
+  isolationNodeUuid?: string; // 隔离节点UUID(解除隔离节点使用)
+  nodeUserDOList?: WorkflowWorkNodeUserDO[]; // 节点设置人员信息列表(上锁人、共锁人)
+  [key: string]: any;
+}
+
+// 作业节点用户信息
+export interface WorkflowWorkNodeUserDO {
+  userId?: number;
+  userName?: string;
+  type?: string; // 用户类型:worker, locker, colocker等
+  [key: string]: any;
+}
+
+// 作业节点数据
+export interface WorkflowWorkNodeDO {
+  id?: number; // 节点ID
+  uuid?: string; // 节点UUID(用于匹配designContent中的节点)
+  nodeName?: string; // 节点名称
+  nodeIcon?: string; // 节点图标
+  type?: string; // 节点类型
+  data?: string; // 节点数据(JSON字符串)
+  position?: string; // 节点位置(JSON字符串)
+  parentUuid?: string; // 父节点UUID
+  childrenUuid?: string; // 子节点UUID
+  formId?: number; // 表单ID
+  isolationType?: string; // 隔离方式
+  isolationPoints?: string; // 隔离点
+  workerUserId?: number; // 负责人ID
+  workerGroupId?: number; // 作业组ID
+  lockPerson?: string; // 上锁人员(兼容旧字段)
+  notifyUserId?: number; // 通知人员ID
+  notifyUserGroupId?: number; // 通知用户组ID
+  notifyTime?: string; // 通知时间
+  nodeUserList?: WorkflowWorkNodeUserDO[]; // 节点用户列表(包含上锁人、共锁人等)
+  [key: string]: any;
+}

Разлика између датотеке није приказан због своје велике величине
+ 950 - 223
src/components/IsolationWork.tsx


+ 151 - 33
src/components/ProcessDesigner.tsx

@@ -18,6 +18,8 @@ import ReactFlow, {
   EdgeLabelRenderer,
   BaseEdge,
   getStraightPath,
+  getBezierPath,
+  getSmoothStepPath,
   EdgeTypes,
 } from 'reactflow';
 import 'reactflow/dist/style.css';
@@ -140,6 +142,7 @@ import { UserVO } from '../types';
 import { segregationPointApi, SegregationPointVO } from '../api/spm';
 import { getFormPage, getForm, FormVO } from '../api/bpm/form';
 import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
+import { dictDataApi, DictDataVO } from '../api/DictData';
 
 // 节点配置
 const nodeConfigs = [
@@ -587,6 +590,8 @@ const nodeTypes = {
 
 // 全局变量存储删除函数(用于边组件)
 let globalDeleteEdgeFn: ((id: string) => void) | null = null;
+// 全局变量存储 edges(用于边组件获取 type)
+let globalEdges: Edge[] = [];
 
 // 自定义边组件 - 定义在组件外部以确保稳定引用
 function CustomEdgeWithDelete({
@@ -598,15 +603,38 @@ function CustomEdgeWithDelete({
   selected,
   markerEnd,
   style,
+  type: propType,
 }: any) {
-  const [edgePath, labelX, labelY] = getStraightPath({
-    sourceX,
-    sourceY,
-    targetX,
-    targetY,
-  });
+  // 从全局 edges 中查找对应的边来获取 type
+  const edge = globalEdges.find(e => e.id === id);
+  const edgeType = edge?.type || propType || 'straight';
+  
+  // 根据边的类型选择路径生成函数
+  let edgePath: string;
+  let labelX: number;
+  let labelY: number;
+  
+  if (edgeType === 'smoothstep') {
+    // 使用 smoothstep 路径(平滑步进曲线,可以拐弯)
+    // 调整 borderRadius 参数使曲线更平滑
+    [edgePath, labelX, labelY] = getSmoothStepPath({
+      sourceX,
+      sourceY,
+      targetX,
+      targetY,
+      borderRadius: 15, // 圆角半径,使曲线更平滑
+    });
+  } else {
+    // 使用直线路径
+    [edgePath, labelX, labelY] = getStraightPath({
+      sourceX,
+      sourceY,
+      targetX,
+      targetY,
+    });
+  }
 
-  console.log('边组件渲染,ID:', id, 'selected:', selected, 'labelX:', labelX, 'labelY:', labelY);
+  console.log('边组件渲染,ID:', id, 'propType:', propType, 'edgeType:', edgeType, 'edge对象:', edge, 'sourceX:', sourceX, 'sourceY:', sourceY, 'targetX:', targetX, 'targetY:', targetY, 'selected:', selected);
 
   return (
     <>
@@ -663,6 +691,7 @@ function CustomEdgeWithDelete({
 const edgeTypes: EdgeTypes = {
   straight: CustomEdgeWithDelete,
   default: CustomEdgeWithDelete,
+  smoothstep: CustomEdgeWithDelete,
 };
 
 
@@ -726,6 +755,8 @@ export default function ProcessDesigner() {
   const [isolationPoints, setIsolationPoints] = useState<SegregationPointVO[]>([]);
   // 表单列表
   const [formList, setFormList] = useState<FormVO[]>([]);
+  // 隔离方式字典数据
+  const [isolationMethodDictList, setIsolationMethodDictList] = useState<DictDataVO[]>([]);
 
   const loadNodeCache = useCallback((nodeId: string) => {
     try {
@@ -750,6 +781,23 @@ export default function ProcessDesigner() {
     console.log('Edges 状态更新:', edges.length, edges);
   }, [edges]);
 
+  // 获取隔离方式字典数据
+  const getIsolationMethodDictList = async () => {
+    try {
+      const response = await dictDataApi.getDictDataPage({
+        pageNo: 1,
+        pageSize: -1,
+        dictType: 'isolation_method',
+      });
+      const data = (response as any)?.data || response;
+      const list = data?.list || [];
+      setIsolationMethodDictList(list);
+    } catch (error: any) {
+      console.error('获取隔离方式字典失败:', error);
+      setIsolationMethodDictList([]);
+    }
+  };
+
   // 加载角色用户列表和隔离点列表
   useEffect(() => {
     const loadRoleUsers = async () => {
@@ -789,6 +837,7 @@ export default function ProcessDesigner() {
     loadRoleUsers();
     loadIsolationPoints();
     loadFormList();
+    getIsolationMethodDictList();
   }, []);
 
   // 节点配置状态
@@ -1072,6 +1121,7 @@ export default function ProcessDesigner() {
             ...updatedEdges[existingEdgeIndex],
             sourceHandle,
             targetHandle,
+            type: 'straight', // 统一使用直线
           };
           // 保存历史
           const newHistory = history.slice(0, historyIndex + 1);
@@ -1081,6 +1131,7 @@ export default function ProcessDesigner() {
           return updatedEdges;
         }
         
+        // 统一使用直线连接
         const edgeId = `edge-${params.source}-${sourceHandle || 'default'}-${params.target}-${targetHandle || 'default'}-${Date.now()}`;
         const newEdge: Edge = {
           id: edgeId,
@@ -1090,11 +1141,14 @@ export default function ProcessDesigner() {
           targetHandle: targetHandle || undefined,
           animated: false,
           style: { strokeWidth: 2, stroke: '#94a3b8' },
-          type: 'straight',
+          type: 'straight', // 统一使用直线
         };
+        
+        console.log('创建新边,类型:', edgeType, '边对象:', newEdge);
         // 直接添加到数组,不使用 addEdge(因为 addEdge 可能会过滤掉某些连接)
         const newEdges = [...eds, newEdge];
         console.log('新连接已添加:', newEdge);
+        console.log('新边的类型:', newEdge.type);
         console.log('当前所有连接数量:', newEdges.length);
         // 保存历史
         const newHistory = history.slice(0, historyIndex + 1);
@@ -1232,6 +1286,11 @@ export default function ProcessDesigner() {
       notificationPerson: source.notificationPerson || '',
       notificationTime: source.notificationTime || '',
     });
+    
+    // 如果是创建作业节点,且当前tab是"提交表单",自动切换到"节点信息"tab
+    if (node.data?.type === 'createJob' && activeTabKey === 'form') {
+      setActiveTabKey('info');
+    }
   }, [loadNodeCache, nodes, setEdges]);
 
   // 画布点击处理(取消选择)
@@ -1257,7 +1316,7 @@ export default function ProcessDesigner() {
     setEdges((eds) => {
       const updatedEdges = eds.map((e) => ({
         ...e,
-        type: 'straight', // 强制设置为 straight 类型
+        type: edge.type || 'straight', // 保持原有类型
         selected: e.id === edge.id,
       }));
       const clickedEdge = updatedEdges.find(e => e.id === edge.id);
@@ -1282,28 +1341,36 @@ export default function ProcessDesigner() {
     setSelectedEdge(null);
   }, [history, historyIndex, nodes]);
 
-  // 更新全局删除函数
+  // 更新全局删除函数和 edges
   useEffect(() => {
     globalDeleteEdgeFn = handleDeleteEdge;
+    globalEdges = edges;
     return () => {
       globalDeleteEdgeFn = null;
+      globalEdges = [];
     };
-  }, [handleDeleteEdge]);
+  }, [handleDeleteEdge, edges]);
 
-  // 确保所有边都使用 'straight' 类型(用于自定义边组件)
+  // 确保所有边都使用直线类型
   useEffect(() => {
     setEdges((eds) => {
-      const needsUpdate = eds.some(e => e.type !== 'straight');
-      if (needsUpdate) {
-        console.log('统一设置所有边的类型为 straight');
-        return eds.map(e => ({
-          ...e,
-          type: 'straight',
-        }));
+      const updatedEdges = eds.map(edge => {
+        // 如果类型不是 straight,统一改为 straight
+        if (edge.type !== 'straight') {
+          return { ...edge, type: 'straight' };
+        }
+        return edge;
+      });
+      
+      // 检查是否有更新
+      const hasUpdate = updatedEdges.some((e, index) => e.type !== eds[index]?.type);
+      if (hasUpdate) {
+        return updatedEdges;
       }
+      
       return eds;
     });
-  }, []); // 只在组件挂载时执行一次
+  }, [setEdges]);
 
   // 删除节点
   const handleDeleteNode = useCallback((nodeId?: string) => {
@@ -2023,14 +2090,14 @@ export default function ProcessDesigner() {
             connectionMode={ConnectionMode.Loose}
             defaultEdgeOptions={{
               style: { strokeWidth: 2, stroke: '#94a3b8' },
-              type: 'straight',
+              // 不设置默认type,让onConnect中的逻辑决定
             }}
             edgesUpdatable={true}
             edgesFocusable={true}
             edgeUpdaterRadius={10}
           >
             <Controls className="!bg-white !border !border-gray-200 !rounded-lg !shadow-md" />
-            <Background variant={BackgroundVariant.Dots} gap={16} size={1} color="#e5e7eb" />
+            <Background variant={BackgroundVariant.Lines} gap={16} size={1} color="#e5e7eb" />
             <MiniMap
               nodeColor={(node) => {
                 const config = nodeConfigs.find(c => c.type === node.type);
@@ -2080,7 +2147,14 @@ export default function ProcessDesigner() {
               <div className="flex-1 p-4 overflow-y-auto">
                 <Tabs
                   activeKey={activeTabKey}
-                  onChange={setActiveTabKey}
+                  onChange={(key) => {
+                    // 如果是创建作业节点,不允许切换到"提交表单"tab
+                    if (key === 'form' && selectedNode.data?.type === 'createJob') {
+                      setActiveTabKey('info');
+                    } else {
+                      setActiveTabKey(key);
+                    }
+                  }}
                   className="[&_.ant-tabs-tab]:!px-4 [&_.ant-tabs-tab]:!py-2.5 [&_.ant-tabs-tab-active]:!text-blue-600 [&_.ant-tabs-tab-active]:!font-semibold [&_.ant-tabs-ink-bar]:!bg-blue-600 [&_.ant-tabs-tab]:!text-gray-600 [&_.ant-tabs-tab]:!border-b-2 [&_.ant-tabs-tab]:!border-transparent [&_.ant-tabs-tab:hover]:!text-blue-500"
                   items={[
                     {
@@ -2408,13 +2482,44 @@ export default function ProcessDesigner() {
                                     className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                     allowClear
                                   >
-                                    {nodes
-                                      .filter(n => n.data?.type === 'isolation')
-                                      .map(node => (
+                                    {(() => {
+                                      // 找到所有与当前"解除隔离"节点连线的"隔离/方案"节点
+                                      // 连线方向:隔离节点(source) -> 解除隔离节点(target)
+                                      const currentNodeId = selectedNode?.id;
+                                      
+                                      // 找到所有以当前节点为 target 的 edge(隔离节点指向解除隔离节点)
+                                      const incomingEdges = edges.filter(edge => 
+                                        String(edge.target) === String(currentNodeId)
+                                      );
+                                      
+                                      // 也检查反向连线(解除隔离节点指向隔离节点,虽然不常见但兼容)
+                                      const outgoingEdges = edges.filter(edge => 
+                                        String(edge.source) === String(currentNodeId)
+                                      );
+                                      
+                                      // 收集所有已连线的隔离节点ID
+                                      const connectedIsolationNodeIds = new Set<string>();
+                                      incomingEdges.forEach(edge => {
+                                        connectedIsolationNodeIds.add(String(edge.source));
+                                      });
+                                      outgoingEdges.forEach(edge => {
+                                        connectedIsolationNodeIds.add(String(edge.target));
+                                      });
+                                      
+                                      // 获取所有隔离节点
+                                      const isolationNodes = nodes.filter(n => n.data?.type === 'isolation');
+                                      
+                                      // 如果有连线,只显示已连线的隔离节点;否则显示所有隔离节点(兼容旧数据)
+                                      const nodesToShow = connectedIsolationNodeIds.size > 0
+                                        ? isolationNodes.filter(n => connectedIsolationNodeIds.has(String(n.id)))
+                                        : isolationNodes;
+                                      
+                                      return nodesToShow.map(node => (
                                         <Select.Option key={node.id} value={node.id}>
                                           {node.data?.label || nodeConfigs.find(c => c.type === 'isolation')?.label || '隔离/方案'}
                                         </Select.Option>
-                                      ))}
+                                      ));
+                                    })()}
                                   </Select>
                                 </div>
                               )}
@@ -2434,9 +2539,11 @@ export default function ProcessDesigner() {
                                   allowClear
                                   disabled={selectedNode.data?.type === 'releaseIsolation'}
                                 >
-                                  <Select.Option value="blindPlate">盲板</Select.Option>
-                                  <Select.Option value="lockTag">上锁挂牌</Select.Option>
-                                  <Select.Option value="remove">拆除</Select.Option>
+                                  {isolationMethodDictList.map((item) => (
+                                    <Select.Option key={item.id} value={item.value}>
+                                      {item.label}
+                                    </Select.Option>
+                                  ))}
                                 </Select>
                               </div>
 
@@ -2465,7 +2572,8 @@ export default function ProcessDesigner() {
                               )}
 
                               {/* 盲板和拆除:显示负责人 */}
-                              {(nodeConfig.isolationMethod === 'blindPlate' || nodeConfig.isolationMethod === 'remove') && (
+                              {/* 字典值:0=盲板,1=上锁挂牌,2=拆除 */}
+                              {(nodeConfig.isolationMethod === '0' || nodeConfig.isolationMethod === '2') && (
                                 <div>
                                   <label className="block text-sm font-medium text-gray-700 mb-2">
                                     负责人
@@ -2491,7 +2599,7 @@ export default function ProcessDesigner() {
                               )}
 
                               {/* 上锁挂牌:显示上锁人和共锁人 */}
-                              {nodeConfig.isolationMethod === 'lockTag' && (
+                              {nodeConfig.isolationMethod === '1' && (
                                 <>
                                   <div>
                                     <label className="block text-sm font-medium text-gray-700 mb-2">
@@ -2820,7 +2928,17 @@ export default function ProcessDesigner() {
                         </div>
                       ),
                     },
-                  ].filter(item => item.key !== 'notification')}
+                  ].filter(item => {
+                    // 过滤掉通知消息tab
+                    if (item.key === 'notification') {
+                      return false;
+                    }
+                    // 如果是创建作业节点,过滤掉提交表单tab
+                    if (item.key === 'form' && selectedNode.data?.type === 'createJob') {
+                      return false;
+                    }
+                    return true;
+                  })}
                 />
               </div>
             </div>

Неке датотеке нису приказане због велике количине промена