2026-06-26 17:28:06 +08:00
|
|
|
import { Button, Descriptions, Modal, Space, Table } from "antd";
|
2026-06-29 11:59:34 +08:00
|
|
|
import { useEffect, useRef, useState } from "react";
|
2026-06-26 17:28:06 +08:00
|
|
|
import FilePreviewModal, { FilePreviewContent } from "~/components/FilePreviewModal";
|
2026-06-29 11:59:34 +08:00
|
|
|
import { resolvePreviewSrc } from "~/components/CertPreviewImg";
|
2026-06-26 17:28:06 +08:00
|
|
|
import { fetchStaffCertDetail, fetchStaffCertListByPersonnelId } from "~/api/staffCertificate";
|
2026-07-02 16:58:07 +08:00
|
|
|
import { fetchOrgPersonnelDetail } from "~/api/qualFiling/personnelHelper";
|
2026-06-26 17:28:06 +08:00
|
|
|
import { safeRequest } from "~/utils";
|
|
|
|
|
|
|
|
|
|
const GENDER_MAP = { 1: "男", 2: "女" };
|
|
|
|
|
const REGISTER_ENGINEER_MAP = { 1: "是", 2: "否" };
|
|
|
|
|
|
|
|
|
|
export default function StaffViewModal({
|
|
|
|
|
open,
|
|
|
|
|
currentId,
|
2026-07-02 16:58:07 +08:00
|
|
|
requestDetails = fetchOrgPersonnelDetail,
|
2026-06-26 17:28:06 +08:00
|
|
|
requestCertList = fetchStaffCertListByPersonnelId,
|
|
|
|
|
requestCertDetails = fetchStaffCertDetail,
|
|
|
|
|
onCancel,
|
|
|
|
|
}) {
|
|
|
|
|
const [info, setInfo] = useState({});
|
|
|
|
|
const [certificates, setCertificates] = useState([]);
|
|
|
|
|
const [detailLoading, setDetailLoading] = useState(false);
|
|
|
|
|
const [attachmentPreview, setAttachmentPreview] = useState(null);
|
|
|
|
|
const [certPreview, setCertPreview] = useState(null);
|
|
|
|
|
const [certPreviewLoading, setCertPreviewLoading] = useState(false);
|
|
|
|
|
const [certPreviewInfo, setCertPreviewInfo] = useState(null);
|
2026-06-29 11:59:34 +08:00
|
|
|
const requestDetailsRef = useRef(requestDetails);
|
|
|
|
|
const requestCertListRef = useRef(requestCertList);
|
|
|
|
|
const requestCertDetailsRef = useRef(requestCertDetails);
|
|
|
|
|
requestDetailsRef.current = requestDetails;
|
|
|
|
|
requestCertListRef.current = requestCertList;
|
|
|
|
|
requestCertDetailsRef.current = requestCertDetails;
|
2026-06-26 17:28:06 +08:00
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
if (!open || !currentId) {
|
|
|
|
|
setInfo({});
|
|
|
|
|
setCertificates([]);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
let cancelled = false;
|
|
|
|
|
setDetailLoading(true);
|
2026-06-29 11:59:34 +08:00
|
|
|
const loadCertList = requestCertListRef.current;
|
2026-06-26 17:28:06 +08:00
|
|
|
Promise.all([
|
2026-06-29 11:59:34 +08:00
|
|
|
safeRequest(requestDetailsRef.current, { id: currentId }),
|
|
|
|
|
typeof loadCertList === "function"
|
|
|
|
|
? Promise.resolve(loadCertList(currentId)).catch((err) => {
|
2026-06-26 17:28:06 +08:00
|
|
|
console.warn("[StaffViewModal] load certificates failed:", err);
|
|
|
|
|
return [];
|
|
|
|
|
})
|
|
|
|
|
: Promise.resolve([]),
|
|
|
|
|
]).then(([detailRes, certList]) => {
|
|
|
|
|
if (!cancelled) {
|
|
|
|
|
setInfo(detailRes?.data || {});
|
|
|
|
|
setCertificates(Array.isArray(certList) ? certList : []);
|
|
|
|
|
}
|
|
|
|
|
}).finally(() => {
|
|
|
|
|
if (!cancelled) {
|
|
|
|
|
setDetailLoading(false);
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
return () => {
|
|
|
|
|
cancelled = true;
|
|
|
|
|
};
|
2026-06-29 11:59:34 +08:00
|
|
|
}, [open, currentId]);
|
2026-06-26 17:28:06 +08:00
|
|
|
|
|
|
|
|
const openCertPreview = async (record) => {
|
|
|
|
|
setCertPreview(record);
|
|
|
|
|
setCertPreviewInfo(null);
|
2026-06-29 11:59:34 +08:00
|
|
|
if (typeof requestCertDetailsRef.current !== "function") {
|
2026-06-26 17:28:06 +08:00
|
|
|
setCertPreviewInfo(record);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
setCertPreviewLoading(true);
|
|
|
|
|
try {
|
2026-06-29 11:59:34 +08:00
|
|
|
const res = await safeRequest(requestCertDetailsRef.current, { id: record.id });
|
2026-06-26 17:28:06 +08:00
|
|
|
setCertPreviewInfo(res?.data || record);
|
|
|
|
|
}
|
|
|
|
|
finally {
|
|
|
|
|
setCertPreviewLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const proofFiles = info.proofMaterials || [];
|
|
|
|
|
const certPreviewFiles = certPreviewInfo?.certImgs?.length
|
|
|
|
|
? certPreviewInfo.certImgs
|
|
|
|
|
: certPreviewInfo?.certImgFiles || [];
|
2026-06-29 11:59:34 +08:00
|
|
|
const certPreviewUrl = resolvePreviewSrc(certPreviewFiles[0]);
|
2026-06-26 17:28:06 +08:00
|
|
|
const certPreviewName = certPreviewFiles[0]?.fileName || certPreviewFiles[0]?.name || certPreviewInfo?.certName;
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<>
|
|
|
|
|
<Modal
|
|
|
|
|
open={open}
|
|
|
|
|
destroyOnClose
|
|
|
|
|
title="查看"
|
|
|
|
|
width={900}
|
|
|
|
|
loading={detailLoading}
|
|
|
|
|
cancelText="关闭"
|
|
|
|
|
okButtonProps={{ style: { display: "none" } }}
|
|
|
|
|
onCancel={onCancel}
|
|
|
|
|
>
|
|
|
|
|
<Descriptions bordered column={2} labelStyle={{ width: 120 }}>
|
|
|
|
|
<Descriptions.Item label="姓名">{info.staffName}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="性别">{GENDER_MAP[info.gender]}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="出生日期">{info.birthDate}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="账号">{info.account}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="部门">{info.deptName}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="岗位">{info.positionName}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="人员类型">{info.personType || "基础人员"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="资质范围">{info.qualScope || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="职业等级">{info.professionalLevel || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="证书编号">{info.evaluatorCertNo || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="学历类型">{info.educationType || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="学历层次">{info.educationLevel || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="职称">{info.titleName || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="是否注册安全工程师">
|
|
|
|
|
{REGISTER_ENGINEER_MAP[info.registerEngineerFlag] ?? "-"}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="身份证号">{info.idCardNo}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="学历">{info.education || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="现住地址" span={2}>{info.homeAddress}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="办公地址" span={2}>{info.officeAddress}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="毕业院校">{info.graduateSchool}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="专业">{info.major}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="出版学术专著、专利、获奖、发表学术论文等" span={2}>
|
|
|
|
|
{info.publications || "-"}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="自我申报的专业能力及认定方式" span={2}>
|
|
|
|
|
{info.abilityDeclaration || "-"}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="主要学习工作经历" span={2}>
|
|
|
|
|
{info.workExperience || "-"}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="申报专业能力证明材料" span={2}>
|
|
|
|
|
{proofFiles.length ? (
|
|
|
|
|
<Space direction="vertical" size={4}>
|
|
|
|
|
{proofFiles.map((file, index) => {
|
|
|
|
|
const fileName = file.name || file.fileName || `专业能力证明${index + 1}.pdf`;
|
|
|
|
|
return (
|
|
|
|
|
<Space key={`${fileName}-${index}`}>
|
|
|
|
|
<Button
|
|
|
|
|
type="link"
|
|
|
|
|
size="small"
|
|
|
|
|
style={{ padding: 0 }}
|
|
|
|
|
onClick={() => setAttachmentPreview({ url: file.url, fileName })}
|
|
|
|
|
>
|
|
|
|
|
查看附件
|
|
|
|
|
</Button>
|
|
|
|
|
<span>{fileName}</span>
|
|
|
|
|
</Space>
|
|
|
|
|
);
|
|
|
|
|
})}
|
|
|
|
|
</Space>
|
|
|
|
|
) : "-"}
|
|
|
|
|
</Descriptions.Item>
|
|
|
|
|
</Descriptions>
|
|
|
|
|
|
|
|
|
|
<div style={{ marginTop: 16, marginBottom: 8, fontWeight: 600 }}>证照信息</div>
|
|
|
|
|
<Table
|
|
|
|
|
size="small"
|
|
|
|
|
bordered
|
|
|
|
|
rowKey="id"
|
|
|
|
|
pagination={false}
|
|
|
|
|
dataSource={certificates}
|
|
|
|
|
locale={{ emptyText: "暂无证照信息" }}
|
|
|
|
|
columns={[
|
|
|
|
|
{ title: "证照名称", dataIndex: "certName" },
|
|
|
|
|
{ title: "证书编号", dataIndex: "certNo" },
|
|
|
|
|
{ title: "证书类别", dataIndex: "certCategory" },
|
|
|
|
|
{ title: "发证机关", dataIndex: "issueOrg" },
|
|
|
|
|
{ title: "有效开始日期", dataIndex: "validStartDate", width: 120 },
|
|
|
|
|
{ title: "有效结束日期", dataIndex: "validEndDate", width: 120 },
|
|
|
|
|
{
|
|
|
|
|
title: "操作",
|
|
|
|
|
width: 90,
|
|
|
|
|
render: (_, record) => (
|
|
|
|
|
<Button type="link" size="small" onClick={() => openCertPreview(record)}>查看证书</Button>
|
|
|
|
|
),
|
|
|
|
|
},
|
|
|
|
|
]}
|
|
|
|
|
/>
|
|
|
|
|
</Modal>
|
|
|
|
|
|
|
|
|
|
<FilePreviewModal
|
|
|
|
|
open={!!attachmentPreview}
|
|
|
|
|
title="附件预览"
|
|
|
|
|
fileName={attachmentPreview?.fileName}
|
|
|
|
|
url={attachmentPreview?.url}
|
|
|
|
|
onCancel={() => setAttachmentPreview(null)}
|
|
|
|
|
/>
|
|
|
|
|
|
|
|
|
|
<Modal
|
|
|
|
|
open={!!certPreview}
|
|
|
|
|
destroyOnClose
|
|
|
|
|
title="证书预览"
|
|
|
|
|
width={720}
|
|
|
|
|
loading={certPreviewLoading}
|
|
|
|
|
cancelText="关闭"
|
|
|
|
|
okText={certPreviewUrl ? "新窗口打开" : "关闭"}
|
|
|
|
|
okButtonProps={{ style: certPreviewUrl ? undefined : { display: "none" } }}
|
|
|
|
|
onCancel={() => {
|
|
|
|
|
setCertPreview(null);
|
|
|
|
|
setCertPreviewInfo(null);
|
|
|
|
|
}}
|
|
|
|
|
onOk={() => {
|
|
|
|
|
if (certPreviewUrl) {
|
|
|
|
|
window.open(certPreviewUrl, "_blank");
|
|
|
|
|
}
|
|
|
|
|
setCertPreview(null);
|
|
|
|
|
setCertPreviewInfo(null);
|
|
|
|
|
}}
|
|
|
|
|
>
|
|
|
|
|
{certPreviewInfo && (
|
|
|
|
|
<div>
|
|
|
|
|
<Descriptions bordered column={1} size="small" style={{ marginBottom: 12 }}>
|
|
|
|
|
<Descriptions.Item label="证照名称">{certPreviewInfo.certName}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="证书编号">{certPreviewInfo.certNo}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="证书类别">{certPreviewInfo.certCategory || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="发证机关">{certPreviewInfo.issueOrg || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="有效开始日期">{certPreviewInfo.validStartDate || "-"}</Descriptions.Item>
|
|
|
|
|
<Descriptions.Item label="有效结束日期">{certPreviewInfo.validEndDate || "-"}</Descriptions.Item>
|
|
|
|
|
</Descriptions>
|
|
|
|
|
<FilePreviewContent url={certPreviewUrl} fileName={certPreviewName} />
|
|
|
|
|
</div>
|
|
|
|
|
)}
|
|
|
|
|
</Modal>
|
|
|
|
|
</>
|
|
|
|
|
);
|
|
|
|
|
}
|