safety-eval-service-frontend/src/pages/Container/EnterpriseInfo/PersonnelInfo/Certificate/index.js

392 lines
15 KiB
JavaScript
Raw Normal View History

2026-06-23 18:07:30 +08:00
import { Connect } from "@cqsjjb/jjb-dva-runtime";
2026-07-07 09:07:19 +08:00
import { Button, DatePicker, Descriptions, Form, Input, message, Modal, Select, Table, Upload } from "antd";
import { UploadOutlined } from "@ant-design/icons";
2026-06-23 18:07:30 +08:00
import { useEffect, useState } from "react";
2026-07-07 13:38:33 +08:00
import dayjs from "dayjs";
2026-06-29 16:45:58 +08:00
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
2026-07-07 09:07:19 +08:00
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
2026-06-23 18:07:30 +08:00
import { NS_STAFF_CERTIFICATE } from "~/enumerate/namespace";
2026-07-07 09:07:19 +08:00
import CertPreviewImg from "~/components/CertPreviewImg";
2026-07-07 13:54:55 +08:00
import AttachmentUpload from "~/components/AttachmentUpload";
2026-06-23 18:07:30 +08:00
import { dateRangeRule } from "~/utils/validators";
2026-06-29 16:45:58 +08:00
2026-07-07 09:07:19 +08:00
const { router } = tools;
2026-06-23 18:07:30 +08:00
function StaffCertificatePage(props) {
2026-07-07 09:07:19 +08:00
const urlParams = Object.fromEntries(new URLSearchParams(window.location.search));
const staffId = urlParams.staffId;
const staffName = urlParams.staffName ? decodeURIComponent(urlParams.staffName) : "";
2026-06-23 18:07:30 +08:00
const [addModalOpen, setAddModalOpen] = useState(false);
const [viewModalOpen, setViewModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
2026-07-07 09:07:19 +08:00
const [searchForm] = Form.useForm();
const { staffCertificate } = props;
const {
staffCertificateList: dataSource,
staffCertificateTotal: total,
staffCertificateLoading: loading,
} = staffCertificate || {};
2026-06-23 18:07:30 +08:00
useEffect(() => {
2026-07-07 09:07:19 +08:00
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
const handleSearch = () => {
props.staffCertificateList({ ...router.query, personnelId: staffId });
};
2026-06-23 18:07:30 +08:00
const goBack = () => {
window.location.href = window.location.pathname.replace(/\/Certificate.*$/, "/List");
};
const onDelete = (id) => {
Modal.confirm({
title: "提示",
content: "数据将会删除,您是否确认删除?",
okText: "是",
cancelText: "否",
onOk: async () => {
2026-07-08 20:31:28 +08:00
const res = await props.staffCertificateRemove({ data: id });
2026-06-23 18:07:30 +08:00
if (res?.success !== false) {
message.success("删除成功");
2026-07-07 09:07:19 +08:00
handleSearch();
2026-06-23 18:07:30 +08:00
}
},
});
};
return (
2026-07-07 11:52:21 +08:00
<PageLayout
2026-07-08 15:23:50 +08:00
history={props.history}
previous
2026-07-07 11:52:21 +08:00
title={`人员证书 - ${staffName || ""}`}
extra={
<Button type="primary" onClick={() => { setCurrentId(""); setAddModalOpen(true); }}>新增证书</Button>
}
>
2026-07-07 09:07:19 +08:00
<SearchForm
style={{ marginBottom: 24 }}
form={searchForm}
loading={loading}
formLine={[
2026-07-07 11:52:21 +08:00
<Form.Item key="certName" name="certName">
2026-07-10 10:03:03 +08:00
<ControlWrapper.Input label="证照名称" placeholder="请输入证照名称" allowClear maxLength={50} />
2026-07-07 11:52:21 +08:00
</Form.Item>,
2026-07-07 09:07:19 +08:00
<Form.Item key="certNo" name="certNo">
2026-07-10 10:03:03 +08:00
<ControlWrapper.Input label="证书编号" placeholder="请输入证书编号" allowClear maxLength={50} />
2026-07-07 09:07:19 +08:00
</Form.Item>,
2026-07-07 11:52:21 +08:00
<Form.Item key="certCategoryName" name="certCategoryName">
2026-07-10 10:03:03 +08:00
<ControlWrapper.Input label="证书类别名称" placeholder="请输入证书类别名称" allowClear maxLength={50} />
2026-07-07 09:07:19 +08:00
</Form.Item>,
2026-07-07 11:52:21 +08:00
<Form.Item key="operationCategoryName" name="operationCategoryName">
2026-07-10 10:03:03 +08:00
<ControlWrapper.Input label="证书作业类别名称" placeholder="请输入证书作业类别名称" allowClear maxLength={50} />
2026-07-07 09:07:19 +08:00
</Form.Item>,
2026-06-23 18:07:30 +08:00
]}
2026-07-07 15:58:05 +08:00
onReset={(values) => {
router.query = { ...values, current: 1, size: 10 };
2026-07-07 09:07:19 +08:00
handleSearch();
}}
2026-07-07 15:58:05 +08:00
onFinish={(values) => {
router.query = { ...values, current: 1, size: 10 };
2026-07-07 09:07:19 +08:00
handleSearch();
}}
2026-06-23 18:07:30 +08:00
/>
<Table
2026-07-07 09:07:19 +08:00
rowKey="id"
2026-06-23 18:07:30 +08:00
columns={[
2026-07-07 11:52:21 +08:00
{ title: "证照名称", dataIndex: "certName" },
{ title: "证书类型", dataIndex: "certTypeName" },
{ title: "证书类别", dataIndex: "certCategoryName" },
{ title: "证书作业类别", dataIndex: "operationCategoryName" },
2026-06-23 18:07:30 +08:00
{ title: "证书编号", dataIndex: "certNo" },
{
title: "操作",
width: 200,
render: (_, record) => (
2026-07-07 09:07:19 +08:00
<TableAction>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setViewModalOpen(true); }}>
2026-06-23 18:07:30 +08:00
查看
</Button>
2026-07-07 09:07:19 +08:00
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setAddModalOpen(true); }}>
2026-06-23 18:07:30 +08:00
编辑
</Button>
2026-07-07 09:07:19 +08:00
<Button danger type="link" size="small" onClick={() => onDelete(record.id)}>删除</Button>
</TableAction>
2026-06-23 18:07:30 +08:00
),
},
]}
2026-07-07 09:07:19 +08:00
dataSource={dataSource}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: Number(router.query.current) || 1,
pageSize: Number(router.query.size) || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleSearch();
},
}}
2026-06-23 18:07:30 +08:00
/>
{addModalOpen && (
<CertModal
open={addModalOpen}
staffId={staffId}
currentId={currentId}
2026-07-07 09:07:19 +08:00
staffCertificateAdd={props.staffCertificateAdd}
staffCertificateEdit={props.staffCertificateEdit}
staffCertificateInfo={props.staffCertificateInfo}
2026-06-23 18:07:30 +08:00
onCancel={() => {
setAddModalOpen(false);
setCurrentId("");
}}
2026-07-07 09:07:19 +08:00
onSuccess={handleSearch}
2026-06-23 18:07:30 +08:00
/>
)}
{viewModalOpen && (
<ViewModal
open={viewModalOpen}
currentId={currentId}
2026-07-07 09:07:19 +08:00
staffCertificateInfo={props.staffCertificateInfo}
2026-06-23 18:07:30 +08:00
onCancel={() => {
setViewModalOpen(false);
setCurrentId("");
}}
/>
)}
2026-06-29 16:45:58 +08:00
</PageLayout>
2026-06-23 18:07:30 +08:00
);
}
function CertModal({
2026-07-07 09:07:19 +08:00
open, staffId, currentId, staffCertificateAdd, staffCertificateEdit, staffCertificateInfo, onCancel, onSuccess,
2026-06-23 18:07:30 +08:00
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
2026-07-07 09:07:19 +08:00
const [uploadFileList, setUploadFileList] = useState([]);
2026-06-23 18:07:30 +08:00
useEffect(() => {
2026-07-07 09:07:19 +08:00
if (!open) return;
2026-06-23 18:07:30 +08:00
if (!currentId) {
form.resetFields();
2026-07-07 09:07:19 +08:00
setUploadFileList([]);
2026-06-23 18:07:30 +08:00
return;
}
let cancelled = false;
(async () => {
setDetailLoading(true);
try {
2026-07-07 09:07:19 +08:00
const res = await staffCertificateInfo({ id: currentId });
if (cancelled || !res?.data) return;
2026-06-23 18:07:30 +08:00
const data = { ...res.data };
2026-07-07 13:38:33 +08:00
// 将 validStartDate/validEndDate 合并为 validDateRangePicker 需要 dayjs 对象)
2026-07-07 09:07:19 +08:00
if (data.validStartDate || data.validEndDate) {
2026-07-07 13:38:33 +08:00
data.validDate = [
data.validStartDate ? dayjs(data.validStartDate) : undefined,
data.validEndDate ? dayjs(data.validEndDate) : undefined,
];
}
// 复核日期需要转为 dayjs 对象
if (data.reviewDate) {
data.reviewDate = dayjs(data.reviewDate);
2026-06-23 18:07:30 +08:00
}
2026-07-07 09:07:19 +08:00
// 处理证书附件 fileList
let fileList = [];
if (Array.isArray(data.certImgFiles)) {
fileList = data.certImgFiles.map((f, i) => ({
uid: f.uid || `-${i}`,
name: f.fileName || f.name || `证书附件${i + 1}`,
status: "done",
url: f.url,
}));
} else if (data.certAttachmentUrl) {
fileList = data.certAttachmentUrl.split(",").filter(Boolean).map((url, i) => ({
uid: `-${i}`,
name: url.split("/").pop() || `证书附件${i + 1}`,
status: "done",
url,
}));
2026-06-23 18:07:30 +08:00
}
2026-07-07 09:07:19 +08:00
data.certAttachmentUrl = fileList;
setUploadFileList(fileList);
form.setFieldsValue(data);
} catch (err) {
console.warn("[StaffCertificate] load detail failed:", err);
} finally {
if (!cancelled) setDetailLoading(false);
2026-06-23 18:07:30 +08:00
}
})();
2026-07-07 09:07:19 +08:00
return () => { cancelled = true; };
2026-06-23 18:07:30 +08:00
}, [open, currentId]);
const handleCancel = () => {
form.resetFields();
2026-07-07 09:07:19 +08:00
setUploadFileList([]);
2026-06-23 18:07:30 +08:00
onCancel();
};
const handleSubmit = async (values) => {
try {
setSubmitting(true);
2026-07-07 09:07:19 +08:00
const payload = {
...values,
personnelId: staffId,
2026-07-07 13:38:33 +08:00
validStartDate: values.validDate?.[0] ? dayjs(values.validDate[0]).format("YYYY-MM-DD") : undefined,
validEndDate: values.validDate?.[1] ? dayjs(values.validDate[1]).format("YYYY-MM-DD") : undefined,
reviewDate: values.reviewDate ? dayjs(values.reviewDate).format("YYYY-MM-DD") : undefined,
2026-07-07 09:07:19 +08:00
certAttachmentUrl: Array.isArray(values.certAttachmentUrl)
? values.certAttachmentUrl.map((f) => f.url || f.response?.url).filter(Boolean).join(",")
: "",
};
delete payload.validDate;
delete payload.certImgFiles;
if (currentId) payload.id = currentId;
const request = currentId ? staffCertificateEdit : staffCertificateAdd;
const res = await request(payload);
2026-06-23 18:07:30 +08:00
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
handleCancel();
onSuccess();
2026-07-07 09:07:19 +08:00
} else {
2026-07-08 20:55:23 +08:00
2026-06-23 18:07:30 +08:00
}
2026-07-07 09:07:19 +08:00
} finally {
2026-06-23 18:07:30 +08:00
setSubmitting(false);
}
};
return (
<Modal
open={open}
2026-07-06 18:06:49 +08:00
destroyOnHidden
2026-06-23 18:07:30 +08:00
title={currentId ? "编辑证书" : "添加证书"}
width={800}
loading={detailLoading}
confirmLoading={submitting}
onCancel={handleCancel}
onOk={form.submit}
>
2026-07-07 09:07:19 +08:00
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item name="certName" label="证照名称" rules={[{ required: true, message: "请输入证照名称" }]}>
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证照名称" maxLength={50} showCount />
2026-07-07 09:07:19 +08:00
</Form.Item>
<Form.Item name="certNo" label="证书编号" rules={[{ required: true, message: "请输入证书编号" }]}>
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证书编号" maxLength={50} showCount />
2026-07-07 09:07:19 +08:00
</Form.Item>
<Form.Item name="issueOrg" label="发证机关" rules={[{ required: true, message: "请输入发证机关" }]}>
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入发证机关" maxLength={100} showCount />
2026-07-07 09:07:19 +08:00
</Form.Item>
<Form.Item
name="validDate"
label="证书有效开始/结束日期"
rules={[{ required: true, message: "请选择证书有效期" }, dateRangeRule()]}
>
<DatePicker.RangePicker style={{ width: "100%" }} />
</Form.Item>
2026-07-07 11:52:21 +08:00
<Form.Item name="certTypeCode" label="证书类型编码">
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证书类型编码" maxLength={50} />
2026-07-07 11:52:21 +08:00
</Form.Item>
<Form.Item name="certTypeName" label="证书类型名称">
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证书类型名称" maxLength={50} />
2026-07-07 11:52:21 +08:00
</Form.Item>
<Form.Item name="certCategoryCode" label="证书类别编码">
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证书类别编码" maxLength={50} />
2026-07-07 11:52:21 +08:00
</Form.Item>
<Form.Item name="certCategoryName" label="证书类别名称">
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证书类别名称" maxLength={50} />
2026-07-07 11:52:21 +08:00
</Form.Item>
<Form.Item name="operationCategoryCode" label="证书作业类别编码">
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证书作业类别编码" maxLength={50} />
2026-07-07 11:52:21 +08:00
</Form.Item>
<Form.Item name="operationCategoryName" label="证书作业类别名称">
2026-07-10 10:03:03 +08:00
<Input placeholder="请输入证书作业类别名称" maxLength={50} />
2026-07-07 11:52:21 +08:00
</Form.Item>
<Form.Item name="reviewDate" label="复核日期">
2026-07-07 09:07:19 +08:00
<DatePicker style={{ width: "100%" }} />
</Form.Item>
2026-07-07 13:54:55 +08:00
<AttachmentUpload name="certAttachmentUrl" label="证书附件" maxCount={3} />
2026-07-07 09:07:19 +08:00
</Form>
2026-06-23 18:07:30 +08:00
</Modal>
);
}
2026-07-07 09:07:19 +08:00
function ViewModal({ open, currentId, staffCertificateInfo, onCancel }) {
2026-06-23 18:07:30 +08:00
const [info, setInfo] = useState({});
const [detailLoading, setDetailLoading] = useState(false);
useEffect(() => {
if (!open || !currentId) {
setInfo({});
return;
}
let cancelled = false;
(async () => {
setDetailLoading(true);
try {
2026-07-07 09:07:19 +08:00
const res = await staffCertificateInfo({ id: currentId });
if (cancelled || !res?.data) return;
2026-06-23 18:07:30 +08:00
const data = { ...res.data };
2026-07-07 09:07:19 +08:00
// 处理 certImgFiles
if (Array.isArray(data.certImgFiles)) {
data.certDisplayFiles = data.certImgFiles;
} else if (data.certAttachmentUrl) {
data.certDisplayFiles = data.certAttachmentUrl.split(",").filter(Boolean).map((url, i) => ({
uid: `-${i}`,
fileName: url.split("/").pop() || `证书附件${i + 1}`,
url,
}));
} else {
data.certDisplayFiles = [];
2026-06-23 18:07:30 +08:00
}
2026-07-07 09:07:19 +08:00
setInfo(data);
} catch (err) {
2026-06-23 18:07:30 +08:00
console.warn("[StaffCertificate] load view failed:", err);
2026-07-07 09:07:19 +08:00
} finally {
if (!cancelled) setDetailLoading(false);
2026-06-23 18:07:30 +08:00
}
})();
2026-07-07 09:07:19 +08:00
return () => { cancelled = true; };
2026-06-23 18:07:30 +08:00
}, [open, currentId]);
return (
<Modal
open={open}
2026-07-06 18:06:49 +08:00
destroyOnHidden
2026-06-23 18:07:30 +08:00
title="证书详情"
width={800}
loading={detailLoading}
cancelText="返回"
okButtonProps={{ style: { display: "none" } }}
onCancel={onCancel}
>
<Descriptions bordered column={1} labelStyle={{ width: 160 }}>
<Descriptions.Item label="证照名称">{info.certName}</Descriptions.Item>
2026-07-07 11:52:21 +08:00
<Descriptions.Item label="证书类型编码">{info.certTypeCode}</Descriptions.Item>
<Descriptions.Item label="证书类型名称">{info.certTypeName}</Descriptions.Item>
<Descriptions.Item label="证书类别编码">{info.certCategoryCode}</Descriptions.Item>
<Descriptions.Item label="证书类别名称">{info.certCategoryName}</Descriptions.Item>
<Descriptions.Item label="证书作业类别编码">{info.operationCategoryCode}</Descriptions.Item>
<Descriptions.Item label="证书作业类别名称">{info.operationCategoryName}</Descriptions.Item>
2026-06-23 18:07:30 +08:00
<Descriptions.Item label="证书编号">{info.certNo}</Descriptions.Item>
<Descriptions.Item label="发证机关">{info.issueOrg}</Descriptions.Item>
<Descriptions.Item label="证书有效开始日期">{info.validStartDate}</Descriptions.Item>
<Descriptions.Item label="证书有效结束日期">{info.validEndDate}</Descriptions.Item>
2026-07-07 11:52:21 +08:00
<Descriptions.Item label="复核日期">{info.reviewDate}</Descriptions.Item>
2026-07-07 09:07:19 +08:00
<Descriptions.Item label="证书图片"><CertPreviewImg files={info.certDisplayFiles} /></Descriptions.Item>
2026-06-23 18:07:30 +08:00
</Descriptions>
</Modal>
);
}
2026-07-07 09:07:19 +08:00
export default Connect([NS_STAFF_CERTIFICATE], true)(AntdTableFuncControl(StaffCertificatePage));