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 {Card} from 'antd';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
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 (
|
||||
<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>
|
||||
);
|
||||
};
|
||||
|
||||
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