|
|
@@ -16,6 +16,8 @@ import {
|
|
|
Minus,
|
|
|
Loader2
|
|
|
} from 'lucide-react';
|
|
|
+import { Timeline, Badge, Card, Collapse, Tag } from 'antd';
|
|
|
+import { CheckCircleOutlined, ClockCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
|
|
import { workJobApi, WorkJobVO, WorkflowWorkNodeDO } from '../api/WorkJob';
|
|
|
import { formatDateWithFormat } from '../utils/formatTime';
|
|
|
|
|
|
@@ -162,6 +164,487 @@ const getNodeStatus = (workNode: WorkflowWorkNodeDO, nodeMap: Map<string, Workfl
|
|
|
return 'pending';
|
|
|
};
|
|
|
|
|
|
+// 工作流渲染组件
|
|
|
+interface WorkflowRendererProps {
|
|
|
+ nodeList: WorkflowWorkNodeDO[];
|
|
|
+ expandedBranchGroups: Set<string>;
|
|
|
+ setExpandedBranchGroups: React.Dispatch<React.SetStateAction<Set<string>>>;
|
|
|
+ getExecutorInfo: (workNode: WorkflowWorkNodeDO, type: string) => string;
|
|
|
+ getNodeIcon: (type: string, status: 'completed' | 'in_progress' | 'pending') => React.ReactNode;
|
|
|
+ formatDate: (date: string | number | Date) => string;
|
|
|
+}
|
|
|
+
|
|
|
+const WorkflowRenderer: React.FC<WorkflowRendererProps> = ({
|
|
|
+ nodeList,
|
|
|
+ expandedBranchGroups,
|
|
|
+ setExpandedBranchGroups,
|
|
|
+ getExecutorInfo,
|
|
|
+ getNodeIcon,
|
|
|
+ formatDate,
|
|
|
+}) => {
|
|
|
+ // 用于控制节点依次渲染的状态
|
|
|
+ const [visibleNodes, setVisibleNodes] = useState<Set<string>>(new Set());
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ // 重置可见节点
|
|
|
+ setVisibleNodes(new Set());
|
|
|
+
|
|
|
+ // 依次显示节点,每个节点延迟 200ms
|
|
|
+ const timers: NodeJS.Timeout[] = [];
|
|
|
+
|
|
|
+ const sortedNodes = [...nodeList].sort((a, b) => {
|
|
|
+ const aId = a.id || 0;
|
|
|
+ const bId = b.id || 0;
|
|
|
+ if (aId !== bId) return aId - bId;
|
|
|
+ const aTime = a.createTime ? new Date(a.createTime).getTime() : 0;
|
|
|
+ const bTime = b.createTime ? new Date(b.createTime).getTime() : 0;
|
|
|
+ return aTime - bTime;
|
|
|
+ });
|
|
|
+
|
|
|
+ sortedNodes.forEach((node, index) => {
|
|
|
+ const timer = setTimeout(() => {
|
|
|
+ setVisibleNodes(prev => new Set([...prev, node.uuid || String(node.id || index)]));
|
|
|
+ }, index * 200 + 100);
|
|
|
+ timers.push(timer);
|
|
|
+ });
|
|
|
+
|
|
|
+ return () => {
|
|
|
+ timers.forEach(timer => clearTimeout(timer));
|
|
|
+ };
|
|
|
+ }, [nodeList]);
|
|
|
+ // 按 parentUuid 分组,找出同父节点的子节点
|
|
|
+ const branchGroups = new Map<string, WorkflowWorkNodeDO[]>();
|
|
|
+ nodeList.forEach((node) => {
|
|
|
+ if (node.parentUuid) {
|
|
|
+ if (!branchGroups.has(node.parentUuid)) {
|
|
|
+ branchGroups.set(node.parentUuid, []);
|
|
|
+ }
|
|
|
+ branchGroups.get(node.parentUuid)!.push(node);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 调试:打印所有分组信息
|
|
|
+ console.log('=== 节点分组信息 ===');
|
|
|
+ branchGroups.forEach((children, parentUuid) => {
|
|
|
+ console.log(`父节点 ${parentUuid} 有 ${children.length} 个子节点:`, children.map(c => ({ uuid: c.uuid, nodeName: c.nodeName })));
|
|
|
+ });
|
|
|
+
|
|
|
+ // 对每个分支组进行排序(按id或创建时间)
|
|
|
+ branchGroups.forEach((children, parentUuid) => {
|
|
|
+ children.sort((a, b) => {
|
|
|
+ const aId = a.id || 0;
|
|
|
+ const bId = b.id || 0;
|
|
|
+ if (aId !== bId) return aId - bId;
|
|
|
+ const aTime = a.createTime ? new Date(a.createTime).getTime() : 0;
|
|
|
+ const bTime = b.createTime ? new Date(b.createTime).getTime() : 0;
|
|
|
+ return aTime - bTime;
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 找出有多个子节点的父节点(需要显示分支的)
|
|
|
+ const parentNodesWithBranches = new Set<string>();
|
|
|
+ branchGroups.forEach((children, parentUuid) => {
|
|
|
+ if (children.length > 1) {
|
|
|
+ parentNodesWithBranches.add(parentUuid);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 构建节点映射,方便查找父节点
|
|
|
+ const nodeMap = new Map<string, WorkflowWorkNodeDO>();
|
|
|
+ nodeList.forEach((node) => {
|
|
|
+ if (node.uuid) {
|
|
|
+ nodeMap.set(node.uuid, node);
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 构建主流程节点列表
|
|
|
+ // 如果节点有多个子节点,在主流程中显示父节点,分支节点隐藏
|
|
|
+ const mainFlowNodes: (WorkflowWorkNodeDO & { isBranchParent?: boolean; branchNodes?: WorkflowWorkNodeDO[]; branchParentUuid?: string; mainNodeUuid?: string })[] = [];
|
|
|
+ const processedNodes = new Set<string>();
|
|
|
+ const allBranchNodeUuids = new Set<string>(); // 记录所有有相同父节点的节点uuid
|
|
|
+
|
|
|
+ // 标记所有有相同父节点的节点(无论数量多少)
|
|
|
+ branchGroups.forEach((children, parentUuid) => {
|
|
|
+ if (children.length > 1) {
|
|
|
+ children.forEach((child) => {
|
|
|
+ allBranchNodeUuids.add(child.uuid || '');
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 按顺序遍历节点列表,构建主流程
|
|
|
+ // 先按id或创建时间排序,确保顺序正确
|
|
|
+ const sortedNodeList = [...nodeList].sort((a, b) => {
|
|
|
+ const aId = a.id || 0;
|
|
|
+ const bId = b.id || 0;
|
|
|
+ if (aId !== bId) return aId - bId;
|
|
|
+ const aTime = a.createTime ? new Date(a.createTime).getTime() : 0;
|
|
|
+ const bTime = b.createTime ? new Date(b.createTime).getTime() : 0;
|
|
|
+ return aTime - bTime;
|
|
|
+ });
|
|
|
+
|
|
|
+ sortedNodeList.forEach((node) => {
|
|
|
+ // 如果节点已经被处理过,直接跳过
|
|
|
+ if (processedNodes.has(node.uuid || '')) {
|
|
|
+ console.log(`节点 ${node.uuid} 已被处理,跳过`);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查这个节点是否有相同父节点的兄弟节点
|
|
|
+ if (node.parentUuid) {
|
|
|
+ const siblings = branchGroups.get(node.parentUuid) || [];
|
|
|
+
|
|
|
+ // 如果有多个同父节点的子节点
|
|
|
+ if (siblings.length > 1) {
|
|
|
+ // 如果是第一个节点(按排序后的顺序),需要处理
|
|
|
+ const firstSibling = siblings[0];
|
|
|
+ if (firstSibling && firstSibling.uuid === node.uuid && !processedNodes.has(node.parentUuid)) {
|
|
|
+ console.log(`处理分支组: 父节点 ${node.parentUuid}, 子节点数量: ${siblings.length}`, siblings.map(s => s.uuid));
|
|
|
+ processedNodes.add(node.parentUuid);
|
|
|
+ // 标记所有兄弟节点已处理(包括第一个)
|
|
|
+ siblings.forEach((sibling) => {
|
|
|
+ processedNodes.add(sibling.uuid || '');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 找到父节点
|
|
|
+ const parentNode = nodeMap.get(node.parentUuid);
|
|
|
+ if (parentNode && !allBranchNodeUuids.has(parentNode.uuid || '')) {
|
|
|
+ // 如果父节点存在且不是分支节点,显示父节点
|
|
|
+ // 所有子节点都显示在展开区域,不显示在主流程
|
|
|
+ console.log(`显示父节点: ${parentNode.uuid}, 所有子节点都放在展开区域:`, siblings.map(s => s.uuid));
|
|
|
+ mainFlowNodes.push({
|
|
|
+ ...parentNode,
|
|
|
+ isBranchParent: true,
|
|
|
+ branchNodes: siblings, // 所有子节点都显示在展开区域
|
|
|
+ branchParentUuid: node.parentUuid,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 如果找不到父节点或父节点也是分支节点,使用第一个分支节点作为代表节点
|
|
|
+ // 但所有子节点(包括第一个)都显示在展开区域
|
|
|
+ const mainNodeUuid = firstSibling.uuid || '';
|
|
|
+ console.log(`使用第一个分支节点作为代表: ${mainNodeUuid}, 所有子节点都放在展开区域:`, siblings.map(s => s.uuid));
|
|
|
+ mainFlowNodes.push({
|
|
|
+ ...firstSibling,
|
|
|
+ isBranchParent: true,
|
|
|
+ branchNodes: siblings, // 所有子节点(包括第一个)都显示在展开区域
|
|
|
+ branchParentUuid: node.parentUuid,
|
|
|
+ mainNodeUuid: mainNodeUuid, // 记录主流程中显示的节点uuid
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 不是第一个节点,或者是已经处理过的分支组,直接跳过
|
|
|
+ console.log(`跳过分支节点: ${node.uuid} (不是第一个或已处理)`);
|
|
|
+ }
|
|
|
+ // 所有兄弟节点都不添加到主流程(它们会在展开时显示)
|
|
|
+ } else {
|
|
|
+ // 只有一个子节点,直接添加到主流程
|
|
|
+ if (!processedNodes.has(node.uuid || '')) {
|
|
|
+ processedNodes.add(node.uuid || '');
|
|
|
+ mainFlowNodes.push(node);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 没有父节点(根节点),直接添加到主流程
|
|
|
+ if (!processedNodes.has(node.uuid || '')) {
|
|
|
+ processedNodes.add(node.uuid || '');
|
|
|
+ mainFlowNodes.push(node);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ console.log('=== 最终主流程节点 ===');
|
|
|
+ mainFlowNodes.forEach((node, index) => {
|
|
|
+ console.log(`${index + 1}. ${node.nodeName} (${node.uuid})`, node.isBranchParent ? `[有分支: ${node.branchNodes?.length || 0}个]` : '');
|
|
|
+ });
|
|
|
+
|
|
|
+ // 切换分支展开/收起
|
|
|
+ const toggleBranchGroup = (parentUuid: string) => {
|
|
|
+ setExpandedBranchGroups((prev) => {
|
|
|
+ const newSet = new Set(prev);
|
|
|
+ if (newSet.has(parentUuid)) {
|
|
|
+ newSet.delete(parentUuid);
|
|
|
+ } else {
|
|
|
+ newSet.add(parentUuid);
|
|
|
+ }
|
|
|
+ return newSet;
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取 Timeline 的颜色
|
|
|
+ const getTimelineColor = (status: 'completed' | 'in_progress' | 'pending') => {
|
|
|
+ if (status === 'completed') return 'green';
|
|
|
+ if (status === 'in_progress') return 'blue';
|
|
|
+ return 'gray';
|
|
|
+ };
|
|
|
+
|
|
|
+ // 获取 Timeline 的图标
|
|
|
+ const getTimelineDot = (status: 'completed' | 'in_progress' | 'pending') => {
|
|
|
+ if (status === 'completed') {
|
|
|
+ return <CheckCircleOutlined style={{ fontSize: '16px', color: '#52c41a' }} />;
|
|
|
+ }
|
|
|
+ if (status === 'in_progress') {
|
|
|
+ return <SyncOutlined spin style={{ fontSize: '16px', color: '#1890ff' }} />;
|
|
|
+ }
|
|
|
+ return <ClockCircleOutlined style={{ fontSize: '16px', color: '#d9d9d9' }} />;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 构建 Timeline 数据
|
|
|
+ const timelineItems = mainFlowNodes.map((node, index) => {
|
|
|
+ // 判断节点状态
|
|
|
+ let status: 'completed' | 'in_progress' | 'pending' = 'pending';
|
|
|
+ if (node.approvalStatus === 'approved') {
|
|
|
+ status = 'completed';
|
|
|
+ } else if (node.approvalStatus === 'unaudited' || node.approvalStatus === 'pending') {
|
|
|
+ if (index === 0) {
|
|
|
+ status = 'completed';
|
|
|
+ } else {
|
|
|
+ const prevNode = mainFlowNodes[index - 1];
|
|
|
+ if (prevNode && prevNode.approvalStatus === 'approved') {
|
|
|
+ status = 'in_progress';
|
|
|
+ } else {
|
|
|
+ status = 'pending';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const executorInfo = getExecutorInfo(node, node.type || '');
|
|
|
+ const time = node.createTime || node.updateTime;
|
|
|
+ const isBranchParent = node.isBranchParent && node.branchNodes && node.branchNodes.length > 1;
|
|
|
+ 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 ? (
|
|
|
+ <div className="mt-3 space-y-2" style={{ width: '100%' }}>
|
|
|
+ {node.branchNodes.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';
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ if (node.approvalStatus === 'approved') {
|
|
|
+ branchStatus = 'in_progress';
|
|
|
+ } else {
|
|
|
+ const hasCompletedBranch = node.branchNodes!.some((n, idx) =>
|
|
|
+ idx < branchIndex && n.approvalStatus === 'approved'
|
|
|
+ );
|
|
|
+ if (hasCompletedBranch) {
|
|
|
+ branchStatus = 'in_progress';
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ const branchExecutorInfo = getExecutorInfo(branchNode, branchNode.type || '');
|
|
|
+ const branchTime = branchNode.createTime || branchNode.updateTime;
|
|
|
+
|
|
|
+ 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 ${
|
|
|
+ branchStatus === 'completed'
|
|
|
+ ? 'border-l-green-500 border-t border-r border-b border-gray-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'
|
|
|
+ }`}
|
|
|
+ style={{
|
|
|
+ animation: isVisible ? 'fadeInUp 0.5s ease-out' : 'none',
|
|
|
+ animationDelay: `${(index * 200 + branchIndex * 100) / 1000}s`,
|
|
|
+ maxWidth: '240px',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className="flex items-start gap-2">
|
|
|
+ <div className="flex-shrink-0 mt-0.5">
|
|
|
+ {getNodeIcon(branchNode.type || '', branchStatus)}
|
|
|
+ </div>
|
|
|
+ <div className="flex-1 min-w-0">
|
|
|
+ <div className="flex items-center gap-1.5 mb-1.5">
|
|
|
+ <span className={`font-semibold text-xs ${
|
|
|
+ branchStatus === 'pending' ? 'text-gray-500' : 'text-gray-900'
|
|
|
+ }`}>
|
|
|
+ {branchNode.nodeName || '未知节点'}
|
|
|
+ </span>
|
|
|
+ <Tag
|
|
|
+ color={getTimelineColor(branchStatus)}
|
|
|
+ className="text-xs"
|
|
|
+ >
|
|
|
+ {branchStatus === 'completed' ? '已完成' : branchStatus === 'in_progress' ? '进行中' : '待处理'}
|
|
|
+ </Tag>
|
|
|
+ </div>
|
|
|
+ <div className={`text-xs ml-6 mb-1 ${
|
|
|
+ branchStatus === 'pending' ? 'text-gray-400' : 'text-gray-600'
|
|
|
+ }`}>
|
|
|
+ {branchExecutorInfo}
|
|
|
+ </div>
|
|
|
+ {branchTime && (
|
|
|
+ <div className="text-xs text-gray-500 ml-6 flex items-center">
|
|
|
+ <Clock className="w-2.5 h-2.5 mr-1" />
|
|
|
+ {formatDate(branchTime)}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ ) : null;
|
|
|
+
|
|
|
+ return {
|
|
|
+ key: nodeKey,
|
|
|
+ dot: getTimelineDot(status),
|
|
|
+ color: getTimelineColor(status),
|
|
|
+ children: (
|
|
|
+ <div
|
|
|
+ className={`transition-all duration-500 ${
|
|
|
+ isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'
|
|
|
+ }`}
|
|
|
+ style={{
|
|
|
+ animation: isVisible ? 'fadeInUp 0.5s ease-out' : 'none',
|
|
|
+ animationDelay: `${index * 200 / 1000}s`,
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ className={`mb-3 p-4 rounded-lg border-l-4 shadow-sm transition-all duration-300 hover:shadow-md bg-white ${
|
|
|
+ status === 'completed'
|
|
|
+ ? 'border-l-green-500 border-t border-r border-b border-gray-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'
|
|
|
+ }`}
|
|
|
+ style={{
|
|
|
+ maxWidth: '100%',
|
|
|
+ width: '100%',
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ <div className="flex items-start justify-between gap-3">
|
|
|
+ <div className="flex-1 min-w-0">
|
|
|
+ <div className="flex items-center gap-2 mb-2">
|
|
|
+ <div className="flex-shrink-0">
|
|
|
+ {getNodeIcon(node.type || '', status)}
|
|
|
+ </div>
|
|
|
+ <span className={`font-semibold text-sm ${
|
|
|
+ status === 'pending' ? 'text-gray-500' : 'text-gray-900'
|
|
|
+ }`}>
|
|
|
+ {node.nodeName || '未知节点'}
|
|
|
+ </span>
|
|
|
+ <Tag
|
|
|
+ color={getTimelineColor(status)}
|
|
|
+ className="text-xs"
|
|
|
+ >
|
|
|
+ {status === 'completed' ? '已完成' : status === 'in_progress' ? '进行中' : '待处理'}
|
|
|
+ </Tag>
|
|
|
+ </div>
|
|
|
+ <div className={`text-xs ml-7 mb-1.5 ${
|
|
|
+ status === 'pending' ? 'text-gray-400' : 'text-gray-600'
|
|
|
+ }`}>
|
|
|
+ {executorInfo}
|
|
|
+ </div>
|
|
|
+ {time && (
|
|
|
+ <div className="text-xs text-gray-500 ml-7 flex items-center">
|
|
|
+ <Clock className="w-3 h-3 mr-1" />
|
|
|
+ {formatDate(time)}
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 分支展开按钮 */}
|
|
|
+ {isBranchParent && (
|
|
|
+ <button
|
|
|
+ onClick={() => toggleBranchGroup(branchParentUuid)}
|
|
|
+ className="flex items-center justify-center w-6 h-6 rounded hover:bg-gray-100 transition-colors flex-shrink-0"
|
|
|
+ title={isExpanded ? '收起分支' : '展开分支'}
|
|
|
+ >
|
|
|
+ {isExpanded ? (
|
|
|
+ <Minus className="w-3.5 h-3.5 text-gray-600" />
|
|
|
+ ) : (
|
|
|
+ <Plus className="w-3.5 h-3.5 text-gray-600" />
|
|
|
+ )}
|
|
|
+ </button>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ {branchContent}
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="workflow-timeline">
|
|
|
+ <style>{`
|
|
|
+ @keyframes fadeInUp {
|
|
|
+ from {
|
|
|
+ opacity: 0;
|
|
|
+ transform: translateY(20px);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ opacity: 1;
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-content {
|
|
|
+ min-height: 60px;
|
|
|
+ max-width: 45%;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-left .ant-timeline-item-content {
|
|
|
+ text-align: right;
|
|
|
+ padding-right: 24px;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-left .ant-timeline-item-content > div {
|
|
|
+ display: inline-block;
|
|
|
+ text-align: left;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 280px;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-right .ant-timeline-item-content {
|
|
|
+ text-align: left;
|
|
|
+ padding-left: 24px;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-right .ant-timeline-item-content > div {
|
|
|
+ display: inline-block;
|
|
|
+ text-align: left;
|
|
|
+ width: 100%;
|
|
|
+ max-width: 280px;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-tail {
|
|
|
+ border-left: 2px solid #e8e8e8;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-head {
|
|
|
+ width: 16px;
|
|
|
+ height: 16px;
|
|
|
+ border-width: 2px;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-head-green {
|
|
|
+ border-color: #52c41a;
|
|
|
+ background-color: #52c41a;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-head-blue {
|
|
|
+ border-color: #1890ff;
|
|
|
+ background-color: #1890ff;
|
|
|
+ }
|
|
|
+ .workflow-timeline .ant-timeline-item-head-gray {
|
|
|
+ border-color: #d9d9d9;
|
|
|
+ background-color: #fff;
|
|
|
+ }
|
|
|
+ `}</style>
|
|
|
+ <Timeline
|
|
|
+ mode="alternate"
|
|
|
+ items={timelineItems}
|
|
|
+ className="custom-timeline"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
export default function WorkJobDetail() {
|
|
|
const navigate = useNavigate();
|
|
|
const [searchParams] = useSearchParams();
|
|
|
@@ -173,6 +656,9 @@ export default function WorkJobDetail() {
|
|
|
// 流程树状态
|
|
|
const [workflowTree, setWorkflowTree] = useState<WorkflowNode[]>([]);
|
|
|
const [expandedBranches, setExpandedBranches] = useState<Set<string>>(new Set());
|
|
|
+
|
|
|
+ // 展开的分支组(按 parentUuid)
|
|
|
+ const [expandedBranchGroups, setExpandedBranchGroups] = useState<Set<string>>(new Set());
|
|
|
|
|
|
// 获取作业详情
|
|
|
useEffect(() => {
|
|
|
@@ -564,7 +1050,19 @@ export default function WorkJobDetail() {
|
|
|
</h2>
|
|
|
</div>
|
|
|
<div className="flex-1 overflow-y-auto p-6">
|
|
|
- {/* 作业流程渲染 - 待重新设计 */}
|
|
|
+ {/* 作业流程渲染 */}
|
|
|
+ {jobDetail?.workflowWorkNodeDOList && Array.isArray(jobDetail.workflowWorkNodeDOList) && jobDetail.workflowWorkNodeDOList.length > 0 ? (
|
|
|
+ <WorkflowRenderer
|
|
|
+ nodeList={jobDetail.workflowWorkNodeDOList}
|
|
|
+ expandedBranchGroups={expandedBranchGroups}
|
|
|
+ setExpandedBranchGroups={setExpandedBranchGroups}
|
|
|
+ getExecutorInfo={getExecutorInfo}
|
|
|
+ getNodeIcon={getNodeIcon}
|
|
|
+ formatDate={formatDateWithFormat}
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <div className="text-center text-gray-400 py-8">暂无流程数据</div>
|
|
|
+ )}
|
|
|
</div>
|
|
|
</div>
|
|
|
|