|
|
@@ -1,4 +1,4 @@
|
|
|
-import React, { useState, useEffect, useCallback, useRef } from 'react';
|
|
|
+import React, { useState, useEffect, useCallback, useRef, useMemo } from 'react';
|
|
|
import { useNavigate } from 'react-router-dom';
|
|
|
import { Plus, Search, Edit2, Trash2, MoreVertical, FileText, Eye, Play, CheckCircle, RefreshCw, Workflow } from 'lucide-react';
|
|
|
import { Button, Input, Space, Select, Table as AntdTable, message, Modal, Form, Row, Col, Tabs, Radio, DatePicker, Checkbox, Tooltip } from 'antd';
|
|
|
@@ -14,7 +14,6 @@ import ReactFlow, {
|
|
|
useEdgesState,
|
|
|
Controls,
|
|
|
Background,
|
|
|
- MiniMap,
|
|
|
NodeTypes,
|
|
|
BackgroundVariant,
|
|
|
Handle,
|
|
|
@@ -459,8 +458,20 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
// 节点保存状态缓存(Map<nodeId, boolean>)- 用于记录当前作业中哪些节点已保存
|
|
|
const [nodeSavedStatusCache, setNodeSavedStatusCache] = useState<Map<string, boolean>>(new Map());
|
|
|
|
|
|
- // 节点类型映射(在组件内部定义以访问 nodeSavedStatusCache)
|
|
|
- const nodeTypes: NodeTypes = {
|
|
|
+ // 计算是否有未保存的节点(用于控制流程管理tab的下一步按钮)
|
|
|
+ const hasUnsavedNodes = useMemo(() => {
|
|
|
+ if (workflowNodes.length === 0) {
|
|
|
+ return false; // 如果没有节点,认为没有未保存的节点
|
|
|
+ }
|
|
|
+ // 检查所有节点是否都已保存
|
|
|
+ return workflowNodes.some(node => {
|
|
|
+ const isSaved = nodeSavedStatusCache.get(node.id) || false;
|
|
|
+ return !isSaved;
|
|
|
+ });
|
|
|
+ }, [workflowNodes, nodeSavedStatusCache]);
|
|
|
+
|
|
|
+ // 节点类型映射(使用 useMemo 避免每次渲染都创建新对象)
|
|
|
+ const nodeTypes: NodeTypes = useMemo(() => ({
|
|
|
createJob: (props: any) => <CustomNode {...props} nodeSavedStatusCache={nodeSavedStatusCache} />,
|
|
|
confirm: (props: any) => <CustomNode {...props} nodeSavedStatusCache={nodeSavedStatusCache} />,
|
|
|
review: (props: any) => <CustomNode {...props} nodeSavedStatusCache={nodeSavedStatusCache} />,
|
|
|
@@ -469,7 +480,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
releaseIsolation: (props: any) => <CustomNode {...props} nodeSavedStatusCache={nodeSavedStatusCache} />,
|
|
|
returnLock: (props: any) => <CustomNode {...props} nodeSavedStatusCache={nodeSavedStatusCache} />,
|
|
|
complete: (props: any) => <CustomNode {...props} nodeSavedStatusCache={nodeSavedStatusCache} />,
|
|
|
- };
|
|
|
+ }), [nodeSavedStatusCache]);
|
|
|
|
|
|
// 角色用户列表和表单列表
|
|
|
const [workflowDrawerUsers, setWorkflowDrawerUsers] = useState<UserVO[]>([]);
|
|
|
@@ -1332,11 +1343,357 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ // 初始化节点状态:调用 checkWorkById 和 selectWorkflowWorkById 接口
|
|
|
+ const initializeNodeStates = async () => {
|
|
|
+ if (!workflowWorkId) {
|
|
|
+ console.warn('workflowWorkId 不存在,无法初始化节点状态');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 1. 调用 checkWorkById 获取未保存的节点列表
|
|
|
+ const checkResponse = await workJobApi.checkWorkById(workflowWorkId);
|
|
|
+ const checkData = (checkResponse as any)?.data || checkResponse;
|
|
|
+ const unsavedNodeMessages = Array.isArray(checkData) ? checkData : (checkData ? [checkData] : []);
|
|
|
+
|
|
|
+ // 2. 调用 selectWorkflowWorkById 获取所有节点数据
|
|
|
+ const detailResponse = await workJobApi.selectWorkflowWorkById(workflowWorkId);
|
|
|
+ const detail = detailResponse as any;
|
|
|
+
|
|
|
+ if (detail.workflowWorkNodeDOList && Array.isArray(detail.workflowWorkNodeDOList)) {
|
|
|
+ // 更新 workflowWorkNodeDOList
|
|
|
+ setWorkflowWorkNodeDOList(detail.workflowWorkNodeDOList);
|
|
|
+
|
|
|
+ // 3. 根据接口数据初始化缓存和节点状态
|
|
|
+ // 创建新的缓存和状态,不使用旧的(因为我们要用接口数据覆盖)
|
|
|
+ const newCache = new Map<string, any>();
|
|
|
+ const newCompleted = new Set<string>();
|
|
|
+ const newSavedStatusCache = new Map<string, boolean>();
|
|
|
+
|
|
|
+ // 遍历所有节点,初始化缓存
|
|
|
+ detail.workflowWorkNodeDOList.forEach((nodeDO: WorkflowWorkNodeDO) => {
|
|
|
+ if (!nodeDO.uuid) return;
|
|
|
+
|
|
|
+ // 解析节点数据
|
|
|
+ let nodeData: any = {};
|
|
|
+ if (nodeDO.data) {
|
|
|
+ try {
|
|
|
+ nodeData = typeof nodeDO.data === 'string' ? JSON.parse(nodeDO.data) : nodeDO.data;
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析节点data失败:', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建节点配置
|
|
|
+ const config = nodeConfigs.find(c => c.type === (nodeDO.type || nodeData.type));
|
|
|
+ const nodeConfig = {
|
|
|
+ nodeName: nodeDO.nodeName || nodeData.label || config?.label || '',
|
|
|
+ nodeIcon: nodeDO.nodeIcon || nodeData.icon || '',
|
|
|
+ responsible: (nodeDO.workerUserId !== null && nodeDO.workerUserId !== undefined && nodeDO.workerUserId !== 0)
|
|
|
+ ? (typeof nodeDO.workerUserId === 'number' ? nodeDO.workerUserId : Number(nodeDO.workerUserId))
|
|
|
+ : (nodeData.workerUserId && nodeData.workerUserId !== '' && nodeData.workerUserId !== '0')
|
|
|
+ ? (typeof nodeData.workerUserId === 'number' ? nodeData.workerUserId : Number(nodeData.workerUserId))
|
|
|
+ : undefined,
|
|
|
+ remark: nodeData.remark || '',
|
|
|
+ submitForm: nodeDO.formId ? (typeof nodeDO.formId === 'number' ? nodeDO.formId : Number(nodeDO.formId)) : (nodeData.submitForm ? (typeof nodeData.submitForm === 'number' ? nodeData.submitForm : Number(nodeData.submitForm)) : undefined),
|
|
|
+ isolationType: (nodeDO.isolationType !== null && nodeDO.isolationType !== undefined) ? String(nodeDO.isolationType) : (nodeData.isolationType || ''),
|
|
|
+ isolationPoints: nodeDO.isolationPoints ? (typeof nodeDO.isolationPoints === 'string' ? JSON.parse(nodeDO.isolationPoints) : nodeDO.isolationPoints) : (nodeData.isolationPoints || []),
|
|
|
+ isolationNode: nodeData.isolationNode || [],
|
|
|
+ isolationNodeUuid: nodeDO.isolationNodeUuid || nodeData.isolationNodeUuid || '',
|
|
|
+ lockPerson: (() => {
|
|
|
+ // 优先从 nodeUserList 中提取
|
|
|
+ let lockPersonId: number | undefined = undefined;
|
|
|
+ if (nodeDO.nodeUserList && Array.isArray(nodeDO.nodeUserList)) {
|
|
|
+ const lockerUser = nodeDO.nodeUserList.find((user: any) => user.type === 'jtlocker');
|
|
|
+ if (lockerUser?.userId) {
|
|
|
+ lockPersonId = typeof lockerUser.userId === 'number' ? lockerUser.userId : Number(lockerUser.userId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return lockPersonId || (nodeData.lockPerson ? (typeof nodeData.lockPerson === 'number' ? nodeData.lockPerson : Number(nodeData.lockPerson)) : undefined);
|
|
|
+ })(),
|
|
|
+ coLockPersons: (() => {
|
|
|
+ // 优先从 nodeUserList 中提取
|
|
|
+ const coLockPersonIds: number[] = [];
|
|
|
+ if (nodeDO.nodeUserList && Array.isArray(nodeDO.nodeUserList)) {
|
|
|
+ nodeDO.nodeUserList.forEach((user: any) => {
|
|
|
+ if (user.type === 'jtcolocker' && user.userId) {
|
|
|
+ coLockPersonIds.push(typeof user.userId === 'number' ? user.userId : Number(user.userId));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return coLockPersonIds.length > 0 ? coLockPersonIds : (nodeData.coLockPersons && Array.isArray(nodeData.coLockPersons) ? nodeData.coLockPersons.map((id: any) => typeof id === 'number' ? id : Number(id)) : []);
|
|
|
+ })(),
|
|
|
+ notificationMethods: nodeData.notificationMethods || {
|
|
|
+ sms: false,
|
|
|
+ message: false,
|
|
|
+ email: false,
|
|
|
+ app: false,
|
|
|
+ },
|
|
|
+ notificationPerson: nodeData.notificationPerson || '',
|
|
|
+ notificationTime: nodeDO.notifyTime || nodeData.notificationTime || '',
|
|
|
+ };
|
|
|
+
|
|
|
+ // 将节点配置存入缓存
|
|
|
+ newCache.set(nodeDO.uuid, nodeConfig);
|
|
|
+
|
|
|
+ // 检查节点是否已保存(如果不在未保存列表中,说明已保存)
|
|
|
+ // 通过检查未保存消息中是否包含该节点的信息来判断
|
|
|
+ const isNodeUnsaved = unsavedNodeMessages.some((msg: string) => {
|
|
|
+ // 如果消息中包含节点名称或UUID,说明该节点未保存
|
|
|
+ return msg.includes(nodeDO.nodeName || '') || msg.includes(nodeDO.uuid || '');
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!isNodeUnsaved) {
|
|
|
+ // 节点已保存,标记为已完成
|
|
|
+ newCompleted.add(nodeDO.uuid);
|
|
|
+ newSavedStatusCache.set(nodeDO.uuid, true);
|
|
|
+ } else {
|
|
|
+ // 节点未保存
|
|
|
+ newSavedStatusCache.set(nodeDO.uuid, false);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 更新缓存和状态
|
|
|
+ setWorkflowNodeConfigCache(newCache);
|
|
|
+ setCompletedNodeIds(newCompleted);
|
|
|
+ setNodeSavedStatusCache(newSavedStatusCache);
|
|
|
+
|
|
|
+ console.log('节点状态初始化完成:', {
|
|
|
+ totalNodes: detail.workflowWorkNodeDOList.length,
|
|
|
+ completedNodes: newCompleted.size,
|
|
|
+ unsavedNodes: unsavedNodeMessages.length,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 找到第一个未保存的节点并自动选中(按照流程顺序)
|
|
|
+ // 等待 workflowNodes 渲染完成后再选中
|
|
|
+ // 使用 setState 的回调形式确保获取最新的 workflowNodes
|
|
|
+ setTimeout(() => {
|
|
|
+ setWorkflowNodes((currentNodes) => {
|
|
|
+ // 使用 getNextIncompleteNode 的逻辑,但基于 nodeSavedStatusCache 来判断
|
|
|
+ // 找到第一个未保存的节点(按照流程顺序)
|
|
|
+ let firstUnsavedNode: Node | null = null;
|
|
|
+
|
|
|
+ if (workflowJson && workflowJson.adjacency && currentNodes.length > 0) {
|
|
|
+ const adjacency = workflowJson.adjacency;
|
|
|
+ const nodeMap = new Map<string, Node>();
|
|
|
+
|
|
|
+ // 初始化节点映射
|
|
|
+ currentNodes.forEach(node => {
|
|
|
+ nodeMap.set(node.id, node);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 辅助函数:检查节点的所有父节点是否都已保存
|
|
|
+ const areAllParentsSaved = (nodeId: string): boolean => {
|
|
|
+ const nodeInfo = adjacency[nodeId];
|
|
|
+ if (!nodeInfo || !nodeInfo.parentUuid || nodeInfo.parentUuid.length === 0) {
|
|
|
+ return true; // 没有父节点,可以执行
|
|
|
+ }
|
|
|
+ return nodeInfo.parentUuid.every((parentId: string) => {
|
|
|
+ const isParentSaved = newSavedStatusCache.get(parentId);
|
|
|
+ return isParentSaved === true; // 父节点已保存
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 辅助函数:获取节点的第一个未保存的子节点
|
|
|
+ const getFirstUnsavedChild = (nodeId: string): string | null => {
|
|
|
+ const nodeInfo = adjacency[nodeId];
|
|
|
+ if (!nodeInfo || !nodeInfo.childrenUuid || nodeInfo.childrenUuid.length === 0) {
|
|
|
+ return null; // 没有子节点
|
|
|
+ }
|
|
|
+
|
|
|
+ // 按顺序查找第一个未保存的子节点
|
|
|
+ for (const childId of nodeInfo.childrenUuid) {
|
|
|
+ const isChildSaved = newSavedStatusCache.get(childId);
|
|
|
+ if (isChildSaved === false && areAllParentsSaved(childId)) {
|
|
|
+ return childId;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return null;
|
|
|
+ };
|
|
|
+
|
|
|
+ // BFS遍历查找第一个未保存的节点
|
|
|
+ const queue: string[] = [];
|
|
|
+ const visited = new Set<string>();
|
|
|
+
|
|
|
+ // 找到所有开始节点(parentUuid 为空的节点)
|
|
|
+ for (const nodeId in adjacency) {
|
|
|
+ const nodeInfo = adjacency[nodeId];
|
|
|
+ if (!nodeInfo.parentUuid || nodeInfo.parentUuid.length === 0) {
|
|
|
+ const isSaved = newSavedStatusCache.get(nodeId);
|
|
|
+ if (isSaved === false) {
|
|
|
+ // 如果开始节点未保存,直接返回
|
|
|
+ firstUnsavedNode = nodeMap.get(nodeId) || null;
|
|
|
+ if (firstUnsavedNode) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果开始节点已保存,将其子节点加入队列
|
|
|
+ if (nodeInfo.childrenUuid && nodeInfo.childrenUuid.length > 0) {
|
|
|
+ nodeInfo.childrenUuid.forEach((childId: string) => {
|
|
|
+ if (!visited.has(childId)) {
|
|
|
+ queue.push(childId);
|
|
|
+ visited.add(childId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果开始节点都已保存,BFS遍历查找下一个未保存的节点
|
|
|
+ if (!firstUnsavedNode) {
|
|
|
+ while (queue.length > 0) {
|
|
|
+ const currentNodeId = queue.shift()!;
|
|
|
+
|
|
|
+ const isSaved = newSavedStatusCache.get(currentNodeId);
|
|
|
+ if (isSaved === false && areAllParentsSaved(currentNodeId)) {
|
|
|
+ firstUnsavedNode = nodeMap.get(currentNodeId) || null;
|
|
|
+ if (firstUnsavedNode) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果当前节点已保存,将其子节点加入队列
|
|
|
+ const nodeInfo = adjacency[currentNodeId];
|
|
|
+ if (nodeInfo && nodeInfo.childrenUuid && nodeInfo.childrenUuid.length > 0) {
|
|
|
+ nodeInfo.childrenUuid.forEach((childId: string) => {
|
|
|
+ if (!visited.has(childId)) {
|
|
|
+ queue.push(childId);
|
|
|
+ visited.add(childId);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 如果没有 adjacency 信息,回退到简单查找
|
|
|
+ firstUnsavedNode = currentNodes.find(node => {
|
|
|
+ const isSaved = newSavedStatusCache.get(node.id);
|
|
|
+ return isSaved === false; // 明确为 false 表示未保存
|
|
|
+ }) || null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!firstUnsavedNode) {
|
|
|
+ console.log('没有找到未保存的节点,所有节点都已保存');
|
|
|
+ return currentNodes;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选中第一个未保存的节点
|
|
|
+ setSelectedWorkflowNode(firstUnsavedNode);
|
|
|
+
|
|
|
+ // 从缓存中获取节点配置
|
|
|
+ const cachedConfig = newCache.get(firstUnsavedNode.id);
|
|
|
+ if (cachedConfig) {
|
|
|
+ setWorkflowNodeConfig(cachedConfig);
|
|
|
+ } else {
|
|
|
+ // 如果缓存中没有,从 nodeDO 中获取
|
|
|
+ const nodeDO = detail.workflowWorkNodeDOList.find(item => item.uuid === firstUnsavedNode.id);
|
|
|
+ if (nodeDO) {
|
|
|
+ let nodeData: any = {};
|
|
|
+ if (nodeDO.data) {
|
|
|
+ try {
|
|
|
+ nodeData = typeof nodeDO.data === 'string' ? JSON.parse(nodeDO.data) : nodeDO.data;
|
|
|
+ } catch (e) {
|
|
|
+ console.error('解析节点data失败:', e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ const config = nodeConfigs.find(c => c.type === (nodeDO.type || nodeData.type));
|
|
|
+ setWorkflowNodeConfig({
|
|
|
+ nodeName: nodeDO.nodeName || nodeData.label || config?.label || '',
|
|
|
+ nodeIcon: nodeDO.nodeIcon || nodeData.icon || '',
|
|
|
+ responsible: (nodeDO.workerUserId !== null && nodeDO.workerUserId !== undefined && nodeDO.workerUserId !== 0)
|
|
|
+ ? (typeof nodeDO.workerUserId === 'number' ? nodeDO.workerUserId : Number(nodeDO.workerUserId))
|
|
|
+ : (nodeData.workerUserId && nodeData.workerUserId !== '' && nodeData.workerUserId !== '0')
|
|
|
+ ? (typeof nodeData.workerUserId === 'number' ? nodeData.workerUserId : Number(nodeData.workerUserId))
|
|
|
+ : undefined,
|
|
|
+ remark: nodeData.remark || '',
|
|
|
+ submitForm: nodeDO.formId ? (typeof nodeDO.formId === 'number' ? nodeDO.formId : Number(nodeDO.formId)) : (nodeData.submitForm ? (typeof nodeData.submitForm === 'number' ? nodeData.submitForm : Number(nodeData.submitForm)) : undefined),
|
|
|
+ isolationType: (nodeDO.isolationType !== null && nodeDO.isolationType !== undefined) ? String(nodeDO.isolationType) : (nodeData.isolationType || ''),
|
|
|
+ isolationPoints: nodeDO.isolationPoints ? (typeof nodeDO.isolationPoints === 'string' ? JSON.parse(nodeDO.isolationPoints) : nodeDO.isolationPoints) : (nodeData.isolationPoints || []),
|
|
|
+ isolationNode: nodeData.isolationNode || [],
|
|
|
+ isolationNodeUuid: nodeDO.isolationNodeUuid || nodeData.isolationNodeUuid || '',
|
|
|
+ lockPerson: (() => {
|
|
|
+ let lockPersonId: number | undefined = undefined;
|
|
|
+ if (nodeDO.nodeUserList && Array.isArray(nodeDO.nodeUserList)) {
|
|
|
+ const lockerUser = nodeDO.nodeUserList.find((user: any) => user.type === 'jtlocker');
|
|
|
+ if (lockerUser?.userId) {
|
|
|
+ lockPersonId = typeof lockerUser.userId === 'number' ? lockerUser.userId : Number(lockerUser.userId);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return lockPersonId || (nodeData.lockPerson ? (typeof nodeData.lockPerson === 'number' ? nodeData.lockPerson : Number(nodeData.lockPerson)) : undefined);
|
|
|
+ })(),
|
|
|
+ coLockPersons: (() => {
|
|
|
+ const coLockPersonIds: number[] = [];
|
|
|
+ if (nodeDO.nodeUserList && Array.isArray(nodeDO.nodeUserList)) {
|
|
|
+ nodeDO.nodeUserList.forEach((user: any) => {
|
|
|
+ if (user.type === 'jtcolocker' && user.userId) {
|
|
|
+ coLockPersonIds.push(typeof user.userId === 'number' ? user.userId : Number(user.userId));
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return coLockPersonIds.length > 0 ? coLockPersonIds : (nodeData.coLockPersons && Array.isArray(nodeData.coLockPersons) ? nodeData.coLockPersons.map((id: any) => typeof id === 'number' ? id : Number(id)) : []);
|
|
|
+ })(),
|
|
|
+ notificationMethods: nodeData.notificationMethods || {
|
|
|
+ sms: false,
|
|
|
+ message: false,
|
|
|
+ email: false,
|
|
|
+ app: false,
|
|
|
+ },
|
|
|
+ notificationPerson: nodeData.notificationPerson || '',
|
|
|
+ notificationTime: nodeDO.notifyTime || nodeData.notificationTime || '',
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 如果既没有缓存也没有 nodeDO,使用节点原始数据
|
|
|
+ const source = firstUnsavedNode.data || {};
|
|
|
+ const config = nodeConfigs.find(c => c.type === source.type);
|
|
|
+ setWorkflowNodeConfig({
|
|
|
+ nodeName: source.label || config?.label || '',
|
|
|
+ nodeIcon: source.icon || '',
|
|
|
+ responsible: source.workerUserId ? (typeof source.workerUserId === 'number' ? source.workerUserId : Number(source.workerUserId)) : undefined,
|
|
|
+ remark: source.remark || '',
|
|
|
+ submitForm: source.submitForm ? (typeof source.submitForm === 'number' ? source.submitForm : Number(source.submitForm)) : undefined,
|
|
|
+ isolationType: source.isolationType || '',
|
|
|
+ isolationPoints: source.isolationPoints || [],
|
|
|
+ isolationNode: source.isolationNode || [],
|
|
|
+ isolationNodeUuid: source.isolationNodeUuid || '',
|
|
|
+ lockPerson: source.lockPerson ? (typeof source.lockPerson === 'number' ? source.lockPerson : Number(source.lockPerson)) : undefined,
|
|
|
+ coLockPersons: source.coLockPersons && Array.isArray(source.coLockPersons) ? source.coLockPersons.map((id: any) => typeof id === 'number' ? id : Number(id)) : [],
|
|
|
+ notificationMethods: source.notificationMethods || {
|
|
|
+ sms: false,
|
|
|
+ message: false,
|
|
|
+ email: false,
|
|
|
+ app: false,
|
|
|
+ },
|
|
|
+ notificationPerson: source.notificationPerson || '',
|
|
|
+ notificationTime: source.notificationTime || '',
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log('自动选中第一个未保存的节点:', firstUnsavedNode.id);
|
|
|
+
|
|
|
+ // 高亮选中的节点
|
|
|
+ return currentNodes.map((node) =>
|
|
|
+ node.id === firstUnsavedNode.id
|
|
|
+ ? { ...node, selected: true }
|
|
|
+ : { ...node, selected: false }
|
|
|
+ );
|
|
|
+ });
|
|
|
+ }, 300); // 延迟300ms,确保 workflowNodes 已经渲染完成
|
|
|
+ }
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('初始化节点状态失败:', error);
|
|
|
+ message.error('加载节点状态失败: ' + (error?.message || '未知错误'));
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
loadRoleUsers();
|
|
|
loadIsolationPoints();
|
|
|
loadFormList();
|
|
|
+ initializeNodeStates();
|
|
|
}
|
|
|
- }, [workJobStep]);
|
|
|
+ }, [workJobStep, workflowWorkId]);
|
|
|
|
|
|
// 验证节点配置是否完整
|
|
|
const validateNodeConfig = useCallback((config: any, nodeType: string): boolean => {
|
|
|
@@ -1515,6 +1872,8 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
|
|
|
// 节点点击事件
|
|
|
const onWorkflowNodeClick = useCallback((event: React.MouseEvent, node: Node) => {
|
|
|
+ console.log('点击节点:', node.id);
|
|
|
+
|
|
|
// 更新节点选中状态
|
|
|
setWorkflowNodes((nds) =>
|
|
|
nds.map((n) =>
|
|
|
@@ -1524,6 +1883,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
)
|
|
|
);
|
|
|
|
|
|
+ // 先设置选中的节点,确保右侧面板显示
|
|
|
setSelectedWorkflowNode(node);
|
|
|
|
|
|
// 优先从 workflowWorkNodeDOList 中查找对应节点数据(通过uuid匹配)
|
|
|
@@ -1604,39 +1964,48 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
notificationTime: nodeDO.notifyTime || source.notificationTime || '',
|
|
|
};
|
|
|
|
|
|
- // 如果 nodeDO 存在且有 id,说明是已保存的节点,直接使用 nodeDO 的数据,不使用缓存
|
|
|
- console.log('设置节点配置 - nodeConfig.responsible:', nodeConfig.responsible);
|
|
|
- if (nodeDO.id) {
|
|
|
- setWorkflowNodeConfig(nodeConfig);
|
|
|
+ // 优先使用缓存中的配置(如果有),这样可以保持用户未保存的修改
|
|
|
+ // 如果缓存中没有,再使用 nodeDO 的数据
|
|
|
+ console.log('设置节点配置 - nodeConfig.responsible:', nodeConfig.responsible, 'node.id:', node.id);
|
|
|
+ const cachedConfig = workflowNodeConfigCache.get(node.id);
|
|
|
+ console.log('缓存中的配置:', cachedConfig);
|
|
|
+ if (cachedConfig) {
|
|
|
+ // 如果缓存中有配置,使用缓存(可能包含用户未保存的修改)
|
|
|
+ console.log('使用缓存配置');
|
|
|
+ // 使用函数式更新确保状态正确更新
|
|
|
+ setWorkflowNodeConfig(() => ({ ...cachedConfig }));
|
|
|
+ } else if (nodeDO.id) {
|
|
|
+ // 如果缓存中没有,但节点已保存,使用 nodeDO 的数据
|
|
|
+ console.log('使用 nodeDO 配置');
|
|
|
+ setWorkflowNodeConfig(() => ({ ...nodeConfig }));
|
|
|
} else {
|
|
|
- // 如果没有保存过,优先使用缓存,否则使用节点数据
|
|
|
- const cachedConfig = workflowNodeConfigCache.get(node.id);
|
|
|
- if (cachedConfig) {
|
|
|
- setWorkflowNodeConfig(cachedConfig);
|
|
|
- } else {
|
|
|
- setWorkflowNodeConfig(nodeConfig);
|
|
|
- }
|
|
|
+ // 如果既没有缓存也没有保存,使用节点数据
|
|
|
+ console.log('使用节点数据配置');
|
|
|
+ setWorkflowNodeConfig(() => ({ ...nodeConfig }));
|
|
|
}
|
|
|
} else {
|
|
|
// 如果没有找到节点数据,使用原有逻辑
|
|
|
+ console.log('没有找到 nodeDO,使用节点数据');
|
|
|
const cachedConfig = workflowNodeConfigCache.get(node.id);
|
|
|
if (cachedConfig) {
|
|
|
- setWorkflowNodeConfig(cachedConfig);
|
|
|
+ console.log('使用缓存配置(无 nodeDO)');
|
|
|
+ setWorkflowNodeConfig(() => cachedConfig);
|
|
|
} else {
|
|
|
+ console.log('使用节点原始数据');
|
|
|
const source = node.data || {};
|
|
|
const config = nodeConfigs.find(c => c.type === source.type);
|
|
|
setWorkflowNodeConfig({
|
|
|
nodeName: source.label || config?.label || '',
|
|
|
nodeIcon: source.icon || '',
|
|
|
- responsible: source.workerUserId || '',
|
|
|
+ responsible: source.workerUserId ? (typeof source.workerUserId === 'number' ? source.workerUserId : Number(source.workerUserId)) : undefined,
|
|
|
remark: source.remark || '',
|
|
|
- submitForm: source.submitForm || '',
|
|
|
+ submitForm: source.submitForm ? (typeof source.submitForm === 'number' ? source.submitForm : Number(source.submitForm)) : undefined,
|
|
|
isolationType: source.isolationType || '',
|
|
|
isolationPoints: source.isolationPoints || [],
|
|
|
isolationNode: source.isolationNode || [],
|
|
|
isolationNodeUuid: source.isolationNodeUuid || '',
|
|
|
- lockPerson: source.lockPerson || '',
|
|
|
- coLockPersons: source.coLockPersons || [],
|
|
|
+ lockPerson: source.lockPerson ? (typeof source.lockPerson === 'number' ? source.lockPerson : Number(source.lockPerson)) : undefined,
|
|
|
+ coLockPersons: source.coLockPersons && Array.isArray(source.coLockPersons) ? source.coLockPersons.map((id: any) => typeof id === 'number' ? id : Number(id)) : [],
|
|
|
notificationMethods: source.notificationMethods || {
|
|
|
sms: false,
|
|
|
message: false,
|
|
|
@@ -1649,17 +2018,19 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
}
|
|
|
}
|
|
|
setWorkflowActiveTabKey('info');
|
|
|
- }, [workflowNodeConfigCache, workflowWorkNodeDOList]);
|
|
|
+ }, [workflowNodeConfigCache, workflowWorkNodeDOList, nodeConfigs]);
|
|
|
|
|
|
// 画布点击事件(取消选择)
|
|
|
const onWorkflowPaneClick = useCallback(() => {
|
|
|
setSelectedWorkflowNode(null);
|
|
|
}, []);
|
|
|
|
|
|
- // 实时更新节点显示
|
|
|
+ // 实时更新节点显示(只在 workflowNodeConfig 变化时更新,避免在切换节点时覆盖)
|
|
|
useEffect(() => {
|
|
|
- if (selectedWorkflowNode) {
|
|
|
+ if (selectedWorkflowNode && workflowNodeConfig.nodeName) {
|
|
|
const { isolationMethod, ...restData } = selectedWorkflowNode.data || {};
|
|
|
+ // 从缓存中读取 completed 状态,保持保存状态不变
|
|
|
+ const isSaved = nodeSavedStatusCache.get(selectedWorkflowNode.id) || false;
|
|
|
const updatedData = {
|
|
|
...restData,
|
|
|
label: workflowNodeConfig.nodeName,
|
|
|
@@ -1676,6 +2047,8 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
notificationMethods: workflowNodeConfig.notificationMethods,
|
|
|
notificationPerson: workflowNodeConfig.notificationPerson,
|
|
|
notificationTime: workflowNodeConfig.notificationTime,
|
|
|
+ // 保持 completed 状态从缓存中读取,不覆盖
|
|
|
+ completed: isSaved,
|
|
|
};
|
|
|
setWorkflowNodes((nds) =>
|
|
|
nds.map((node) =>
|
|
|
@@ -1685,7 +2058,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
)
|
|
|
);
|
|
|
}
|
|
|
- }, [workflowNodeConfig, selectedWorkflowNode, setWorkflowNodes]);
|
|
|
+ }, [workflowNodeConfig, selectedWorkflowNode?.id, setWorkflowNodes, nodeSavedStatusCache]);
|
|
|
|
|
|
// 作业管理删除处理
|
|
|
const handleWorkJobDelete = async (id: number) => {
|
|
|
@@ -2707,6 +3080,53 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
},
|
|
|
];
|
|
|
|
|
|
+ // 获取作业状态样式
|
|
|
+ const getWorkJobStatusStyle = (status: string | number | undefined): React.CSSProperties => {
|
|
|
+ if (!status) {
|
|
|
+ return {
|
|
|
+ backgroundColor: '#e5e5e5',
|
|
|
+ color: '#333333',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 从字典中查找状态文本
|
|
|
+ const statusItem = jobStatusDictList.find(item => String(item.value) === String(status));
|
|
|
+ const statusText = statusItem ? (statusItem.label || '') : String(status || '');
|
|
|
+ const statusTextLower = statusText.toLowerCase();
|
|
|
+
|
|
|
+ // 根据状态文本判断颜色
|
|
|
+ // 待执行、待发布:灰色 #e5e5e5
|
|
|
+ if (statusTextLower.includes('待执行') || statusTextLower.includes('待发布') ||
|
|
|
+ statusTextLower.includes('pending') || statusTextLower.includes('unreleased')) {
|
|
|
+ return {
|
|
|
+ backgroundColor: '#e5e5e5',
|
|
|
+ color: '#333333',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ // 进行中:蓝色 #1677ff
|
|
|
+ if (statusTextLower.includes('进行中') || statusTextLower.includes('执行中') ||
|
|
|
+ statusTextLower.includes('running') || statusTextLower.includes('in_progress')) {
|
|
|
+ return {
|
|
|
+ backgroundColor: '#1677ff',
|
|
|
+ color: '#ffffff',
|
|
|
+ };
|
|
|
+ }
|
|
|
+ // 已完成:绿色 #0acb57
|
|
|
+ if (statusTextLower.includes('已完成') || statusTextLower.includes('执行完成') ||
|
|
|
+ statusTextLower.includes('completed') || statusTextLower.includes('完成')) {
|
|
|
+ return {
|
|
|
+ backgroundColor: '#0acb57',
|
|
|
+ color: '#ffffff',
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有匹配到,默认灰色
|
|
|
+ return {
|
|
|
+ backgroundColor: '#e5e5e5',
|
|
|
+ color: '#333333',
|
|
|
+ };
|
|
|
+ };
|
|
|
+
|
|
|
// 作业管理表格列配置
|
|
|
const workJobColumns: ColumnsType<WorkJobVO> = [
|
|
|
{
|
|
|
@@ -2782,12 +3202,13 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
width: '10%',
|
|
|
render: (status: string | number) => {
|
|
|
const statusItem = jobStatusDictList.find(item => String(item.value) === String(status));
|
|
|
- const statusInfo = statusItem
|
|
|
- ? { text: statusItem.label, className: 'bg-gray-100 text-gray-700' }
|
|
|
- : { text: String(status || '-'), className: 'bg-gray-100 text-gray-700' };
|
|
|
+ const statusText = statusItem ? (statusItem.label || '') : String(status || '-');
|
|
|
return (
|
|
|
- <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${statusInfo.className}`}>
|
|
|
- {statusInfo.text}
|
|
|
+ <span
|
|
|
+ className="inline-flex px-3 py-1 rounded-lg text-xs"
|
|
|
+ style={getWorkJobStatusStyle(status)}
|
|
|
+ >
|
|
|
+ {statusText}
|
|
|
</span>
|
|
|
);
|
|
|
},
|
|
|
@@ -3768,15 +4189,6 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
>
|
|
|
<Controls className="!bg-white !border !border-gray-200 !rounded-lg !shadow-md" />
|
|
|
<Background variant={BackgroundVariant.Dots} gap={16} size={1} color="#e5e7eb" />
|
|
|
- <MiniMap
|
|
|
- nodeColor={(node) => {
|
|
|
- const config = nodeConfigs.find(c => c.type === node.type);
|
|
|
- if (config?.iconColorCustom) return config.iconColorCustom;
|
|
|
- return '#6b7280';
|
|
|
- }}
|
|
|
- maskColor="rgba(0, 0, 0, 0.05)"
|
|
|
- className="!bg-white !border !border-gray-200 !rounded-lg"
|
|
|
- />
|
|
|
</ReactFlow>
|
|
|
</div>
|
|
|
) : (
|
|
|
@@ -4727,10 +5139,10 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
: { ...node, selected: false }
|
|
|
)
|
|
|
);
|
|
|
- // 滚动到节点位置
|
|
|
- if (workflowReactFlowInstance) {
|
|
|
- workflowReactFlowInstance.fitView({ padding: 0.2, duration: 300, nodes: [nextNode] });
|
|
|
- }
|
|
|
+ // 不自动聚焦,保持当前视图,让用户能看到其他节点
|
|
|
+ // if (workflowReactFlowInstance) {
|
|
|
+ // workflowReactFlowInstance.fitView({ padding: 0.2, duration: 300, nodes: [nextNode] });
|
|
|
+ // }
|
|
|
}, 100);
|
|
|
message.success('节点配置已保存,请继续配置下一个节点');
|
|
|
} else {
|
|
|
@@ -4743,11 +5155,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
|
|
|
// 如果返回的错误信息数组为空或null,表示所有节点配置完毕
|
|
|
if (!errorMessages || errorMessages.length === 0) {
|
|
|
- message.success('所有节点配置已完成');
|
|
|
- // 延迟一下再跳转,让用户看到成功提示
|
|
|
- setTimeout(() => {
|
|
|
- setWorkJobStep(2);
|
|
|
- }, 500);
|
|
|
+ message.success('所有节点配置已完成,可以点击"下一步"继续');
|
|
|
} else {
|
|
|
// 有错误信息,显示弹框
|
|
|
Modal.warning({
|
|
|
@@ -4773,11 +5181,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
message.error(error?.message || '检查作业失败');
|
|
|
}
|
|
|
} else {
|
|
|
- message.success('所有节点配置已完成');
|
|
|
- // 延迟一下再跳转,让用户看到成功提示
|
|
|
- setTimeout(() => {
|
|
|
- setWorkJobStep(2);
|
|
|
- }, 500);
|
|
|
+ message.success('所有节点配置已完成,可以点击"下一步"继续');
|
|
|
}
|
|
|
}
|
|
|
} catch (error: any) {
|
|
|
@@ -4998,19 +5402,8 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
// 成功后进入下一步
|
|
|
setWorkJobStep(1);
|
|
|
|
|
|
- // 如果有 workflowWorkId,获取详情并加载 workflowWorkNodeDOList
|
|
|
- if (currentWorkId) {
|
|
|
- try {
|
|
|
- const detailResponse = await workJobApi.selectWorkflowWorkById(currentWorkId);
|
|
|
- const detail = detailResponse as any;
|
|
|
- if (detail.workflowWorkNodeDOList && Array.isArray(detail.workflowWorkNodeDOList)) {
|
|
|
- setWorkflowWorkNodeDOList(detail.workflowWorkNodeDOList);
|
|
|
- }
|
|
|
- } catch (error: any) {
|
|
|
- console.error('获取作业节点列表失败:', error);
|
|
|
- // 不阻止进入下一步,只是不加载节点列表
|
|
|
- }
|
|
|
- }
|
|
|
+ // 注意:节点状态初始化会在 useEffect 中自动执行(当 workJobStep === 1 且 workflowWorkId 存在时)
|
|
|
+ // 这里不需要手动加载,让 useEffect 统一处理
|
|
|
} catch (error: any) {
|
|
|
if (error.errorFields) {
|
|
|
// 表单验证失败
|
|
|
@@ -5038,7 +5431,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
<div className="flex items-center justify-between w-full">
|
|
|
<div className="flex items-center text-red-600 text-sm">
|
|
|
<span className="mr-1">⚠️</span>
|
|
|
- <span>提示:流程中的每一个节点都需要单独配置并保存,未保存的节点将不会生效。</span>
|
|
|
+ <span>提示:流程中的每一个节点都需要单独配置并保存,方可点击 "下一步"。</span>
|
|
|
</div>
|
|
|
<div className="flex items-center gap-2">
|
|
|
<Button onClick={() => setWorkJobStep(0)}>
|
|
|
@@ -5048,462 +5441,51 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
|
|
|
<>
|
|
|
<Button
|
|
|
type="primary"
|
|
|
+ disabled={hasUnsavedNodes}
|
|
|
onClick={async () => {
|
|
|
- if (!selectedWorkflowNode) {
|
|
|
- message.warning('请先选择一个节点进行配置');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 验证当前节点配置
|
|
|
- const isValid = validateNodeConfig(workflowNodeConfig, selectedWorkflowNode.data?.type || '');
|
|
|
- if (!isValid) {
|
|
|
- message.warning('请完成当前节点的必填项配置');
|
|
|
+ // 调用检查接口,检查是否有未保存的节点
|
|
|
+ if (!workflowWorkId) {
|
|
|
+ message.warning('作业ID不存在,无法检查');
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
try {
|
|
|
- // 查找对应的 workflowWorkNodeDO 节点(通过uuid匹配)
|
|
|
- const nodeDO = workflowWorkNodeDOList.find(item => item.uuid === selectedWorkflowNode.id);
|
|
|
-
|
|
|
- // 在外部作用域定义,确保在自动切换节点时可以使用
|
|
|
- let mergedWorkflowWorkNodeDOList: WorkflowWorkNodeDO[] = [];
|
|
|
-
|
|
|
- if (nodeDO && nodeDO.id && workflowWorkId) {
|
|
|
- // 构建节点用户信息列表
|
|
|
- const nodeUserDOList: WorkflowWorkNodeUserDO[] = [];
|
|
|
-
|
|
|
- // 如果是解除隔离节点,需要复制隔离节点的数据
|
|
|
- if (selectedWorkflowNode.data?.type === 'releaseIsolation' && workflowNodeConfig.isolationNodeUuid) {
|
|
|
- // 从 workflowWorkNodeDOList 中查找对应的隔离节点数据
|
|
|
- const isolationNodeDO = workflowWorkNodeDOList.find(item =>
|
|
|
- item.uuid === workflowNodeConfig.isolationNodeUuid && item.type === 'isolation'
|
|
|
- );
|
|
|
-
|
|
|
- if (isolationNodeDO) {
|
|
|
- // 复制隔离节点的 nodeUserList(上锁人、共锁人等)
|
|
|
- if (isolationNodeDO.nodeUserList && Array.isArray(isolationNodeDO.nodeUserList)) {
|
|
|
- isolationNodeDO.nodeUserList.forEach((user: any) => {
|
|
|
- if (user.type === 'jtlocker' || user.type === 'jtcolocker') {
|
|
|
- nodeUserDOList.push({
|
|
|
- userId: user.userId,
|
|
|
- type: user.type,
|
|
|
- });
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 如果不是解除隔离节点,使用原有逻辑
|
|
|
- // 如果隔离方式是"上锁挂牌"(字典值:1),添加上锁人员和共锁人员
|
|
|
- if (workflowNodeConfig.isolationType === '1') {
|
|
|
- // 添加上锁人员
|
|
|
- if (workflowNodeConfig.lockPerson) {
|
|
|
- nodeUserDOList.push({
|
|
|
- userId: Number(workflowNodeConfig.lockPerson),
|
|
|
- type: 'jtlocker',
|
|
|
- });
|
|
|
- }
|
|
|
-
|
|
|
- // 添加共锁人员(可以多选)
|
|
|
- if (workflowNodeConfig.coLockPersons && workflowNodeConfig.coLockPersons.length > 0) {
|
|
|
- workflowNodeConfig.coLockPersons.forEach((userId: string | number) => {
|
|
|
- nodeUserDOList.push({
|
|
|
- userId: Number(userId),
|
|
|
- type: 'jtcolocker',
|
|
|
- });
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 调用更新节点接口
|
|
|
- const formId = workflowNodeConfig.submitForm ? Number(workflowNodeConfig.submitForm) : undefined;
|
|
|
-
|
|
|
- // 如果 formId 存在,先调用接口获取表单数据
|
|
|
- let formData: string | undefined = undefined;
|
|
|
- if (formId) {
|
|
|
- try {
|
|
|
- const formResponse = await getForm(formId);
|
|
|
- // 获取 data 字段的内容(如果接口返回的是 { code, data, message } 格式)
|
|
|
- let formDataObj: any = formResponse;
|
|
|
- if (formResponse && typeof formResponse === 'object' && 'data' in formResponse) {
|
|
|
- formDataObj = (formResponse as any).data || formResponse;
|
|
|
- }
|
|
|
- // 将整个对象内容转换成 JSON 字符串传递给 formData 字段
|
|
|
- formData = JSON.stringify(formDataObj);
|
|
|
- console.log('表单数据已转换为字符串:', formData);
|
|
|
- } catch (error) {
|
|
|
- console.error('获取表单数据失败:', error);
|
|
|
- // 即使获取失败,也继续保存节点,只是不传递 formData
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- const updateParam: UpdateWorkflowWorkNodeParam = {
|
|
|
- nodeId: nodeDO.id,
|
|
|
- nodeName: workflowNodeConfig.nodeName,
|
|
|
- formId: formId,
|
|
|
- workerUserId: workflowNodeConfig.responsible ? Number(workflowNodeConfig.responsible) : undefined,
|
|
|
- isolationType: workflowNodeConfig.isolationType || undefined,
|
|
|
- isolationPoints: workflowNodeConfig.isolationPoints && workflowNodeConfig.isolationPoints.length > 0
|
|
|
- ? JSON.stringify(workflowNodeConfig.isolationPoints)
|
|
|
- : undefined,
|
|
|
- // 如果是解除隔离节点,传递选中的隔离节点UUID
|
|
|
- isolationNodeUuid: (selectedWorkflowNode.data?.type === 'releaseIsolation' && workflowNodeConfig.isolationNodeUuid)
|
|
|
- ? workflowNodeConfig.isolationNodeUuid
|
|
|
- : undefined,
|
|
|
- nodeUserDOList: nodeUserDOList.length > 0 ? nodeUserDOList : undefined,
|
|
|
- // 如果获取到了表单数据,将整个对象内容转换成字符串传递给 formData 字段
|
|
|
- formData: formData,
|
|
|
- };
|
|
|
-
|
|
|
- await workJobApi.updateWorkflowWorkNode(updateParam);
|
|
|
-
|
|
|
- // 更新成功后,立即调用详情接口获取最新数据
|
|
|
- let mergedWorkflowWorkNodeDOList: WorkflowWorkNodeDO[] = [];
|
|
|
- if (workflowWorkId) {
|
|
|
- try {
|
|
|
- const detailResponse = await workJobApi.selectWorkflowWorkById(workflowWorkId);
|
|
|
- const detail = detailResponse as any;
|
|
|
- if (detail.workflowWorkNodeDOList && Array.isArray(detail.workflowWorkNodeDOList)) {
|
|
|
- // 使用接口返回的最新数据更新 workflowWorkNodeDOList
|
|
|
- // 合并更新:保留现有数据,只更新接口返回的数据
|
|
|
- // 先计算合并后的数据,然后再更新状态
|
|
|
- const prev = workflowWorkNodeDOList;
|
|
|
- console.log('合并更新前的 workflowWorkNodeDOList:', prev);
|
|
|
- console.log('接口返回的 workflowWorkNodeDOList:', detail.workflowWorkNodeDOList);
|
|
|
-
|
|
|
- // 创建一个映射,方便查找
|
|
|
- const prevMap = new Map<string | number, WorkflowWorkNodeDO>();
|
|
|
- prev.forEach(item => {
|
|
|
- if (item.id) prevMap.set(item.id, item);
|
|
|
- if (item.uuid) prevMap.set(item.uuid, item);
|
|
|
- });
|
|
|
-
|
|
|
- const updatedList = detail.workflowWorkNodeDOList.map((newItem: WorkflowWorkNodeDO) => {
|
|
|
- // 查找现有列表中对应的节点(通过 id 或 uuid 匹配)
|
|
|
- const existingItem = (newItem.id && prevMap.get(newItem.id)) ||
|
|
|
- (newItem.uuid && prevMap.get(newItem.uuid)) ||
|
|
|
- null;
|
|
|
-
|
|
|
- // 如果找到现有节点,合并数据(优先使用接口返回的数据,但保留现有数据中接口没有返回的字段)
|
|
|
- if (existingItem) {
|
|
|
- // 合并数据,确保关键字段不被覆盖为空
|
|
|
- // 只有当接口返回的值是有效的(不是 null、undefined、0 或空字符串)时,才使用接口的值
|
|
|
- const merged: WorkflowWorkNodeDO = {
|
|
|
- ...existingItem,
|
|
|
- ...newItem,
|
|
|
- // 如果接口返回的 workerUserId 是 null、undefined 或 0,保留现有的值
|
|
|
- workerUserId: (newItem.workerUserId !== null && newItem.workerUserId !== undefined && newItem.workerUserId !== 0)
|
|
|
- ? newItem.workerUserId
|
|
|
- : existingItem.workerUserId,
|
|
|
- // 如果接口返回的 formId 是 null、undefined 或 0,保留现有的值
|
|
|
- formId: (newItem.formId !== null && newItem.formId !== undefined && newItem.formId !== 0)
|
|
|
- ? newItem.formId
|
|
|
- : existingItem.formId,
|
|
|
- // 如果接口返回的 isolationType 是 null 或 undefined,保留现有的值
|
|
|
- isolationType: (newItem.isolationType !== null && newItem.isolationType !== undefined && newItem.isolationType !== '')
|
|
|
- ? newItem.isolationType
|
|
|
- : existingItem.isolationType,
|
|
|
- // 如果接口返回的 isolationPoints 是 null 或 undefined,保留现有的值
|
|
|
- isolationPoints: (newItem.isolationPoints !== null && newItem.isolationPoints !== undefined && newItem.isolationPoints !== '')
|
|
|
- ? newItem.isolationPoints
|
|
|
- : existingItem.isolationPoints,
|
|
|
- // 如果接口返回的 nodeUserList 是空数组或不存在,保留现有的值
|
|
|
- nodeUserList: (newItem.nodeUserList && Array.isArray(newItem.nodeUserList) && newItem.nodeUserList.length > 0)
|
|
|
- ? newItem.nodeUserList
|
|
|
- : (existingItem.nodeUserList || []),
|
|
|
- };
|
|
|
-
|
|
|
- console.log(`合并节点 ${newItem.uuid || newItem.id}:`, {
|
|
|
- existing: { workerUserId: existingItem.workerUserId, formId: existingItem.formId },
|
|
|
- new: { workerUserId: newItem.workerUserId, formId: newItem.formId },
|
|
|
- merged: { workerUserId: merged.workerUserId, formId: merged.formId }
|
|
|
- });
|
|
|
-
|
|
|
- return merged;
|
|
|
- }
|
|
|
-
|
|
|
- // 如果没找到,直接使用接口返回的数据
|
|
|
- return newItem;
|
|
|
- });
|
|
|
-
|
|
|
- // 如果接口返回的列表中有新节点(不在现有列表中的),也要添加
|
|
|
- const existingUuids = new Set<string | number>();
|
|
|
- prev.forEach(item => {
|
|
|
- if (item.id) existingUuids.add(item.id);
|
|
|
- if (item.uuid) existingUuids.add(item.uuid);
|
|
|
- });
|
|
|
-
|
|
|
- const newItems = detail.workflowWorkNodeDOList.filter((item: WorkflowWorkNodeDO) => {
|
|
|
- const itemKey = item.id || item.uuid;
|
|
|
- return itemKey && !existingUuids.has(itemKey);
|
|
|
- });
|
|
|
-
|
|
|
- mergedWorkflowWorkNodeDOList = [...updatedList, ...newItems];
|
|
|
- console.log('合并更新后的 workflowWorkNodeDOList:', mergedWorkflowWorkNodeDOList);
|
|
|
-
|
|
|
- // 更新状态
|
|
|
- setWorkflowWorkNodeDOList(mergedWorkflowWorkNodeDOList);
|
|
|
- }
|
|
|
- } catch (error: any) {
|
|
|
- console.error('获取最新节点数据失败:', error);
|
|
|
- // 如果接口调用失败,仍然使用本地更新逻辑作为后备
|
|
|
- setWorkflowWorkNodeDOList(prev => prev.map(item => {
|
|
|
- if (item.id === nodeDO.id) {
|
|
|
- return {
|
|
|
- ...item,
|
|
|
- nodeName: workflowNodeConfig.nodeName,
|
|
|
- formId: workflowNodeConfig.submitForm ? Number(workflowNodeConfig.submitForm) : undefined,
|
|
|
- workerUserId: workflowNodeConfig.responsible ? Number(workflowNodeConfig.responsible) : undefined,
|
|
|
- isolationType: workflowNodeConfig.isolationType || undefined,
|
|
|
- isolationPoints: workflowNodeConfig.isolationPoints && workflowNodeConfig.isolationPoints.length > 0
|
|
|
- ? JSON.stringify(workflowNodeConfig.isolationPoints)
|
|
|
- : undefined,
|
|
|
- lockPerson: workflowNodeConfig.lockPerson ? String(workflowNodeConfig.lockPerson) : undefined,
|
|
|
- notifyTime: workflowNodeConfig.notificationTime || undefined,
|
|
|
- data: JSON.stringify((() => {
|
|
|
- const existingData = item.data ? (typeof item.data === 'string' ? JSON.parse(item.data) : item.data) : {};
|
|
|
- const { isolationMethod, ...restExistingData } = existingData;
|
|
|
- return {
|
|
|
- ...restExistingData,
|
|
|
- label: workflowNodeConfig.nodeName,
|
|
|
- icon: workflowNodeConfig.nodeIcon,
|
|
|
- responsible: workflowNodeConfig.responsible ? String(workflowNodeConfig.responsible) : '',
|
|
|
- remark: workflowNodeConfig.remark,
|
|
|
- submitForm: workflowNodeConfig.submitForm ? String(workflowNodeConfig.submitForm) : '',
|
|
|
- isolationType: workflowNodeConfig.isolationType,
|
|
|
- isolationPoints: workflowNodeConfig.isolationPoints,
|
|
|
- isolationNode: workflowNodeConfig.isolationNode,
|
|
|
- isolationNodeUuid: workflowNodeConfig.isolationNodeUuid,
|
|
|
- lockPerson: workflowNodeConfig.lockPerson ? String(workflowNodeConfig.lockPerson) : '',
|
|
|
- coLockPersons: workflowNodeConfig.coLockPersons.map((id: any) => String(id)),
|
|
|
- notificationMethods: workflowNodeConfig.notificationMethods,
|
|
|
- notificationPerson: workflowNodeConfig.notificationPerson,
|
|
|
- notificationTime: workflowNodeConfig.notificationTime,
|
|
|
- };
|
|
|
- })()),
|
|
|
- };
|
|
|
- }
|
|
|
- return item;
|
|
|
- }));
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // 缓存当前节点配置
|
|
|
- const newCache = new Map(workflowNodeConfigCache);
|
|
|
- newCache.set(selectedWorkflowNode.id, { ...workflowNodeConfig });
|
|
|
- setWorkflowNodeConfigCache(newCache);
|
|
|
-
|
|
|
- // 标记当前节点为已完成
|
|
|
- const newCompleted = new Set(completedNodeIds);
|
|
|
- newCompleted.add(selectedWorkflowNode.id);
|
|
|
- setCompletedNodeIds(newCompleted);
|
|
|
-
|
|
|
- // 更新节点保存状态缓存
|
|
|
- const newSavedStatusCache = new Map(nodeSavedStatusCache);
|
|
|
- newSavedStatusCache.set(selectedWorkflowNode.id, true);
|
|
|
- setNodeSavedStatusCache(newSavedStatusCache);
|
|
|
-
|
|
|
- // 更新节点显示
|
|
|
- const { isolationMethod, ...restData } = selectedWorkflowNode.data || {};
|
|
|
- const updatedData = {
|
|
|
- ...restData,
|
|
|
- label: workflowNodeConfig.nodeName,
|
|
|
- icon: workflowNodeConfig.nodeIcon,
|
|
|
- responsible: workflowNodeConfig.responsible,
|
|
|
- remark: workflowNodeConfig.remark,
|
|
|
- submitForm: workflowNodeConfig.submitForm,
|
|
|
- isolationType: workflowNodeConfig.isolationType,
|
|
|
- isolationPoints: workflowNodeConfig.isolationPoints,
|
|
|
- isolationNode: workflowNodeConfig.isolationNode,
|
|
|
- isolationNodeUuid: workflowNodeConfig.isolationNodeUuid,
|
|
|
- lockPerson: workflowNodeConfig.lockPerson,
|
|
|
- coLockPersons: workflowNodeConfig.coLockPersons,
|
|
|
- notificationMethods: workflowNodeConfig.notificationMethods,
|
|
|
- notificationPerson: workflowNodeConfig.notificationPerson,
|
|
|
- notificationTime: workflowNodeConfig.notificationTime,
|
|
|
- completed: true, // 标记为已完成
|
|
|
- };
|
|
|
- setWorkflowNodes((nds) =>
|
|
|
- nds.map((node) =>
|
|
|
- node.id === selectedWorkflowNode.id
|
|
|
- ? { ...node, data: updatedData, selected: false }
|
|
|
- : node
|
|
|
- )
|
|
|
- );
|
|
|
+ const checkResponse = await workJobApi.checkWorkById(workflowWorkId);
|
|
|
+ const checkData = (checkResponse as any)?.data || checkResponse;
|
|
|
+ const errorMessages = Array.isArray(checkData) ? checkData : (checkData ? [checkData] : []);
|
|
|
|
|
|
- // 查找下一个未完成的节点(使用新的completedSet,而不是状态中的)
|
|
|
- // 优先查找当前节点的子节点
|
|
|
- const nextNode = getNextIncompleteNode(newCompleted, selectedWorkflowNode.id);
|
|
|
- if (nextNode) {
|
|
|
- // 自动切换到下一个节点
|
|
|
- // 使用合并后的最新数据(如果有的话),否则使用状态中的数据
|
|
|
- const latestWorkflowWorkNodeDOList = mergedWorkflowWorkNodeDOList.length > 0
|
|
|
- ? mergedWorkflowWorkNodeDOList
|
|
|
- : workflowWorkNodeDOList;
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
- setSelectedWorkflowNode(nextNode);
|
|
|
- const cachedConfig = newCache.get(nextNode.id);
|
|
|
- if (cachedConfig) {
|
|
|
- setWorkflowNodeConfig(cachedConfig);
|
|
|
- } else {
|
|
|
- // 从最新的 workflowWorkNodeDOList 中查找节点数据
|
|
|
- const nextNodeDO = latestWorkflowWorkNodeDOList.find(item => item.uuid === nextNode.id);
|
|
|
- if (nextNodeDO) {
|
|
|
- let nextNodeData: any = {};
|
|
|
- if (nextNodeDO.data) {
|
|
|
- try {
|
|
|
- nextNodeData = typeof nextNodeDO.data === 'string' ? JSON.parse(nextNodeDO.data) : nextNodeDO.data;
|
|
|
- } catch (e) {
|
|
|
- console.error('解析节点data失败:', e);
|
|
|
- }
|
|
|
- }
|
|
|
- const source = nextNodeData || nextNode.data || {};
|
|
|
- const config = nodeConfigs.find(c => c.type === (nextNodeDO.type || source.type));
|
|
|
- setWorkflowNodeConfig({
|
|
|
- nodeName: nextNodeDO.nodeName || source.label || config?.label || '',
|
|
|
- nodeIcon: nextNodeDO.nodeIcon || source.icon || '',
|
|
|
- responsible: (nextNodeDO.workerUserId !== null && nextNodeDO.workerUserId !== undefined && nextNodeDO.workerUserId !== 0)
|
|
|
- ? (typeof nextNodeDO.workerUserId === 'number' ? nextNodeDO.workerUserId : Number(nextNodeDO.workerUserId))
|
|
|
- : (source.workerUserId && source.workerUserId !== '' && source.workerUserId !== '0')
|
|
|
- ? (typeof source.workerUserId === 'number' ? source.workerUserId : Number(source.workerUserId))
|
|
|
- : undefined,
|
|
|
- remark: source.remark || '',
|
|
|
- submitForm: nextNodeDO.formId ? (typeof nextNodeDO.formId === 'number' ? nextNodeDO.formId : Number(nextNodeDO.formId)) : (source.submitForm ? (typeof source.submitForm === 'number' ? source.submitForm : Number(source.submitForm)) : undefined),
|
|
|
- isolationType: (nextNodeDO.isolationType !== null && nextNodeDO.isolationType !== undefined) ? String(nextNodeDO.isolationType) : (source.isolationType || ''),
|
|
|
- isolationPoints: nextNodeDO.isolationPoints ? (typeof nextNodeDO.isolationPoints === 'string' ? JSON.parse(nextNodeDO.isolationPoints) : nextNodeDO.isolationPoints) : (source.isolationPoints || []),
|
|
|
- isolationNode: source.isolationNode || [],
|
|
|
- isolationNodeUuid: nextNodeDO.isolationNodeUuid || source.isolationNodeUuid || '',
|
|
|
- lockPerson: (() => {
|
|
|
- // 优先从 nodeUserList 中提取
|
|
|
- let lockPersonId: number | undefined = undefined;
|
|
|
- if (nextNodeDO.nodeUserList && Array.isArray(nextNodeDO.nodeUserList)) {
|
|
|
- const lockerUser = nextNodeDO.nodeUserList.find((user: any) => user.type === 'jtlocker');
|
|
|
- if (lockerUser?.userId) {
|
|
|
- lockPersonId = typeof lockerUser.userId === 'number' ? lockerUser.userId : Number(lockerUser.userId);
|
|
|
- }
|
|
|
- }
|
|
|
- return lockPersonId || (source.lockPerson ? (typeof source.lockPerson === 'number' ? source.lockPerson : Number(source.lockPerson)) : undefined);
|
|
|
- })(),
|
|
|
- coLockPersons: (() => {
|
|
|
- // 优先从 nodeUserList 中提取
|
|
|
- const coLockPersonIds: number[] = [];
|
|
|
- if (nextNodeDO.nodeUserList && Array.isArray(nextNodeDO.nodeUserList)) {
|
|
|
- nextNodeDO.nodeUserList.forEach((user: any) => {
|
|
|
- if (user.type === 'jtcolocker' && user.userId) {
|
|
|
- coLockPersonIds.push(typeof user.userId === 'number' ? user.userId : Number(user.userId));
|
|
|
- }
|
|
|
- });
|
|
|
- }
|
|
|
- return coLockPersonIds.length > 0 ? coLockPersonIds : (source.coLockPersons && Array.isArray(source.coLockPersons) ? source.coLockPersons.map((id: any) => typeof id === 'number' ? id : Number(id)) : []);
|
|
|
- })(),
|
|
|
- notificationMethods: source.notificationMethods || {
|
|
|
- sms: false,
|
|
|
- message: false,
|
|
|
- email: false,
|
|
|
- app: false,
|
|
|
- },
|
|
|
- notificationPerson: source.notificationPerson || '',
|
|
|
- notificationTime: nextNodeDO.notifyTime || source.notificationTime || '',
|
|
|
- });
|
|
|
- } else {
|
|
|
- const source = nextNode.data || {};
|
|
|
- const config = nodeConfigs.find(c => c.type === source.type);
|
|
|
- setWorkflowNodeConfig({
|
|
|
- nodeName: source.label || config?.label || '',
|
|
|
- nodeIcon: source.icon || '',
|
|
|
- responsible: source.responsible ? (typeof source.responsible === 'number' ? source.responsible : Number(source.responsible)) : undefined,
|
|
|
- remark: source.remark || '',
|
|
|
- submitForm: source.submitForm ? (typeof source.submitForm === 'number' ? source.submitForm : Number(source.submitForm)) : undefined,
|
|
|
- isolationType: source.isolationType || '',
|
|
|
- isolationPoints: source.isolationPoints || [],
|
|
|
- isolationNode: source.isolationNode || [],
|
|
|
- isolationNodeUuid: source.isolationNodeUuid || '',
|
|
|
- lockPerson: source.lockPerson || '',
|
|
|
- coLockPersons: source.coLockPersons || [],
|
|
|
- notificationMethods: source.notificationMethods || {
|
|
|
- sms: false,
|
|
|
- message: false,
|
|
|
- email: false,
|
|
|
- app: false,
|
|
|
- },
|
|
|
- notificationPerson: source.notificationPerson || '',
|
|
|
- notificationTime: source.notificationTime || '',
|
|
|
- });
|
|
|
- }
|
|
|
- }
|
|
|
- // 高亮下一个节点
|
|
|
- setWorkflowNodes((nds) =>
|
|
|
- nds.map((node) =>
|
|
|
- node.id === nextNode.id
|
|
|
- ? { ...node, selected: true }
|
|
|
- : { ...node, selected: false }
|
|
|
- )
|
|
|
- );
|
|
|
- // 滚动到节点位置
|
|
|
- if (workflowReactFlowInstance) {
|
|
|
- workflowReactFlowInstance.fitView({ padding: 0.2, duration: 300, nodes: [nextNode] });
|
|
|
- }
|
|
|
- }, 100);
|
|
|
- message.success('节点配置已保存,请继续配置下一个节点');
|
|
|
+ // 如果返回的错误信息数组为空或null,表示所有节点配置完毕,可以进入下一个tab
|
|
|
+ if (!errorMessages || errorMessages.length === 0) {
|
|
|
+ message.success('所有节点配置已完成');
|
|
|
+ // 进入下一个tab
|
|
|
+ setWorkJobStep(2);
|
|
|
} else {
|
|
|
- // 所有节点都已完成,调用检查接口
|
|
|
- if (workflowWorkId) {
|
|
|
- try {
|
|
|
- const checkResponse = await workJobApi.checkWorkById(workflowWorkId);
|
|
|
- const checkData = (checkResponse as any)?.data || checkResponse;
|
|
|
- const errorMessages = Array.isArray(checkData) ? checkData : (checkData ? [checkData] : []);
|
|
|
-
|
|
|
- // 如果返回的错误信息数组为空或null,表示所有节点配置完毕
|
|
|
- if (!errorMessages || errorMessages.length === 0) {
|
|
|
- message.success('所有节点配置已完成');
|
|
|
- // 延迟一下再跳转,让用户看到成功提示
|
|
|
- setTimeout(() => {
|
|
|
- setWorkJobStep(2);
|
|
|
- }, 500);
|
|
|
- } else {
|
|
|
- // 有错误信息,显示弹框
|
|
|
- Modal.warning({
|
|
|
- title: '节点配置未完成',
|
|
|
- width: 500,
|
|
|
- content: (
|
|
|
- <div style={{ marginTop: 16 }}>
|
|
|
- <p style={{ marginBottom: 12, color: '#666' }}>以下节点配置不完整,请检查:</p>
|
|
|
- <ul style={{ margin: 0, paddingLeft: 20 }}>
|
|
|
- {errorMessages.map((msg: string, index: number) => (
|
|
|
- <li key={index} style={{ marginBottom: 8, color: '#ff4d4f' }}>
|
|
|
- {msg}
|
|
|
- </li>
|
|
|
- ))}
|
|
|
- </ul>
|
|
|
- </div>
|
|
|
- ),
|
|
|
- okText: '我知道了',
|
|
|
- });
|
|
|
- }
|
|
|
- } catch (error: any) {
|
|
|
- console.error('检查作业失败:', error);
|
|
|
- message.error(error?.message || '检查作业失败');
|
|
|
- }
|
|
|
- } else {
|
|
|
- message.success('所有节点配置已完成');
|
|
|
- // 延迟一下再跳转,让用户看到成功提示
|
|
|
- setTimeout(() => {
|
|
|
- setWorkJobStep(2);
|
|
|
- }, 500);
|
|
|
- }
|
|
|
+ // 有错误信息,显示弹框,不能进入下一个tab
|
|
|
+ Modal.warning({
|
|
|
+ title: '节点配置未完成',
|
|
|
+ width: 500,
|
|
|
+ content: (
|
|
|
+ <div style={{ marginTop: 16 }}>
|
|
|
+ <p style={{ marginBottom: 12, color: '#666' }}>以下节点配置不完整,请检查:</p>
|
|
|
+ <ul style={{ margin: 0, paddingLeft: 20 }}>
|
|
|
+ {errorMessages.map((msg: string, index: number) => (
|
|
|
+ <li key={index} style={{ marginBottom: 8, color: '#ff4d4f' }}>
|
|
|
+ {msg}
|
|
|
+ </li>
|
|
|
+ ))}
|
|
|
+ </ul>
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ okText: '我知道了',
|
|
|
+ });
|
|
|
}
|
|
|
} catch (error: any) {
|
|
|
- console.error('保存节点配置失败:', error);
|
|
|
- message.error(error?.message || '保存节点配置失败');
|
|
|
+ console.error('检查作业失败:', error);
|
|
|
+ message.error(error?.message || '检查作业失败');
|
|
|
}
|
|
|
}}
|
|
|
>
|
|
|
- {completedNodeIds.size === workflowNodes.length && workflowNodes.length > 0
|
|
|
- ? '完成流程设置'
|
|
|
- : '保存'}
|
|
|
+ 下一步
|
|
|
</Button>
|
|
|
{/* <Button
|
|
|
type="default"
|