This commit is contained in:
asp_ly 2024-12-17 21:02:15 +08:00
parent 2515ab3d14
commit 3374885ce2
4 changed files with 102 additions and 110 deletions

View File

@ -1,129 +1,115 @@
import React from 'react'; import React from 'react';
import { Modal, Steps, Card, Tag, Row, Col } from 'antd'; import { Modal, Steps, Card, Descriptions, Tag, Timeline } from 'antd';
import { CheckCircleOutlined, LoadingOutlined, CloseCircleOutlined } from '@ant-design/icons'; import { WorkflowHistoricalInstance, WorkflowInstanceStage } from '../types';
import './styles.css'; import dayjs from 'dayjs';
interface DetailModalProps { interface DetailModalProps {
visible: boolean; visible: boolean;
onCancel: () => void; onCancel: () => void;
instanceData?: any; instanceData?: WorkflowHistoricalInstance;
} }
const mockInstanceDetail = { const DetailModal: React.FC<DetailModalProps> = ({ visible, onCancel, instanceData }) => {
nodes: [ if (!instanceData) return null;
{
id: 'start',
name: '开始',
status: 'COMPLETED',
startTime: '2024-12-17 16:25:00',
endTime: '2024-12-17 16:25:01',
},
{
id: 'script1',
name: '环境检查',
status: 'COMPLETED',
startTime: '2024-12-17 16:25:02',
endTime: '2024-12-17 16:25:10',
output: '环境检查完成,所有依赖已就绪'
},
{
id: 'script2',
name: '部署应用',
status: 'RUNNING',
startTime: '2024-12-17 16:25:11',
output: '正在部署应用...'
},
{
id: 'end',
name: '结束',
status: 'PENDING'
}
]
};
const getStepStatus = (status: string) => { const getStatusTag = (status: string) => {
switch (status) { const statusMap: Record<string, { color: string; text: string }> = {
COMPLETED: { color: 'success', text: '已完成' },
RUNNING: { color: 'processing', text: '运行中' },
FAILED: { color: 'error', text: '失败' },
TERMINATED: { color: 'warning', text: '已终止' },
NOT_STARTED: { color: 'default', text: '未执行' }
};
const statusInfo = statusMap[status] || { color: 'default', text: status };
return <Tag color={statusInfo.color}>{statusInfo.text}</Tag>;
};
const getNodeTypeText = (nodeType: string) => {
const nodeTypeMap: Record<string, string> = {
startEvent: '开始节点',
endEvent: '结束节点',
serviceTask: '服务任务',
userTask: '用户任务',
scriptTask: '脚本任务'
};
return nodeTypeMap[nodeType] || nodeType;
};
const getStepStatus = (stage: WorkflowInstanceStage) => {
switch (stage.status) {
case 'COMPLETED': case 'COMPLETED':
return 'finish'; return 'finish';
case 'RUNNING': case 'RUNNING':
return 'process'; return 'process';
case 'ERROR': case 'FAILED':
return 'error'; return 'error';
case 'TERMINATED':
return 'wait';
default: default:
return 'wait'; return 'wait';
} }
};
const getStatusTag = (status: string) => {
const statusConfig = {
COMPLETED: { color: 'success', icon: <CheckCircleOutlined />, text: '已完成' },
RUNNING: { color: 'processing', icon: <LoadingOutlined />, text: '执行中' },
ERROR: { color: 'error', icon: <CloseCircleOutlined />, text: '错误' },
PENDING: { color: 'default', text: '等待中' }
}; };
const config = statusConfig[status] || statusConfig.PENDING;
return (
<Tag color={config.color} icon={config.icon}>
{config.text}
</Tag>
);
};
const DetailModal: React.FC<DetailModalProps> = ({ visible, onCancel }) => {
const data = mockInstanceDetail;
const currentNodeIndex = data.nodes.findIndex(node => node.status === 'RUNNING');
const currentNode = data.nodes[currentNodeIndex];
return ( return (
<Modal <Modal
title="流程实例详情" title="流程执行详情"
open={visible} open={visible}
onCancel={onCancel} onCancel={onCancel}
width={1200}
footer={null} footer={null}
width={800}
> >
<div className="workflow-steps"> <Card className="mb-4">
<Descriptions title="基本信息" bordered column={2}>
<Descriptions.Item label="业务标识">{instanceData.businessKey}</Descriptions.Item>
<Descriptions.Item label="状态">{getStatusTag(instanceData.status)}</Descriptions.Item>
<Descriptions.Item label="开始时间">{dayjs(instanceData.startTime).format('YYYY-MM-DD HH:mm:ss')}</Descriptions.Item>
<Descriptions.Item label="结束时间">
{instanceData.endTime ? dayjs(instanceData.endTime).format('YYYY-MM-DD HH:mm:ss') : '暂无'}
</Descriptions.Item>
<Descriptions.Item label="流程实例ID">{instanceData.processInstanceId}</Descriptions.Item>
<Descriptions.Item label="流程定义ID">{instanceData.processDefinitionId}</Descriptions.Item>
</Descriptions>
</Card>
<Card title="执行阶段" className="mb-4">
<Steps <Steps
current={currentNodeIndex} progressDot
items={data.nodes.map(node => ({ current={instanceData.stages.length}
title: node.name, items={instanceData.stages.map((stage) => ({
status: getStepStatus(node.status) title: stage.nodeName,
description: (
<div className="text-xs">
<div>{getNodeTypeText(stage.nodeType)}</div>
<div>{getStatusTag(stage.status)}</div>
</div>
),
status: getStepStatus(stage)
}))} }))}
/> />
</div> </Card>
<Card className="current-node-info" title="当前节点详情"> <Card title="详细时间线">
{currentNode && ( <Timeline
<Row gutter={16}> items={instanceData.stages.map((stage) => ({
<Col span={8}> color: stage.status === 'COMPLETED' ? 'green' :
<div className="info-item"> stage.status === 'FAILED' ? 'red' :
<div className="info-label"></div> stage.status === 'RUNNING' ? 'blue' : 'gray',
<div className="info-value">{currentNode.name}</div> children: (
<div>
<div className="font-medium">{stage.nodeName} ({getNodeTypeText(stage.nodeType)})</div>
<div className="text-gray-500 text-sm">
{dayjs(stage.startTime).format('YYYY-MM-DD HH:mm:ss')}
</div> </div>
</Col> {stage.endTime && (
<Col span={8}> <div className="text-gray-500 text-sm">
<div className="info-item"> {dayjs(stage.endTime).format('YYYY-MM-DD HH:mm:ss')}
<div className="info-label"></div>
<div className="info-value">{getStatusTag(currentNode.status)}</div>
</div> </div>
</Col>
<Col span={8}>
<div className="info-item">
<div className="info-label"></div>
<div className="info-value">{currentNode.startTime || '-'}</div>
</div>
</Col>
{currentNode.output && (
<Col span={24}>
<div className="info-item">
<div className="info-label"></div>
<div className="output-block">{currentNode.output}</div>
</div>
</Col>
)}
</Row>
)} )}
<div>{getStatusTag(stage.status)}</div>
</div>
)
}))}
/>
</Card> </Card>
</Modal> </Modal>
); );

View File

@ -17,13 +17,19 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
const [data, setData] = useState<WorkflowHistoricalInstance[]>([]); const [data, setData] = useState<WorkflowHistoricalInstance[]>([]);
const [total, setTotal] = useState(0); const [total, setTotal] = useState(0);
const [query, setQuery] = useState({ const [query, setQuery] = useState({
pageNum: DEFAULT_CURRENT, pageNum: DEFAULT_CURRENT - 1,
pageSize: DEFAULT_PAGE_SIZE, pageSize: DEFAULT_PAGE_SIZE,
workflowDefinitionId
}); });
const [detailVisible, setDetailVisible] = useState(false); const [detailVisible, setDetailVisible] = useState(false);
const [selectedInstance, setSelectedInstance] = useState<WorkflowHistoricalInstance>(); const [selectedInstance, setSelectedInstance] = useState<WorkflowHistoricalInstance>();
useEffect(() => {
setQuery(prev => ({
...prev,
workflowDefinitionId
}));
}, [workflowDefinitionId]);
const loadData = async () => { const loadData = async () => {
setLoading(true); setLoading(true);
try { try {
@ -118,11 +124,11 @@ const HistoryModal: React.FC<HistoryModalProps> = ({ visible, onCancel, workflow
current: query.pageNum + 1, current: query.pageNum + 1,
pageSize: query.pageSize, pageSize: query.pageSize,
total: total, total: total,
onChange: (page, pageSize) => setQuery({ onChange: (page, pageSize) => setQuery(prev => ({
...query, ...prev,
pageNum: page - 1, pageNum: page - 1,
pageSize pageSize
}), })),
showSizeChanger: true, showSizeChanger: true,
showQuickJumper: true, showQuickJumper: true,
}} }}

View File

@ -63,7 +63,7 @@ const WorkflowInstanceList: React.FC = () => {
render: (time: string) => time || '暂无' render: (time: string) => time || '暂无'
}, },
{ {
title: '状态', title: '最后执行状态',
dataIndex: 'lastExecutionStatus', dataIndex: 'lastExecutionStatus',
key: 'lastExecutionStatus', key: 'lastExecutionStatus',
render: (status: string) => { render: (status: string) => {

View File

@ -17,4 +17,4 @@ export const getWorkflowInstances = (params?: WorkflowTemplateWithInstancesQuery
* @param params * @param params
*/ */
export const getHistoricalInstances = (params?: WorkflowHistoricalInstanceQuery) => export const getHistoricalInstances = (params?: WorkflowHistoricalInstanceQuery) =>
request.post<Page<WorkflowHistoricalInstance>>(`${INSTANCE_URL}/historical-instances`, params); request.get<Page<WorkflowHistoricalInstance>>(`${INSTANCE_URL}/historical-instances`, {params});