| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757 |
- import React, { useState, useEffect, useRef } from 'react';
- import { useNavigate, useSearchParams } from 'react-router-dom';
- import {
- User,
- CheckCircle2,
- Settings,
- FileText,
- Shield,
- Lock,
- Flag,
- Star,
- ArrowLeft,
- Clock,
- List
- } from 'lucide-react';
- import ReactFlow, {
- Node,
- Edge,
- useNodesState,
- useEdgesState,
- NodeTypes,
- ConnectionMode,
- Handle,
- Position,
- BaseEdge,
- EdgeTypes,
- getStraightPath,
- } from 'reactflow';
- import 'reactflow/dist/style.css';
- import { workJobApi, WorkJobVO } from '../api/WorkJob';
- import { formatDateWithFormat } from '../utils/formatTime';
- interface WorkflowStep {
- id: string;
- type: string;
- title: string;
- assignee?: string;
- assigneeName?: string;
- time?: string;
- status: 'completed' | 'in_progress' | 'pending';
- icon: React.ReactNode;
- hasDetail?: boolean;
- }
- // 流转记录数据类型
- interface FlowRecord {
- id: string;
- taskNode: string; // 任务节点
- executor: string; // 执行人
- startTime?: string; // 开始时间
- endTime?: string; // 结束时间
- taskStatus: 'completed' | 'in_progress' | 'pending'; // 任务状态
- executionDescription?: string; // 执行说明
- duration?: string; // 耗时
- }
- // 使用 ReactFlow 默认的连线效果,不需要自定义边组件
- // 节点类型映射(简化版,只读模式)
- const nodeTypes: NodeTypes = {
- createJob: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- confirm: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- review: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- inputInfo: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- isolation: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- releaseIsolation: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- returnLock: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- complete: ({ data, selected }: any) => <CustomNode data={data} selected={selected} />,
- };
- // 简化的自定义节点组件(只读模式)
- function CustomNode({ data, selected, id }: any) {
- const getNodeIcon = () => {
- switch (data?.type) {
- case 'createJob':
- return <CheckCircle2 className="w-6 h-6" />;
- case 'review':
- case 'confirm':
- return <Settings className="w-6 h-6" />;
- case 'inputInfo':
- return <FileText className="w-6 h-6" />;
- case 'isolation':
- return <Shield className="w-6 h-6" />;
- case 'releaseIsolation':
- case 'returnLock':
- return <Lock className="w-6 h-6" />;
- case 'complete':
- return <Flag className="w-6 h-6" />;
- default:
- return <FileText className="w-6 h-6" />;
- }
- };
- // 根据状态确定节点颜色
- const getStatusColor = () => {
- if (data?.status === 'completed') {
- return { icon: 'text-green-600' };
- } else if (data?.status === 'in_progress') {
- return { icon: 'text-blue-600' };
- } else {
- return { icon: 'text-gray-400' };
- }
- };
- // 获取人员信息
- const getExecutorInfo = () => {
- const workNode = data?.workNode;
- if (!workNode) return '待处理';
-
- // 尝试从多个字段获取执行人名称
- // 优先从 nodeUserList 中查找 worker 类型的用户
- let executorName = '';
- if (workNode.nodeUserList && Array.isArray(workNode.nodeUserList)) {
- const workerUser = workNode.nodeUserList.find((user: any) => user.type === 'worker' || !user.type);
- if (workerUser && workerUser.userName) {
- executorName = workerUser.userName;
- }
- }
-
- // 如果没有从 nodeUserList 找到,尝试其他字段
- if (!executorName) {
- if (workNode.workerUserName) {
- executorName = workNode.workerUserName;
- } else if (workNode.initiatorName) {
- executorName = workNode.initiatorName;
- } else if (workNode.workerUserId) {
- executorName = String(workNode.workerUserId);
- } else if (workNode.initiator) {
- executorName = workNode.initiator;
- }
- }
-
- if (!executorName) return '待处理';
-
- if (data?.type === 'createJob') {
- return `发起人: ${executorName}`;
- } else if (data?.type === 'review' || data?.type === 'confirm') {
- return `审核人: ${executorName}`;
- } else if (data?.type === 'isolation' || data?.type === 'releaseIsolation' || data?.type === 'returnLock') {
- return `操作人: ${executorName}`;
- } else {
- return `执行人: ${executorName}`;
- }
- };
- const statusColor = getStatusColor();
- return (
- <div className="flex items-center gap-3 py-2 relative">
- {/* 连接点 - 隐藏但保留用于连接 */}
- <Handle
- id="top-target"
- type="target"
- position={Position.Top}
- className="!w-0 !h-0 !border-0 !bg-transparent !opacity-0 !pointer-events-none"
- style={{ top: -6, left: '50%', transform: 'translateX(-50%)', visibility: 'hidden' }}
- isConnectable={false}
- />
- <Handle
- id="bottom-source"
- type="source"
- position={Position.Bottom}
- className="!w-0 !h-0 !border-0 !bg-transparent !opacity-0 !pointer-events-none"
- style={{ bottom: -6, left: '50%', transform: 'translateX(-50%)', visibility: 'hidden' }}
- isConnectable={false}
- />
-
- {/* 图标 */}
- <div className={`${statusColor.icon} flex items-center justify-center flex-shrink-0`}>
- {getNodeIcon()}
- </div>
-
- {/* 节点名称和人员信息 */}
- <div className="flex flex-col gap-1">
- <div className="font-semibold text-base text-gray-900">
- {data?.label || data?.nodeName || '节点'}
- </div>
- <div className="text-sm text-gray-600">
- {getExecutorInfo()}
- </div>
- </div>
- </div>
- );
- }
- export default function WorkJobDetail() {
- const navigate = useNavigate();
- const [searchParams] = useSearchParams();
- const jobId = searchParams.get('id');
-
- const [jobDetail, setJobDetail] = useState<WorkJobVO | null>(null);
- const [loading, setLoading] = useState(true);
-
- // ReactFlow 状态
- const [nodes, setNodes, onNodesChange] = useNodesState([]);
- const [edges, setEdges, onEdgesChange] = useEdgesState([]);
- const reactFlowWrapper = useRef<HTMLDivElement>(null);
- const [reactFlowInstance, setReactFlowInstance] = useState<any>(null);
- // 获取作业详情
- useEffect(() => {
- if (jobId) {
- loadJobDetail();
- }
- }, [jobId]);
- const loadJobDetail = async () => {
- if (!jobId) return;
- try {
- setLoading(true);
- const response = await workJobApi.selectWorkflowWorkById(Number(jobId));
- // 处理响应数据,可能包含 data 字段
- const data = (response as any)?.data || response;
- setJobDetail(data);
- console.log('作业详情数据:', data);
-
- // 加载完成后,解析 designContent 并渲染流程
- if (data?.designContent) {
- loadWorkflowFromDesignContent(data);
- }
- } catch (error: any) {
- console.error('获取作业详情失败:', error);
- } finally {
- setLoading(false);
- }
- };
- // 从 designContent 加载流程并渲染到 ReactFlow
- const loadWorkflowFromDesignContent = (jobData: WorkJobVO) => {
- try {
- // 解析 designContent
- let designContentData: any = null;
- if (jobData.designContent) {
- try {
- designContentData = typeof jobData.designContent === 'string'
- ? JSON.parse(jobData.designContent)
- : jobData.designContent;
- } catch (e) {
- console.warn('解析 designContent 失败:', e);
- return;
- }
- }
- if (!designContentData || !designContentData.nodes || !Array.isArray(designContentData.nodes)) {
- console.warn('designContent 数据格式不正确');
- return;
- }
- // 构建节点映射(通过 uuid)
- const nodeMap = new Map<string, any>();
- if (jobData.workflowWorkNodeDOList && Array.isArray(jobData.workflowWorkNodeDOList)) {
- jobData.workflowWorkNodeDOList.forEach((node: any) => {
- if (node.uuid) {
- nodeMap.set(node.uuid, node);
- }
- } );
- }
- const getNodeStatus = (nodeUuid: string): 'completed' | 'in_progress' | 'pending' => {
- const workNode = nodeMap.get(nodeUuid);
- if (!workNode) return 'pending';
-
- if (workNode.approvalStatus === 'approved') {
- return 'completed';
- } else if (workNode.approvalStatus === 'unaudited' || workNode.approvalStatus === 'pending') {
- // 检查父节点是否已完成
- if (!workNode.parentUuid || workNode.parentUuid === '') {
- return 'completed'; // 根节点
- }
- const parentNode = nodeMap.get(workNode.parentUuid);
- if (parentNode && parentNode.approvalStatus === 'approved') {
- return 'in_progress';
- }
- return 'pending';
- }
- return 'pending';
- };
- // 转换为 ReactFlow 的 nodes
- // 将横向布局转换为纵向布局:交换 x 和 y 坐标
- const importedNodes: Node[] = designContentData.nodes.map((node: any) => {
- const nodeId = node.id || node.uuid;
- const nodeData = node.data || {};
- const workNode = nodeMap.get(nodeId);
- const status = getNodeStatus(nodeId);
- // 获取原始位置
- const originalPosition = node.position || { x: 0, y: 0 };
-
- // 交换 x 和 y 坐标,实现横向到纵向的转换
- // 同时应用缩放因子,使节点间距更紧凑(0.6 表示缩小到原来的 60%)
- const spacingScale = 0.6;
- const verticalPosition = {
- x: originalPosition.y * spacingScale,
- y: originalPosition.x * spacingScale,
- };
- return {
- id: nodeId,
- type: node.type || 'createJob',
- position: verticalPosition,
- data: {
- ...nodeData,
- label: nodeData.label || node.label || node.nodeName || workNode?.nodeName || '节点',
- type: node.type || nodeData.type || 'createJob',
- status: status,
- workNode: workNode, // 传递 workNode 以便获取人员信息
- },
- };
- });
- // 转换为 ReactFlow 的 edges
- // 统一使用垂直布局:所有连接线都从节点底部垂直向下
- const importedEdges: Edge[] = (designContentData.edges || []).map((edge: any) => {
- // 检查节点位置,确保 source 在上,target 在下
- const sourceNode = importedNodes.find((n: Node) => n.id === edge.source);
- const targetNode = importedNodes.find((n: Node) => n.id === edge.target);
-
- // 如果 target 在 source 上方,交换它们(确保连接线从上到下)
- let finalSource = edge.source;
- let finalTarget = edge.target;
- if (sourceNode && targetNode && targetNode.position.y < sourceNode.position.y) {
- finalSource = edge.target;
- finalTarget = edge.source;
- }
-
- // 使用最终确定的 source 节点状态来确定边的颜色
- const sourceStatus = getNodeStatus(finalSource);
-
- // 根据状态确定边的颜色
- let edgeColor = '#d1d5db'; // 默认灰色
- let edgeStyle = 'dashed';
- if (sourceStatus === 'completed') {
- edgeColor = '#10b981'; // 绿色
- edgeStyle = 'solid';
- } else if (sourceStatus === 'in_progress') {
- edgeColor = '#3b82f6'; // 蓝色
- edgeStyle = 'solid';
- }
-
- return {
- id: edge.id || `${finalSource}-${finalTarget}`,
- source: finalSource,
- target: finalTarget,
- sourceHandle: 'bottom-source', // 统一使用底部
- targetHandle: 'top-target', // 统一使用顶部
- // 不指定 type,使用 ReactFlow 默认的连线效果
- style: {
- strokeWidth: 4,
- stroke: edgeColor,
- strokeDasharray: edgeStyle === 'dashed' ? '5,5' : undefined,
- },
- };
- });
- console.log('导入的节点:', importedNodes);
- console.log('导入的连线:', importedEdges);
- setNodes(importedNodes);
- setEdges(importedEdges);
- // 自动适应视图
- if (reactFlowInstance && importedNodes.length > 0) {
- setTimeout(() => {
- reactFlowInstance.fitView({ padding: 0.2, duration: 300 });
- }, 100);
- }
- } catch (error) {
- console.error('加载流程失败:', error);
- }
- };
- // 格式化状态文本
- const getStatusText = (status: string | number | undefined): string => {
- const statusMap: Record<string, string> = {
- 'pending': '待执行',
- 'running': '执行中',
- 'completed': '执行完成',
- 'rejected': '已退回',
- 'skipped': '已跳过',
- '进行中': '执行中',
- '已完成': '执行完成',
- '待开始': '待执行',
- '已取消': '已取消',
- };
- return statusMap[String(status).toLowerCase()] || String(status) || '未知';
- };
- // 格式化状态样式
- const getStatusClassName = (status: string | number | undefined): string => {
- const statusStr = String(status).toLowerCase();
- if (statusStr === 'running' || statusStr === '执行中') {
- return 'bg-blue-100 text-blue-700';
- }
- if (statusStr === 'completed' || statusStr === '执行完成' || statusStr === '已完成') {
- return 'bg-green-100 text-green-700';
- }
- if (statusStr === 'pending' || statusStr === '待执行' || statusStr === '待开始') {
- return 'bg-gray-100 text-gray-700';
- }
- return 'bg-gray-100 text-gray-700';
- };
- // 构建工作流步骤
- const buildWorkflowSteps = (): WorkflowStep[] => {
- // 这里应该根据实际的作业数据构建步骤
- // 目前使用模拟数据,后续需要根据实际API返回的数据结构调整
- const steps: WorkflowStep[] = [
- {
- id: '1',
- type: 'initiator',
- title: '发起人',
- assigneeName: jobDetail?.initiatorName || jobDetail?.initiator || '张三',
- time: jobDetail?.initiationTime || jobDetail?.initiateTime
- ? new Date((jobDetail.initiationTime || jobDetail.initiateTime) as string | number | Date).toLocaleString('zh-CN', {
- year: 'numeric',
- month: '2-digit',
- day: '2-digit',
- hour: '2-digit',
- minute: '2-digit',
- second: '2-digit'
- })
- : '2024-01-15 10:30:00',
- status: 'completed',
- icon: <CheckCircle2 className="w-5 h-5" />,
- },
- {
- id: '2',
- type: 'review',
- title: '审核/确认',
- assigneeName: '李四',
- time: '2024-01-15 10:35:00',
- status: 'in_progress',
- icon: <Settings className="w-5 h-5" />,
- hasDetail: true,
- },
- {
- id: '3',
- type: 'input',
- title: '录入/表单',
- assigneeName: '王五',
- status: 'pending',
- icon: <FileText className="w-5 h-5" />,
- },
- {
- id: '4',
- type: 'isolation',
- title: '隔离/方案',
- assigneeName: '赵六',
- status: 'pending',
- icon: <Shield className="w-5 h-5" />,
- },
- {
- id: '5',
- type: 'lock',
- title: '取锁/共锁',
- assigneeName: '钱七',
- status: 'pending',
- icon: <Lock className="w-5 h-5" />,
- },
- {
- id: '6',
- type: 'unlock',
- title: '还锁',
- assigneeName: '孙八',
- status: 'pending',
- icon: <Lock className="w-5 h-5" />,
- },
- {
- id: '7',
- type: 'complete',
- title: '完成/结束',
- status: 'pending',
- icon: <Flag className="w-5 h-5" />,
- },
- ];
- return steps;
- };
- const workflowSteps = buildWorkflowSteps();
- // 构建流转记录数据
- const buildFlowRecords = (): FlowRecord[] => {
- if (!jobDetail?.workflowWorkNodeDOList || !Array.isArray(jobDetail.workflowWorkNodeDOList)) {
- return [];
- }
- const records: FlowRecord[] = jobDetail.workflowWorkNodeDOList.map((node: any, index: number) => {
- // 根据审批状态确定任务状态
- let taskStatus: 'completed' | 'in_progress' | 'pending' = 'pending';
- if (node.approvalStatus === 'approved') {
- taskStatus = 'completed';
- } else if (node.approvalStatus === 'unaudited' || node.approvalStatus === 'pending') {
- // 检查是否是第一个节点或者前面的节点已完成
- if (index === 0) {
- taskStatus = 'completed';
- } else {
- const prevNode = jobDetail.workflowWorkNodeDOList[index - 1];
- if (prevNode && prevNode.approvalStatus === 'approved') {
- taskStatus = 'in_progress';
- } else {
- taskStatus = 'pending';
- }
- }
- }
- // 格式化时间
- const startTime = node.createTime ? formatDateWithFormat(node.createTime) : undefined;
-
- // 获取执行人(如果有)
- const executor = node.workerUserId || node.initiatorName || '';
- // 获取描述信息
- let description = node.approvalOpinion || '';
- if (!description || description === 'pending') {
- if (taskStatus === 'completed') {
- description = '任务已完成';
- } else if (taskStatus === 'in_progress') {
- description = '任务正在进行中';
- } else {
- description = '等待开始';
- }
- }
- return {
- id: String(node.id || index),
- taskNode: node.nodeName || '未知节点',
- executor: executor,
- startTime: startTime,
- endTime: undefined,
- taskStatus: taskStatus,
- executionDescription: description,
- duration: undefined,
- };
- });
- return records;
- };
- // 获取流转记录状态文本
- const getFlowRecordStatusText = (status: FlowRecord['taskStatus']): string => {
- const statusMap: Record<FlowRecord['taskStatus'], string> = {
- 'completed': '已完成',
- 'in_progress': '执行中',
- 'pending': '待处理',
- };
- return statusMap[status] || '未知';
- };
- // 获取流转记录状态样式
- const getFlowRecordStatusClassName = (status: FlowRecord['taskStatus']): string => {
- const statusMap: Record<FlowRecord['taskStatus'], string> = {
- 'completed': 'bg-green-100 text-green-700',
- 'in_progress': 'bg-blue-100 text-blue-700',
- 'pending': 'bg-gray-100 text-gray-700',
- };
- return statusMap[status] || 'bg-gray-100 text-gray-700';
- };
- const flowRecords = buildFlowRecords();
- if (loading) {
- return (
- <div className="min-h-screen bg-gray-50 flex items-center justify-center">
- <div className="text-gray-500">加载中...</div>
- </div>
- );
- }
- if (!jobDetail) {
- return (
- <div className="min-h-screen bg-gray-50 flex items-center justify-center">
- <div className="text-gray-500">作业不存在</div>
- </div>
- );
- }
- return (
- <div className="h-screen bg-gray-50 flex flex-col">
- <div className="w-full max-w-none mx-auto px-8 py-6 flex-1 flex flex-col min-h-0">
- {/* 头部区域 */}
- <div className="bg-white rounded-xl shadow-sm border border-gray-200 p-6 mb-6 flex-shrink-0" style={{ marginLeft: '20px', marginRight: '20px' }}>
- <div className="flex items-start justify-between">
- <div className="flex-1">
- {/* 编号 */}
- <div className="text-sm text-gray-600 mb-2">
- 编号:<span className="font-medium text-gray-900">{jobDetail?.orderNo || jobDetail?.code || '-'}</span>
- </div>
-
- {/* 标题和状态按钮(同一行) */}
- <div className="flex items-center gap-3 mb-3">
- <h1 className="text-2xl font-semibold text-gray-900">
- {jobDetail?.name || '-'}
- </h1>
- <span className={`inline-flex px-4 py-1.5 rounded-full text-sm font-medium ${getStatusClassName(jobDetail?.status)}`}>
- {getStatusText(jobDetail?.status)}
- </span>
- </div>
- {/* 发起人信息 */}
- <div className="flex items-center gap-2 text-sm text-gray-600">
- <div className="w-5 h-5 rounded-full bg-blue-100 flex items-center justify-center flex-shrink-0">
- <User className="w-3.5 h-3.5 text-blue-600" />
- </div>
- <span>{jobDetail?.initiatorName || jobDetail?.initiator || '-'}</span>
- <span className="text-gray-300">|</span>
- <span>
- {jobDetail?.initiationTime
- ? formatDateWithFormat(jobDetail.initiationTime as string | number | Date)
- : '-'} 提交
- </span>
- </div>
- </div>
-
- {/* 返回按钮 */}
- <button
- onClick={() => navigate(-1)}
- className="flex items-center justify-center w-10 h-10 rounded-lg border border-gray-300 hover:bg-gray-50 hover:border-gray-400 transition-colors flex-shrink-0"
- title="返回作业管理"
- >
- <ArrowLeft className="w-5 h-5 text-gray-600" />
- </button>
- </div>
- </div>
- {/* 任务详情和执行记录区域 - 左右两栏布局 */}
- <div className="flex-1 flex gap-8 mb-6 min-h-0">
- {/* 左侧:任务详情 */}
- <div className="bg-white rounded-xl shadow-sm border border-gray-200 flex-1 flex flex-col min-h-0" style={{ marginLeft: '20px',maxHeight:'750px' }}>
- <div className="px-6 py-4 border-b border-gray-200">
- <h2 className="text-lg font-semibold text-blue-600 flex items-center gap-2">
- <FileText className="w-5 h-5" />
- 作业流程
- </h2>
- </div>
- <div className="flex-1 overflow-y-auto p-6">
- {/* 作业流程渲染 - 使用 ReactFlow */}
- {(() => {
- if (!jobDetail?.designContent || nodes.length === 0) {
- return (
- <div className="flex items-center justify-center h-full text-gray-400">
- {loading ? '加载中...' : '暂无流程数据'}
- </div>
- );
- }
- return (
- <div className="relative" style={{ minHeight: '100%', height: '600px' }} ref={reactFlowWrapper}>
- <ReactFlow
- nodes={nodes}
- edges={edges}
- onNodesChange={onNodesChange}
- onEdgesChange={onEdgesChange}
- nodeTypes={nodeTypes}
- fitView
- onInit={setReactFlowInstance}
- nodesDraggable={false}
- nodesConnectable={false}
- elementsSelectable={false}
- connectionMode={ConnectionMode.Loose}
- defaultEdgeOptions={{
- style: { strokeWidth: 4 },
- }}
- >
- </ReactFlow>
- </div>
- );
- })()}
- </div>
- </div>
- {/* 右侧:执行记录 */}
- <div className="bg-white rounded-xl shadow-sm border border-gray-200 flex-1 flex flex-col min-h-0 flex-shrink-0" style={{ marginRight: '20px',maxHeight:'750px' }}>
- <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
- <h2 className="text-lg font-semibold text-blue-600 flex items-center gap-2">
- <Clock className="w-5 h-5" />
- 执行记录
- </h2>
- </div>
- <div className="flex-1 overflow-y-auto p-4">
- <div className="space-y-3">
- {flowRecords.map((record, index) => {
- const isCompleted = record.taskStatus === 'completed';
- const isInProgress = record.taskStatus === 'in_progress';
- const isPending = record.taskStatus === 'pending';
- // 根据状态获取描述信息
- let description = record.executionDescription || '';
- if (!description) {
- if (isCompleted) {
- description = '任务已完成';
- } else if (isInProgress) {
- description = '任务正在进行中';
- } else {
- description = '等待开始';
- }
- }
- return (
- <div
- key={record.id}
- className="bg-white rounded-lg border border-gray-200 p-4 hover:shadow-md transition-shadow"
- style={{
- borderLeftWidth: '4px',
- borderLeftColor: isCompleted ? '#10b981' : isInProgress ? '#3b82f6' : '#9ca3af',
- }}
- >
- <div className="flex items-start justify-between">
- <div className="flex-1">
- {/* 任务节点名称 */}
- <div className="font-semibold text-base text-gray-900 mb-2">
- {record.taskNode}
- </div>
- {/* 执行人信息 */}
- <div className="text-sm text-gray-600 mb-2">
- {record.executor ? (
- <>
- {record.taskNode === '作业申请' ? '发起人' :
- record.taskNode === '审核审批' ? '审核人' :
- record.taskNode === '上锁操作' || record.taskNode === '共锁操作' ? '操作人' :
- '执行人'}: {record.executor}
- </>
- ) : '待处理'}
- </div>
- {/* 描述信息 */}
- <div className="text-sm text-gray-500">
- {description}
- </div>
- {/* 时间信息 */}
- {record.startTime && (
- <div className="text-xs text-gray-400 mt-2">
- {record.startTime}
- </div>
- )}
- </div>
- {/* 状态标签 */}
- <div className="ml-4 flex-shrink-0">
- <span className={`inline-flex px-3 py-1 rounded-full text-xs font-medium ${getFlowRecordStatusClassName(record.taskStatus)}`}>
- {getFlowRecordStatusText(record.taskStatus)}
- </span>
- </div>
- </div>
- </div>
- );
- })}
- </div>
- </div>
- </div>
- </div>
- </div>
- </div>
- );
- }
|