Преглед на файлове

流程设计功能拆分上锁 共锁 解除共锁 解锁流程

wyn преди 1 седмица
родител
ревизия
eaa6423f72

+ 14 - 4
src/components/Dashboard.tsx

@@ -16,6 +16,12 @@ import dayjs, { Dayjs } from 'dayjs';
 import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
 import { useTranslation } from 'react-i18next';
 import FormUploadField from './FormUploadField';
+import {
+  isWorkflowCoLockType,
+  isWorkflowLockSchemeType,
+  isWorkflowUnlockCoLockType,
+  isWorkflowUnlockSchemeType,
+} from '../utils/workflowNodeTypes';
 
 // 辅助函数:安全地将值转换为 dayjs 对象
 const safeToDayjs = (value: any): dayjs.Dayjs | null => {
@@ -1244,8 +1250,10 @@ export default function Dashboard() {
       
       // 根据节点类型决定是否需要获取表单
       const nodeType = detailDataWithWorkInfo.type || detailDataWithWorkInfo.nodeType || '';
-      const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
-      const isReleaseIsolation = nodeType === 'releaseIsolation' || nodeType === '解除隔离';
+      const isIsolation =
+        isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+      const isReleaseIsolation =
+        isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
       const isReturnLock = nodeType === 'returnLock';
       const isolationType = String(detailDataWithWorkInfo?.isolationType ?? '').trim();
       const isBlindOrDismantle = isolationType === '0' || isolationType === '2'; // 0=盲板 2=拆除
@@ -2500,8 +2508,10 @@ export default function Dashboard() {
                 const isApproved = taskDetailData?.approvalStatus === 'approved';
                 const nodeType = String(taskDetailData?.type || taskDetailData?.nodeType || '').trim();
                 const isReview = nodeType === 'review';
-                const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
-                const isReleaseIsolation = nodeType === 'releaseIsolation' || nodeType === '解除隔离';
+                const isIsolation =
+                  isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+                const isReleaseIsolation =
+                  isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
                 const isReturnLock = nodeType === 'returnLock';
                 const hasFormIdForCurrentNode = taskDetailData?.formId !== undefined && taskDetailData?.formId !== null && (
                   typeof taskDetailData.formId === 'number' || (typeof taskDetailData.formId === 'string' && String(taskDetailData.formId).trim() !== '')

+ 14 - 4
src/components/ExecutorDashboard.tsx

@@ -13,6 +13,12 @@ import { useTranslation } from 'react-i18next';
 import dayjs, { Dayjs } from 'dayjs';
 import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
 import FormUploadField from './FormUploadField';
+import {
+  isWorkflowCoLockType,
+  isWorkflowLockSchemeType,
+  isWorkflowUnlockCoLockType,
+  isWorkflowUnlockSchemeType,
+} from '../utils/workflowNodeTypes';
 import { dictDataApi, DictDataVO } from '../api/DictData';
 // @ts-ignore
 import urgecy1Icon from '../assets/urgecy1.png';
@@ -1090,8 +1096,10 @@ export default function ExecutorDashboard() {
       
       // 根据节点类型决定是否需要获取表单
       const nodeType = detailDataWithWorkInfo.type || detailDataWithWorkInfo.nodeType || '';
-      const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
-      const isReleaseIsolation = nodeType === 'releaseIsolation' || nodeType === '解除隔离';
+      const isIsolation =
+        isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+      const isReleaseIsolation =
+        isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
       const isReturnLock = nodeType === 'returnLock';
       const isolationType = String(detailDataWithWorkInfo?.isolationType ?? '').trim();
       const isBlindOrDismantle = isolationType === '0' || isolationType === '2'; // 0=盲板 2=拆除
@@ -1822,8 +1830,10 @@ export default function ExecutorDashboard() {
                 const isApproved = taskDetailData?.approvalStatus === 'approved' || taskDetailData?.approvalStatus === 3;
                 const nodeType = String(taskDetailData?.type || taskDetailData?.nodeType || '').trim();
                 const isReview = nodeType === 'review';
-                const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
-                const isReleaseIsolation = nodeType === 'releaseIsolation' || nodeType === '解除隔离';
+                const isIsolation =
+                  isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+                const isReleaseIsolation =
+                  isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
                 const isReturnLock = nodeType === 'returnLock';
                 const hasFormIdForCurrentNode = taskDetailData?.formId !== undefined && taskDetailData?.formId !== null && (
                   typeof taskDetailData.formId === 'number' || (typeof taskDetailData.formId === 'string' && String(taskDetailData.formId).trim() !== '')

Файловите разлики са ограничени, защото са твърде много
+ 511 - 168
src/components/IsolationWork.tsx


+ 14 - 4
src/components/MyTask.tsx

@@ -16,6 +16,12 @@ import urgecy2Icon from '../assets/urgecy2.png';
 import urgecy3Icon from '../assets/urgecy3.png';
 import { useTranslation } from 'react-i18next';
 import FormUploadField from './FormUploadField';
+import {
+  isWorkflowCoLockType,
+  isWorkflowLockSchemeType,
+  isWorkflowUnlockCoLockType,
+  isWorkflowUnlockSchemeType,
+} from '../utils/workflowNodeTypes';
 import './IsolationWorkListTable.css';
 
 // 辅助函数:安全地将值转换为 dayjs 对象
@@ -1061,8 +1067,10 @@ export default function MyTask() {
       // 根据节点类型决定是否需要获取表单
       // isolation、releaseIsolation 和 returnLock 不需要表单,其他类型(review 和其他)需要根据 formId 获取表单
       const nodeType = detailDataWithWorkInfo.type || detailDataWithWorkInfo.nodeType || '';
-      const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
-      const isReleaseIsolation = nodeType === 'releaseIsolation' || nodeType === '解除隔离';
+      const isIsolation =
+        isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+      const isReleaseIsolation =
+        isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
       const isReturnLock = nodeType === 'returnLock';
       const isolationType = String(detailDataWithWorkInfo?.isolationType ?? '').trim();
       const isBlindOrDismantle = isolationType === '0' || isolationType === '2'; // 0=盲板 2=拆除
@@ -2066,8 +2074,10 @@ export default function MyTask() {
                 console.log('MyTask: 渲染内容区域 - 节点类型', nodeType, 'detailData:', detailData);
                
                 const isReview = nodeType === 'review';
-                const isIsolation = nodeType === 'isolation';
-                const isReleaseIsolation = nodeType === 'releaseIsolation';
+                const isIsolation =
+                  isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+                const isReleaseIsolation =
+                  isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
                 const isReturnLock = nodeType === 'returnLock';
                 
                 console.log('MyTask: 节点类型判断结果', { 

+ 408 - 116
src/components/ProcessDesigner.tsx

@@ -36,6 +36,7 @@ import {
   SafetyOutlined,
   UnlockOutlined,
   LockOutlined,
+  KeyOutlined,
   CheckSquareOutlined,
   CloseOutlined,
   MenuFoldOutlined,
@@ -148,6 +149,15 @@ import { getFormPage, getForm, FormVO } from '../api/bpm/form';
 import { setConfAndFields2, FormCreateData } from '../utils/formCreate';
 import { dictDataApi, DictDataVO } from '../api/DictData';
 import FormUploadField from './FormUploadField';
+import {
+  isWorkflowCoLockType,
+  isWorkflowIsolationSchemePanelType,
+  isWorkflowLockSchemeType,
+  isWorkflowUnlockCoLockType,
+  isWorkflowUnlockParentMatch,
+  isWorkflowUnlockSchemeType,
+  resolveWorkflowPaletteType,
+} from '../utils/workflowNodeTypes';
 
 /** 抄送部门/人员 ID:支持数组、逗号分隔字符串、JSON 数组字符串,兼容旧 cc* 单选 */
 function parseWorkflowCopyIds(multi: unknown, legacySingle: unknown): string[] {
@@ -236,9 +246,9 @@ const nodeConfigs = [
     borderColor: 'border-purple-100',
   },
   {
-    type: 'isolation',
-    label: '隔离/方案',
-    icon: SafetyOutlined,
+    type: 'lock',
+    label: '上锁',
+    icon: LockOutlined,
     bgColor: 'bg-white',
     bgColorCustom: '#ffffff',
     iconColor: 'text-red-600',
@@ -246,8 +256,8 @@ const nodeConfigs = [
     borderColor: 'border-red-100',
   },
   {
-    type: 'releaseIsolation',
-    label: '解除隔离',
+    type: 'unlock',
+    label: '解',
     icon: UnlockOutlined,
     bgColor: 'bg-white',
     bgColorCustom: '#ffffff',
@@ -255,6 +265,26 @@ const nodeConfigs = [
     iconColorCustom: '#38bdf8',
     borderColor: 'border-yellow-100',
   },
+  {
+    type: 'coLock',
+    label: '共锁',
+    icon: TeamOutlined,
+    bgColor: 'bg-white',
+    bgColorCustom: '#ffffff',
+    iconColor: 'text-red-600',
+    iconColorCustom: '#f87272',
+    borderColor: 'border-red-100',
+  },
+  {
+    type: 'unlockCoLock',
+    label: '解除共锁',
+    icon: KeyOutlined,
+    bgColor: 'bg-white',
+    bgColorCustom: '#ffffff',
+    iconColor: 'text-yellow-600',
+    iconColorCustom: '#38bdf8',
+    borderColor: 'border-yellow-100',
+  },
   {
     type: 'returnLock',
     label: '还锁',
@@ -286,6 +316,7 @@ const availableIcons = [
   { name: 'SafetyOutlined', component: SafetyOutlined, label: '安全' },
   { name: 'UnlockOutlined', component: UnlockOutlined, label: '解锁' },
   { name: 'LockOutlined', component: LockOutlined, label: '锁定' },
+  { name: 'KeyOutlined', component: KeyOutlined, label: '钥匙' },
   { name: 'CheckSquareOutlined', component: CheckSquareOutlined, label: '完成' },
   { name: 'HomeOutlined', component: HomeOutlined, label: '首页' },
   { name: 'UserOutlined', component: UserOutlined, label: '用户' },
@@ -384,6 +415,7 @@ const iconNameMap: Record<string, React.ComponentType<any>> = {
   SafetyOutlined,
   UnlockOutlined,
   LockOutlined,
+  KeyOutlined,
   CheckSquareOutlined,
   HomeOutlined,
   UserOutlined,
@@ -479,13 +511,14 @@ function CustomNode({ data, selected, id }: any) {
   let Icon = FileTextOutlined;
   let config = null;
   let iconImagePath: string | null = null;
+  const paletteType = resolveWorkflowPaletteType(data.type);
   
   // 检查是否是图片文件名(如 "1000.png")
   if (data.icon && /^\d+\.png$/.test(data.icon)) {
     // 是图片文件名,获取对应的图片路径
     iconImagePath = getIconPathByFileName(data.icon);
     // 尝试找到对应的配置(保持颜色等样式)
-    config = nodeConfigs.find(c => c.type === data.type) || nodeConfigs[0];
+    config = nodeConfigs.find(c => c.type === paletteType) || nodeConfigs[0];
   } else if (data.icon && iconNameMap[data.icon]) {
     // 使用自定义图标组件
     Icon = iconNameMap[data.icon];
@@ -493,11 +526,11 @@ function CustomNode({ data, selected, id }: any) {
     config = nodeConfigs.find(c => c.icon === Icon);
     if (!config) {
       // 如果找不到配置,使用默认配置
-      config = nodeConfigs.find(c => c.type === data.type) || nodeConfigs[0];
+      config = nodeConfigs.find(c => c.type === paletteType) || nodeConfigs[0];
     }
   } else {
     // 使用节点类型对应的图标
-    config = nodeConfigs.find(c => c.type === data.type);
+    config = nodeConfigs.find(c => c.type === paletteType);
     Icon = config?.icon || FileTextOutlined;
   }
   // 从节点ID中提取序号,或使用data中的nodeId
@@ -661,6 +694,14 @@ function ReleaseIsolationNode(props: any) {
   return <CustomNode {...props} />;
 }
 
+function CoLockNode(props: any) {
+  return <CustomNode {...props} />;
+}
+
+function UnlockCoLockNode(props: any) {
+  return <CustomNode {...props} />;
+}
+
 function ReturnLockNode(props: any) {
   return <CustomNode {...props} />;
 }
@@ -675,8 +716,12 @@ const nodeTypes = {
   confirm: ConfirmNode,
   review: ReviewNode,
   inputInfo: InputInfoNode,
+  lock: IsolationNode,
   isolation: IsolationNode,
+  unlock: ReleaseIsolationNode,
   releaseIsolation: ReleaseIsolationNode,
+  coLock: CoLockNode,
+  unlockCoLock: UnlockCoLockNode,
   returnLock: ReturnLockNode,
   complete: CompleteNode,
 };
@@ -824,6 +869,10 @@ const getIconCategoryByNodeType = (nodeType: string): string => {
     'confirm': '确认',
     'review': '审核',
     'inputInfo': '录入',
+    'lock': '能量隔离',
+    'unlock': '解除隔离',
+    'coLock': '能量隔离',
+    'unlockCoLock': '解除隔离',
     'isolation': '能量隔离',
     'releaseIsolation': '解除隔离',
     'returnLock': '结束',
@@ -1521,8 +1570,10 @@ export default function ProcessDesigner() {
       };
       // 缓存配置
       saveNodeCache(selectedNode.id, updatedData);
-      const isIsolationNode = selectedNode.data?.type === 'isolation';
-      const isolationSyncPayload = isIsolationNode
+      const isLockSchemeSource = isWorkflowLockSchemeType(selectedNode.data?.type);
+      const isCoLockSchemeSource = isWorkflowCoLockType(selectedNode.data?.type);
+      const isolationSyncPayload =
+        isLockSchemeSource || isCoLockSchemeSource
         ? {
             isolationType: nodeConfig.isolationType,
             isolationPoints: nodeConfig.isolationPoints,
@@ -1532,7 +1583,17 @@ export default function ProcessDesigner() {
             workerUserId: responsibleValue,
           }
         : null;
-      // 实时更新节点显示;若当前为隔离/方案节点,则把修改内容同步到所有已关联的解除隔离节点
+      /** 上锁节点变更时同步到「共锁」节点:不覆盖共锁节点自身的共锁人 */
+      const lockSchemeToCoLockPayload = isLockSchemeSource
+        ? {
+            isolationType: nodeConfig.isolationType,
+            isolationPoints: nodeConfig.isolationPoints,
+            lockPerson: nodeConfig.lockPerson,
+            responsible: responsibleValue,
+            workerUserId: responsibleValue,
+          }
+        : null;
+      // 实时更新节点显示;若当前为上锁/共锁节点,则把修改内容同步到已关联的解锁/解除共锁节点
       setNodes((nds) =>
         nds.map((node) => {
           if (node.id === selectedNode.id) {
@@ -1540,7 +1601,8 @@ export default function ProcessDesigner() {
           }
           if (
             isolationSyncPayload &&
-            node.data?.type === 'releaseIsolation' &&
+            isWorkflowUnlockSchemeType(node.data?.type) &&
+            isLockSchemeSource &&
             String(node.data?.isolationNodeUuid) === String(selectedNode.id)
           ) {
             const releaseData = {
@@ -1550,6 +1612,34 @@ export default function ProcessDesigner() {
             saveNodeCache(node.id, releaseData);
             return { ...node, data: releaseData };
           }
+          if (
+            isolationSyncPayload &&
+            isWorkflowUnlockCoLockType(node.data?.type) &&
+            isCoLockSchemeSource &&
+            String(node.data?.isolationNodeUuid) === String(selectedNode.id)
+          ) {
+            const releaseData = {
+              ...node.data,
+              ...isolationSyncPayload,
+            };
+            saveNodeCache(node.id, releaseData);
+            return { ...node, data: releaseData };
+          }
+          if (
+            lockSchemeToCoLockPayload &&
+            isWorkflowCoLockType(node.data?.type) &&
+            isLockSchemeSource &&
+            String(node.data?.isolationNodeUuid) === String(selectedNode.id)
+          ) {
+            const prevCo = Array.isArray(node.data?.coLockPersons) ? node.data.coLockPersons : [];
+            const releaseData = {
+              ...node.data,
+              ...lockSchemeToCoLockPayload,
+              coLockPersons: prevCo,
+            };
+            saveNodeCache(node.id, releaseData);
+            return { ...node, data: releaseData };
+          }
           return node;
         })
       );
@@ -1997,9 +2087,9 @@ export default function ProcessDesigner() {
     const nodeData = node.data || {};
     const cache = loadNodeCache(node.id);
     const source = cache || nodeData;
-    const config = nodeConfigs.find(c => c.type === source.type);
+    const config = nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(source.type));
     
-    // 如果是解除隔离节点且已选择了隔离节点,则从隔离节点获取配置
+    // 如果是解锁/解除共锁节点且已选择了关联方案节点,则从关联节点获取配置
     let isolationType = source.isolationType || '';
     let isolationPointsData = source.isolationPoints || [];
     let lockPerson = source.lockPerson || '';
@@ -2011,8 +2101,15 @@ export default function ProcessDesigner() {
         ? String(source.responsible)
         : '';
     
-    if (source.type === 'releaseIsolation' && source.isolationNodeUuid) {
-      const targetNode = nodes.find(n => n.id === source.isolationNodeUuid && n.data?.type === 'isolation');
+    if (
+      (isWorkflowUnlockSchemeType(source.type) || isWorkflowUnlockCoLockType(source.type)) &&
+      source.isolationNodeUuid
+    ) {
+      const targetNode = nodes.find(
+        (n) =>
+          n.id === source.isolationNodeUuid &&
+          isWorkflowUnlockParentMatch(source.type, n.data?.type)
+      );
       if (targetNode) {
         // 优先从缓存中读取,缓存中没有则从 node.data 读取
         const targetCache = loadNodeCache(targetNode.id);
@@ -2028,6 +2125,24 @@ export default function ProcessDesigner() {
             ? String(targetSource.responsible)
             : '';
       }
+    } else if (isWorkflowCoLockType(source.type) && source.isolationNodeUuid) {
+      const targetNode = nodes.find(
+        (n) =>
+          n.id === source.isolationNodeUuid && isWorkflowLockSchemeType(n.data?.type)
+      );
+      if (targetNode) {
+        const targetCache = loadNodeCache(targetNode.id);
+        const targetSource = targetCache || targetNode.data || {};
+        isolationType = targetSource.isolationType || '';
+        isolationPointsData = targetSource.isolationPoints || [];
+        lockPerson = targetSource.lockPerson || '';
+        responsible = (targetSource.workerUserId !== undefined && targetSource.workerUserId !== null && targetSource.workerUserId !== '')
+          ? String(targetSource.workerUserId)
+          : (targetSource.responsible !== undefined && targetSource.responsible !== null && targetSource.responsible !== '')
+            ? String(targetSource.responsible)
+            : '';
+        coLockPersons = Array.isArray(source.coLockPersons) ? source.coLockPersons : [];
+      }
     }
     
     setNodeConfig({
@@ -3117,7 +3232,7 @@ export default function ProcessDesigner() {
         if (updatedNode) {
           const cache = loadNodeCache(updatedNode.id);
           const source = cache || updatedNode.data || {};
-          const config = nodeConfigs.find(c => c.type === source.type);
+          const config = nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(source.type));
           setNodeConfig({
             nodeName: source.label || config?.label || '',
             nodeIcon: source.icon || source.type || '',
@@ -3175,6 +3290,10 @@ export default function ProcessDesigner() {
       confirm: '该节点为作业确认,为"上一节点"操作分配确认模式及确认人员权限',
       review: '该节点为作业审核,为"上一节点"操作分配审核模式及审核人员权限',
       inputInfo: '该节点为作业录入提交,可提交信息或图片,主要为"信息确认"',
+      lock: '该节点为作业隔离类型选择,主要包括盲板,上锁挂牌,拆除等。',
+      unlock: '该节点在解锁时与所选上锁节点保持隔离方式等信息一致。',
+      coLock: '该节点需选择流程中的上锁任务并配置共锁人;隔离信息随所选上锁任务一致。',
+      unlockCoLock: '该节点在解除共锁时与所选共锁节点保持隔离方式等信息一致。',
       isolation: '该节点为作业隔离类型选择,主要包括盲板,上锁挂牌,拆除等。',
       releaseIsolation: '该节点为作业隔离类型选择,主要包括盲板,上锁挂牌,拆除等。',
       returnLock: '该节点为还锁操作,归还钥匙,确认隔离操作完成',
@@ -3183,6 +3302,11 @@ export default function ProcessDesigner() {
     return descriptions[type] || '该节点的功能描述。';
   };
 
+  const selectedDataType = selectedNode?.data?.type || '';
+  const isUnlockDesignerReadOnly =
+    isWorkflowUnlockSchemeType(selectedDataType) || isWorkflowUnlockCoLockType(selectedDataType);
+  const isSchemeDesignerPanel = isWorkflowIsolationSchemePanelType(selectedDataType);
+
   return (
     <div className="h-screen w-screen flex flex-col bg-gray-50">
       {/* 顶部工具栏 */}
@@ -3344,7 +3468,9 @@ export default function ProcessDesigner() {
             <Background variant={BackgroundVariant.Lines} gap={16} size={1} color="#e5e7eb" />
             <MiniMap
               nodeColor={(node) => {
-                const config = nodeConfigs.find(c => c.type === node.type);
+                const config = nodeConfigs.find(
+                  (c) => c.type === resolveWorkflowPaletteType(node.type as string)
+                );
                 if (config?.color === 'bg-blue-500') return '#3b82f6';
                 if (config?.color === 'bg-green-500') return '#10b981';
                 if (config?.color === 'bg-orange-500') return '#f97316';
@@ -3413,8 +3539,8 @@ export default function ProcessDesigner() {
                         <div className="space-y-5">
                           {/* 节点名称 */}
                           <div>
-                            {/* 描述 - 创建作业、确认、审核、录入信息、隔离方案和解除隔离节点显示在节点名称顶部 */}
-                            {(selectedNode.data?.type === 'createJob' || selectedNode.data?.type === 'confirm' || selectedNode.data?.type === 'review' || selectedNode.data?.type === 'inputInfo' || selectedNode.data?.type === 'isolation' || selectedNode.data?.type === 'releaseIsolation' || selectedNode.data?.type === 'returnLock' || selectedNode.data?.type === 'complete') && (
+                            {/* 描述 - 创建作业、确认、审核、录入信息、上锁/解锁/共锁类节点、还锁与结束节点显示在节点名称顶部 */}
+                            {(selectedNode.data?.type === 'createJob' || selectedNode.data?.type === 'confirm' || selectedNode.data?.type === 'review' || selectedNode.data?.type === 'inputInfo' || isSchemeDesignerPanel || selectedNode.data?.type === 'returnLock' || selectedNode.data?.type === 'complete') && (
                               <div className="text-xs text-gray-500 leading-relaxed mb-2">
                                 {getNodeDescription(selectedNode.data?.type || '')}
                               </div>
@@ -3427,12 +3553,12 @@ export default function ProcessDesigner() {
                             </label>
                             {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && (
                               <p className="text-xs text-gray-500 mb-2.5 leading-relaxed">
-                                (默认名称: {nodeConfigs.find(c => c.type === selectedNode.data?.type)?.label || '节点名称'},可根据需求调整)
+                                (默认名称: {nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(selectedNode.data?.type))?.label || '节点名称'},可根据需求调整)
                               </p>
                             )}
                             {selectedNode.data?.type === 'createJob' && (
                               <p className="text-xs text-gray-500 mb-2.5 leading-relaxed">
-                                (默认名称: {nodeConfigs.find(c => c.type === selectedNode.data?.type)?.label || '节点名称'},可根据需求调整)
+                                (默认名称: {nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(selectedNode.data?.type))?.label || '节点名称'},可根据需求调整)
                               </p>
                             )}
                             <Input
@@ -3575,7 +3701,7 @@ export default function ProcessDesigner() {
                                   let Icon = FileTextOutlined;
                                   let config = null;
                                     const iconType = selectedNode.data?.type;
-                                    config = nodeConfigs.find(c => c.type === iconType);
+                                    config = nodeConfigs.find(c => c.type === resolveWorkflowPaletteType(iconType));
                                     Icon = config?.icon || FileTextOutlined;
                                   
                                   return (
@@ -3644,8 +3770,8 @@ export default function ProcessDesigner() {
                             </div>
                           )}
 
-                          {/* 负责人 - 创建作业、隔离方案和解除隔离节点不显示 */}
-                          {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && selectedNode.data?.type !== 'review' && selectedNode.data?.type !== 'isolation' && selectedNode.data?.type !== 'releaseIsolation' && (
+                          {/* 负责人 - 创建作业与上锁/解锁/共锁类方案节点不显示 */}
+                          {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && selectedNode.data?.type !== 'review' && !isSchemeDesignerPanel && (
                             <div>
                               <label className="block text-sm font-medium text-gray-700 mb-2">
                                 负责人
@@ -3669,8 +3795,8 @@ export default function ProcessDesigner() {
                             </div>
                           )}
 
-                          {/* 备注 - 创建作业、确认、审核、录入信息、隔离方案、解除隔离、还锁和完成节点不显示 */}
-                          {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && selectedNode.data?.type !== 'review' && selectedNode.data?.type !== 'inputInfo' && selectedNode.data?.type !== 'isolation' && selectedNode.data?.type !== 'releaseIsolation' && selectedNode.data?.type !== 'returnLock' && selectedNode.data?.type !== 'complete' && (
+                          {/* 备注 - 创建作业、确认、审核、录入信息、上锁/解锁/共锁类、还锁和完成节点不显示 */}
+                          {selectedNode.data?.type !== 'createJob' && selectedNode.data?.type !== 'confirm' && selectedNode.data?.type !== 'review' && selectedNode.data?.type !== 'inputInfo' && !isSchemeDesignerPanel && selectedNode.data?.type !== 'returnLock' && selectedNode.data?.type !== 'complete' && (
                             <div>
                               <label className="block text-sm font-medium text-gray-700 mb-2">
                                 备注
@@ -3753,22 +3879,27 @@ export default function ProcessDesigner() {
                             </div>
                           </div>
 
-                          {/* 隔离/方案 和 解除隔离 节点特有的字段 */}
-                          {(selectedNode.data?.type === 'isolation' || selectedNode.data?.type === 'releaseIsolation') && (
+                          {/* 上锁/解锁/共锁/解除共锁 节点特有的字段 */}
+                          {isSchemeDesignerPanel && (
                             <>
-                              {/* 解除隔离节点:选择隔离节点 */}
-                              {selectedNode.data?.type === 'releaseIsolation' && (
+                              {(isWorkflowUnlockSchemeType(selectedDataType) ||
+                                isWorkflowUnlockCoLockType(selectedDataType)) && (
                                 <div>
                                   <label className="block text-sm font-medium text-gray-700 mb-2">
-                                    选择隔离节点
+                                    {isWorkflowUnlockCoLockType(selectedDataType)
+                                      ? '选择共锁任务'
+                                      : '选择上锁任务'}
                                   </label>
                                   <Select
                                     value={nodeConfig.isolationNodeUuid || undefined}
                                     onChange={(value) => {
                                       setNodeConfig((prev) => {
-                                        const target = nodes.find(n => n.id === value && n.data?.type === 'isolation');
+                                        const target = nodes.find(
+                                          (n) =>
+                                            n.id === value &&
+                                            isWorkflowUnlockParentMatch(selectedDataType, n.data?.type)
+                                        );
                                         if (target) {
-                                          // 优先从缓存中读取,缓存中没有则从 node.data 读取
                                           const cache = loadNodeCache(target.id);
                                           const source = cache || target.data || {};
                                           return {
@@ -3787,13 +3918,16 @@ export default function ProcessDesigner() {
                                         return { ...prev, isolationNodeUuid: value || '' };
                                       });
                                     }}
-                                    placeholder="请选择隔离节点"
+                                    placeholder={
+                                      isWorkflowUnlockCoLockType(selectedDataType)
+                                        ? '请选择共锁任务'
+                                        : '请选择上锁任务'
+                                    }
                                     className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                     allowClear
                                   >
                                     {(() => {
-                                      // 从当前「解除隔离」节点向前(沿连线反向)查找所有上游节点,再从中筛出「隔离/方案」节点
-                                      // 这样即使中间隔了多个节点(如 隔离 -> A -> B -> 解除隔离),下拉也能出现对应隔离节点
+                                      const wantCoLockParent = isWorkflowUnlockCoLockType(selectedDataType);
                                       const currentNodeId = selectedNode?.id;
                                       if (!currentNodeId) return [];
 
@@ -3803,7 +3937,7 @@ export default function ProcessDesigner() {
                                       while (queue.length > 0) {
                                         const nextQueue: string[] = [];
                                         for (const id of queue) {
-                                          edges.forEach(edge => {
+                                          edges.forEach((edge) => {
                                             if (String(edge.target) !== id) return;
                                             const src = String(edge.source);
                                             if (upstreamIds.has(src)) return;
@@ -3814,13 +3948,23 @@ export default function ProcessDesigner() {
                                         queue = nextQueue;
                                       }
 
-                                      const isolationNodes = nodes.filter(n => n.data?.type === 'isolation');
-                                      const nodesToShow = isolationNodes.filter(n => upstreamIds.has(String(n.id)));
-                                      const nodesToShowFinal = nodesToShow.length > 0 ? nodesToShow : isolationNodes;
+                                      const schemeNodes = nodes.filter((n) =>
+                                        wantCoLockParent
+                                          ? isWorkflowCoLockType(n.data?.type)
+                                          : isWorkflowLockSchemeType(n.data?.type)
+                                      );
+                                      const nodesToShow = schemeNodes.filter((n) =>
+                                        upstreamIds.has(String(n.id))
+                                      );
+                                      const nodesToShowFinal =
+                                        nodesToShow.length > 0 ? nodesToShow : schemeNodes;
+                                      const defaultLabel = wantCoLockParent
+                                        ? nodeConfigs.find((c) => c.type === 'coLock')?.label || '共锁'
+                                        : nodeConfigs.find((c) => c.type === 'lock')?.label || '上锁';
 
-                                      return nodesToShowFinal.map(node => (
+                                      return nodesToShowFinal.map((node) => (
                                         <Select.Option key={node.id} value={node.id}>
-                                          {node.data?.label || nodeConfigs.find(c => c.type === 'isolation')?.label || '隔离/方案'}
+                                          {node.data?.label || defaultLabel}
                                         </Select.Option>
                                       ));
                                     })()}
@@ -3828,122 +3972,270 @@ export default function ProcessDesigner() {
                                 </div>
                               )}
 
-                              {/* 隔离方式 - 隔离方案节点可编辑,解除隔离节点只读(根据选择的隔离节点自动填充) */}
-                              <div>
-                                <label className="block text-sm font-medium text-gray-700 mb-2">
-                                  隔离方式
-                                </label>
-                                <Select
-                                  value={nodeConfig.isolationType || undefined}
-                                  onChange={(value) =>
-                                    setNodeConfig({ ...nodeConfig, isolationType: value || '' })
-                                  }
-                                  placeholder="请选择隔离方式"
-                                  className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
-                                  allowClear
-                                  disabled={selectedNode.data?.type === 'releaseIsolation'}
-                                >
-                                  {isolationTypeDictList.map((item) => (
-                                    <Select.Option key={item.id} value={item.value}>
-                                      {item.label}
-                                    </Select.Option>
-                                  ))}
-                                </Select>
-                              </div>
-
-                              {/* 隔离点选择(可多选)- 隔离方案节点显示,解除隔离只读展示 */}
-                              {(selectedNode.data?.type === 'isolation' || selectedNode.data?.type === 'releaseIsolation') && (
+                              {isWorkflowUnlockCoLockType(selectedDataType) &&
+                                !!nodeConfig.isolationNodeUuid && (
                                 <div>
                                   <label className="block text-sm font-medium text-gray-700 mb-2">
-                                    隔离点选择(可多选)
+                                    共锁人(可多选)
                                   </label>
                                   <Select
                                     mode="multiple"
-                                    value={nodeConfig.isolationPoints}
+                                    value={nodeConfig.coLockPersons}
                                     onChange={(value) =>
-                                      setNodeConfig({ ...nodeConfig, isolationPoints: value })
+                                      setNodeConfig({ ...nodeConfig, coLockPersons: value })
                                     }
-                                    placeholder="请选择隔离点"
+                                    placeholder="请选择共锁人"
                                     className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
                                     allowClear
-                                    disabled={selectedNode.data?.type === 'releaseIsolation'}
+                                    disabled={isUnlockDesignerReadOnly}
                                   >
-                                    {isolationPoints.map((point: any, index) => (
-                                      <Select.Option key={point.pointId || point.id || `point-${index}`} value={point.pointId || point.id}>{point.pointName}</Select.Option>
+                                    {colockerUsers.map((user) => (
+                                      <Select.Option key={user.id} value={user.id}>
+                                        {user.nickname || user.username}
+                                      </Select.Option>
                                     ))}
                                   </Select>
                                 </div>
                               )}
 
-                              {/* 盲板和拆除:显示负责人 */}
-                              {/* 字典值:0=盲板,1=上锁挂牌,2=拆除 */}
-                              {(nodeConfig.isolationType === '0' || nodeConfig.isolationType === '2') && (
-                                <div>
-                                  <label className="block text-sm font-medium text-gray-700 mb-2">
-                                    负责人
-                                  </label>
-                                  <Select
-                                    value={nodeConfig.responsible || undefined}
-                                    onChange={(value) =>
-                                      setNodeConfig({ ...nodeConfig, responsible: value || '' })
-                                    }
-                                    placeholder="请选择负责人"
-                                    className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
-                                    allowClear
-                                    disabled={selectedNode.data?.type === 'releaseIsolation'}
-                                  >
-                                    {drawerUsers.map(user => (
-                                      <Select.Option key={user.id} value={user.id}>{user.nickname || user.username}</Select.Option>
-                                    ))}
-                                  </Select>
-                                  <p className="text-xs text-gray-500 mt-1.5 leading-relaxed">
-                                    对该任务或步骤节点进行处理的人员,若不选则需要在创建作业时进行选择。
-                                  </p>
-                                </div>
+                              {isWorkflowCoLockType(selectedDataType) && (
+                                <>
+                                  <div>
+                                    <label className="block text-sm font-medium text-gray-700 mb-2">
+                                      选择上锁任务{' '}
+                                      <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                                    </label>
+                                    <Select
+                                      value={nodeConfig.isolationNodeUuid || undefined}
+                                      onChange={(value) => {
+                                        setNodeConfig((prev) => {
+                                          const target = nodes.find(
+                                            (n) =>
+                                              n.id === value && isWorkflowLockSchemeType(n.data?.type)
+                                          );
+                                          if (target) {
+                                            const cache = loadNodeCache(target.id);
+                                            const source = cache || target.data || {};
+                                            const resp =
+                                              source.workerUserId !== undefined &&
+                                              source.workerUserId !== null &&
+                                              source.workerUserId !== ''
+                                                ? String(source.workerUserId)
+                                                : source.responsible || '';
+                                            return {
+                                              ...prev,
+                                              isolationNodeUuid: value || '',
+                                              isolationType: source.isolationType || '',
+                                              isolationPoints: source.isolationPoints || [],
+                                              isolationNode: Array.isArray(source.isolationNode)
+                                                ? source.isolationNode
+                                                : source.isolationNode
+                                                  ? [source.isolationNode]
+                                                  : [],
+                                              responsible: resp,
+                                              lockPerson: source.lockPerson || '',
+                                              coLockPersons: prev.coLockPersons,
+                                            };
+                                          }
+                                          return { ...prev, isolationNodeUuid: value || '' };
+                                        });
+                                      }}
+                                      placeholder="请选择上锁任务"
+                                      className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
+                                      allowClear
+                                    >
+                                      {(() => {
+                                        const currentNodeId = selectedNode?.id;
+                                        if (!currentNodeId) return [];
+
+                                        let queue = [String(currentNodeId)];
+                                        const upstreamIds = new Set<string>();
+
+                                        while (queue.length > 0) {
+                                          const nextQueue: string[] = [];
+                                          for (const id of queue) {
+                                            edges.forEach((edge) => {
+                                              if (String(edge.target) !== id) return;
+                                              const src = String(edge.source);
+                                              if (upstreamIds.has(src)) return;
+                                              upstreamIds.add(src);
+                                              nextQueue.push(src);
+                                            });
+                                          }
+                                          queue = nextQueue;
+                                        }
+
+                                        const schemeNodes = nodes.filter((n) =>
+                                          isWorkflowLockSchemeType(n.data?.type)
+                                        );
+                                        const nodesToShow = schemeNodes.filter((n) =>
+                                          upstreamIds.has(String(n.id))
+                                        );
+                                        const nodesToShowFinal =
+                                          nodesToShow.length > 0 ? nodesToShow : schemeNodes;
+                                        const defaultLabel =
+                                          nodeConfigs.find((c) => c.type === 'lock')?.label || '上锁';
+
+                                        return nodesToShowFinal.map((node) => (
+                                          <Select.Option key={node.id} value={node.id}>
+                                            {node.data?.label || defaultLabel}
+                                          </Select.Option>
+                                        ));
+                                      })()}
+                                    </Select>
+                                  </div>
+                                  <div>
+                                    <label className="block text-sm font-medium text-gray-700 mb-2">
+                                      共锁人(可多选)
+                                    </label>
+                                    <Select
+                                      mode="multiple"
+                                      value={nodeConfig.coLockPersons}
+                                      onChange={(value) =>
+                                        setNodeConfig({ ...nodeConfig, coLockPersons: value })
+                                      }
+                                      placeholder="请选择共锁人"
+                                      className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
+                                      allowClear
+                                    >
+                                      {colockerUsers.map((user) => (
+                                        <Select.Option key={user.id} value={user.id}>
+                                          {user.nickname || user.username}
+                                        </Select.Option>
+                                      ))}
+                                    </Select>
+                                  </div>
+                                </>
                               )}
 
-                              {/* 上锁挂牌:显示上锁人和共锁人 */}
-                              {nodeConfig.isolationType === '1' && (
+                              {!isWorkflowCoLockType(selectedDataType) &&
+                                !isWorkflowUnlockCoLockType(selectedDataType) && (
                                 <>
                                   <div>
                                     <label className="block text-sm font-medium text-gray-700 mb-2">
-                                      上锁人
+                                      隔离方式
                                     </label>
                                     <Select
-                                      value={nodeConfig.lockPerson || undefined}
+                                      value={nodeConfig.isolationType || undefined}
                                       onChange={(value) =>
-                                        setNodeConfig({ ...nodeConfig, lockPerson: value || '' })
+                                        setNodeConfig({ ...nodeConfig, isolationType: value || '' })
                                       }
-                                      placeholder="请选择上锁人"
+                                      placeholder="请选择隔离方式"
                                       className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
                                       allowClear
-                                      disabled={selectedNode.data?.type === 'releaseIsolation'}
+                                      disabled={isUnlockDesignerReadOnly}
                                     >
-                                      {lockerUsers.map(user => (
-                                        <Select.Option key={user.id} value={user.id}>{user.nickname || user.username}</Select.Option>
+                                      {isolationTypeDictList.map((item) => (
+                                        <Select.Option key={item.id} value={item.value}>
+                                          {item.label}
+                                        </Select.Option>
                                       ))}
                                     </Select>
                                   </div>
+
                                   <div>
                                     <label className="block text-sm font-medium text-gray-700 mb-2">
-                                      共锁人(可多选)
+                                      隔离点选择(可多选)
                                     </label>
                                     <Select
                                       mode="multiple"
-                                      value={nodeConfig.coLockPersons}
+                                      value={nodeConfig.isolationPoints}
                                       onChange={(value) =>
-                                        setNodeConfig({ ...nodeConfig, coLockPersons: value })
+                                        setNodeConfig({ ...nodeConfig, isolationPoints: value })
                                       }
-                                      placeholder="请选择共锁人"
+                                      placeholder="请选择隔离点"
                                       className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
                                       allowClear
-                                      disabled={selectedNode.data?.type === 'releaseIsolation'}
+                                      disabled={isUnlockDesignerReadOnly}
                                     >
-                                      {colockerUsers.map(user => (
-                                        <Select.Option key={user.id} value={user.id}>{user.nickname || user.username}</Select.Option>
+                                      {isolationPoints.map((point: any, index) => (
+                                        <Select.Option
+                                          key={point.pointId || point.id || `point-${index}`}
+                                          value={point.pointId || point.id}
+                                        >
+                                          {point.pointName}
+                                        </Select.Option>
                                       ))}
                                     </Select>
                                   </div>
+
+                                  {(nodeConfig.isolationType === '0' || nodeConfig.isolationType === '2') && (
+                                    <div>
+                                      <label className="block text-sm font-medium text-gray-700 mb-2">
+                                        负责人
+                                      </label>
+                                      <Select
+                                        value={nodeConfig.responsible || undefined}
+                                        onChange={(value) =>
+                                          setNodeConfig({ ...nodeConfig, responsible: value || '' })
+                                        }
+                                        placeholder="请选择负责人"
+                                        className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
+                                        allowClear
+                                        disabled={isUnlockDesignerReadOnly}
+                                      >
+                                        {drawerUsers.map((user) => (
+                                          <Select.Option key={user.id} value={user.id}>
+                                            {user.nickname || user.username}
+                                          </Select.Option>
+                                        ))}
+                                      </Select>
+                                      <p className="text-xs text-gray-500 mt-1.5 leading-relaxed">
+                                        对该任务或步骤节点进行处理的人员,若不选则需要在创建作业时进行选择。
+                                      </p>
+                                    </div>
+                                  )}
+
+                                  {nodeConfig.isolationType === '1' && (
+                                    <>
+                                      <div>
+                                        <label className="block text-sm font-medium text-gray-700 mb-2">
+                                          上锁人
+                                        </label>
+                                        <Select
+                                          value={nodeConfig.lockPerson || undefined}
+                                          onChange={(value) =>
+                                            setNodeConfig({ ...nodeConfig, lockPerson: value || '' })
+                                          }
+                                          placeholder="请选择上锁人"
+                                          className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!h-10"
+                                          allowClear
+                                          disabled={isUnlockDesignerReadOnly}
+                                        >
+                                          {lockerUsers.map((user) => (
+                                            <Select.Option key={user.id} value={user.id}>
+                                              {user.nickname || user.username}
+                                            </Select.Option>
+                                          ))}
+                                        </Select>
+                                      </div>
+                                      {!isWorkflowLockSchemeType(selectedDataType) &&
+                                        !isWorkflowUnlockSchemeType(selectedDataType) && (
+                                        <div>
+                                          <label className="block text-sm font-medium text-gray-700 mb-2">
+                                            共锁人(可多选)
+                                          </label>
+                                          <Select
+                                            mode="multiple"
+                                            value={nodeConfig.coLockPersons}
+                                            onChange={(value) =>
+                                              setNodeConfig({ ...nodeConfig, coLockPersons: value })
+                                            }
+                                            placeholder="请选择共锁人"
+                                            className="w-full [&_.ant-select-selector]:!rounded-lg [&_.ant-select-selector]:!min-h-10"
+                                            allowClear
+                                            disabled={isUnlockDesignerReadOnly}
+                                          >
+                                            {colockerUsers.map((user) => (
+                                              <Select.Option key={user.id} value={user.id}>
+                                                {user.nickname || user.username}
+                                              </Select.Option>
+                                            ))}
+                                          </Select>
+                                        </div>
+                                      )}
+                                    </>
+                                  )}
                                 </>
                               )}
                             </>

+ 14 - 4
src/components/TaskManagement.tsx

@@ -16,6 +16,12 @@ import urgecy2Icon from '../assets/urgecy2.png';
 import urgecy3Icon from '../assets/urgecy3.png';
 import { useTranslation } from 'react-i18next';
 import FormUploadField from './FormUploadField';
+import {
+  isWorkflowCoLockType,
+  isWorkflowLockSchemeType,
+  isWorkflowUnlockCoLockType,
+  isWorkflowUnlockSchemeType,
+} from '../utils/workflowNodeTypes';
 import './IsolationWorkListTable.css';
 
 // 辅助函数:安全地将值转换为 dayjs 对象
@@ -1098,8 +1104,10 @@ export default function TaskManagement() {
       // 根据节点类型决定是否需要获取表单
       // isolation、releaseIsolation 和 returnLock 不需要表单,其他类型(review 和其他)需要根据 formId 获取表单
       const nodeType = detailDataWithWorkInfo.type || detailDataWithWorkInfo.nodeType || '';
-      const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
-      const isReleaseIsolation = nodeType === 'releaseIsolation' || nodeType === '解除隔离';
+      const isIsolation =
+        isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+      const isReleaseIsolation =
+        isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
       const isReturnLock = nodeType === 'returnLock';
       const isolationType = String(detailDataWithWorkInfo?.isolationType ?? '').trim();
       const isBlindOrDismantle = isolationType === '0' || isolationType === '2'; // 0=盲板 2=拆除
@@ -2276,8 +2284,10 @@ export default function TaskManagement() {
                 }
 
                 const isReview = nodeType === 'review';
-                const isIsolation = nodeType === 'isolation';
-                const isReleaseIsolation = nodeType === 'releaseIsolation';
+                const isIsolation =
+                  isWorkflowLockSchemeType(nodeType) || isWorkflowCoLockType(nodeType);
+                const isReleaseIsolation =
+                  isWorkflowUnlockSchemeType(nodeType) || isWorkflowUnlockCoLockType(nodeType);
                 const isReturnLock = nodeType === 'returnLock';
                 
                 console.log('TaskManagement: 节点类型判断结果', { 

+ 39 - 5
src/components/WorkJobArchiveReport.tsx

@@ -1,7 +1,7 @@
 import React, { useState, useEffect, useRef } from 'react';
 import { useSearchParams, useNavigate } from 'react-router-dom';
 import { toast } from 'sonner';
-import { ArrowLeft, Check, CheckCircle2, Clock, FileDown, FileText, Flag, Lock, Loader2, Printer, Settings, Shield } from 'lucide-react';
+import { ArrowLeft, Check, CheckCircle2, Clock, FileDown, FileText, Flag, Lock, Loader2, Printer, Settings, Shield, Unlock, Users } from 'lucide-react';
 import ReactFlow, { useNodesState, useEdgesState, Background, BackgroundVariant, Handle, Position, ReactFlowProvider } from 'reactflow';
 import type { Node as FlowNode, Edge, NodeTypes } from 'reactflow';
 import 'reactflow/dist/style.css';
@@ -65,8 +65,12 @@ const getNodeIcon = (type: string, status: 'completed' | 'in_progress' | 'pendin
     case 'review':
     case 'confirm': return <Settings className={`w-5 h-5 ${iconClass}`} />;
     case 'inputInfo': return <FileText className={`w-5 h-5 ${iconClass}`} />;
-    case 'isolation': return <Shield className={`w-5 h-5 ${iconClass}`} />;
-    case 'releaseIsolation':
+    case 'lock':
+    case 'isolation': return <Lock className={`w-5 h-5 ${iconClass}`} />;
+    case 'unlock':
+    case 'releaseIsolation': return <Unlock className={`w-5 h-5 ${iconClass}`} />;
+    case 'coLock': return <Users className={`w-5 h-5 ${iconClass}`} />;
+    case 'unlockCoLock': return <Unlock className={`w-5 h-5 ${iconClass}`} />;
     case 'returnLock': return <Lock className={`w-5 h-5 ${iconClass}`} />;
     case 'complete': return <Flag className={`w-5 h-5 ${iconClass}`} />;
     default: return <FileText className={`w-5 h-5 ${iconClass}`} />;
@@ -134,8 +138,12 @@ const archiveNodeTypes: NodeTypes = {
   confirm: SimpleCustomNode,
   review: SimpleCustomNode,
   inputInfo: SimpleCustomNode,
+  lock: SimpleCustomNode,
   isolation: SimpleCustomNode,
+  unlock: SimpleCustomNode,
   releaseIsolation: SimpleCustomNode,
+  coLock: SimpleCustomNode,
+  unlockCoLock: SimpleCustomNode,
   returnLock: SimpleCustomNode,
   complete: SimpleCustomNode,
   default: SimpleCustomNode,
@@ -332,7 +340,20 @@ function parseDesignContentLikeJobDetail(
 
     const nodeMap = new Map<string, { nodeName?: string; approvalStatus?: string | number; type?: string; nodeIcon?: string; id?: number; [key: string]: any }>();
     workflowWorkNodeDOList.forEach((n) => { if (n.uuid) nodeMap.set(String(n.uuid), n); });
-    const validTypes = new Set(['createJob', 'confirm', 'review', 'inputInfo', 'isolation', 'releaseIsolation', 'returnLock', 'complete']);
+    const validTypes = new Set([
+      'createJob',
+      'confirm',
+      'review',
+      'inputInfo',
+      'lock',
+      'isolation',
+      'unlock',
+      'releaseIsolation',
+      'coLock',
+      'unlockCoLock',
+      'returnLock',
+      'complete',
+    ]);
     const convertedNodes: FlowNode[] = rawNodes
       .map((node: any) => {
         const nodeId = node.uuid ?? node.id;
@@ -394,7 +415,20 @@ function buildFlowFromNodeList(
   if (list.length === 0) return { nodes: [], edges: [] };
   const sorted = [...list].sort((a, b) => (a.createTime ?? 0) - (b.createTime ?? 0));
   const gap = 180;
-  const validTypes = new Set(['createJob', 'confirm', 'review', 'inputInfo', 'isolation', 'releaseIsolation', 'returnLock', 'complete']);
+  const validTypes = new Set([
+    'createJob',
+    'confirm',
+    'review',
+    'inputInfo',
+    'lock',
+    'isolation',
+    'unlock',
+    'releaseIsolation',
+    'coLock',
+    'unlockCoLock',
+    'returnLock',
+    'complete',
+  ]);
   const nodes: FlowNode[] = sorted
     .filter((n) => n.uuid)
     .map((n, i) => {

+ 152 - 53
src/components/WorkJobDetail.tsx

@@ -5,7 +5,6 @@ import {
   CheckCircle2, 
   Settings, 
   FileText, 
-  Shield, 
   Lock, 
   Flag, 
   Star,
@@ -14,7 +13,9 @@ import {
   List,
   Plus,
   Minus,
-  Loader2
+  Loader2,
+  Unlock,
+  Users,
 } from 'lucide-react';
 import { Timeline, Badge, Card, Collapse, Tag, Descriptions, Button } from 'antd';
 import { CheckCircleOutlined, ClockCircleOutlined, SyncOutlined } from '@ant-design/icons';
@@ -43,6 +44,14 @@ import { getFormPage } from '../api/bpm/form';
 import { segregationPointApi } from '../api/spm';
 import { Select, Input, Checkbox, Tabs, Tooltip } from 'antd';
 import { useTranslation } from 'react-i18next';
+import {
+  isWorkflowCoLockType,
+  isWorkflowIsolationSchemePanelType,
+  isWorkflowLockSchemeType,
+  isWorkflowUnlockCoLockType,
+  isWorkflowUnlockParentMatch,
+  isWorkflowUnlockSchemeType,
+} from '../utils/workflowNodeTypes';
 
 interface WorkflowStep {
   id: string;
@@ -296,8 +305,12 @@ const nodeTypes: NodeTypes = {
   confirm: SimpleCustomNode,
   review: SimpleCustomNode,
   inputInfo: SimpleCustomNode,
+  lock: SimpleCustomNode,
   isolation: SimpleCustomNode,
+  unlock: SimpleCustomNode,
   releaseIsolation: SimpleCustomNode,
+  coLock: SimpleCustomNode,
+  unlockCoLock: SimpleCustomNode,
   returnLock: SimpleCustomNode,
   complete: SimpleCustomNode,
 };
@@ -318,9 +331,16 @@ const getNodeIcon = (type: string, status: 'completed' | 'in_progress' | 'pendin
       return <Settings className={`w-5 h-5 ${iconClass}`} />;
     case 'inputInfo':
       return <FileText className={`w-5 h-5 ${iconClass}`} />;
+    case 'lock':
     case 'isolation':
-      return <Shield className={`w-5 h-5 ${iconClass}`} />;
+      return <Lock className={`w-5 h-5 ${iconClass}`} />;
+    case 'unlock':
     case 'releaseIsolation':
+      return <Unlock className={`w-5 h-5 ${iconClass}`} />;
+    case 'coLock':
+      return <Users className={`w-5 h-5 ${iconClass}`} />;
+    case 'unlockCoLock':
+      return <Unlock className={`w-5 h-5 ${iconClass}`} />;
     case 'returnLock':
       return <Lock className={`w-5 h-5 ${iconClass}`} />;
     case 'complete':
@@ -1362,7 +1382,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
   
   // 如果是解除隔离节点,需要从对应的隔离方案节点获取数据
   let isolationNodeData: any = null;
-  if (nodeData.type === 'releaseIsolation') {
+  if (isWorkflowUnlockSchemeType(nodeData.type) || isWorkflowUnlockCoLockType(nodeData.type)) {
     // 优先使用从隔离方案节点复制的数据(在 loadDesignContentToReactFlow 中已处理)
     isolationNodeData = nodeData.isolationNodeData;
     
@@ -1370,7 +1390,11 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
     if (!isolationNodeData) {
       const isolationNodeUuid = workNode?.isolationNodeUuid || nodeData.isolationNodeUuid || '';
       if (isolationNodeUuid) {
-        const isolationWorkNode = workflowWorkNodeDOList.find(n => n.uuid === isolationNodeUuid && n.type === 'isolation');
+        const isolationWorkNode = workflowWorkNodeDOList.find(
+          (n) =>
+            n.uuid === isolationNodeUuid &&
+            isWorkflowUnlockParentMatch(nodeData.type, n.type)
+        );
         if (isolationWorkNode) {
           isolationNodeData = {
             isolationType: isolationWorkNode.isolationType || '',
@@ -1488,7 +1512,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
           </div>
 
           {/* 负责人 - 根据节点类型显示 */}
-          {nodeData.type !== 'createJob' && nodeData.type !== 'isolation' && nodeData.type !== 'releaseIsolation' && (
+          {nodeData.type !== 'createJob' && !isWorkflowIsolationSchemePanelType(nodeData.type) && (
             <div>
               <label className="block text-sm font-medium text-gray-700 mb-2">
                 {t('workJobDetail.responsible')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
@@ -1503,7 +1527,7 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
           )}
 
           {/* 备注 */}
-          {nodeData.type !== 'createJob' && nodeData.type !== 'confirm' && nodeData.type !== 'review' && nodeData.type !== 'inputInfo' && nodeData.type !== 'isolation' && nodeData.type !== 'releaseIsolation' && nodeData.type !== 'returnLock' && nodeData.type !== 'complete' && (
+          {nodeData.type !== 'createJob' && nodeData.type !== 'confirm' && nodeData.type !== 'review' && nodeData.type !== 'inputInfo' && !isWorkflowIsolationSchemePanelType(nodeData.type) && nodeData.type !== 'returnLock' && nodeData.type !== 'complete' && (
             <div>
               <label className="block text-sm font-medium text-gray-700 mb-2">
                 {t('workJobDetail.remark')}
@@ -1538,74 +1562,131 @@ const NodeInfoPanel: React.FC<NodeInfoPanelProps> = ({
             </div>
 
             {/* 隔离/方案 和 解除隔离 节点特有的字段 */}
-            {(nodeData.type === 'isolation' || nodeData.type === 'releaseIsolation') && (
+            {isWorkflowIsolationSchemePanelType(nodeData.type) && (
               <>
                 {/* 解除隔离节点:选择隔离节点 */}
-                {nodeData.type === 'releaseIsolation' && nodeConfig.isolationNodeUuid && (
+                {(isWorkflowUnlockSchemeType(nodeData.type) || isWorkflowUnlockCoLockType(nodeData.type)) &&
+                  nodeConfig.isolationNodeUuid && (
                   <div>
                     <label className="block text-sm font-medium text-gray-700 mb-2">
-                      {t('workJobDetail.selectIsolationNode')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                      {isWorkflowUnlockCoLockType(nodeData.type)
+                        ? t('workJobDetail.selectCoLockTask')
+                        : t('workJobDetail.selectLockTask')}{' '}
+                      <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                     </label>
                     <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
                       {(() => {
-                        const isolationNode = workflowWorkNodeDOList.find(n => n.uuid === nodeConfig.isolationNodeUuid && n.type === 'isolation');
+                        const isolationNode = workflowWorkNodeDOList.find(
+                          (n) =>
+                            n.uuid === nodeConfig.isolationNodeUuid &&
+                            isWorkflowUnlockParentMatch(nodeData.type, n.type)
+                        );
                         return isolationNode?.nodeName || nodeConfig.isolationNodeUuid || '-';
                       })()}
                     </div>
                   </div>
                 )}
 
-                {/* 隔离方式 */}
-                <div>
-                  <label className="block text-sm font-medium text-gray-700 mb-2">
-                    {t('workJobDetail.isolationType')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
-                  </label>
-                  <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
-                    {getIsolationTypeName()}
+                {/* 解除共锁:选定共锁任务后仅展示共锁人 */}
+                {isWorkflowUnlockCoLockType(nodeData.type) && nodeConfig.isolationNodeUuid && (
+                  <div>
+                    <label className="block text-sm font-medium text-gray-700 mb-2">
+                      {t('workJobDetail.colockerPerson')}
+                    </label>
+                    <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
+                      {getCoLockPersonNames()}
+                    </div>
                   </div>
-                </div>
+                )}
 
-                {/* 隔离点选择(可多选) */}
-                <div>
-                  <label className="block text-sm font-medium text-gray-700 mb-2">
-                    {t('workJobDetail.isolationPointsSelect')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
-                  </label>
-                  <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
-                    {getIsolationPointNames()}
+                {/* 共锁节点:关联上锁任务 + 共锁人 */}
+                {isWorkflowCoLockType(nodeData.type) && nodeConfig.isolationNodeUuid && (
+                  <div>
+                    <label className="block text-sm font-medium text-gray-700 mb-2">
+                      {t('workJobDetail.selectLockTask')}{' '}
+                      <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                    </label>
+                    <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
+                      {(() => {
+                        const lockNode = workflowWorkNodeDOList.find(
+                          (n) =>
+                            n.uuid === nodeConfig.isolationNodeUuid &&
+                            isWorkflowLockSchemeType(n.type)
+                        );
+                        return lockNode?.nodeName || nodeConfig.isolationNodeUuid || '-';
+                      })()}
+                    </div>
                   </div>
-                </div>
-
-                {/* 盲板和拆除:显示负责人 */}
-                {(nodeConfig.isolationType === '0' || nodeConfig.isolationType === '2') && (
+                )}
+                {isWorkflowCoLockType(nodeData.type) && (
                   <div>
                     <label className="block text-sm font-medium text-gray-700 mb-2">
-                      {t('workJobDetail.responsible')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                      {t('workJobDetail.colockerPerson')}
                     </label>
                     <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
-                      {getResponsibleName()}
+                      {getCoLockPersonNames()}
                     </div>
                   </div>
                 )}
 
-                {/* 上锁挂牌:显示上锁人和共锁人 */}
-                {nodeConfig.isolationType === '1' && (
+                {!isWorkflowCoLockType(nodeData.type) && !isWorkflowUnlockCoLockType(nodeData.type) && (
                   <>
+                    {/* 隔离方式 */}
                     <div>
                       <label className="block text-sm font-medium text-gray-700 mb-2">
-                        {t('workJobDetail.lockerPerson')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                        {t('workJobDetail.isolationType')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                       </label>
                       <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
-                        {getLockPersonName()}
+                        {getIsolationTypeName()}
                       </div>
                     </div>
+
+                    {/* 隔离点选择(可多选) */}
                     <div>
                       <label className="block text-sm font-medium text-gray-700 mb-2">
-                        {t('workJobDetail.colockerPerson')}
+                        {t('workJobDetail.isolationPointsSelect')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
                       </label>
                       <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
-                        {getCoLockPersonNames()}
+                        {getIsolationPointNames()}
                       </div>
                     </div>
+
+                    {/* 盲板和拆除:显示负责人 */}
+                    {(nodeConfig.isolationType === '0' || nodeConfig.isolationType === '2') && (
+                      <div>
+                        <label className="block text-sm font-medium text-gray-700 mb-2">
+                          {t('workJobDetail.responsible')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                        </label>
+                        <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
+                          {getResponsibleName()}
+                        </div>
+                      </div>
+                    )}
+
+                    {/* 上锁挂牌:上锁节点不展示共锁人 */}
+                    {nodeConfig.isolationType === '1' && (
+                      <>
+                        <div>
+                          <label className="block text-sm font-medium text-gray-700 mb-2">
+                            {t('workJobDetail.lockerPerson')} <span className="text-red-500" style={{ color: '#ef4444' }}>*</span>
+                          </label>
+                          <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
+                            {getLockPersonName()}
+                          </div>
+                        </div>
+                        {!isWorkflowLockSchemeType(nodeData.type) &&
+                          !isWorkflowUnlockSchemeType(nodeData.type) && (
+                          <div>
+                            <label className="block text-sm font-medium text-gray-700 mb-2">
+                              {t('workJobDetail.colockerPerson')}
+                            </label>
+                            <div className="text-sm text-gray-900 bg-gray-50 px-3 py-2 rounded-lg border border-gray-200">
+                              {getCoLockPersonNames()}
+                            </div>
+                          </div>
+                        )}
+                      </>
+                    )}
                   </>
                 )}
               </>
@@ -2029,14 +2110,21 @@ export default function WorkJobDetail() {
         // 节点ID(用于显示):优先从 workNode 获取,如果没有则从 nodeData 获取
         const displayNodeId = workNode?.id ? String(workNode.id) : (nodeData.nodeId || '');
         
-        // 如果是解除隔离节点,需要从对应的隔离方案节点复制数据
+        // 解锁/解除共锁:从对应的上锁或共锁节点复制数据
         let isolationNodeData: any = null;
-        if (nodeType === 'releaseIsolation') {
+        if (
+          isWorkflowUnlockSchemeType(nodeType) ||
+          isWorkflowUnlockCoLockType(nodeType)
+        ) {
           // 获取隔离节点UUID(从 workNode 或 nodeData 中获取)
           const isolationNodeUuid = workNode?.isolationNodeUuid || nodeData.isolationNodeUuid || '';
           if (isolationNodeUuid) {
-            // 找到对应的隔离方案节点
-            const isolationWorkNode = workflowWorkNodeDOList.find(n => n.uuid === isolationNodeUuid && n.type === 'isolation');
+            // 找到对应的方案源节点
+            const isolationWorkNode = workflowWorkNodeDOList.find(
+              (n) =>
+                n.uuid === isolationNodeUuid &&
+                isWorkflowUnlockParentMatch(nodeType, n.type)
+            );
             if (isolationWorkNode) {
               // 复制隔离方案节点的数据
               isolationNodeData = {
@@ -2535,23 +2623,23 @@ export default function WorkJobDetail() {
       },
       {
         id: '4',
-        type: 'isolation',
-        title: '隔离/方案',
+        type: 'lock',
+        title: '上锁',
         assigneeName: '赵六',
         status: 'pending',
-        icon: <Shield className="w-5 h-5" />,
+        icon: <Lock className="w-5 h-5" />,
       },
       {
         id: '5',
-        type: 'lock',
-        title: '取锁/共锁',
+        type: 'unlock',
+        title: '锁',
         assigneeName: '钱七',
         status: 'pending',
-        icon: <Lock className="w-5 h-5" />,
+        icon: <Unlock className="w-5 h-5" />,
       },
       {
         id: '6',
-        type: 'unlock',
+        type: 'returnLock',
         title: '还锁',
         assigneeName: '孙八',
         status: 'pending',
@@ -2581,12 +2669,17 @@ export default function WorkJobDetail() {
     let executorName = '';
     let isolationWorkNode: WorkflowWorkNodeDO | undefined = undefined;
     
-    // 如果是解除隔离节点且没有执行人,尝试从对应的隔离方案节点获取
-    if (type === 'releaseIsolation' && !workNode.workerUserId && !workNode.workerUserName) {
+    // 解锁/解除共锁且无执行人时,从关联方案源节点获取
+    if (
+      (isWorkflowUnlockSchemeType(type) || isWorkflowUnlockCoLockType(type)) &&
+      !workNode.workerUserId &&
+      !workNode.workerUserName
+    ) {
       const isolationNodeUuid = workNode.isolationNodeUuid;
       if (isolationNodeUuid && jobDetail?.workflowWorkNodeDOList) {
         isolationWorkNode = jobDetail.workflowWorkNodeDOList.find(
-          n => n.uuid === isolationNodeUuid && n.type === 'isolation'
+          (n) =>
+            n.uuid === isolationNodeUuid && isWorkflowUnlockParentMatch(type, n.type)
         );
         if (isolationWorkNode) {
           // 使用隔离方案节点的数据
@@ -2630,7 +2723,13 @@ export default function WorkJobDetail() {
       return `发起人: ${executorName}`;
     } else if (type === 'review' || type === 'confirm') {
       return `审核人: ${executorName}`;
-    } else if (type === 'isolation' || type === 'releaseIsolation' || type === 'returnLock') {
+    } else if (
+      isWorkflowLockSchemeType(type) ||
+      isWorkflowCoLockType(type) ||
+      isWorkflowUnlockSchemeType(type) ||
+      isWorkflowUnlockCoLockType(type) ||
+      type === 'returnLock'
+    ) {
       return `操作人: ${executorName}`;
     } else {
       return `任务负责人: ${executorName}`;

+ 2 - 0
src/locales/en.json

@@ -1224,6 +1224,8 @@
     "submitForm": "Submit Form",
     "businessForm": "Business Form",
     "selectIsolationNode": "Select Isolation Node",
+    "selectLockTask": "Select Lock Task",
+    "selectCoLockTask": "Select Co-lock Task",
     "isolationType": "Isolation Type",
     "isolationPointsSelect": "Isolation Points (multi-select)",
     "lockerPerson": "Locker",

+ 2 - 0
src/locales/zh.json

@@ -1225,6 +1225,8 @@
     "submitForm": "提交表单",
     "businessForm": "业务表单",
     "selectIsolationNode": "选择隔离节点",
+    "selectLockTask": "选择上锁任务",
+    "selectCoLockTask": "选择共锁任务",
     "isolationType": "隔离方式",
     "isolationPointsSelect": "隔离点选择(可多选)",
     "lockerPerson": "上锁人",

Някои файлове не бяха показани, защото твърде много файлове са промени