ソースを参照

修复作业详情中作业流程里时间轴渲染

pm 4 ヶ月 前
コミット
4f7101cdf1
1 ファイル変更171 行追加38 行削除
  1. 171 38
      src/components/WorkJobDetail.tsx

+ 171 - 38
src/components/WorkJobDetail.tsx

@@ -380,12 +380,69 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
   // 获取 Timeline 的图标
   const getTimelineDot = (status: 'completed' | 'in_progress' | 'pending') => {
     if (status === 'completed') {
-      return <CheckCircleOutlined style={{ fontSize: '16px', color: '#52c41a' }} />;
+      return (
+        <div style={{ 
+          width: '28px', 
+          height: '28px',
+          minWidth: '28px',
+          minHeight: '28px',
+          maxWidth: '28px',
+          maxHeight: '28px',
+          backgroundColor: '#52c41a', 
+          borderRadius: '50%',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          boxShadow: '0 0 0 4px rgba(82, 196, 26, 0.15), 0 0 0 8px rgba(82, 196, 26, 0.08)',
+          boxSizing: 'border-box',
+          flexShrink: 0
+        }}>
+          <CheckCircleOutlined style={{ fontSize: '20px', color: 'white', display: 'block' }} />
+        </div>
+      );
     }
     if (status === 'in_progress') {
-      return <SyncOutlined spin style={{ fontSize: '16px', color: '#1890ff' }} />;
+      return (
+        <div style={{ 
+          width: '28px', 
+          height: '28px',
+          minWidth: '28px',
+          minHeight: '28px',
+          maxWidth: '28px',
+          maxHeight: '28px',
+          backgroundColor: '#1890ff', 
+          borderRadius: '50%',
+          display: 'flex',
+          alignItems: 'center',
+          justifyContent: 'center',
+          boxShadow: '0 0 0 4px rgba(24, 144, 255, 0.15), 0 0 0 8px rgba(24, 144, 255, 0.08)',
+          boxSizing: 'border-box',
+          flexShrink: 0
+        }}>
+          <SyncOutlined spin style={{ fontSize: '20px', color: 'white', display: 'block' }} />
+        </div>
+      );
     }
-    return <ClockCircleOutlined style={{ fontSize: '16px', color: '#d9d9d9' }} />;
+    return (
+      <div style={{ 
+        width: '28px', 
+        height: '28px',
+        minWidth: '28px',
+        minHeight: '28px',
+        maxWidth: '28px',
+        maxHeight: '28px',
+        backgroundColor: '#d9d9d9', 
+        borderRadius: '50%',
+        display: 'flex',
+        alignItems: 'center',
+        justifyContent: 'center',
+        boxShadow: '0 0 0 4px rgba(217, 217, 217, 0.15), 0 0 0 8px rgba(217, 217, 217, 0.08)',
+        boxSizing: 'border-box',
+        flexShrink: 0
+      }}>
+        <ClockCircleOutlined style={{ fontSize: '20px', color: 'white', display: 'block' }} />
+      </div>
+    );
   };
 
   // 构建 Timeline 数据
@@ -409,35 +466,42 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
 
     const executorInfo = getExecutorInfo(node, node.type || '');
     const time = node.createTime || node.updateTime;
-    const isBranchParent = node.isBranchParent && node.branchNodes && node.branchNodes.length > 1;
+    
+    // 获取主节点uuid(如果存在mainNodeUuid则使用它,否则使用当前节点的uuid)
+    const mainNodeUuid = (node as any).mainNodeUuid || node.uuid || '';
+    // 过滤掉主节点,只显示其他分支节点
+    const otherBranchNodes = node.isBranchParent && node.branchNodes 
+      ? node.branchNodes.filter(branchNode => branchNode.uuid !== mainNodeUuid)
+      : [];
+    
+    // 只有当有其他分支节点时才显示展开按钮
+    const isBranchParent = node.isBranchParent && node.branchNodes && node.branchNodes.length > 1 && otherBranchNodes.length > 0;
     const branchParentUuid = (node as any).branchParentUuid || node.parentUuid || node.uuid || '';
     const isExpanded = isBranchParent && expandedBranchGroups.has(branchParentUuid);
     const nodeKey = node.uuid || String(node.id || index);
     const isVisible = visibleNodes.has(nodeKey);
 
     // 构建分支节点内容
-    const branchContent = isBranchParent && isExpanded && node.branchNodes ? (
+    const branchContent = isBranchParent && isExpanded && otherBranchNodes.length > 0 ? (
       <div className="mt-3 space-y-2" style={{ width: '100%' }}>
-        {node.branchNodes.map((branchNode, branchIndex) => {
+        {otherBranchNodes.map((branchNode, branchIndex) => {
           let branchStatus: 'completed' | 'in_progress' | 'pending' = 'pending';
           if (branchNode.approvalStatus === 'approved') {
             branchStatus = 'completed';
           } else if (branchNode.approvalStatus === 'unaudited' || branchNode.approvalStatus === 'pending') {
-            if (branchIndex > 0) {
-              const prevBranch = node.branchNodes![branchIndex - 1];
-              if (prevBranch && prevBranch.approvalStatus === 'approved') {
-                branchStatus = 'in_progress';
-              }
+            // 检查主节点状态
+            if (node.approvalStatus === 'approved') {
+              branchStatus = 'in_progress';
             } else {
-              if (node.approvalStatus === 'approved') {
+              // 检查前面的分支节点(包括主节点)是否有已完成的
+              const allNodesBefore = node.branchNodes!.filter(n => {
+                const nodeIndex = node.branchNodes!.findIndex(bn => bn.uuid === n.uuid);
+                const currentIndex = node.branchNodes!.findIndex(bn => bn.uuid === branchNode.uuid);
+                return nodeIndex < currentIndex;
+              });
+              const hasCompletedBefore = allNodesBefore.some(n => n.approvalStatus === 'approved');
+              if (hasCompletedBefore) {
                 branchStatus = 'in_progress';
-              } else {
-                const hasCompletedBranch = node.branchNodes!.some((n, idx) => 
-                  idx < branchIndex && n.approvalStatus === 'approved'
-                );
-                if (hasCompletedBranch) {
-                  branchStatus = 'in_progress';
-                }
               }
             }
           }
@@ -448,12 +512,12 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
           return (
             <div
               key={branchNode.uuid || branchNode.id || branchIndex}
-              className={`mb-2 p-3 rounded-lg border-l-3 shadow-sm transition-all duration-300 hover:shadow bg-white ${
+              className={`mb-2 p-3 rounded-lg border shadow-md transition-all duration-300 hover:shadow-lg ${
                 branchStatus === 'completed' 
-                  ? 'border-l-green-500 border-t border-r border-b border-gray-200' 
+                  ? 'bg-emerald-50 border-emerald-200' 
                   : branchStatus === 'in_progress' 
-                  ? 'border-l-blue-500 border-t border-r border-b border-gray-200' 
-                  : 'border-l-gray-400 border-t border-r border-b border-gray-200'
+                  ? 'bg-sky-50 border-sky-200' 
+                  : 'bg-gray-50 border-gray-200'
               }`}
               style={{
                 animation: isVisible ? 'fadeInUp 0.5s ease-out' : 'none',
@@ -501,7 +565,7 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
     return {
       key: nodeKey,
       dot: getTimelineDot(status),
-      color: getTimelineColor(status),
+      color: undefined,
       children: (
         <div 
           className={`transition-all duration-500 ${
@@ -513,12 +577,12 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
           }}
         >
           <div 
-            className={`mb-3 p-4 rounded-lg border-l-4 shadow-sm transition-all duration-300 hover:shadow-md bg-white ${
+            className={`mb-3 p-4 rounded-lg border shadow-md transition-all duration-300 hover:shadow-lg ${
               status === 'completed' 
-                ? 'border-l-green-500 border-t border-r border-b border-gray-200' 
+                ? 'bg-emerald-50 border-emerald-200' 
                 : status === 'in_progress' 
-                ? 'border-l-blue-500 border-t border-r border-b border-gray-200' 
-                : 'border-l-gray-400 border-t border-r border-b border-gray-200'
+                ? 'bg-sky-50 border-sky-200' 
+                : 'bg-gray-50 border-gray-200'
             }`}
             style={{ 
               maxWidth: '100%', 
@@ -616,24 +680,93 @@ const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
           max-width: 280px;
         }
         .workflow-timeline .ant-timeline-item-tail {
-          border-left: 2px solid #e8e8e8;
+          border-left: 3px solid #1890ff;
         }
         .workflow-timeline .ant-timeline-item-head {
-          width: 16px;
-          height: 16px;
-          border-width: 2px;
+          width: 14px;
+          height: 14px;
+          border-width: 0;
+          background-color: #1890ff;
+          border-radius: 50%;
+          box-shadow: 0 0 0 3px rgba(24, 144, 255, 0.1);
         }
         .workflow-timeline .ant-timeline-item-head-green {
-          border-color: #52c41a;
-          background-color: #52c41a;
+          width: 28px !important;
+          height: 28px !important;
+          min-width: 28px !important;
+          min-height: 28px !important;
+          max-width: 28px !important;
+          max-height: 28px !important;
+          background-color: #52c41a !important;
+          border-radius: 50% !important;
+          border: none !important;
+          box-shadow: 0 0 0 4px rgba(82, 196, 26, 0.15), 0 0 0 8px rgba(82, 196, 26, 0.08) !important;
+          display: flex !important;
+          align-items: center !important;
+          justify-content: center !important;
+          box-sizing: border-box !important;
+          flex-shrink: 0 !important;
+          padding: 0 !important;
+        }
+        .workflow-timeline .ant-timeline-item-head-green .anticon,
+        .workflow-timeline .ant-timeline-item-head-green svg {
+          color: white !important;
+          fill: white !important;
+          font-size: 20px !important;
+          width: 20px !important;
+          height: 20px !important;
         }
         .workflow-timeline .ant-timeline-item-head-blue {
-          border-color: #1890ff;
-          background-color: #1890ff;
+          width: 28px !important;
+          height: 28px !important;
+          min-width: 28px !important;
+          min-height: 28px !important;
+          max-width: 28px !important;
+          max-height: 28px !important;
+          background-color: #1890ff !important;
+          border-radius: 50% !important;
+          border: none !important;
+          box-shadow: 0 0 0 4px rgba(24, 144, 255, 0.15), 0 0 0 8px rgba(24, 144, 255, 0.08) !important;
+          display: flex !important;
+          align-items: center !important;
+          justify-content: center !important;
+          box-sizing: border-box !important;
+          flex-shrink: 0 !important;
+          padding: 0 !important;
+        }
+        .workflow-timeline .ant-timeline-item-head-blue .anticon,
+        .workflow-timeline .ant-timeline-item-head-blue svg {
+          color: white !important;
+          fill: white !important;
+          font-size: 20px !important;
+          width: 20px !important;
+          height: 20px !important;
         }
         .workflow-timeline .ant-timeline-item-head-gray {
-          border-color: #d9d9d9;
-          background-color: #fff;
+          width: 28px !important;
+          height: 28px !important;
+          min-width: 28px !important;
+          min-height: 28px !important;
+          max-width: 28px !important;
+          max-height: 28px !important;
+          background-color: #d9d9d9 !important;
+          border-radius: 50% !important;
+          border: none !important;
+          box-shadow: 0 0 0 4px rgba(217, 217, 217, 0.15), 0 0 0 8px rgba(217, 217, 217, 0.08) !important;
+          display: flex !important;
+          align-items: center !important;
+          justify-content: center !important;
+          box-sizing: border-box !important;
+          flex-shrink: 0 !important;
+          padding: 0 !important;
+        }
+        .workflow-timeline .ant-timeline-item-head-gray .anticon,
+        .workflow-timeline .ant-timeline-item-head-gray svg {
+          color: white !important;
+          fill: white !important;
+          font-size: 20px !important;
+          width: 20px !important;
+          height: 20px !important;
         }
       `}</style>
       <Timeline