222 lines
7.0 KiB
TypeScript
222 lines
7.0 KiB
TypeScript
import React, { useState } from 'react';
|
|||
|
|
import { Card, Typography, Table, Button, Space, Modal, Form, Select, Input, message, Tag, Steps, Divider, Switch, Badge } from 'antd';
|
||
|
|
import { EditOutlined, PlusOutlined, SettingOutlined, CheckCircleOutlined, ClockCircleOutlined, SyncOutlined } from '@ant-design/icons';
|
||
|
|
|
||
|
|
const { Title, Paragraph, Text } = Typography;
|
||
|
|
const { Option } = Select;
|
||
|
|
|
||
|
|
interface ProcessNode {
|
||
|
|
id: string;
|
||
|
|
name: string;
|
||
|
|
role: string;
|
||
|
|
roleName: string;
|
||
|
|
order: number;
|
||
|
|
enabled: boolean;
|
||
|
|
}
|
||
|
|
|
||
|
|
const ProcessManagement: React.FC = () => {
|
||
|
|
const [loading, setLoading] = useState(false);
|
||
|
|
const [modalVisible, setModalVisible] = useState(false);
|
||
|
|
const [editingNode, setEditingNode] = useState<ProcessNode | null>(null);
|
||
|
|
const [form] = Form.useForm();
|
||
|
|
|
||
|
|
// 流程节点数据
|
||
|
|
const [nodes, setNodes] = useState<ProcessNode[]>([
|
||
|
|
{ id: '1', name: '发起申请', role: 'applicant', roleName: '申请人(任意角色)', order: 1, enabled: true },
|
||
|
|
{ id: '2', name: '审批', role: 'admin', roleName: '管理员', order: 2, enabled: true },
|
||
|
|
{ id: '3', name: '执行付款', role: 'admin', roleName: '管理员', order: 3, enabled: true },
|
||
|
|
]);
|
||
|
|
|
||
|
|
// 角色选项
|
||
|
|
const roleOptions = [
|
||
|
|
{ value: 'applicant', label: '申请人(任意角色)' },
|
||
|
|
{ value: 'admin', label: '管理员' },
|
||
|
|
{ value: 'finance', label: '财务专员' },
|
||
|
|
{ value: 'manager', label: '项目经理' },
|
||
|
|
];
|
||
|
|
|
||
|
|
// 流程类型
|
||
|
|
const processTypes = [
|
||
|
|
{ key: 'advance', name: '预支申请', description: '员工预支款项申请流程' },
|
||
|
|
{ key: 'reimbursement', name: '报销申请', description: '费用报销申请流程' },
|
||
|
|
{ key: 'payment', name: '付款申请', description: '供应商付款申请流程' },
|
||
|
|
{ key: 'verification', name: '核销申请', description: '单据核销申请流程' },
|
||
|
|
];
|
||
|
|
|
||
|
|
const handleEdit = (node: ProcessNode) => {
|
||
|
|
setEditingNode(node);
|
||
|
|
form.setFieldsValue({
|
||
|
|
role: node.role
|
||
|
|
});
|
||
|
|
setModalVisible(true);
|
||
|
|
};
|
||
|
|
|
||
|
|
const handleSave = () => {
|
||
|
|
form.validateFields().then(values => {
|
||
|
|
if (editingNode) {
|
||
|
|
const updatedNodes = nodes.map(n => {
|
||
|
|
if (n.id === editingNode.id) {
|
||
|
|
const roleOption = roleOptions.find(r => r.value === values.role);
|
||
|
|
return { ...n, role: values.role, roleName: roleOption?.label || values.role };
|
||
|
|
}
|
||
|
|
return n;
|
||
|
|
});
|
||
|
|
setNodes(updatedNodes);
|
||
|
|
message.success('节点配置已保存');
|
||
|
|
}
|
||
|
|
setModalVisible(false);
|
||
|
|
});
|
||
|
|
};
|
||
|
|
|
||
|
|
const getStatusTag = (enabled: boolean) => {
|
||
|
|
return enabled ? <Tag color="success">已启用</Tag> : <Tag color="default">已禁用</Tag>;
|
||
|
|
};
|
||
|
|
|
||
|
|
const getStepStatus = (order: number) => {
|
||
|
|
if (order === 1) return 'finish';
|
||
|
|
if (order === 2) return 'process';
|
||
|
|
return 'wait';
|
||
|
|
};
|
||
|
|
|
||
|
|
const columns = [
|
||
|
|
{
|
||
|
|
title: '顺序',
|
||
|
|
dataIndex: 'order',
|
||
|
|
key: 'order',
|
||
|
|
width: 80,
|
||
|
|
render: (v: number) => <Badge count={v} style={{ backgroundColor: '#1890ff' }} />
|
||
|
|
},
|
||
|
|
{ title: '节点名称', dataIndex: 'name', key: 'name', width: 150 },
|
||
|
|
{
|
||
|
|
title: '执行角色',
|
||
|
|
dataIndex: 'roleName',
|
||
|
|
key: 'roleName',
|
||
|
|
render: (v: string, r: ProcessNode) => (
|
||
|
|
<Space>
|
||
|
|
<Tag color={r.role === 'admin' ? 'blue' : r.role === 'finance' ? 'green' : 'default'}>
|
||
|
|
{v}
|
||
|
|
</Tag>
|
||
|
|
</Space>
|
||
|
|
)
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: '状态',
|
||
|
|
dataIndex: 'enabled',
|
||
|
|
key: 'enabled',
|
||
|
|
width: 100,
|
||
|
|
render: (v: boolean) => getStatusTag(v)
|
||
|
|
},
|
||
|
|
{
|
||
|
|
title: '操作',
|
||
|
|
key: 'action',
|
||
|
|
width: 120,
|
||
|
|
render: (_: any, record: ProcessNode) => (
|
||
|
|
<Space>
|
||
|
|
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}>
|
||
|
|
编辑
|
||
|
|
</Button>
|
||
|
|
</Space>
|
||
|
|
)
|
||
|
|
}
|
||
|
|
];
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div style={{ padding: 24 }}>
|
||
|
|
<div style={{ marginBottom: 24 }}>
|
||
|
|
<Title level={3} style={{ marginBottom: 8 }}>流程管理</Title>
|
||
|
|
<Paragraph type="secondary">
|
||
|
|
配置财务申请的审批流程节点,支持自定义执行角色
|
||
|
|
</Paragraph>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
{/* 流程图示 */}
|
||
|
|
<Card title="当前流程图" style={{ marginBottom: 24 }}>
|
||
|
|
<Steps current={1} style={{ marginTop: 16 }}>
|
||
|
|
{nodes.filter(n => n.enabled).map((node, index) => (
|
||
|
|
<Steps.Step
|
||
|
|
key={node.id}
|
||
|
|
title={node.name}
|
||
|
|
description={node.roleName}
|
||
|
|
status={getStepStatus(node.order)}
|
||
|
|
icon={
|
||
|
|
node.order === 1 ? <PlusOutlined /> :
|
||
|
|
node.order === 2 ? <CheckCircleOutlined /> :
|
||
|
|
<SyncOutlined />
|
||
|
|
}
|
||
|
|
/>
|
||
|
|
))}
|
||
|
|
</Steps>
|
||
|
|
<Divider />
|
||
|
|
<Paragraph type="secondary" style={{ marginBottom: 0 }}>
|
||
|
|
<Text strong>说明:</Text>
|
||
|
|
当前流程为「申请人 → 管理员审批 → 管理员执行」,后续可在下方修改执行角色为财务专员。
|
||
|
|
</Paragraph>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* 节点配置表 */}
|
||
|
|
<Card title="节点配置">
|
||
|
|
<Table
|
||
|
|
columns={columns}
|
||
|
|
dataSource={nodes}
|
||
|
|
rowKey="id"
|
||
|
|
pagination={false}
|
||
|
|
size="middle"
|
||
|
|
/>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* 流程类型说明 */}
|
||
|
|
<Card title="适用流程" style={{ marginTop: 24 }}>
|
||
|
|
<Table
|
||
|
|
columns={[
|
||
|
|
{ title: '流程类型', dataIndex: 'name', key: 'name', width: 150 },
|
||
|
|
{ title: '说明', dataIndex: 'description', key: 'description' },
|
||
|
|
{
|
||
|
|
title: '状态',
|
||
|
|
key: 'status',
|
||
|
|
width: 100,
|
||
|
|
render: () => <Tag color="success">已启用</Tag>
|
||
|
|
}
|
||
|
|
]}
|
||
|
|
dataSource={processTypes}
|
||
|
|
rowKey="key"
|
||
|
|
pagination={false}
|
||
|
|
size="middle"
|
||
|
|
/>
|
||
|
|
</Card>
|
||
|
|
|
||
|
|
{/* 编辑节点弹窗 */}
|
||
|
|
<Modal
|
||
|
|
title={`编辑节点:${editingNode?.name}`}
|
||
|
|
open={modalVisible}
|
||
|
|
onCancel={() => setModalVisible(false)}
|
||
|
|
onOk={handleSave}
|
||
|
|
width={500}
|
||
|
|
>
|
||
|
|
<Form form={form} layout="vertical">
|
||
|
|
<Form.Item label="节点名称">
|
||
|
|
<Input value={editingNode?.name} disabled />
|
||
|
|
</Form.Item>
|
||
|
|
<Form.Item
|
||
|
|
name="role"
|
||
|
|
label="执行角色"
|
||
|
|
rules={[{ required: true, message: '请选择执行角色' }]}
|
||
|
|
>
|
||
|
|
<Select placeholder="选择执行角色">
|
||
|
|
{roleOptions.map(opt => (
|
||
|
|
<Option key={opt.value} value={opt.value}>{opt.label}</Option>
|
||
|
|
))}
|
||
|
|
</Select>
|
||
|
|
</Form.Item>
|
||
|
|
</Form>
|
||
|
|
<div style={{ padding: 12, background: '#fffbe6', borderRadius: 6, marginTop: 16 }}>
|
||
|
|
<Text type="warning">
|
||
|
|
⚠️ 修改执行角色会影响所有使用此流程的申请。建议在公司有财务专员后再将执行节点改为财务角色。
|
||
|
|
</Text>
|
||
|
|
</div>
|
||
|
|
</Modal>
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
};
|
||
|
|
|
||
|
|
export default ProcessManagement;
|