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

392 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, DatePicker, Descriptions, Form, Input, message, Modal, Select, Table, Upload } from "antd";
import { UploadOutlined } from "@ant-design/icons";
import { useEffect, useState } from "react";
import dayjs from "dayjs";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
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";
import { NS_STAFF_CERTIFICATE } from "~/enumerate/namespace";
import CertPreviewImg from "~/components/CertPreviewImg";
import AttachmentUpload from "~/components/AttachmentUpload";
import { dateRangeRule } from "~/utils/validators";
const { router } = tools;
function StaffCertificatePage(props) {
const urlParams = Object.fromEntries(new URLSearchParams(window.location.search));
const staffId = urlParams.staffId;
const staffName = urlParams.staffName ? decodeURIComponent(urlParams.staffName) : "";
const [addModalOpen, setAddModalOpen] = useState(false);
const [viewModalOpen, setViewModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
const [searchForm] = Form.useForm();
const { staffCertificate } = props;
const {
staffCertificateList: dataSource,
staffCertificateTotal: total,
staffCertificateLoading: loading,
} = staffCertificate || {};
useEffect(() => {
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
const handleSearch = () => {
props.staffCertificateList({ ...router.query, personnelId: staffId });
};
const goBack = () => {
window.location.href = window.location.pathname.replace(/\/Certificate.*$/, "/List");
};
const onDelete = (id) => {
Modal.confirm({
title: "提示",
content: "数据将会删除,您是否确认删除?",
okText: "是",
cancelText: "否",
onOk: async () => {
const res = await props.staffCertificateRemove({ id });
if (res?.success !== false) {
message.success("删除成功");
handleSearch();
}
},
});
};
return (
<PageLayout
history={props.history}
previous
title={`人员证书 - ${staffName || ""}`}
extra={
<Button type="primary" onClick={() => { setCurrentId(""); setAddModalOpen(true); }}>新增证书</Button>
}
>
<SearchForm
style={{ marginBottom: 24 }}
form={searchForm}
loading={loading}
formLine={[
<Form.Item key="certName" name="certName">
<ControlWrapper.Input label="证照名称" placeholder="请输入证照名称" allowClear />
</Form.Item>,
<Form.Item key="certNo" name="certNo">
<ControlWrapper.Input label="证书编号" placeholder="请输入证书编号" allowClear />
</Form.Item>,
<Form.Item key="certCategoryName" name="certCategoryName">
<ControlWrapper.Input label="证书类别名称" placeholder="请输入证书类别名称" allowClear />
</Form.Item>,
<Form.Item key="operationCategoryName" name="operationCategoryName">
<ControlWrapper.Input label="证书作业类别名称" placeholder="请输入证书作业类别名称" allowClear />
</Form.Item>,
]}
onReset={(values) => {
router.query = { ...values, current: 1, size: 10 };
handleSearch();
}}
onFinish={(values) => {
router.query = { ...values, current: 1, size: 10 };
handleSearch();
}}
/>
<Table
rowKey="id"
columns={[
{ title: "证照名称", dataIndex: "certName" },
{ title: "证书类型", dataIndex: "certTypeName" },
{ title: "证书类别", dataIndex: "certCategoryName" },
{ title: "证书作业类别", dataIndex: "operationCategoryName" },
{ title: "证书编号", dataIndex: "certNo" },
{
title: "操作",
width: 200,
render: (_, record) => (
<TableAction>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setViewModalOpen(true); }}>
查看
</Button>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setAddModalOpen(true); }}>
编辑
</Button>
<Button danger type="link" size="small" onClick={() => onDelete(record.id)}>删除</Button>
</TableAction>
),
},
]}
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();
},
}}
/>
{addModalOpen && (
<CertModal
open={addModalOpen}
staffId={staffId}
currentId={currentId}
staffCertificateAdd={props.staffCertificateAdd}
staffCertificateEdit={props.staffCertificateEdit}
staffCertificateInfo={props.staffCertificateInfo}
onCancel={() => {
setAddModalOpen(false);
setCurrentId("");
}}
onSuccess={handleSearch}
/>
)}
{viewModalOpen && (
<ViewModal
open={viewModalOpen}
currentId={currentId}
staffCertificateInfo={props.staffCertificateInfo}
onCancel={() => {
setViewModalOpen(false);
setCurrentId("");
}}
/>
)}
</PageLayout>
);
}
function CertModal({
open, staffId, currentId, staffCertificateAdd, staffCertificateEdit, staffCertificateInfo, onCancel, onSuccess,
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [uploadFileList, setUploadFileList] = useState([]);
useEffect(() => {
if (!open) return;
if (!currentId) {
form.resetFields();
setUploadFileList([]);
return;
}
let cancelled = false;
(async () => {
setDetailLoading(true);
try {
const res = await staffCertificateInfo({ id: currentId });
if (cancelled || !res?.data) return;
const data = { ...res.data };
// 将 validStartDate/validEndDate 合并为 validDateRangePicker 需要 dayjs 对象)
if (data.validStartDate || data.validEndDate) {
data.validDate = [
data.validStartDate ? dayjs(data.validStartDate) : undefined,
data.validEndDate ? dayjs(data.validEndDate) : undefined,
];
}
// 复核日期需要转为 dayjs 对象
if (data.reviewDate) {
data.reviewDate = dayjs(data.reviewDate);
}
// 处理证书附件 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,
}));
}
data.certAttachmentUrl = fileList;
setUploadFileList(fileList);
form.setFieldsValue(data);
} catch (err) {
console.warn("[StaffCertificate] load detail failed:", err);
} finally {
if (!cancelled) setDetailLoading(false);
}
})();
return () => { cancelled = true; };
}, [open, currentId]);
const handleCancel = () => {
form.resetFields();
setUploadFileList([]);
onCancel();
};
const handleSubmit = async (values) => {
try {
setSubmitting(true);
const payload = {
...values,
personnelId: staffId,
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,
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);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
handleCancel();
onSuccess();
} else {
message.error(res?.message || "操作失败");
}
} finally {
setSubmitting(false);
}
};
return (
<Modal
open={open}
destroyOnHidden
title={currentId ? "编辑证书" : "添加证书"}
width={800}
loading={detailLoading}
confirmLoading={submitting}
onCancel={handleCancel}
onOk={form.submit}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item name="certName" label="证照名称" rules={[{ required: true, message: "请输入证照名称" }]}>
<Input placeholder="请输入证照名称" />
</Form.Item>
<Form.Item name="certNo" label="证书编号" rules={[{ required: true, message: "请输入证书编号" }]}>
<Input placeholder="请输入证书编号" />
</Form.Item>
<Form.Item name="issueOrg" label="发证机关" rules={[{ required: true, message: "请输入发证机关" }]}>
<Input placeholder="请输入发证机关" />
</Form.Item>
<Form.Item
name="validDate"
label="证书有效开始/结束日期"
rules={[{ required: true, message: "请选择证书有效期" }, dateRangeRule()]}
>
<DatePicker.RangePicker style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="certTypeCode" label="证书类型编码">
<Input placeholder="请输入证书类型编码" />
</Form.Item>
<Form.Item name="certTypeName" label="证书类型名称">
<Input placeholder="请输入证书类型名称" />
</Form.Item>
<Form.Item name="certCategoryCode" label="证书类别编码">
<Input placeholder="请输入证书类别编码" />
</Form.Item>
<Form.Item name="certCategoryName" label="证书类别名称">
<Input placeholder="请输入证书类别名称" />
</Form.Item>
<Form.Item name="operationCategoryCode" label="证书作业类别编码">
<Input placeholder="请输入证书作业类别编码" />
</Form.Item>
<Form.Item name="operationCategoryName" label="证书作业类别名称">
<Input placeholder="请输入证书作业类别名称" />
</Form.Item>
<Form.Item name="reviewDate" label="复核日期">
<DatePicker style={{ width: "100%" }} />
</Form.Item>
<AttachmentUpload name="certAttachmentUrl" label="证书附件" maxCount={3} />
</Form>
</Modal>
);
}
function ViewModal({ open, currentId, staffCertificateInfo, onCancel }) {
const [info, setInfo] = useState({});
const [detailLoading, setDetailLoading] = useState(false);
useEffect(() => {
if (!open || !currentId) {
setInfo({});
return;
}
let cancelled = false;
(async () => {
setDetailLoading(true);
try {
const res = await staffCertificateInfo({ id: currentId });
if (cancelled || !res?.data) return;
const data = { ...res.data };
// 处理 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 = [];
}
setInfo(data);
} catch (err) {
console.warn("[StaffCertificate] load view failed:", err);
} finally {
if (!cancelled) setDetailLoading(false);
}
})();
return () => { cancelled = true; };
}, [open, currentId]);
return (
<Modal
open={open}
destroyOnHidden
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>
<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>
<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>
<Descriptions.Item label="复核日期">{info.reviewDate}</Descriptions.Item>
<Descriptions.Item label="证书图片"><CertPreviewImg files={info.certDisplayFiles} /></Descriptions.Item>
</Descriptions>
</Modal>
);
}
export default Connect([NS_STAFF_CERTIFICATE], true)(AntdTableFuncControl(StaffCertificatePage));