Эх сурвалжийг харах

修复流程节点渲染重叠以及任务的弹框渲染自定义表单问题

pm 2 сар өмнө
parent
commit
47d51f3138

+ 8 - 0
src/Dashboard.tsx

@@ -1235,6 +1235,10 @@ export default function Dashboard() {
                                     key={subItem.key}
                                     onClick={() => {
                                       console.log('点击子菜单:', { menuKey: item.key, subMenuKey: subItem.key });
+                                      // 如果当前在个人中心页面,先关闭个人中心
+                                      if (showProfileSettings) {
+                                        setShowProfileSettings(false);
+                                      }
                                       // 如果当前在详情页,导航回对应的菜单页面
                                       if (location.pathname.startsWith('/lock-cabinet/detail')) {
                                         navigate('/dashboard');
@@ -1354,6 +1358,10 @@ export default function Dashboard() {
                     title={t('nav.notificationManagement', '通知管理')}
                     // title=""
                     onClick={() => {
+                      // 如果当前在个人中心页面,先关闭个人中心
+                      if (showProfileSettings) {
+                        setShowProfileSettings(false);
+                      }
                       const firstSubMenu = filteredSubMenuConfig['notificationManagement']?.[0]?.key || '';
                       // 使用 handleMenuChange 更新菜单状态并同步更新 URL hash
                       handleMenuChange('notificationManagement', firstSubMenu);

+ 87 - 61
src/components/Dashboard.tsx

@@ -1223,6 +1223,13 @@ export default function Dashboard() {
         (typeof detailDataWithWorkInfo.formData === 'object' && Object.keys(detailDataWithWorkInfo.formData).length > 0)
       );
       
+      // 当前节点无表单时清空表单 state,避免完成/结束节点误展示上一节点的自定义表单
+      if (!hasFormId && !(isApproved && hasFormData)) {
+        setFormData({ rule: [], option: {} });
+        setOriginalFields([]);
+        setOriginalConf('');
+      }
+      
       // 打开盲板/拆除详情时先清空设备编号和附件,后续若有 formData 再回填
       if (isIsolation || isReleaseIsolation) {
         const isolationType = String(detailDataWithWorkInfo.isolationType ?? '').trim();
@@ -1318,14 +1325,33 @@ export default function Dashboard() {
               taskDetailForm.setFieldsValue(convertedFormValues);
               console.log('Dashboard: 表单数据已回填(从 formData)', convertedFormValues);
             }, 100);
+            // 有 conf/fields 时也回显 deviceNumber、attachments;attachments 可能是 JSON 字符串
+            const normAttachments = (att: any): any[] => {
+              if (att == null) return []; if (Array.isArray(att)) return att;
+              if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return [];
+            };
+            const listInConf = normAttachments(parsedFormData.attachments).length ? normAttachments(parsedFormData.attachments) : normAttachments((detailDataWithWorkInfo as any).attachments);
+            const deviceNumInConf = parsedFormData.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+            const hasDeviceInConf = deviceNumInConf !== undefined && deviceNumInConf !== null;
+            if (hasDeviceInConf || listInConf.length > 0) {
+              setIsolationDeviceNumber(deviceNumInConf ?? '');
+              setIsolationFileList(
+                listInConf.map((item: any, idx: number) => ({
+                  uid: `echo-${idx}-${item.url || item.name || idx}`,
+                  name: item.name || `文件${idx + 1}`,
+                  url: item.url || item.response,
+                  status: 'done',
+                  response: item.url || item.response,
+                }))
+              );
+            }
           } else if (
-            parsedFormData.deviceNumber !== undefined ||
-            (Array.isArray(parsedFormData.attachments) && parsedFormData.attachments.length > 0)
+            (() => { const d = parsedFormData.deviceNumber; const a = parsedFormData.attachments; if (d !== undefined && d !== null) return true; if (a == null) return false; if (Array.isArray(a) && a.length > 0) return true; if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) && p.length > 0; } catch { return false; } } return false; })()
           ) {
+            const listElse = (() => { const a = parsedFormData.attachments; if (a == null) return []; if (Array.isArray(a)) return a; if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) ? p : []; } catch { return []; } } return []; })();
             setIsolationDeviceNumber(parsedFormData.deviceNumber ?? '');
-            const list = Array.isArray(parsedFormData.attachments) ? parsedFormData.attachments : [];
             setIsolationFileList(
-              list.map((item: any, idx: number) => ({
+              listElse.map((item: any, idx: number) => ({
                 uid: `echo-${idx}-${item.url || item.name || idx}`,
                 name: item.name || `文件${idx + 1}`,
                 url: item.url || item.response,
@@ -1390,8 +1416,9 @@ export default function Dashboard() {
               
               setConfAndFields2(setFormData, conf, fields);
               
-              // 如果有表单数据值,回填到表单
-              if (detailDataWithWorkInfo.formData) {
+              // 仅任务已完成(approved)时回显表单数据;进行中一律不回显并清空表单
+              const shouldEchoFormData = detailDataWithWorkInfo.formData && isApproved;
+              if (shouldEchoFormData) {
                 try {
                   let formValues = typeof detailDataWithWorkInfo.formData === 'string' 
                     ? JSON.parse(detailDataWithWorkInfo.formData) 
@@ -1458,6 +1485,28 @@ export default function Dashboard() {
                 } catch (e) {
                   console.error('Dashboard: 解析表单数据失败', e);
                 }
+              } else if (!isApproved) {
+                // 任务进行中:清空自定义表单,避免带出历史或隔离方案等填写内容
+                setTimeout(() => { try { taskDetailForm.resetFields(); } catch (_) { /* ignore */ } }, 100);
+              }
+              // 隔离/解除隔离完成查看详情:从 formData 回显 deviceNumber、attachments(attachments 可能是 JSON 字符串)
+              if (isApproved && (isIsolation || isReleaseIsolation)) {
+                try {
+                  const normAtt = (att: any): any[] => { if (att == null) return []; if (Array.isArray(att)) return att; if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return []; };
+                  let deviceNum: any; let listF: any[];
+                  if (detailDataWithWorkInfo.formData) {
+                    const parsed = typeof detailDataWithWorkInfo.formData === 'string' ? JSON.parse(detailDataWithWorkInfo.formData) : detailDataWithWorkInfo.formData;
+                    listF = normAtt(parsed.attachments).length ? normAtt(parsed.attachments) : normAtt((detailDataWithWorkInfo as any).attachments);
+                    deviceNum = parsed.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+                  } else {
+                    deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+                    listF = normAtt((detailDataWithWorkInfo as any).attachments);
+                  }
+                  if ((deviceNum !== undefined && deviceNum !== null) || listF.length > 0) {
+                    setIsolationDeviceNumber(deviceNum ?? '');
+                    setIsolationFileList(listF.map((item: any, idx: number) => ({ uid: `echo-${idx}-${item.url || item.name || idx}`, name: item.name || `文件${idx + 1}`, url: item.url || item.response, status: 'done', response: item.url || item.response })));
+                  }
+                } catch (_) { /* ignore */ }
               }
             } else {
               console.warn('Dashboard: 表单详情缺少配置或字段', { conf: !!conf, fields: !!fields });
@@ -1472,6 +1521,17 @@ export default function Dashboard() {
         })();
       } else {
         setFormLoading(false);
+        if (isApproved && (isIsolation || isReleaseIsolation)) {
+          try {
+            const normAtt = (att: any): any[] => { if (att == null) return []; if (Array.isArray(att)) return att; if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return []; };
+            const deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+            const listF = normAtt((detailDataWithWorkInfo as any).attachments);
+            if ((deviceNum !== undefined && deviceNum !== null) || listF.length > 0) {
+              setIsolationDeviceNumber(deviceNum ?? '');
+              setIsolationFileList(listF.map((item: any, idx: number) => ({ uid: `echo-${idx}-${item.url || item.name || idx}`, name: item.name || `文件${idx + 1}`, url: item.url || item.response, status: 'done', response: item.url || item.response })));
+            }
+          } catch (_) { /* ignore */ }
+        }
       }
       
       // 回显审核意见
@@ -2435,6 +2495,9 @@ export default function Dashboard() {
                 const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
                 const isReleaseIsolation = nodeType === 'releaseIsolation' || 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() !== '')
+                );
 
                 // 隔离/方案节点、解除隔离节点和还锁节点
                 if (isIsolation || isReleaseIsolation || isReturnLock) {
@@ -2456,20 +2519,7 @@ export default function Dashboard() {
                     return (
                       <div key="isolation-device-form" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                         <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                          <Card
-                            style={{
-                              width: '100%',
-                              maxWidth: 500,
-                              margin: '0 auto',
-                              boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                              borderRadius: 16,
-                              border: '1px solid rgba(22, 119, 255, 0.15)',
-                              overflow: 'hidden',
-                              background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                            }}
-                            bodyStyle={{ padding: 0 }}
-                          >
-                            <div style={{ padding: '24px 32px 28px' }}>
+                          <div style={{ width: '100%' }}>
                               <div style={{ marginBottom: 26 }}>
                                 <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 14, fontWeight: 600, color: '#1f2937', marginBottom: 12 }}>
                                   <span style={{ width: 28, height: 28, borderRadius: 8, background: '#e6f4ff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
@@ -2576,8 +2626,7 @@ export default function Dashboard() {
                                   ) : null}
                                 </div>
                               )}
-                            </div>
-                          </Card>
+                          </div>
                         </div>
                         <div className="flex justify-end gap-3" style={{ padding: '20px 24px', borderTop: '1px solid rgba(22, 119, 255, 0.1)', flexShrink: 0, background: 'linear-gradient(0deg, #f0f7ff 0%, #fafcff 60%, #fff 100%)' }}>
                           {!isApproved ? (
@@ -2675,7 +2724,7 @@ export default function Dashboard() {
 
                   return (
                     <div style={{ flex: 1, minHeight: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                      <Card style={{ width: '100%', maxWidth: 500, textAlign: 'center', borderRadius: 16, boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)', border: '1px solid rgba(22, 119, 255, 0.15)', background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)' }} bodyStyle={{ padding: '48px 40px' }}>
+                      <div style={{ width: '100%', textAlign: 'center', padding: '24px 0' }}>
                         <div style={{ marginBottom: 28 }}>
                           <span style={{ width: 88, height: 88, borderRadius: 20, background: 'linear-gradient(135deg, #1677ff 0%, #4096ff 50%, #69b1ff 100%)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 8px 24px rgba(22, 119, 255, 0.35)' }}>
                             <LockOutlined style={{ fontSize: 42, color: '#fff' }} />
@@ -2686,33 +2735,20 @@ export default function Dashboard() {
                           <KeyOutlined />
                           <span>完成取锁、取钥匙后,任务将自动推进</span>
                         </div>
-                      </Card>
+                      </div>
                     </div>
                   );
                 }
 
-                // 审核类型节点 (review) - 样式与盲板/拆除、确认节点统一
+                // 审核类型节点 (review) - 表单直接放在外层,取消内置白框
                 if (isReview) {
                   return (
                     <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                       <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                        <Card
-                          style={{
-                            width: '100%',
-                            maxWidth: 500,
-                            margin: '0 auto',
-                            boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                            borderRadius: 16,
-                            border: '1px solid rgba(22, 119, 255, 0.15)',
-                            overflow: 'hidden',
-                            background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                          }}
-                          bodyStyle={{ padding: 0 }}
-                        >
-                          <div style={{ padding: '24px 32px 28px' }}>
+                        <div style={{ width: '100%' }}>
                             {formLoading ? (
                               <div className="py-8 text-center text-gray-500">表单加载中...</div>
-                            ) : formData.rule && formData.rule.length > 0 ? (
+                            ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
                               <div className="space-y-4">
                                 {(() => {
                                   const formConfig = formData.option?.formConfig || defaultFormConfig;
@@ -2739,14 +2775,13 @@ export default function Dashboard() {
                                 <div style={{ fontSize: 18, fontWeight: 600, color: '#1f2937', lineHeight: 1.6 }}>无需填写表单,直接点击底部按钮提交即可</div>
                               </div>
                             )}
-                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)', backgroundColor: 'rgba(22, 119, 255, 0.04)', padding: '16px', borderRadius: 12, border: '1px solid rgba(22, 119, 255, 0.1)' }}>
+                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)' }}>
                               <label className="text-sm font-medium text-gray-700 whitespace-nowrap pt-2" style={{ width: defaultFormConfig.labelWidth ? `${defaultFormConfig.labelWidth - 20}px` : '80px', textAlign: defaultFormConfig.labelPosition === 'left' ? 'left' : defaultFormConfig.labelPosition === 'right' ? 'right' : 'left', flexShrink: 0 }}>{t('form.reviewComment')}</label>
                               <div className="flex-1">
                                 <Input.TextArea rows={4} value={approvalComment} onChange={(e) => setApprovalComment(e.target.value)} placeholder={t('form.reviewCommentPlaceholder')} maxLength={500} showCount disabled={isApproved} readOnly={isApproved} style={{ borderRadius: 10, borderColor: '#d9e8ff' }} />
                               </div>
                             </div>
-                          </div>
-                        </Card>
+                        </div>
                       </div>
                       <div className="flex justify-end gap-3" style={{ padding: '20px 24px', borderTop: '1px solid rgba(22, 119, 255, 0.1)', flexShrink: 0, background: 'linear-gradient(0deg, #f0f7ff 0%, #fafcff 60%, #fff 100%)' }}>
                         <Button
@@ -2898,15 +2933,16 @@ export default function Dashboard() {
                   );
                 }
 
-                // 其他节点类型(需要根据 formId 获取表单)
+                // 其他节点类型(录入信息等)- 与审核节点统一样式:渐变背景、无内置白框
                 return (
                   <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
-                    <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
-                      {/* 自定义表单 */}
+                    <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
+                      <div style={{ width: '100%' }}>
+                      {/* 自定义表单:仅当前节点有 formId 时渲染 */}
                       {formLoading ? (
                         <div className="py-8 text-center text-gray-500">表单加载中...</div>
-                      ) : formData.rule && formData.rule.length > 0 ? (
-                        <div>
+                      ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
+                        <div className="space-y-4">
                           {(() => {
                             const formConfig = formData.option?.formConfig || defaultFormConfig;
                             const layoutColumns = formConfig.layoutColumns || 1;
@@ -2961,18 +2997,7 @@ export default function Dashboard() {
                             background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)',
                           }}
                         >
-                          <Card
-                            style={{
-                              width: '100%',
-                              maxWidth: 500,
-                              textAlign: 'center',
-                              borderRadius: 16,
-                              boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                              border: '1px solid rgba(22, 119, 255, 0.15)',
-                              background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                            }}
-                            bodyStyle={{ padding: '48px 40px' }}
-                          >
+                          <div style={{ width: '100%', textAlign: 'center', padding: '24px 0' }}>
                             <div style={{ marginBottom: 24 }}>
                               <span
                                 style={{
@@ -2995,9 +3020,10 @@ export default function Dashboard() {
                             <div style={{ marginTop: 12, fontSize: 13, color: '#69b1ff' }}>
                               确认无误后点击「完成」结束本节点
                             </div>
-                          </Card>
+                          </div>
                         </div>
                       )}
+                      </div>
                     </div>
                     {/* 底部按钮 - 取消 + 提交/完成 */}
                     <div

+ 87 - 61
src/components/ExecutorDashboard.tsx

@@ -1069,6 +1069,13 @@ export default function ExecutorDashboard() {
         (typeof detailDataWithWorkInfo.formData === 'object' && Object.keys(detailDataWithWorkInfo.formData).length > 0)
       );
       
+      // 当前节点无表单时清空表单 state,避免完成/结束节点误展示上一节点的自定义表单
+      if (!hasFormId && !(isApproved && hasFormData)) {
+        setFormData({ rule: [], option: {} });
+        setOriginalFields([]);
+        setOriginalConf('');
+      }
+      
       // 打开盲板/拆除详情时先清空设备编号和附件,后续若有 formData 再回填
       if (isIsolation || isReleaseIsolation) {
         const isolationType = String(detailDataWithWorkInfo.isolationType ?? '').trim();
@@ -1164,14 +1171,33 @@ export default function ExecutorDashboard() {
               taskDetailForm.setFieldsValue(convertedFormValues);
               console.log('ExecutorDashboard: 表单数据已回填(从 formData)', convertedFormValues);
             }, 100);
+            // 有 conf/fields 时也回显 deviceNumber、attachments;attachments 可能是 JSON 字符串
+            const normAttachments = (att: any): any[] => {
+              if (att == null) return []; if (Array.isArray(att)) return att;
+              if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return [];
+            };
+            const listInConf = normAttachments(parsedFormData.attachments).length ? normAttachments(parsedFormData.attachments) : normAttachments((detailDataWithWorkInfo as any).attachments);
+            const deviceNumInConf = parsedFormData.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+            const hasDeviceInConf = deviceNumInConf !== undefined && deviceNumInConf !== null;
+            if (hasDeviceInConf || listInConf.length > 0) {
+              setIsolationDeviceNumber(deviceNumInConf ?? '');
+              setIsolationFileList(
+                listInConf.map((item: any, idx: number) => ({
+                  uid: `echo-${idx}-${item.url || item.name || idx}`,
+                  name: item.name || `文件${idx + 1}`,
+                  url: item.url || item.response,
+                  status: 'done',
+                  response: item.url || item.response,
+                }))
+              );
+            }
           } else if (
-            parsedFormData.deviceNumber !== undefined ||
-            (Array.isArray(parsedFormData.attachments) && parsedFormData.attachments.length > 0)
+            (() => { const d = parsedFormData.deviceNumber; const a = parsedFormData.attachments; if (d !== undefined && d !== null) return true; if (a == null) return false; if (Array.isArray(a) && a.length > 0) return true; if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) && p.length > 0; } catch { return false; } } return false; })()
           ) {
+            const listElse = (() => { const a = parsedFormData.attachments; if (a == null) return []; if (Array.isArray(a)) return a; if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) ? p : []; } catch { return []; } } return []; })();
             setIsolationDeviceNumber(parsedFormData.deviceNumber ?? '');
-            const list = Array.isArray(parsedFormData.attachments) ? parsedFormData.attachments : [];
             setIsolationFileList(
-              list.map((item: any, idx: number) => ({
+              listElse.map((item: any, idx: number) => ({
                 uid: `echo-${idx}-${item.url || item.name || idx}`,
                 name: item.name || `文件${idx + 1}`,
                 url: item.url || item.response,
@@ -1236,8 +1262,9 @@ export default function ExecutorDashboard() {
               
               setConfAndFields2(setFormData, conf, fields);
               
-              // 如果有表单数据值,回填到表单
-              if (detailDataWithWorkInfo.formData) {
+              // 仅任务已完成(approved)时回显表单数据;进行中一律不回显并清空表单
+              const shouldEchoFormData = detailDataWithWorkInfo.formData && isApproved;
+              if (shouldEchoFormData) {
                 try {
                   let formValues = typeof detailDataWithWorkInfo.formData === 'string' 
                     ? JSON.parse(detailDataWithWorkInfo.formData) 
@@ -1304,6 +1331,28 @@ export default function ExecutorDashboard() {
                 } catch (e) {
                   console.error('ExecutorDashboard: 解析表单数据失败', e);
                 }
+              } else if (!isApproved) {
+                // 任务进行中:清空自定义表单,避免带出历史或隔离方案等填写内容
+                setTimeout(() => { try { taskDetailForm.resetFields(); } catch (_) { /* ignore */ } }, 100);
+              }
+              // 隔离/解除隔离完成查看详情:从 formData 或节点详情顶层回显(部分解除隔离节点仅顶层有数据)
+              if (isApproved && (isIsolation || isReleaseIsolation)) {
+                try {
+                  const normAtt = (att: any): any[] => { if (att == null) return []; if (Array.isArray(att)) return att; if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return []; };
+                  let deviceNum: any; let listF: any[];
+                  if (detailDataWithWorkInfo.formData) {
+                    const parsed = typeof detailDataWithWorkInfo.formData === 'string' ? JSON.parse(detailDataWithWorkInfo.formData) : detailDataWithWorkInfo.formData;
+                    listF = normAtt(parsed.attachments).length ? normAtt(parsed.attachments) : normAtt((detailDataWithWorkInfo as any).attachments);
+                    deviceNum = parsed.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+                  } else {
+                    deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+                    listF = normAtt((detailDataWithWorkInfo as any).attachments);
+                  }
+                  if ((deviceNum !== undefined && deviceNum !== null) || listF.length > 0) {
+                    setIsolationDeviceNumber(deviceNum ?? '');
+                    setIsolationFileList(listF.map((item: any, idx: number) => ({ uid: `echo-${idx}-${item.url || item.name || idx}`, name: item.name || `文件${idx + 1}`, url: item.url || item.response, status: 'done', response: item.url || item.response })));
+                  }
+                } catch (_) { /* ignore */ }
               }
             } else {
               console.warn('ExecutorDashboard: 表单详情缺少配置或字段', { conf: !!conf, fields: !!fields });
@@ -1318,6 +1367,17 @@ export default function ExecutorDashboard() {
         })();
       } else {
         setFormLoading(false);
+        if (isApproved && (isIsolation || isReleaseIsolation)) {
+          try {
+            const normAtt = (att: any): any[] => { if (att == null) return []; if (Array.isArray(att)) return att; if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return []; };
+            const deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+            const listF = normAtt((detailDataWithWorkInfo as any).attachments);
+            if ((deviceNum !== undefined && deviceNum !== null) || listF.length > 0) {
+              setIsolationDeviceNumber(deviceNum ?? '');
+              setIsolationFileList(listF.map((item: any, idx: number) => ({ uid: `echo-${idx}-${item.url || item.name || idx}`, name: item.name || `文件${idx + 1}`, url: item.url || item.response, status: 'done', response: item.url || item.response })));
+            }
+          } catch (_) { /* ignore */ }
+        }
       }
       
       // 回显审核意见
@@ -1766,6 +1826,9 @@ export default function ExecutorDashboard() {
                 const isIsolation = nodeType === 'isolation' || nodeType === '隔离' || nodeType === '隔离/方案';
                 const isReleaseIsolation = nodeType === 'releaseIsolation' || 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() !== '')
+                );
 
                 // 隔离/方案节点、解除隔离节点和还锁节点
                 if (isIsolation || isReleaseIsolation || isReturnLock) {
@@ -1787,20 +1850,7 @@ export default function ExecutorDashboard() {
                     return (
                       <div key="isolation-device-form" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                         <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                          <Card
-                            style={{
-                              width: '100%',
-                              maxWidth: 500,
-                              margin: '0 auto',
-                              boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                              borderRadius: 16,
-                              border: '1px solid rgba(22, 119, 255, 0.15)',
-                              overflow: 'hidden',
-                              background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                            }}
-                            bodyStyle={{ padding: 0 }}
-                          >
-                            <div style={{ padding: '24px 32px 28px' }}>
+                          <div style={{ width: '100%' }}>
                               <div style={{ marginBottom: 26 }}>
                                 <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 14, fontWeight: 600, color: '#1f2937', marginBottom: 12 }}>
                                   <span style={{ width: 28, height: 28, borderRadius: 8, background: '#e6f4ff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
@@ -1907,8 +1957,7 @@ export default function ExecutorDashboard() {
                                   ) : null}
                                 </div>
                               )}
-                            </div>
-                          </Card>
+                          </div>
                         </div>
                         <div className="flex justify-end gap-3" style={{ padding: '20px 24px', borderTop: '1px solid rgba(22, 119, 255, 0.1)', flexShrink: 0, background: 'linear-gradient(0deg, #f0f7ff 0%, #fafcff 60%, #fff 100%)' }}>
                           {!isApproved ? (
@@ -2006,7 +2055,7 @@ export default function ExecutorDashboard() {
 
                   return (
                     <div style={{ flex: 1, minHeight: 0, overflow: 'hidden', display: 'flex', alignItems: 'center', justifyContent: 'center', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                      <Card style={{ width: '100%', maxWidth: 500, textAlign: 'center', borderRadius: 16, boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)', border: '1px solid rgba(22, 119, 255, 0.15)', background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)' }} bodyStyle={{ padding: '48px 40px' }}>
+                      <div style={{ width: '100%', textAlign: 'center', padding: '24px 0' }}>
                         <div style={{ marginBottom: 28 }}>
                           <span style={{ width: 88, height: 88, borderRadius: 20, background: 'linear-gradient(135deg, #1677ff 0%, #4096ff 50%, #69b1ff 100%)', display: 'inline-flex', alignItems: 'center', justifyContent: 'center', boxShadow: '0 8px 24px rgba(22, 119, 255, 0.35)' }}>
                             <LockOutlined style={{ fontSize: 42, color: '#fff' }} />
@@ -2017,33 +2066,20 @@ export default function ExecutorDashboard() {
                           <KeyOutlined />
                           <span>完成取锁、取钥匙后,任务将自动推进</span>
                         </div>
-                      </Card>
+                      </div>
                     </div>
                   );
                 }
 
-                // 审核类型节点 (review) - 样式与盲板/拆除、确认节点统一
+                // 审核类型节点 (review) - 表单直接放在外层,取消内置白框
                 if (isReview) {
                   return (
                     <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                       <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                        <Card
-                          style={{
-                            width: '100%',
-                            maxWidth: 500,
-                            margin: '0 auto',
-                            boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                            borderRadius: 16,
-                            border: '1px solid rgba(22, 119, 255, 0.15)',
-                            overflow: 'hidden',
-                            background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                          }}
-                          bodyStyle={{ padding: 0 }}
-                        >
-                          <div style={{ padding: '24px 32px 28px' }}>
+                        <div style={{ width: '100%' }}>
                             {formLoading ? (
                               <div className="py-8 text-center text-gray-500">{t('cockpit.formLoading')}</div>
-                            ) : formData.rule && formData.rule.length > 0 ? (
+                            ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
                               <div className="space-y-4">
                                 {(() => {
                                   const formConfig = formData.option?.formConfig || defaultFormConfig;
@@ -2070,14 +2106,13 @@ export default function ExecutorDashboard() {
                                 <div style={{ fontSize: 18, fontWeight: 600, color: '#1f2937', lineHeight: 1.6 }}>无需填写表单,直接点击底部按钮提交即可</div>
                               </div>
                             )}
-                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)', backgroundColor: 'rgba(22, 119, 255, 0.04)', padding: '16px', borderRadius: 12, border: '1px solid rgba(22, 119, 255, 0.1)' }}>
+                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)' }}>
                               <label className="text-sm font-medium text-gray-700 whitespace-nowrap pt-2" style={{ width: defaultFormConfig.labelWidth ? `${defaultFormConfig.labelWidth - 20}px` : '80px', textAlign: defaultFormConfig.labelPosition === 'left' ? 'left' : defaultFormConfig.labelPosition === 'right' ? 'right' : 'left', flexShrink: 0 }}>{t('form.reviewComment')}</label>
                               <div className="flex-1">
                                 <Input.TextArea rows={4} value={approvalComment} onChange={(e) => setApprovalComment(e.target.value)} placeholder={t('form.reviewCommentPlaceholder')} maxLength={500} showCount disabled={isApproved} readOnly={isApproved} style={{ borderRadius: 10, borderColor: '#d9e8ff' }} />
                               </div>
                             </div>
-                          </div>
-                        </Card>
+                        </div>
                       </div>
                       <div className="flex justify-end gap-3" style={{ padding: '20px 24px', borderTop: '1px solid rgba(22, 119, 255, 0.1)', flexShrink: 0, background: 'linear-gradient(0deg, #f0f7ff 0%, #fafcff 60%, #fff 100%)' }}>
                         <Button
@@ -2237,15 +2272,16 @@ export default function ExecutorDashboard() {
                   );
                 }
 
-                // 其他节点类型(需要根据 formId 获取表单)
+                // 其他节点类型(录入信息等)- 与审核节点统一样式:渐变背景、无内置白框
                 return (
                   <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
-                    <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
-                      {/* 自定义表单 */}
+                    <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
+                      <div style={{ width: '100%' }}>
+                      {/* 自定义表单:仅当前节点有 formId 时渲染 */}
                       {formLoading ? (
                         <div className="py-8 text-center text-gray-500">{t('cockpit.formLoading')}</div>
-                      ) : formData.rule && formData.rule.length > 0 ? (
-                        <div>
+                      ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
+                        <div className="space-y-4">
                           {(() => {
                             const formConfig = formData.option?.formConfig || defaultFormConfig;
                             const layoutColumns = formConfig.layoutColumns || 1;
@@ -2300,18 +2336,7 @@ export default function ExecutorDashboard() {
                             background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)',
                           }}
                         >
-                          <Card
-                            style={{
-                              width: '100%',
-                              maxWidth: 500,
-                              textAlign: 'center',
-                              borderRadius: 16,
-                              boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                              border: '1px solid rgba(22, 119, 255, 0.15)',
-                              background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                            }}
-                            bodyStyle={{ padding: '48px 40px' }}
-                          >
+                          <div style={{ width: '100%', textAlign: 'center', padding: '24px 0' }}>
                             <div style={{ marginBottom: 24 }}>
                               <span
                                 style={{
@@ -2334,9 +2359,10 @@ export default function ExecutorDashboard() {
                             <div style={{ marginTop: 12, fontSize: 13, color: '#69b1ff' }}>
                               确认无误后点击「完成」结束本节点
                             </div>
-                          </Card>
+                          </div>
                         </div>
                       )}
+                      </div>
                     </div>
                     {/* 底部按钮 - 取消 + 提交/完成 */}
                     <div

+ 48 - 0
src/components/FlowEdge.tsx

@@ -0,0 +1,48 @@
+import React from 'react';
+import { BaseEdge, EdgeTypes } from 'reactflow';
+import { getFlowEdgePath, FLOW_EDGE_DEFAULT_FIXED_LENGTH } from '../utils/flowEdgeUtils';
+
+/**
+ * 与流程设计器一致的边组件:固定线段长度 + smoothstep/straight,供作业详情、作业编辑等复用
+ */
+function FlowEdge({
+  id,
+  sourceX,
+  sourceY,
+  targetX,
+  targetY,
+  data,
+  type: propType,
+  markerStart,
+  style,
+  selected,
+}: any) {
+  const edgeType = (data?.edgeType as string) || propType || 'straight';
+  const fixedLength = (data?.fixedLength as number) ?? FLOW_EDGE_DEFAULT_FIXED_LENGTH;
+
+  const [edgePath] = getFlowEdgePath(sourceX, sourceY, targetX, targetY, {
+    fixedLength,
+    type: edgeType,
+  });
+
+  return (
+    <BaseEdge
+      id={id}
+      path={edgePath}
+      markerStart={markerStart}
+      style={{
+        ...style,
+        strokeWidth: selected ? 3 : 2,
+        stroke: selected ? '#3b82f6' : (style?.stroke ?? '#000000'),
+      }}
+    />
+  );
+}
+
+export const sharedFlowEdgeTypes: EdgeTypes = {
+  straight: FlowEdge,
+  default: FlowEdge,
+  smoothstep: FlowEdge,
+};
+
+export default FlowEdge;

+ 37 - 55
src/components/IsolationWork.tsx

@@ -21,10 +21,9 @@ import ReactFlow, {
   Handle,
   Position,
   ConnectionMode,
-  BaseEdge,
-  getStraightPath,
-  EdgeTypes,
 } from 'reactflow';
+import { sharedFlowEdgeTypes } from './FlowEdge';
+import { getLayoutedNodes, shouldApplyLayout } from '../utils/flowLayout';
 import 'reactflow/dist/style.css';
 import {
   ToolOutlined,
@@ -359,47 +358,6 @@ function CustomNode({ data, selected, id, nodeSavedStatusCache }: any) {
 
 // 节点类型映射(将在组件内部重新定义以访问状态)
 
-// 自定义边组件
-function CustomEdgeWithDelete({
-  id,
-  sourceX,
-  sourceY,
-  targetX,
-  targetY,
-  selected,
-  markerEnd,
-  markerStart,
-  style,
-}: any) {
-  const [edgePath] = getStraightPath({
-    sourceX,
-    sourceY,
-    targetX,
-    targetY,
-  });
-
-  return (
-    <>
-      <BaseEdge
-        id={id}
-        path={edgePath}
-        markerEnd={markerEnd}
-        markerStart={markerStart}
-        style={{
-          ...style,
-          strokeWidth: selected ? 3 : 2,
-          stroke: selected ? '#3b82f6' : (style?.stroke || '#000000'),
-        }}
-      />
-    </>
-  );
-}
-
-const edgeTypes: EdgeTypes = {
-  straight: CustomEdgeWithDelete,
-  default: CustomEdgeWithDelete,
-};
-
 export default function IsolationWork({ subMenu }: IsolationWorkProps) {
   const { t, i18n } = useTranslation();
   const navigate = useNavigate();
@@ -1268,7 +1226,11 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
             target: edge.target,
             sourceHandle: sourceHandle,
             targetHandle: targetHandle,
-            type: 'straight',
+            type: edge.type || 'straight',
+            data: {
+              fixedLength: edge.data?.fixedLength ?? 150,
+              edgeType: edge.type || 'straight',
+            },
             style: { strokeWidth: 2, stroke: '#000000' },
             markerStart: {
               type: 'arrowclosed',
@@ -1280,7 +1242,10 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
         console.log('导入的节点:', importedNodes);
         console.log('导入的连线:', importedEdges);
         
-        setWorkflowNodes(importedNodes);
+        const finalNodes = shouldApplyLayout(importedNodes, importedEdges)
+          ? getLayoutedNodes(importedNodes, importedEdges)
+          : importedNodes;
+        setWorkflowNodes(finalNodes);
         setWorkflowEdges(importedEdges);
         
         // 清空之前的缓存和完成状态
@@ -1290,8 +1255,8 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
         setNodeSavedStatusCache(new Map());
         
         // 自动选中第一个节点
-        if (importedNodes.length > 0) {
-          const firstNode = importedNodes[0];
+        if (finalNodes.length > 0) {
+          const firstNode = finalNodes[0];
           setTimeout(() => {
             setSelectedWorkflowNode(firstNode);
             const source = firstNode.data || {};
@@ -1372,7 +1337,11 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
         id: edge.id,
         source: edge.source,
         target: edge.target,
-        type: 'straight',
+        type: edge.type || 'straight',
+        data: {
+          fixedLength: edge.data?.fixedLength ?? 150,
+          edgeType: edge.type || 'straight',
+        },
         style: { strokeWidth: 2, stroke: '#000000' },
         markerStart: {
           type: 'arrowclosed',
@@ -1380,7 +1349,10 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
         },
       }));
       
-      setWorkflowNodes(importedNodes);
+      const finalNodes = shouldApplyLayout(importedNodes, importedEdges)
+        ? getLayoutedNodes(importedNodes, importedEdges)
+        : importedNodes;
+      setWorkflowNodes(finalNodes);
       setWorkflowEdges(importedEdges);
     }
   };
@@ -2354,7 +2326,11 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
                 target: edge.target,
                 sourceHandle: sourceHandle,
                 targetHandle: targetHandle,
-                type: 'straight',
+                type: edge.type || 'straight',
+                data: {
+                  fixedLength: edge.data?.fixedLength ?? 150,
+                  edgeType: edge.type || 'straight',
+                },
                 style: { strokeWidth: 2, stroke: '#000000' },
                 markerStart: {
                   type: 'arrowclosed',
@@ -2363,7 +2339,10 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
               };
             });
             
-            setWorkflowNodes(importedNodes);
+            const finalNodes = shouldApplyLayout(importedNodes, importedEdges)
+              ? getLayoutedNodes(importedNodes, importedEdges)
+              : importedNodes;
+            setWorkflowNodes(finalNodes);
             setWorkflowEdges(importedEdges);
             
             // 清空之前的缓存和完成状态
@@ -2373,9 +2352,9 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
             setNodeSavedStatusCache(new Map());
             
             // 自动选中第一个节点
-            if (importedNodes.length > 0) {
+            if (finalNodes.length > 0) {
               setTimeout(() => {
-                const firstNode = importedNodes[0];
+                const firstNode = finalNodes[0];
                 setSelectedWorkflowNode(firstNode);
                 
                 // 查找对应的 nodeDO
@@ -4520,7 +4499,7 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
                             nodesConnectable={!isViewMode}
                             elementsSelectable={!isViewMode}
                             nodeTypes={nodeTypes}
-                            edgeTypes={edgeTypes}
+                            edgeTypes={sharedFlowEdgeTypes}
                             fitView
                             onInit={(instance) => {
                               setWorkflowReactFlowInstance(instance);
@@ -4530,6 +4509,9 @@ export default function IsolationWork({ subMenu }: IsolationWorkProps) {
                               }, 100);
                             }}
                             connectionMode={ConnectionMode.Loose}
+                            snapToGrid={true}
+                            snapGrid={[16, 16]}
+                            connectionRadius={40}
                             defaultEdgeOptions={{
                               style: { strokeWidth: 2, stroke: '#000000' },
                               type: 'straight',

+ 100 - 74
src/components/MyTask.tsx

@@ -1063,6 +1063,13 @@ export default function MyTask() {
         'detailDataWithWorkInfo': detailDataWithWorkInfo,
       });
       
+      // 当前节点无表单(无 formId 且不会从 formData 解析出表单)时清空表单 state,避免完成/结束节点误展示上一节点的自定义表单
+      if (!hasFormId && !(isApproved && hasFormData)) {
+        setFormData({ rule: [], option: {} });
+        setOriginalFields([]);
+        setOriginalConf('');
+      }
+      
       // 打开盲板/拆除详情时先清空设备编号和附件,后续若有 formData 再回填
       if (isIsolation || isReleaseIsolation) {
         const isolationType = String(detailDataWithWorkInfo.isolationType ?? '').trim();
@@ -1180,15 +1187,42 @@ export default function MyTask() {
               detailForm.setFieldsValue(convertedFormValues);
               console.log('MyTask: 表单数据已回填(从 formData)', convertedFormValues);
             }, 100);
+            // 有 conf/fields 时也回显 deviceNumber、attachments;attachments 可能是 JSON 字符串
+            const normAttachments = (att: any): any[] => {
+              if (att == null) return []; if (Array.isArray(att)) return att;
+              if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return [];
+            };
+            const listInConf = normAttachments(parsedFormData.attachments).length ? normAttachments(parsedFormData.attachments) : normAttachments((detailDataWithWorkInfo as any).attachments);
+            const deviceNumInConf = parsedFormData.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+            const hasDeviceInConf = deviceNumInConf !== undefined && deviceNumInConf !== null;
+            if (hasDeviceInConf || listInConf.length > 0) {
+              setIsolationDeviceNumber(deviceNumInConf ?? '');
+              setIsolationFileList(
+                listInConf.map((item: any, idx: number) => ({
+                  uid: `echo-${idx}-${item.url || item.name || idx}`,
+                  name: item.name || `文件${idx + 1}`,
+                  url: item.url || item.response,
+                  status: 'done',
+                  response: item.url || item.response,
+                }))
+              );
+              console.log('MyTask: 盲板/拆除 deviceNumber/attachments 已回显', { deviceNumber: deviceNumInConf, attachmentsCount: listInConf.length });
+            }
           } else if (
-            parsedFormData.deviceNumber !== undefined ||
-            (Array.isArray(parsedFormData.attachments) && parsedFormData.attachments.length > 0)
+            (() => {
+              const d = parsedFormData.deviceNumber; const a = parsedFormData.attachments;
+              if (d !== undefined && d !== null) return true;
+              if (a == null) return false; if (Array.isArray(a) && a.length > 0) return true;
+              if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) && p.length > 0; } catch { return false; } } return false;
+            })()
           ) {
-            // 盲板/拆除 表单回显:deviceNumber + attachments
+            const listElse = (() => {
+              const a = parsedFormData.attachments; if (a == null) return []; if (Array.isArray(a)) return a;
+              if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) ? p : []; } catch { return []; } } return [];
+            })();
             setIsolationDeviceNumber(parsedFormData.deviceNumber ?? '');
-            const list = Array.isArray(parsedFormData.attachments) ? parsedFormData.attachments : [];
             setIsolationFileList(
-              list.map((item: any, idx: number) => ({
+              listElse.map((item: any, idx: number) => ({
                 uid: `echo-${idx}-${item.url || item.name || idx}`,
                 name: item.name || `文件${idx + 1}`,
                 url: item.url || item.response,
@@ -1196,7 +1230,7 @@ export default function MyTask() {
                 response: item.url || item.response,
               }))
             );
-            console.log('MyTask: 盲板/拆除 formData 已回显', { deviceNumber: parsedFormData.deviceNumber, attachmentsCount: list.length });
+            console.log('MyTask: 盲板/拆除 formData 已回显', { deviceNumber: parsedFormData.deviceNumber, attachmentsCount: listElse.length });
           } else {
             // 完成/结束等节点可能无表单内容,不提示“表单数据不完整”,有则显示、无则不显示即可
             console.warn('MyTask: formData 中缺少 conf 或 fields', { conf: !!conf, fields: !!fields });
@@ -1282,8 +1316,9 @@ export default function MyTask() {
                 fieldsCount: Array.isArray(fields) ? fields.length : 0 
               });
               
-              // 如果有表单数据值,回填到表单(在表单配置设置后)
-              if (detailDataWithWorkInfo.formData) {
+              // 仅任务已完成(approved)时回显表单数据;进行中一律不回显并清空表单
+              const shouldEchoFormData = detailDataWithWorkInfo.formData && isApproved;
+              if (shouldEchoFormData) {
                 try {
                   let formValues = typeof detailDataWithWorkInfo.formData === 'string' 
                     ? JSON.parse(detailDataWithWorkInfo.formData) 
@@ -1381,6 +1416,31 @@ export default function MyTask() {
                 } catch (e) {
                   console.error('MyTask: 解析表单数据失败', e);
                 }
+              } else if (!isApproved) {
+                // 任务进行中:清空自定义表单,避免带出历史或隔离方案等填写内容
+                setTimeout(() => { try { detailForm.resetFields(); } catch (_) { /* ignore */ } }, 100);
+              }
+              // 隔离/解除隔离完成查看详情:从 formData 回显 deviceNumber、attachments
+              // 隔离/解除隔离完成查看详情:从 formData 或节点详情顶层回显(部分解除隔离节点仅顶层有数据)
+              if (isApproved && (isIsolation || isReleaseIsolation)) {
+                try {
+                  const normAtt = (att: any): any[] => { if (att == null) return []; if (Array.isArray(att)) return att; if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return []; };
+                  let deviceNum: any; let listF: any[];
+                  if (detailDataWithWorkInfo.formData) {
+                    const parsed = typeof detailDataWithWorkInfo.formData === 'string' ? JSON.parse(detailDataWithWorkInfo.formData) : detailDataWithWorkInfo.formData;
+                    listF = normAtt(parsed.attachments).length ? normAtt(parsed.attachments) : normAtt((detailDataWithWorkInfo as any).attachments);
+                    deviceNum = parsed.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+                  } else {
+                    deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+                    listF = normAtt((detailDataWithWorkInfo as any).attachments);
+                  }
+                  const hasDev = deviceNum !== undefined && deviceNum !== null;
+                  if (hasDev || listF.length > 0) {
+                    setIsolationDeviceNumber(deviceNum ?? '');
+                    setIsolationFileList(listF.map((item: any, idx: number) => ({ uid: `echo-${idx}-${item.url || item.name || idx}`, name: item.name || `文件${idx + 1}`, url: item.url || item.response, status: 'done', response: item.url || item.response })));
+                    console.log('MyTask: formId 分支 deviceNumber/attachments 已回显', { deviceNumber: deviceNum, attachmentsCount: listF.length });
+                  }
+                } catch (_) { /* ignore */ }
               }
             } else {
               console.warn('MyTask: 表单详情缺少配置或字段', { conf: !!conf, fields: !!fields, formDetail });
@@ -1396,6 +1456,17 @@ export default function MyTask() {
         })();
       } else {
         setFormLoading(false);
+        if (isApproved && (isIsolation || isReleaseIsolation)) {
+          try {
+            const normAtt = (att: any): any[] => { if (att == null) return []; if (Array.isArray(att)) return att; if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return []; };
+            const deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+            const listF = normAtt((detailDataWithWorkInfo as any).attachments);
+            if ((deviceNum !== undefined && deviceNum !== null) || listF.length > 0) {
+              setIsolationDeviceNumber(deviceNum ?? '');
+              setIsolationFileList(listF.map((item: any, idx: number) => ({ uid: `echo-${idx}-${item.url || item.name || idx}`, name: item.name || `文件${idx + 1}`, url: item.url || item.response, status: 'done', response: item.url || item.response })));
+            }
+          } catch (_) { /* ignore */ }
+        }
         if (isIsolation || isReleaseIsolation || isReturnLock) {
           console.log('MyTask: ⚠️ 隔离类型节点,跳过表单配置获取', { nodeType, isIsolation, isReleaseIsolation, isReturnLock });
         } else if (!hasFormId) {
@@ -1957,6 +2028,9 @@ export default function MyTask() {
                   'typeof detailData.type': typeof detailData.type,
                   'String(detailData.type)': String(detailData.type)
                 });
+                const hasFormIdForCurrentNode = detailData?.formId !== undefined && detailData?.formId !== null && (
+                  typeof detailData.formId === 'number' || (typeof detailData.formId === 'string' && String(detailData.formId).trim() !== '')
+                );
 
                 // 隔离/方案节点、解除隔离节点和还锁节点
                 if (isIsolation || isReleaseIsolation || isReturnLock) {
@@ -1978,20 +2052,7 @@ export default function MyTask() {
                     return (
                       <div key="isolation-device-form" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                         <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                          <Card
-                            style={{
-                              width: '100%',
-                              maxWidth: 500,
-                              margin: '0 auto',
-                              boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                              borderRadius: 16,
-                              border: '1px solid rgba(22, 119, 255, 0.15)',
-                              overflow: 'hidden',
-                              background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                            }}
-                            bodyStyle={{ padding: 0 }}
-                          >
-                            <div style={{ padding: '24px 32px 28px' }}>
+                          <div style={{ width: '100%' }}>
                               <div style={{ marginBottom: 26 }}>
                                 <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 14, fontWeight: 600, color: '#1f2937', marginBottom: 12 }}>
                                   <span style={{ width: 28, height: 28, borderRadius: 8, background: '#e6f4ff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
@@ -2138,8 +2199,7 @@ export default function MyTask() {
                                   ) : null}
                                 </div>
                               )}
-                            </div>
-                          </Card>
+                          </div>
                         </div>
                         <div className="flex justify-end gap-3" style={{ padding: '20px 24px', borderTop: '1px solid rgba(22, 119, 255, 0.1)', flexShrink: 0, background: 'linear-gradient(0deg, #f0f7ff 0%, #fafcff 60%, #fff 100%)' }}>
                           {!isApproved ? (
@@ -2258,18 +2318,7 @@ export default function MyTask() {
                         background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)',
                       }}
                     >
-                      <Card
-                        style={{
-                          width: '100%',
-                          maxWidth: 500,
-                          textAlign: 'center',
-                          borderRadius: 16,
-                          boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                          border: '1px solid rgba(22, 119, 255, 0.15)',
-                          background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                        }}
-                        bodyStyle={{ padding: '48px 40px' }}
-                      >
+                      <div style={{ width: '100%', textAlign: 'center', padding: '24px 0' }}>
                         <div style={{ marginBottom: 28 }}>
                           <span
                             style={{
@@ -2301,34 +2350,21 @@ export default function MyTask() {
                           <KeyOutlined />
                           <span>完成取锁、取钥匙后,任务将自动推进</span>
                         </div>
-                      </Card>
+                      </div>
                     </div>
                   );
                   return isolationContent;
                 }
 
-                // 审核类型节点 (review) - 样式与盲板/拆除、确认节点统一
+                // 审核类型节点 (review) - 表单直接放在外层,取消内置白框
                 if (isReview) {
                   return (
                     <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                       <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                        <Card
-                          style={{
-                            width: '100%',
-                            maxWidth: 500,
-                            margin: '0 auto',
-                            boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                            borderRadius: 16,
-                            border: '1px solid rgba(22, 119, 255, 0.15)',
-                            overflow: 'hidden',
-                            background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                          }}
-                          bodyStyle={{ padding: 0 }}
-                        >
-                          <div style={{ padding: '24px 32px 28px' }}>
+                        <div style={{ width: '100%' }}>
                             {formLoading ? (
                               <div className="py-8 text-center text-gray-500">{t('form.formLoading')}</div>
-                            ) : formData.rule && formData.rule.length > 0 ? (
+                            ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
                               <div className="space-y-4">
                                 {(() => {
                                   const formConfig = formData.option?.formConfig || defaultFormConfig;
@@ -2366,14 +2402,13 @@ export default function MyTask() {
                                 <div style={{ fontSize: 18, fontWeight: 600, color: '#1f2937', lineHeight: 1.6 }}>无需填写表单,直接点击底部按钮提交即可</div>
                               </div>
                             )}
-                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)', backgroundColor: 'rgba(22, 119, 255, 0.04)', padding: '16px', borderRadius: 12, border: '1px solid rgba(22, 119, 255, 0.1)' }}>
+                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)' }}>
                               <label className="text-sm font-medium text-gray-700 whitespace-nowrap pt-2" style={{ width: defaultFormConfig.labelWidth ? `${defaultFormConfig.labelWidth - 20}px` : '80px', textAlign: defaultFormConfig.labelPosition === 'left' ? 'left' : defaultFormConfig.labelPosition === 'right' ? 'right' : 'left', flexShrink: 0 }}>{t('form.reviewComment')}</label>
                               <div className="flex-1">
                                 <Input.TextArea rows={4} value={approvalComment} onChange={(e) => setApprovalComment(e.target.value)} placeholder={t('form.reviewCommentPlaceholder')} maxLength={500} showCount disabled={isApproved} readOnly={isApproved} style={{ borderRadius: 10, borderColor: '#d9e8ff' }} />
                               </div>
                             </div>
-                          </div>
-                        </Card>
+                        </div>
                       </div>
                       <div className="flex justify-end gap-3" style={{ padding: '20px 24px', borderTop: '1px solid rgba(22, 119, 255, 0.1)', flexShrink: 0, background: 'linear-gradient(0deg, #f0f7ff 0%, #fafcff 60%, #fff 100%)' }}>
                         <Button
@@ -2605,15 +2640,16 @@ export default function MyTask() {
                   );
                 }
 
-                // 其他节点类型(需要根据 formId 获取表单)
+                // 其他节点类型(录入信息等)- 与审核节点统一样式:渐变背景、无内置白框
                 return (
                   <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
-                    <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
-                    {/* 自定义表单 */}
+                    <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
+                      <div style={{ width: '100%' }}>
+                    {/* 自定义表单:仅当前节点有 formId 时渲染 */}
                     {formLoading ? (
                       <div className="py-8 text-center text-gray-500">{t('form.formLoading')}</div>
-                    ) : formData.rule && formData.rule.length > 0 ? (
-                      <div>
+                    ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
+                      <div className="space-y-4">
                         {(() => {
                           const formConfig = formData.option?.formConfig || defaultFormConfig;
                           const layoutColumns = formConfig.layoutColumns || 1;
@@ -2669,18 +2705,7 @@ export default function MyTask() {
                           background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)',
                         }}
                       >
-                        <Card
-                          style={{
-                            width: '100%',
-                            maxWidth: 500,
-                            textAlign: 'center',
-                            borderRadius: 16,
-                            boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                            border: '1px solid rgba(22, 119, 255, 0.15)',
-                            background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                          }}
-                          bodyStyle={{ padding: '48px 40px' }}
-                        >
+                        <div style={{ width: '100%', textAlign: 'center', padding: '24px 0' }}>
                           <div style={{ marginBottom: 24 }}>
                             <span
                               style={{
@@ -2703,10 +2728,11 @@ export default function MyTask() {
                           <div style={{ marginTop: 12, fontSize: 13, color: '#69b1ff' }}>
                             确认无误后点击「完成」结束本节点
                           </div>
-                        </Card>
+                        </div>
                       </div>
                     )}
 
+                      </div>
                     </div>
                     {/* 底部按钮 */}
                     <div

+ 127 - 61
src/components/TaskManagement.tsx

@@ -1076,6 +1076,13 @@ export default function TaskManagement() {
         'detailDataWithWorkInfo': detailDataWithWorkInfo,
       });
       
+      // 当前节点无表单(无 formId 且不会从 formData 解析出表单)时清空表单 state,避免完成/结束节点误展示上一节点的自定义表单
+      if (!hasFormId && !(isApproved && hasFormData)) {
+        setFormData({ rule: [], option: {} });
+        setOriginalFields([]);
+        setOriginalConf('');
+      }
+      
       // 打开盲板/拆除详情时先清空设备编号和附件,后续若有 formData 再回填
       if (isIsolation || isReleaseIsolation) {
         const isolationType = String(detailDataWithWorkInfo.isolationType ?? '').trim();
@@ -1193,15 +1200,44 @@ export default function TaskManagement() {
               detailForm.setFieldsValue(convertedFormValues);
               console.log('TaskManagement: 表单数据已回填(从 formData)', convertedFormValues);
             }, 100);
+            // 有 conf/fields 时也回显 deviceNumber、attachments(解除隔离/隔离方案完成查看详情);attachments 可能是 JSON 字符串
+            const normAttachments = (att: any): any[] => {
+              if (att == null) return [];
+              if (Array.isArray(att)) return att;
+              if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } }
+              return [];
+            };
+            const listInConf = normAttachments(parsedFormData.attachments).length ? normAttachments(parsedFormData.attachments) : normAttachments((detailDataWithWorkInfo as any).attachments);
+            const deviceNumInConf = parsedFormData.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+            const hasDeviceInConf = deviceNumInConf !== undefined && deviceNumInConf !== null;
+            if (hasDeviceInConf || listInConf.length > 0) {
+              setIsolationDeviceNumber(deviceNumInConf ?? '');
+              setIsolationFileList(
+                listInConf.map((item: any, idx: number) => ({
+                  uid: `echo-${idx}-${item.url || item.name || idx}`,
+                  name: item.name || `文件${idx + 1}`,
+                  url: item.url || item.response,
+                  status: 'done',
+                  response: item.url || item.response,
+                }))
+              );
+              console.log('TaskManagement: 盲板/拆除 deviceNumber/attachments 已回显', { deviceNumber: parsedFormData.deviceNumber, attachmentsCount: listInConf.length });
+            }
           } else if (
             parsedFormData.deviceNumber !== undefined ||
-            (Array.isArray(parsedFormData.attachments) && parsedFormData.attachments.length > 0)
+            (() => { const a = parsedFormData.attachments; if (a == null) return false; if (Array.isArray(a)) return a.length > 0; if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) && p.length > 0; } catch { return false; } } return false; })()
           ) {
-            // 盲板/拆除 表单回显:deviceNumber + attachments
+            // 盲板/拆除 仅 deviceNumber/attachments 无 conf/fields 时的回显
+            const listElse = (() => {
+              const a = parsedFormData.attachments;
+              if (a == null) return [];
+              if (Array.isArray(a)) return a;
+              if (typeof a === 'string' && a.trim()) { try { const p = JSON.parse(a); return Array.isArray(p) ? p : []; } catch { return []; } }
+              return [];
+            })();
             setIsolationDeviceNumber(parsedFormData.deviceNumber ?? '');
-            const list = Array.isArray(parsedFormData.attachments) ? parsedFormData.attachments : [];
             setIsolationFileList(
-              list.map((item: any, idx: number) => ({
+              listElse.map((item: any, idx: number) => ({
                 uid: `echo-${idx}-${item.url || item.name || idx}`,
                 name: item.name || `文件${idx + 1}`,
                 url: item.url || item.response,
@@ -1209,7 +1245,7 @@ export default function TaskManagement() {
                 response: item.url || item.response,
               }))
             );
-            console.log('TaskManagement: 盲板/拆除 formData 已回显', { deviceNumber: parsedFormData.deviceNumber, attachmentsCount: list.length });
+            console.log('TaskManagement: 盲板/拆除 formData 已回显', { deviceNumber: parsedFormData.deviceNumber, attachmentsCount: listElse.length });
           } else {
             // 完成/结束等节点可能无表单内容,不提示“表单数据不完整”,有则显示、无则不显示即可
             console.warn('TaskManagement: formData 中缺少 conf 或 fields', { conf: !!conf, fields: !!fields });
@@ -1295,8 +1331,9 @@ export default function TaskManagement() {
                 fieldsCount: Array.isArray(fields) ? fields.length : 0 
               });
               
-              // 如果有表单数据值,回填到表单(在表单配置设置后)
-              if (detailDataWithWorkInfo.formData) {
+              // 仅任务已完成(approved)时回显表单数据;进行中一律不回显并清空表单
+              const shouldEchoFormData = detailDataWithWorkInfo.formData && isApproved;
+              if (shouldEchoFormData) {
                 try {
                   let formValues = typeof detailDataWithWorkInfo.formData === 'string' 
                     ? JSON.parse(detailDataWithWorkInfo.formData) 
@@ -1394,6 +1431,44 @@ export default function TaskManagement() {
                 } catch (e) {
                   console.error('TaskManagement: 解析表单数据失败', e);
                 }
+              } else if (!isApproved) {
+                // 任务进行中:清空自定义表单,避免带出历史或隔离方案等填写内容
+                setTimeout(() => { try { detailForm.resetFields(); } catch (_) { /* ignore */ } }, 100);
+              }
+              // 隔离/解除隔离完成查看详情:从 formData 或节点详情顶层回显 deviceNumber、attachments(部分解除隔离节点仅顶层有数据)
+              if (isApproved && (isIsolation || isReleaseIsolation)) {
+                try {
+                  const normAtt = (att: any): any[] => {
+                    if (att == null) return []; if (Array.isArray(att)) return att;
+                    if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return [];
+                  };
+                  let deviceNum: any;
+                  let listF: any[];
+                  if (detailDataWithWorkInfo.formData) {
+                    const parsed = typeof detailDataWithWorkInfo.formData === 'string'
+                      ? JSON.parse(detailDataWithWorkInfo.formData)
+                      : detailDataWithWorkInfo.formData;
+                    listF = normAtt(parsed.attachments).length ? normAtt(parsed.attachments) : normAtt((detailDataWithWorkInfo as any).attachments);
+                    deviceNum = parsed.deviceNumber ?? (detailDataWithWorkInfo as any).deviceNumber;
+                  } else {
+                    deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+                    listF = normAtt((detailDataWithWorkInfo as any).attachments);
+                  }
+                  const hasDev = deviceNum !== undefined && deviceNum !== null;
+                  if (hasDev || listF.length > 0) {
+                    setIsolationDeviceNumber(deviceNum ?? '');
+                    setIsolationFileList(
+                      listF.map((item: any, idx: number) => ({
+                        uid: `echo-${idx}-${item.url || item.name || idx}`,
+                        name: item.name || `文件${idx + 1}`,
+                        url: item.url || item.response,
+                        status: 'done',
+                        response: item.url || item.response,
+                      }))
+                    );
+                    console.log('TaskManagement: formId 分支 deviceNumber/attachments 已回显', { deviceNumber: deviceNum, attachmentsCount: listF.length });
+                  }
+                } catch (_) { /* ignore */ }
               }
             } else {
               console.warn('TaskManagement: 表单详情缺少配置或字段', { conf: !!conf, fields: !!fields, formDetail });
@@ -1409,6 +1484,30 @@ export default function TaskManagement() {
         })();
       } else {
         setFormLoading(false);
+        // 隔离/解除隔离已完成但未走 formData 或 formId 分支时,从节点详情顶层回显 deviceNumber、attachments
+        if (isApproved && (isIsolation || isReleaseIsolation)) {
+          try {
+            const normAtt = (att: any): any[] => {
+              if (att == null) return []; if (Array.isArray(att)) return att;
+              if (typeof att === 'string' && att.trim()) { try { const p = JSON.parse(att); return Array.isArray(p) ? p : []; } catch { return []; } } return [];
+            };
+            const deviceNum = (detailDataWithWorkInfo as any).deviceNumber;
+            const listF = normAtt((detailDataWithWorkInfo as any).attachments);
+            if ((deviceNum !== undefined && deviceNum !== null) || listF.length > 0) {
+              setIsolationDeviceNumber(deviceNum ?? '');
+              setIsolationFileList(
+                listF.map((item: any, idx: number) => ({
+                  uid: `echo-${idx}-${item.url || item.name || idx}`,
+                  name: item.name || `文件${idx + 1}`,
+                  url: item.url || item.response,
+                  status: 'done',
+                  response: item.url || item.response,
+                }))
+              );
+              console.log('TaskManagement: 隔离/解除隔离(无formId分支) deviceNumber/attachments 已从顶层回显', { deviceNumber: deviceNum, attachmentsCount: listF.length });
+            }
+          } catch (_) { /* ignore */ }
+        }
         if (isIsolation || isReleaseIsolation || isReturnLock) {
           console.log('TaskManagement: ⚠️ 隔离类型节点,跳过表单配置获取', { nodeType, isIsolation, isReleaseIsolation, isReturnLock });
         } else if (!hasFormId) {
@@ -1970,6 +2069,10 @@ export default function TaskManagement() {
                   'typeof detailData.type': typeof detailData.type,
                   'String(detailData.type)': String(detailData.type)
                 });
+                // 当前节点是否有表单:无 formId 的节点(如完成/结束)不应渲染自定义表单
+                const hasFormIdForCurrentNode = detailData?.formId !== undefined && detailData?.formId !== null && (
+                  typeof detailData.formId === 'number' || (typeof detailData.formId === 'string' && String(detailData.formId).trim() !== '')
+                );
 
                 // 隔离/方案节点、解除隔离节点和还锁节点
                 if (isIsolation || isReleaseIsolation || isReturnLock) {
@@ -1991,20 +2094,7 @@ export default function TaskManagement() {
                     return (
                       <div key="isolation-device-form" style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                         <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                          <Card
-                            style={{
-                              width: '100%',
-                              maxWidth: 500,
-                              margin: '0 auto',
-                              boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                              borderRadius: 16,
-                              border: '1px solid rgba(22, 119, 255, 0.15)',
-                              overflow: 'hidden',
-                              background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                            }}
-                            bodyStyle={{ padding: 0 }}
-                          >
-                            <div style={{ padding: '24px 32px 28px' }}>
+                          <div style={{ width: '100%' }}>
                               <div style={{ marginBottom: 26 }}>
                                 <label style={{ display: 'flex', alignItems: 'center', gap: 8, fontSize: 14, fontWeight: 600, color: '#1f2937', marginBottom: 12 }}>
                                   <span style={{ width: 28, height: 28, borderRadius: 8, background: '#e6f4ff', display: 'inline-flex', alignItems: 'center', justifyContent: 'center' }}>
@@ -2152,8 +2242,7 @@ export default function TaskManagement() {
                                   ) : null}
                                 </div>
                               )}
-                            </div>
-                          </Card>
+                          </div>
                         </div>
                         <div className="flex justify-end gap-3" style={{ padding: '20px 24px', borderTop: '1px solid rgba(22, 119, 255, 0.1)', flexShrink: 0, background: 'linear-gradient(0deg, #f0f7ff 0%, #fafcff 60%, #fff 100%)' }}>
                           {!isApproved ? (
@@ -2321,29 +2410,16 @@ export default function TaskManagement() {
                   return isolationContent;
                 }
 
-                // 审核类型节点 (review) - 样式与盲板/拆除、确认节点统一:背景渐变 + Card 容器 + 底部按钮栏
+                // 审核类型节点 (review) - 表单直接放在外层,取消内置白框避免挤压
                 if (isReview) {
                   return (
                     <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
                       <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
-                        <Card
-                          style={{
-                            width: '100%',
-                            maxWidth: 500,
-                            margin: '0 auto',
-                            boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                            borderRadius: 16,
-                            border: '1px solid rgba(22, 119, 255, 0.15)',
-                            overflow: 'hidden',
-                            background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                          }}
-                          bodyStyle={{ padding: 0 }}
-                        >
-                          <div style={{ padding: '24px 32px 28px' }}>
-                            {/* 自定义表单 */}
+                        <div style={{ width: '100%' }}>
+                            {/* 自定义表单:仅当前节点有 formId 时渲染,避免完成/结束节点误展示上一节点表单 */}
                             {formLoading ? (
                               <div className="py-8 text-center text-gray-500">{t('form.formLoading')}</div>
-                            ) : formData.rule && formData.rule.length > 0 ? (
+                            ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
                               <div className="space-y-4">
                                 {(() => {
                                   const formConfig = formData.option?.formConfig || defaultFormConfig;
@@ -2390,7 +2466,7 @@ export default function TaskManagement() {
                             )}
 
                             {/* 审核意见 */}
-                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)', backgroundColor: 'rgba(22, 119, 255, 0.04)', padding: '16px', borderRadius: 12, border: '1px solid rgba(22, 119, 255, 0.1)' }}>
+                            <div className="flex items-start" style={{ gap: 16, marginTop: 24, paddingTop: 24, borderTop: '1px solid rgba(22, 119, 255, 0.12)' }}>
                               <label className="text-sm font-medium text-gray-700 whitespace-nowrap pt-2" style={{ width: defaultFormConfig.labelWidth ? `${defaultFormConfig.labelWidth - 20}px` : '80px', textAlign: defaultFormConfig.labelPosition === 'left' ? 'left' : defaultFormConfig.labelPosition === 'right' ? 'right' : 'left', flexShrink: 0 }}>
                                 {t('form.reviewComment')}
                               </label>
@@ -2408,8 +2484,7 @@ export default function TaskManagement() {
                                 />
                               </div>
                             </div>
-                          </div>
-                        </Card>
+                        </div>
                       </div>
                       {/* 底部按钮 - 与盲板/拆除、确认节点一致 */}
                       <div
@@ -2650,15 +2725,16 @@ export default function TaskManagement() {
                   );
                 }
 
-                // 其他节点类型(需要根据 formId 获取表单)
+                // 其他节点类型(录入信息等,需要根据 formId 获取表单)- 与审核节点统一样式:渐变背景、无内置白框
                 return (
                   <div style={{ display: 'flex', flexDirection: 'column', height: '100%', overflow: 'hidden' }}>
-                    <div className="space-y-6" style={{ padding: '0 24px', flex: 1, overflowY: 'auto', minHeight: 0 }}>
-                    {/* 自定义表单 */}
+                    <div style={{ flex: 1, minHeight: 0, overflow: 'auto', padding: '24px', background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)' }}>
+                      <div style={{ width: '100%' }}>
+                    {/* 自定义表单:仅当前节点有 formId 时渲染,避免完成/结束节点误展示上一节点表单 */}
                     {formLoading ? (
                       <div className="py-8 text-center text-gray-500">{t('form.formLoading')}</div>
-                    ) : formData.rule && formData.rule.length > 0 ? (
-                      <div>
+                    ) : hasFormIdForCurrentNode && formData.rule && formData.rule.length > 0 ? (
+                      <div className="space-y-4">
                         {(() => {
                           const formConfig = formData.option?.formConfig || defaultFormConfig;
                           const layoutColumns = formConfig.layoutColumns || 1;
@@ -2714,18 +2790,7 @@ export default function TaskManagement() {
                           background: 'linear-gradient(165deg, #f0f7ff 0%, #fafbff 45%, #fff 100%)',
                         }}
                       >
-                        <Card
-                          style={{
-                            width: '100%',
-                            maxWidth: 500,
-                            textAlign: 'center',
-                            borderRadius: 16,
-                            boxShadow: '0 8px 32px rgba(22, 119, 255, 0.12), 0 2px 8px rgba(0,0,0,0.04)',
-                            border: '1px solid rgba(22, 119, 255, 0.15)',
-                            background: 'linear-gradient(180deg, #ffffff 0%, #fafcff 100%)',
-                          }}
-                          bodyStyle={{ padding: '48px 40px' }}
-                        >
+                        <div style={{ width: '100%', textAlign: 'center', padding: '24px 0' }}>
                           <div style={{ marginBottom: 24 }}>
                             <span
                               style={{
@@ -2748,10 +2813,11 @@ export default function TaskManagement() {
                           <div style={{ marginTop: 12, fontSize: 13, color: '#69b1ff' }}>
                             确认无误后点击「完成」结束本节点
                           </div>
-                        </Card>
+                        </div>
                       </div>
                     )}
 
+                      </div>
                     </div>
                     {/* 底部按钮 */}
                     <div

+ 17 - 8
src/components/WorkJobDetail.tsx

@@ -27,11 +27,12 @@ import ReactFlow, {
   Background,
   BackgroundVariant,
   NodeTypes,
-  EdgeTypes,
   ConnectionMode,
   Handle,
   Position,
 } from 'reactflow';
+import { sharedFlowEdgeTypes } from './FlowEdge';
+import { getLayoutedNodes, shouldApplyLayout } from '../utils/flowLayout';
 import 'reactflow/dist/style.css';
 import { workJobApi, WorkJobVO, WorkflowWorkNodeDO } from '../api/WorkJob';
 import { formatDateWithFormat } from '../utils/formatTime';
@@ -2092,6 +2093,10 @@ export default function WorkJobDetail() {
           targetHandle: edge.targetHandle,
           type: edge.type || 'straight',
           animated: false,
+          data: {
+            fixedLength: edge.data?.fixedLength ?? 150,
+            edgeType: edge.type || 'straight',
+          },
           style: { strokeWidth: 2, stroke: '#000000' },
           markerStart: {
             type: 'arrowclosed' as any,
@@ -2100,16 +2105,19 @@ export default function WorkJobDetail() {
         };
       });
 
-      setNodes(convertedNodes);
+      const finalNodes = shouldApplyLayout(convertedNodes, convertedEdges)
+        ? getLayoutedNodes(convertedNodes, convertedEdges)
+        : convertedNodes;
+      setNodes(finalNodes);
       setEdges(convertedEdges);
       
       // 根据流程顺序找到默认选中的节点:优先选中第一个进行中的节点,如果没有则选中第一个待执行的节点
       // 使用adjacency结构(类似编辑弹框的逻辑),按照childrenUuid顺序查找
       let defaultSelectedNode: Node | null = null;
       
-      // 构建节点映射
+      // 构建节点映射(使用最终用于渲染的节点列表)
       const nodeIdMap = new Map<string, Node>();
-      convertedNodes.forEach(node => {
+      finalNodes.forEach(node => {
         nodeIdMap.set(node.id, node);
       });
       
@@ -2129,19 +2137,16 @@ export default function WorkJobDetail() {
         convertedEdges.forEach(edge => {
           const sourceId = edge.source;
           const targetId = edge.target;
-          
+          if (!finalNodes.some((n) => n.id === sourceId) || !finalNodes.some((n) => n.id === targetId)) return;
           if (!adjacency[sourceId]) {
             adjacency[sourceId] = { parentUuid: [], childrenUuid: [] };
           }
           if (!adjacency[targetId]) {
             adjacency[targetId] = { parentUuid: [], childrenUuid: [] };
           }
-          
-          // source的子节点包含target
           if (!adjacency[sourceId].childrenUuid.includes(targetId)) {
             adjacency[sourceId].childrenUuid.push(targetId);
           }
-          // target的父节点包含source
           if (!adjacency[targetId].parentUuid.includes(sourceId)) {
             adjacency[targetId].parentUuid.push(sourceId);
           }
@@ -2756,9 +2761,13 @@ export default function WorkJobDetail() {
                     onNodesChange={onNodesChange}
                     onEdgesChange={onEdgesChange}
                     nodeTypes={nodeTypes}
+                    edgeTypes={sharedFlowEdgeTypes}
                     fitView
                     onInit={setReactFlowInstance}
                     connectionMode={ConnectionMode.Loose}
+                    snapToGrid={true}
+                    snapGrid={[16, 16]}
+                    connectionRadius={40}
                     defaultEdgeOptions={{
                       style: { strokeWidth: 2, stroke: '#000000' },
                       markerStart: {

+ 54 - 0
src/utils/flowEdgeUtils.ts

@@ -0,0 +1,54 @@
+import { getStraightPath, getSmoothStepPath } from 'reactflow';
+
+const DEFAULT_FIXED_LENGTH = 150;
+
+/**
+ * 与流程设计器一致的边路径计算:支持固定线段长度与 smoothstep/straight 类型
+ */
+export function getFlowEdgePath(
+  sourceX: number,
+  sourceY: number,
+  targetX: number,
+  targetY: number,
+  options?: { fixedLength?: number; type?: string }
+): [string, number, number] {
+  const fixedLength = options?.fixedLength ?? DEFAULT_FIXED_LENGTH;
+  const edgeType = options?.type || 'straight';
+
+  const dx = targetX - sourceX;
+  const dy = targetY - sourceY;
+  const actualDistance = Math.sqrt(dx * dx + dy * dy);
+
+  let adjustedSourceX = sourceX;
+  let adjustedSourceY = sourceY;
+  let adjustedTargetX = targetX;
+  let adjustedTargetY = targetY;
+
+  if (fixedLength > 0 && actualDistance > fixedLength && actualDistance > 0.001) {
+    const unitX = dx / actualDistance;
+    const unitY = dy / actualDistance;
+    const shortenDistance = (actualDistance - fixedLength) / 2;
+    adjustedSourceX = sourceX + unitX * shortenDistance;
+    adjustedSourceY = sourceY + unitY * shortenDistance;
+    adjustedTargetX = targetX - unitX * shortenDistance;
+    adjustedTargetY = targetY - unitY * shortenDistance;
+  }
+
+  if (edgeType === 'smoothstep') {
+    return getSmoothStepPath({
+      sourceX: adjustedSourceX,
+      sourceY: adjustedSourceY,
+      targetX: adjustedTargetX,
+      targetY: adjustedTargetY,
+      borderRadius: 15,
+    });
+  }
+  return getStraightPath({
+    sourceX: adjustedSourceX,
+    sourceY: adjustedSourceY,
+    targetX: adjustedTargetX,
+    targetY: adjustedTargetY,
+  });
+}
+
+export const FLOW_EDGE_DEFAULT_FIXED_LENGTH = DEFAULT_FIXED_LENGTH;

+ 85 - 0
src/utils/flowLayout.ts

@@ -0,0 +1,85 @@
+import type { Node, Edge } from 'reactflow';
+
+const HORIZONTAL_SPACING = 220;
+const VERTICAL_SPACING = 120;
+
+/**
+ * 判断是否应对节点应用自动布局(避免重叠)。
+ * 当绝大多数节点在 (0,0) 或存在大量同位置节点时返回 true。
+ */
+export function shouldApplyLayout(nodes: Node[], edges: Edge[]): boolean {
+  if (!nodes.length) return false;
+  const atOrigin = nodes.filter((n) => n.position.x === 0 && n.position.y === 0).length;
+  if (atOrigin >= nodes.length * 0.8) return true;
+  const positionKey = (n: Node) => `${Math.round(n.position.x)}_${Math.round(n.position.y)}`;
+  const byPos = new Map<string, number>();
+  nodes.forEach((n) => {
+    const k = positionKey(n);
+    byPos.set(k, (byPos.get(k) || 0) + 1);
+  });
+  const maxSamePosition = Math.max(...byPos.values(), 0);
+  return maxSamePosition >= Math.max(2, Math.ceil(nodes.length * 0.3));
+}
+
+/**
+ * 根据边的拓扑关系计算层级,再按层级排布节点位置,与流程设计器风格一致,避免重叠。
+ */
+export function getLayoutedNodes(
+  nodes: Node[],
+  edges: Edge[],
+  options?: { horizontalSpacing?: number; verticalSpacing?: number }
+): Node[] {
+  const hSpacing = options?.horizontalSpacing ?? HORIZONTAL_SPACING;
+  const vSpacing = options?.verticalSpacing ?? VERTICAL_SPACING;
+
+  const idToNode = new Map<string, Node>();
+  nodes.forEach((n) => idToNode.set(n.id, n));
+
+  const parents = new Map<string, string[]>();
+  const children = new Map<string, string[]>();
+  nodes.forEach((n) => {
+    parents.set(n.id, []);
+    children.set(n.id, []);
+  });
+  edges.forEach((e) => {
+    if (!idToNode.has(e.source) || !idToNode.has(e.target)) return;
+    const pl = parents.get(e.target) ?? [];
+    if (!pl.includes(e.source)) pl.push(e.source);
+    parents.set(e.target, pl);
+    const cl = children.get(e.source) ?? [];
+    if (!cl.includes(e.target)) cl.push(e.target);
+    children.set(e.source, cl);
+  });
+
+  const levels = new Map<string, number>();
+  function getLevel(id: string): number {
+    if (levels.has(id)) return levels.get(id)!;
+    const pr = parents.get(id) ?? [];
+    const l = pr.length === 0 ? 0 : 1 + Math.max(...pr.map((pid) => getLevel(pid)));
+    levels.set(id, l);
+    return l;
+  }
+  nodes.forEach((n) => getLevel(n.id));
+
+  const byLevel = new Map<number, string[]>();
+  levels.forEach((level, id) => {
+    const list = byLevel.get(level) ?? [];
+    list.push(id);
+    byLevel.set(level, list);
+  });
+  const sortedLevels = Array.from(byLevel.keys()).sort((a, b) => a - b);
+
+  const positionById = new Map<string, { x: number; y: number }>();
+  sortedLevels.forEach((level) => {
+    const ids = byLevel.get(level) ?? [];
+    const y = level * vSpacing;
+    ids.forEach((id, i) => {
+      positionById.set(id, { x: i * hSpacing, y });
+    });
+  });
+
+  return nodes.map((n) => ({
+    ...n,
+    position: positionById.get(n.id) ?? n.position,
+  }));
+}