1
This commit is contained in:
parent
45e6d70ca4
commit
b3241f51fb
132
frontend/src/pages/Workflow/Instance/components/DetailModal.tsx
Normal file
132
frontend/src/pages/Workflow/Instance/components/DetailModal.tsx
Normal file
@ -0,0 +1,132 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Modal, Steps, Card, Tag, Row, Col } from 'antd';
|
||||||
|
import { CheckCircleOutlined, LoadingOutlined, CloseCircleOutlined } from '@ant-design/icons';
|
||||||
|
import './styles.css';
|
||||||
|
|
||||||
|
interface DetailModalProps {
|
||||||
|
visible: boolean;
|
||||||
|
onCancel: () => void;
|
||||||
|
instanceData?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
const mockInstanceDetail = {
|
||||||
|
nodes: [
|
||||||
|
{
|
||||||
|
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) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'COMPLETED':
|
||||||
|
return 'finish';
|
||||||
|
case 'RUNNING':
|
||||||
|
return 'process';
|
||||||
|
case 'ERROR':
|
||||||
|
return 'error';
|
||||||
|
default:
|
||||||
|
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 (
|
||||||
|
<Modal
|
||||||
|
title="流程实例详情"
|
||||||
|
open={visible}
|
||||||
|
onCancel={onCancel}
|
||||||
|
footer={null}
|
||||||
|
width={800}
|
||||||
|
>
|
||||||
|
<div className="workflow-steps">
|
||||||
|
<Steps
|
||||||
|
current={currentNodeIndex}
|
||||||
|
items={data.nodes.map(node => ({
|
||||||
|
title: node.name,
|
||||||
|
status: getStepStatus(node.status)
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Card className="current-node-info" title="当前节点详情">
|
||||||
|
{currentNode && (
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={8}>
|
||||||
|
<div className="info-item">
|
||||||
|
<div className="info-label">节点名称</div>
|
||||||
|
<div className="info-value">{currentNode.name}</div>
|
||||||
|
</div>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<div className="info-item">
|
||||||
|
<div className="info-label">状态</div>
|
||||||
|
<div className="info-value">{getStatusTag(currentNode.status)}</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>
|
||||||
|
)}
|
||||||
|
</Card>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DetailModal;
|
||||||
49
frontend/src/pages/Workflow/Instance/components/styles.css
Normal file
49
frontend/src/pages/Workflow/Instance/components/styles.css
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
.workflow-steps {
|
||||||
|
margin: 24px 0;
|
||||||
|
padding: 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-steps .ant-steps-item-tail::after {
|
||||||
|
background-color: #e8e8e8 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.workflow-steps .ant-steps-item-finish .ant-steps-item-tail::after {
|
||||||
|
background-color: #52c41a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-node-info {
|
||||||
|
margin: 24px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-label {
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.output-block {
|
||||||
|
margin-top: 8px;
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-family: monospace;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 优化步骤连接线 */
|
||||||
|
.ant-steps-item-finish .ant-steps-item-tail::after {
|
||||||
|
background-color: #52c41a !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-steps-item-process .ant-steps-item-tail::after {
|
||||||
|
background-color: #1890ff !important;
|
||||||
|
}
|
||||||
@ -1,12 +1,122 @@
|
|||||||
import React from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import {Card} from 'antd';
|
import { Card, Table, Tag, Space, Empty } from 'antd';
|
||||||
|
import type { ColumnsType } from 'antd/es/table';
|
||||||
|
import { getWorkflowInstances } from './service';
|
||||||
|
import { WorkflowTemplateWithInstances } from './types';
|
||||||
|
import { Page } from '@/types/base';
|
||||||
|
import DetailModal from './components/DetailModal';
|
||||||
|
|
||||||
|
const WorkflowInstanceList: React.FC = () => {
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [data, setData] = useState<WorkflowTemplateWithInstances[]>([]);
|
||||||
|
const [query, setQuery] = useState({
|
||||||
|
current: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
const [detailVisible, setDetailVisible] = useState(false);
|
||||||
|
const [selectedInstance, setSelectedInstance] = useState<WorkflowTemplateWithInstances>();
|
||||||
|
|
||||||
|
const loadData = async (params: any) => {
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await getWorkflowInstances(params);
|
||||||
|
setData(result?.content || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载流程实例失败:', error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
loadData(query);
|
||||||
|
}, [query]);
|
||||||
|
|
||||||
|
const handleViewDetail = (record: WorkflowTemplateWithInstances) => {
|
||||||
|
setSelectedInstance(record);
|
||||||
|
setDetailVisible(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const columns: ColumnsType<WorkflowTemplateWithInstances> = [
|
||||||
|
{
|
||||||
|
title: '流程名称',
|
||||||
|
dataIndex: 'name',
|
||||||
|
key: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '业务标识',
|
||||||
|
dataIndex: 'businessKey',
|
||||||
|
key: 'businessKey',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '最后执行时间',
|
||||||
|
dataIndex: 'lastExecutionTime',
|
||||||
|
key: 'lastExecutionTime',
|
||||||
|
render: (time: string) => time || '暂无'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '状态',
|
||||||
|
dataIndex: 'lastExecutionStatus',
|
||||||
|
key: 'lastExecutionStatus',
|
||||||
|
render: (status: string) => {
|
||||||
|
if (!status) return '暂无';
|
||||||
|
const statusMap: Record<string, { color: string; text: string }> = {
|
||||||
|
TERMINATED: { color: 'red', text: '已终止' },
|
||||||
|
COMPLETED: { color: 'green', text: '已完成' },
|
||||||
|
RUNNING: { color: 'blue', text: '运行中' },
|
||||||
|
};
|
||||||
|
const statusInfo = statusMap[status] || { color: 'default', text: status };
|
||||||
|
return <Tag color={statusInfo.color}>{statusInfo.text}</Tag>;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'action',
|
||||||
|
render: (_, record) => (
|
||||||
|
<Space size="middle">
|
||||||
|
<a onClick={() => handleViewDetail(record)}>查看详情</a>
|
||||||
|
{record?.lastExecutionStatus === 'RUNNING' && (
|
||||||
|
<a onClick={() => console.log('终止流程', record)}>终止流程</a>
|
||||||
|
)}
|
||||||
|
</Space>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const handleTableChange = (pagination: any) => {
|
||||||
|
setQuery({
|
||||||
|
...query,
|
||||||
|
current: pagination.current,
|
||||||
|
pageSize: pagination.pageSize,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const WorkflowInstance: React.FC = () => {
|
|
||||||
return (
|
return (
|
||||||
<Card title="流程实例">
|
<Card title="流程实例">
|
||||||
<div>流程实例列表页面</div>
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={data}
|
||||||
|
loading={loading}
|
||||||
|
rowKey="id"
|
||||||
|
locale={{
|
||||||
|
emptyText: <Empty description="暂无数据" />
|
||||||
|
}}
|
||||||
|
pagination={{
|
||||||
|
current: query.current,
|
||||||
|
pageSize: query.pageSize,
|
||||||
|
total: data?.length || 0,
|
||||||
|
showSizeChanger: true,
|
||||||
|
showQuickJumper: true,
|
||||||
|
}}
|
||||||
|
onChange={handleTableChange}
|
||||||
|
/>
|
||||||
|
<DetailModal
|
||||||
|
visible={detailVisible}
|
||||||
|
onCancel={() => setDetailVisible(false)}
|
||||||
|
instanceData={selectedInstance}
|
||||||
|
/>
|
||||||
</Card>
|
</Card>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default WorkflowInstance;
|
export default WorkflowInstanceList;
|
||||||
12
frontend/src/pages/Workflow/Instance/service.ts
Normal file
12
frontend/src/pages/Workflow/Instance/service.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import request from '@/utils/request';
|
||||||
|
import { WorkflowTemplateWithInstances, WorkflowTemplateWithInstancesQuery } from './types';
|
||||||
|
import { Page } from '@/types/base';
|
||||||
|
|
||||||
|
const INSTANCE_URL = '/api/v1/workflow/instance';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取流程实例列表
|
||||||
|
* @param params 查询参数
|
||||||
|
*/
|
||||||
|
export const getWorkflowInstances = (params?: WorkflowTemplateWithInstancesQuery) =>
|
||||||
|
request.get<Page<WorkflowTemplateWithInstances>>(`${INSTANCE_URL}/templates-with-instances`, { params });
|
||||||
13
frontend/src/pages/Workflow/Instance/types.ts
Normal file
13
frontend/src/pages/Workflow/Instance/types.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Page } from '@/types/base';
|
||||||
|
|
||||||
|
export interface WorkflowTemplateWithInstances {
|
||||||
|
id: number;
|
||||||
|
name: string;
|
||||||
|
businessKey: string;
|
||||||
|
lastExecutionTime: string;
|
||||||
|
lastExecutionStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface WorkflowTemplateWithInstancesQuery {
|
||||||
|
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user