safety-eval-service-frontend/src/components/StaffViewModal/index.js

394 lines
14 KiB
JavaScript

import { Button, Descriptions, Image, Modal, Table, Upload } from "antd";
import { useEffect, useRef, useState } from "react";
import FilePreviewModal, {
FilePreviewContent,
} from "~/components/FilePreviewModal";
import { resolvePreviewSrc } from "~/components/CertPreviewImg";
import {
fetchStaffCertDetail,
fetchStaffCertListByPersonnelId,
} from "~/api/staffCertificate";
import { fetchOrgPersonnelDetail } from "~/utils/qualFiling/personnelHelper";
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP, TITLE_LEVEL_MAP } from "~/enumerate/enterpriseOptions";
const GENDER_MAP = { 1: "男", 2: "女" };
const REGISTER_ENGINEER_MAP = { 1: "是", 2: "否" };
export default function StaffViewModal({
open,
currentId,
requestDetails = fetchOrgPersonnelDetail,
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);
const requestDetailsRef = useRef(requestDetails);
const requestCertListRef = useRef(requestCertList);
const requestCertDetailsRef = useRef(requestCertDetails);
requestDetailsRef.current = requestDetails;
requestCertListRef.current = requestCertList;
requestCertDetailsRef.current = requestCertDetails;
useEffect(() => {
if (!open || !currentId) {
setInfo({});
setCertificates([]);
return;
}
let cancelled = false;
setDetailLoading(true);
const loadCertList = requestCertListRef.current;
Promise.all([
Promise.resolve(requestDetailsRef.current({ id: currentId })).catch(
(err) => {
console.warn("[StaffViewModal] load detail failed:", err);
return { data: {} };
},
),
typeof loadCertList === "function"
? Promise.resolve(loadCertList(currentId)).catch((err) => {
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;
};
}, [open, currentId]);
const openCertPreview = async (record) => {
setCertPreview(record);
setCertPreviewInfo(null);
if (typeof requestCertDetailsRef.current !== "function") {
setCertPreviewInfo(record);
return;
}
setCertPreviewLoading(true);
try {
const res = await requestCertDetailsRef.current({ id: record.id });
setCertPreviewInfo(res?.data || record);
} catch (err) {
console.warn("[StaffViewModal] load cert detail failed:", err);
setCertPreviewInfo(record);
} finally {
setCertPreviewLoading(false);
}
};
const proofMaterialUrl = info.proofMaterialUrl
? JSON.parse(info.proofMaterialUrl)
: [];
const titleCodeArr = Array.isArray(info.titleCodeArr)
? info.titleCodeArr.map((item) => {
let proveUrlList = [];
if (item.proveUrl) {
try {
proveUrlList =
typeof item.proveUrl === "string"
? JSON.parse(item.proveUrl)
: item.proveUrl;
} catch {
proveUrlList = [];
}
}
return {
industryName:
QUALIFICATION_INDUSTRY_OPTIONS_MAP[item.industryCode] ||
item.industryCode ||
"-",
titleName: TITLE_LEVEL_MAP[item.titleCode] || item.titleCode || "-",
proveUrlList,
};
})
: [];
const renderFileList = (files) => {
if (!files?.length) return "-";
return files.map((item) => {
const isImage = /\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i.test(
item.url,
);
return (
<div key={item.uid || item.url}>
{isImage ? (
<Image
src={item.url}
style={{ maxWidth: 150, cursor: "pointer" }}
preview={{ mask: item.fileName || item.name || "预览" }}
/>
) : (
<Button type="link" onClick={() => window.open(item.url)}>
{item.fileName || item.name || item.url}
</Button>
)}
</div>
);
});
};
const certPreviewFiles = certPreviewInfo?.certImgs?.length
? certPreviewInfo.certImgs
: certPreviewInfo?.certImgFiles || [];
const certPreviewUrl = resolvePreviewSrc(certPreviewFiles[0]);
const certPreviewName =
certPreviewFiles[0]?.fileName ||
certPreviewFiles[0]?.name ||
certPreviewInfo?.certName;
return (
<>
<Modal
open={open}
destroyOnHidden
title="查看"
width={900}
loading={detailLoading}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
onCancel={onCancel}
>
<Descriptions bordered column={2} labelStyle={{ width: 120 }}>
<Descriptions.Item label="姓名">{info.userName}</Descriptions.Item>
<Descriptions.Item label="性别">
{GENDER_MAP[info.genderCode]}
</Descriptions.Item>
<Descriptions.Item label="出生日期">
{info.birthDate}
</Descriptions.Item>
<Descriptions.Item label="参加工作时间">
{info.joinWorkDate}
</Descriptions.Item>
<Descriptions.Item label="账号">{info.account}</Descriptions.Item>
<Descriptions.Item label="部门">{info.deptName}</Descriptions.Item>
<Descriptions.Item label="岗位">{info.postName}</Descriptions.Item>
<Descriptions.Item label="资质范围">
{QUALIFICATION_INDUSTRY_OPTIONS_MAP[info.qualScope] || info.qualScope || "-"}
</Descriptions.Item>
<Descriptions.Item label="学历类型">
{info.educationTypeName || "-"}
</Descriptions.Item>
<Descriptions.Item label="学历层次">
{info.educationLevelName || "-"}
</Descriptions.Item>
<Descriptions.Item label="职称信息" span={2}>
{titleCodeArr.length ? (
<Table
size="small"
bordered
pagination={false}
rowKey={(_, idx) => idx}
dataSource={titleCodeArr}
columns={[
{ title: "行业", dataIndex: "industryName", width: 150 },
{ title: "职称", dataIndex: "titleName", width: 100 },
{
title: "证明材料",
dataIndex: "proveUrlList",
render: renderFileList,
},
]}
/>
) : (
"-"
)}
</Descriptions.Item>
<Descriptions.Item label="是否注册安全工程师">
{REGISTER_ENGINEER_MAP[info.registerEngineerFlag] ?? "-"}
</Descriptions.Item>
<Descriptions.Item label="是否技术负责人">
{info.technicalDirectorFlag === true ? "是" : info.technicalDirectorFlag === false ? "否" : "-"}
</Descriptions.Item>
<Descriptions.Item label="是否过程控制负责人">
{info.processControlLeaderFlag === true ? "是" : info.processControlLeaderFlag === false ? "否" : "-"}
</Descriptions.Item>
<Descriptions.Item label="身份证号">
{info.idCardNo}
</Descriptions.Item>
<Descriptions.Item label="现住地址" span={2}>
{info.currentAddress}
</Descriptions.Item>
<Descriptions.Item label="办公地址" span={2}>
{info.officeAddress}
</Descriptions.Item>
<Descriptions.Item label="毕业院校">
{info.graduateSchool}
</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}>
{proofMaterialUrl.length
? proofMaterialUrl.map((item) => {
const isImage =
/\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i.test(item.url);
return (
<div key={item.id}>
{isImage ? (
<Image
src={item.url}
style={{ maxWidth: 200, cursor: "pointer" }}
preview={{
mask: item.fileName || item.name || "预览",
}}
/>
) : (
<Button
type="link"
onClick={() => window.open(item.url)}
>
{item.fileName || item.name || item.url}
</Button>
)}
</div>
);
})
: "-"}
</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",ellipsis: true },
{ title: "证书编号", dataIndex: "certNo",ellipsis: true },
{ title: "证书类别", dataIndex: "certTypeName",ellipsis: true },
{ title: "发证机关", dataIndex: "issueOrg",ellipsis: true },
{ 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}
destroyOnHidden
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.certTypeName || "-"}
</Descriptions.Item>
<Descriptions.Item label="发证机关">
{certPreviewInfo.issueOrg || "-"}
</Descriptions.Item>
<Descriptions.Item label="有效开始日期">
{certPreviewInfo.validStartDate || "-"}
</Descriptions.Item>
<Descriptions.Item label="有效结束日期">
{certPreviewInfo.validEndDate || "-"}
</Descriptions.Item>
<Descriptions.Item label="查看证书">
{certPreviewInfo.certAttachmentUrl &&
certPreviewInfo.certAttachmentUrl.split(",").map((item) => {
const isImage = /\.(png|jpe?g|gif|bmp|webp|svg)(\?|#|$)/i.test(item);
return (
<div key={item} style={{ marginBottom: 4 }}>
{isImage ? (
<Image src={item} width={100} style={{ cursor: "pointer" }} />
) : (
<a onClick={() => window.open(item)}>{item}</a>
)}
</div>
);
})}
</Descriptions.Item>
</Descriptions>
</div>
)}
</Modal>
</>
);
}