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

456 lines
17 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({ data: 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"
rules={[{ max: 200, message: "证照名称不能超过200个字符" }]}
>
<ControlWrapper.Input label="证照名称" placeholder="请输入证照名称" allowClear maxLength={200} />
</Form.Item>,
<Form.Item
key="certNo"
name="certNo"
rules={[{ max: 100, message: "证书编号不能超过100个字符" }]}
>
<ControlWrapper.Input label="证书编号" placeholder="请输入证书编号" allowClear maxLength={100} />
</Form.Item>,
<Form.Item
key="certCategoryName"
name="certCategoryName"
rules={[{ max: 50, message: "证书类别名称不能超过50个字符" }]}
>
<ControlWrapper.Input label="证书类别名称" placeholder="请输入证书类别名称" allowClear maxLength={50} />
</Form.Item>,
<Form.Item
key="operationCategoryName"
name="operationCategoryName"
rules={[{ max: 50, message: "证书作业类别名称不能超过50个字符" }]}
>
<ControlWrapper.Input label="证书作业类别名称" placeholder="请输入证书作业类别名称" allowClear maxLength={50} />
</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 {
}
} 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: "请输入证照名称" },
{ max: 200, message: "证照名称不能超过200个字符" },
]}
>
<Input placeholder="请输入证照名称" maxLength={200} showCount />
</Form.Item>
<Form.Item
name="certNo"
label="证书编号"
rules={[
{ required: true, message: "请输入证书编号" },
{ max: 100, message: "证书编号不能超过100个字符" },
]}
>
<Input placeholder="请输入证书编号" maxLength={100} showCount />
</Form.Item>
<Form.Item
name="issueOrg"
label="发证机关"
rules={[
{ required: true, message: "请输入发证机关" },
{ max: 200, message: "发证机关不能超过200个字符" },
]}
>
<Input placeholder="请输入发证机关" maxLength={200} showCount />
</Form.Item>
<Form.Item
name="validDate"
label="证书有效开始/结束日期"
rules={[{ required: true, message: "请选择证书有效期" }, dateRangeRule()]}
>
<DatePicker.RangePicker style={{ width: "100%" }} />
</Form.Item>
<Form.Item
name="certTypeCode"
label="证书类型编码"
rules={[{ max: 32, message: "证书类型编码不能超过32个字符" }]}
>
<Input placeholder="请输入证书类型编码" maxLength={32} showCount />
</Form.Item>
<Form.Item
name="certTypeName"
label="证书类型名称"
rules={[{ max: 50, message: "证书类型名称不能超过50个字符" }]}
>
<Input placeholder="请输入证书类型名称" maxLength={50} showCount />
</Form.Item>
<Form.Item
name="certCategoryCode"
label="证书类别编码"
rules={[{ max: 32, message: "证书类别编码不能超过32个字符" }]}
>
<Input placeholder="请输入证书类别编码" maxLength={32} showCount />
</Form.Item>
<Form.Item
name="certCategoryName"
label="证书类别名称"
rules={[{ max: 50, message: "证书类别名称不能超过50个字符" }]}
>
<Input placeholder="请输入证书类别名称" maxLength={50} showCount />
</Form.Item>
<Form.Item
name="operationCategoryCode"
label="证书作业类别编码"
rules={[{ max: 32, message: "证书作业类别编码不能超过32个字符" }]}
>
<Input placeholder="请输入证书作业类别编码" maxLength={32} showCount />
</Form.Item>
<Form.Item
name="operationCategoryName"
label="证书作业类别名称"
rules={[{ max: 50, message: "证书作业类别名称不能超过50个字符" }]}
>
<Input placeholder="请输入证书作业类别名称" maxLength={50} showCount />
</Form.Item>
<Form.Item name="reviewDate" label="复核日期">
<DatePicker style={{ width: "100%" }} />
</Form.Item>
<AttachmentUpload name="certAttachmentUrl" label="证书附件" maxCount={10} accept="image/*,.pdf"
extra={'请上传证书附件支持10个以内的图片和PDF格式'}
/>
</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));