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

流程设计页面节点设置中必填表单保存时添加校验

wyn пре 1 недеља
родитељ
комит
31b64b90a5
2 измењених фајлова са 130 додато и 9 уклоњено
  1. 70 8
      src/components/ProcessDesigner.tsx
  2. 60 1
      src/utils/workflowNodeTypes.ts

+ 70 - 8
src/components/ProcessDesigner.tsx

@@ -159,6 +159,7 @@ import {
   isWorkflowUnlockSchemeType,
   normalizeIsolationTypeForCoLockFamily,
   resolveWorkflowPaletteType,
+  validateWorkflowDesignerSubmitTabForSave,
   WORKFLOW_CO_LOCK_FAMILY_ISOLATION_TYPE,
 } from '../utils/workflowNodeTypes';
 import { generateIconPaths, getIconPathByFileName, getWorkflowNodeDescription } from '../utils/workflowNodePanelUi';
@@ -2549,15 +2550,31 @@ export default function ProcessDesigner() {
         }),
       };
 
-      // 将整个 JSON 作为 content 字段传递
-      const content = JSON.stringify(exportData, null, 2);
-      
       // 必须有流程ID才能保存(因为新建时只传了name,设计完成后必须调用更新接口)
       if (!workflowId) {
         message.warning('无法保存:缺少流程ID,请先创建流程');
         return;
       }
 
+      const submitTabValidationError = validateWorkflowDesignerSubmitTabForSave(
+        nodes.map((n) => {
+          const cachedData = loadNodeCache(n.id);
+          const mergedData = (cachedData ? { ...n.data, ...cachedData } : n.data || {}) as Record<
+            string,
+            unknown
+          >;
+          return {
+            id: n.id,
+            reactFlowType: String(n.type ?? ''),
+            mergedData,
+          };
+        })
+      );
+      if (submitTabValidationError) {
+        message.warning(submitTabValidationError);
+        return;
+      }
+
       // 从暂存的详情中获取 name 和 description
       const name = workflowDetail?.name || '';
       const description = workflowDetail?.description || '';
@@ -2670,15 +2687,31 @@ export default function ProcessDesigner() {
         }),
       };
 
-      // 将整个 JSON 作为 content 字段传递
-      const content = JSON.stringify(exportData, null, 2);
-      
       // 必须有流程ID才能保存(因为新建时只传了name,设计完成后必须调用更新接口)
       if (!workflowId) {
         message.warning('无法保存:缺少流程ID,请先创建流程');
         return;
       }
 
+      const submitTabValidationError = validateWorkflowDesignerSubmitTabForSave(
+        nodes.map((n) => {
+          const cachedData = loadNodeCache(n.id);
+          const mergedData = (cachedData ? { ...n.data, ...cachedData } : n.data || {}) as Record<
+            string,
+            unknown
+          >;
+          return {
+            id: n.id,
+            reactFlowType: String(n.type ?? ''),
+            mergedData,
+          };
+        })
+      );
+      if (submitTabValidationError) {
+        message.warning(submitTabValidationError);
+        return;
+      }
+
       // 从暂存的详情中获取 name 和 description
       const name = workflowDetail?.name || '';
       const description = workflowDetail?.description || '';
@@ -2826,6 +2859,25 @@ export default function ProcessDesigner() {
               return;
             }
 
+            const submitTabValidationError = validateWorkflowDesignerSubmitTabForSave(
+              nodes.map((n) => {
+                const cachedData = loadNodeCache(n.id);
+                const mergedData = (cachedData ? { ...n.data, ...cachedData } : n.data || {}) as Record<
+                  string,
+                  unknown
+                >;
+                return {
+                  id: n.id,
+                  reactFlowType: String(n.type ?? ''),
+                  mergedData,
+                };
+              })
+            );
+            if (submitTabValidationError) {
+              message.warning(submitTabValidationError);
+              return;
+            }
+
             const name = workflowDetail?.name || '';
             const description = workflowDetail?.description || '';
 
@@ -3794,8 +3846,18 @@ export default function ProcessDesigner() {
                                 <div>
                                   <label className="block text-sm font-medium text-gray-700 mb-2">
                                     {isWorkflowUnlockCoLockType(selectedDataType)
-                                      ? '选择共锁任务'
-                                      : '选择上锁任务'}
+                                      ? (
+                                        <>
+                                          选择共锁任务{' '}
+                                          <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                                        </>
+                                      )
+                                      : (
+                                        <>
+                                          选择上锁任务{' '}
+                                          <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                                        </>
+                                      )}
                                   </label>
                                   <Select
                                     value={nodeConfig.isolationNodeUuid || undefined}

+ 60 - 1
src/utils/workflowNodeTypes.ts

@@ -28,11 +28,70 @@ export function isWorkflowUnlockLikeValidationType(t: string | undefined | null)
   return isWorkflowUnlockSchemeType(t) || isWorkflowUnlockCoLockType(t);
 }
 
-/** 共锁、解除共锁:业务提交表单不强制(右侧面板不标必填,与作业流程校验一致) */
+/**
+ * 共锁:提交表单不强制(右侧面板不标红星)。
+ * 解除共锁:提交表单仍不强制,但「选择共锁任务」单独必填(见 validateWorkflowDesignerSubmitTabForSave)。
+ */
 export function isWorkflowCoLockFamilySubmitFormOptional(t: string | undefined | null): boolean {
   return isWorkflowCoLockType(t) || isWorkflowUnlockCoLockType(t);
 }
 
+/** 流程设计器保存前:合并节点 data 与右侧面板缓存后的快照(由调用方组装) */
+export type WorkflowDesignerMergedNodeInput = {
+  id: string;
+  reactFlowType: string;
+  mergedData: Record<string, unknown>;
+};
+
+/**
+ * 流程设计器保存前校验「提交表单」页签:
+ * - 除创建作业、共锁/解除共锁族外,提交表单必填;
+ * - 共锁须选择关联上锁任务(isolationNodeUuid,与面板红星一致);
+ * - 解锁须选择关联上锁任务(isolationNodeUuid);
+ * - 解除共锁须选择关联共锁任务(isolationNodeUuid)。
+ */
+export function validateWorkflowDesignerSubmitTabForSave(
+  items: WorkflowDesignerMergedNodeInput[]
+): string | null {
+  for (const { id, reactFlowType, mergedData } of items) {
+    const dataType = String(mergedData?.type ?? reactFlowType ?? '');
+    if (dataType === 'createJob') continue;
+
+    if (!isWorkflowCoLockFamilySubmitFormOptional(dataType)) {
+      const fid = mergedData.formId ?? mergedData.submitForm;
+      if (fid === undefined || fid === null || String(fid).trim() === '') {
+        const name = String(mergedData.label ?? id);
+        return `节点「${name}」未选择提交表单,请在「提交表单」页签中完善`;
+      }
+    }
+
+    if (isWorkflowCoLockType(dataType)) {
+      const iso = mergedData.isolationNodeUuid;
+      if (iso === undefined || iso === null || String(iso).trim() === '') {
+        const name = String(mergedData.label ?? id);
+        return `节点「${name}」未选择上锁任务,请在「提交表单」页签中完善`;
+      }
+    }
+
+    if (isWorkflowUnlockSchemeType(dataType)) {
+      const iso = mergedData.isolationNodeUuid;
+      if (iso === undefined || iso === null || String(iso).trim() === '') {
+        const name = String(mergedData.label ?? id);
+        return `节点「${name}」未选择上锁任务,请在「提交表单」页签中完善`;
+      }
+    }
+
+    if (isWorkflowUnlockCoLockType(dataType)) {
+      const iso = mergedData.isolationNodeUuid;
+      if (iso === undefined || iso === null || String(iso).trim() === '') {
+        const name = String(mergedData.label ?? id);
+        return `节点「${name}」未选择共锁任务,请在「提交表单」页签中完善`;
+      }
+    }
+  }
+  return null;
+}
+
 /** 共锁/解除共锁仅随「上锁挂牌」存在,流程 JSON 与接口 isolationType 固定为字典值 1(字符串) */
 export const WORKFLOW_CO_LOCK_FAMILY_ISOLATION_TYPE = '1';