Initial commit: ERP system with advance verification fixes

This commit is contained in:
System Administrator
2026-03-25 23:55:36 +07:00
commit 563ca12d76
5920 changed files with 828689 additions and 0 deletions
@@ -0,0 +1,130 @@
import React from 'react';
import { Card, Typography, Descriptions, Tag, Row, Col, Progress, Divider } from 'antd';
import {
CloudServerOutlined,
DatabaseOutlined,
NodeIndexOutlined,
CheckCircleOutlined,
InfoCircleOutlined
} from '@ant-design/icons';
const { Title, Paragraph, Text } = Typography;
const AboutPage: React.FC = () => {
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}>
<Title level={3} style={{ marginBottom: 8 }}></Title>
<Paragraph type="secondary"></Paragraph>
</div>
<Row gutter={24}>
<Col span={16}>
<Card title={<><InfoCircleOutlined /> </>}>
<Descriptions bordered column={2}>
<Descriptions.Item label="系统名称">ERP</Descriptions.Item>
<Descriptions.Item label="系统版本">V1.0.0</Descriptions.Item>
<Descriptions.Item label="开发团队"></Descriptions.Item>
<Descriptions.Item label="上线日期">20263</Descriptions.Item>
<Descriptions.Item label="技术架构">
<Tag color="blue">React 18</Tag>
<Tag color="green">Ant Design 5</Tag>
<Tag color="purple">Node.js</Tag>
<Tag color="orange">PostgreSQL</Tag>
</Descriptions.Item>
<Descriptions.Item label="部署环境">
<Tag color="cyan"></Tag>
</Descriptions.Item>
<Descriptions.Item label="前端框架">Vite + React + TypeScript</Descriptions.Item>
<Descriptions.Item label="后端框架">Express.js + PostgreSQL</Descriptions.Item>
</Descriptions>
</Card>
<Card title={<><CheckCircleOutlined /> </>} style={{ marginTop: 24 }}>
<Row gutter={[16, 16]}>
<Col span={8}>
<Card size="small" style={{ textAlign: 'center' }}>
<Title level={5}></Title>
<Text type="secondary"></Text>
</Card>
</Col>
<Col span={8}>
<Card size="small" style={{ textAlign: 'center' }}>
<Title level={5}></Title>
<Text type="secondary"></Text>
</Card>
</Col>
<Col span={8}>
<Card size="small" style={{ textAlign: 'center' }}>
<Title level={5}></Title>
<Text type="secondary"></Text>
</Card>
</Col>
<Col span={8}>
<Card size="small" style={{ textAlign: 'center' }}>
<Title level={5}></Title>
<Text type="secondary"></Text>
</Card>
</Col>
<Col span={8}>
<Card size="small" style={{ textAlign: 'center' }}>
<Title level={5}></Title>
<Text type="secondary"></Text>
</Card>
</Col>
<Col span={8}>
<Card size="small" style={{ textAlign: 'center' }}>
<Title level={5}></Title>
<Text type="secondary"></Text>
</Card>
</Col>
</Row>
</Card>
</Col>
<Col span={8}>
<Card title={<><CloudServerOutlined /> </>}>
<div style={{ marginBottom: 16 }}>
<Text type="secondary">CPU使用率</Text>
<Progress percent={45} status="active" />
</div>
<div style={{ marginBottom: 16 }}>
<Text type="secondary">使</Text>
<Progress percent={60} strokeColor="#52c41a" />
</div>
<div style={{ marginBottom: 16 }}>
<Text type="secondary"></Text>
<Progress percent={35} strokeColor="#1890ff" />
</div>
<Divider />
<Descriptions column={1} size="small">
<Descriptions.Item label="服务器IP">43.161.248.209</Descriptions.Item>
<Descriptions.Item label="操作系统">OpenCloudOS 9</Descriptions.Item>
<Descriptions.Item label="Node版本">v22.22.1</Descriptions.Item>
</Descriptions>
</Card>
<Card title={<><DatabaseOutlined /> </>} style={{ marginTop: 24 }}>
<div style={{ textAlign: 'center', padding: 20 }}>
<CheckCircleOutlined style={{ fontSize: 48, color: '#52c41a' }} />
<Title level={4} style={{ margin: '16px 0 8px' }}></Title>
<Text type="secondary">PostgreSQL 15</Text>
</div>
<Divider />
<Descriptions column={1} size="small">
<Descriptions.Item label="数据库名">company_finance_db</Descriptions.Item>
<Descriptions.Item label="连接状态"></Descriptions.Item>
<Descriptions.Item label="最近备份">2026-03-19 00:00</Descriptions.Item>
</Descriptions>
</Card>
</Col>
</Row>
<Card style={{ marginTop: 24, background: '#f6ffed', borderColor: '#b7eb8f' }}>
<Text>© 2026 ERP系统 - V1.0.0</Text>
</Card>
</div>
);
};
export default AboutPage;
@@ -0,0 +1,97 @@
import React from 'react';
import { Card, Typography, Button, Table, Tag, Space, Modal, Form, Input, DatePicker, message, Row, Col, Progress } from 'antd';
import { DownloadOutlined, UploadOutlined, DeleteOutlined, ClockCircleOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
const { Title, Paragraph, Text } = Typography;
const BackupPage: React.FC = () => {
const [loading, setLoading] = React.useState(false);
const [backuping, setBackuping] = React.useState(false);
const columns = [
{ title: '备份名称', dataIndex: 'name', key: 'name' },
{ title: '备份时间', dataIndex: 'time', key: 'time' },
{ title: '文件大小', dataIndex: 'size', key: 'size' },
{ title: '备份类型', dataIndex: 'type', key: 'type', render: (v: string) => <Tag color={v === 'auto' ? 'blue' : 'green'}>{v === 'auto' ? '自动' : '手动'}</Tag> },
{ title: '状态', dataIndex: 'status', key: 'status', render: (v: string) => <Tag color={v === 'success' ? 'success' : 'error'}>{v === 'success' ? '成功' : '失败'}</Tag> },
{
title: '操作',
key: 'action',
render: () => (
<Space>
<Button size="small" type="link" icon={<DownloadOutlined />}></Button>
<Button size="small" type="link" icon={<UploadOutlined />}></Button>
<Button size="small" danger type="link" icon={<DeleteOutlined />}></Button>
</Space>
)
}
];
const data = [
{ key: '1', name: 'backup-20260319.sql', time: '2026-03-19 00:00', size: '15.2 MB', type: 'auto', status: 'success' },
{ key: '2', name: 'backup-20260318.sql', time: '2026-03-18 00:00', size: '14.8 MB', type: 'auto', status: 'success' },
{ key: '3', name: 'backup-manual-20260317.sql', time: '2026-03-17 15:30', size: '14.5 MB', type: 'manual', status: 'success' },
];
const handleBackup = () => {
setBackuping(true);
setTimeout(() => {
message.success('备份创建成功');
setBackuping(false);
}, 2000);
};
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}>
<Title level={3} style={{ marginBottom: 8 }}></Title>
<Paragraph type="secondary"></Paragraph>
</div>
<Row gutter={16} style={{ marginBottom: 24 }}>
<Col span={6}>
<Card>
<Text type="secondary"></Text>
<Title level={2} style={{ margin: '8px 0 0' }}>3</Title>
</Card>
</Col>
<Col span={6}>
<Card>
<Text type="secondary"></Text>
<Title level={2} style={{ margin: '8px 0 0' }}>44.5 MB</Title>
</Card>
</Col>
<Col span={6}>
<Card>
<Text type="secondary"></Text>
<Title level={4} style={{ margin: '8px 0 0' }}>2026-03-19 00:00</Title>
</Card>
</Col>
<Col span={6}>
<Card>
<Text type="secondary"></Text>
<Progress percent={30} size="small" style={{ marginTop: 8 }} />
<Text type="secondary">300 MB / 1 GB</Text>
</Card>
</Col>
</Row>
<Card
title="备份列表"
extra={
<Space>
<Button icon={<ClockCircleOutlined />}></Button>
<Button type="primary" icon={<DownloadOutlined />} loading={backuping} onClick={handleBackup}>
</Button>
</Space>
}
>
<Table columns={columns} dataSource={data} loading={loading} pagination={{ pageSize: 10 }} />
</Card>
</div>
);
};
export default BackupPage;
@@ -0,0 +1,221 @@
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;