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

394 lines
14 KiB
JavaScript
Raw Normal View History

2026-07-08 14:44:41 +08:00
import { Button, Descriptions, Image, Modal, Table, Upload } from "antd";
import { useEffect, useRef, useState } from "react";
2026-07-08 14:44:41 +08:00
import FilePreviewModal, {
FilePreviewContent,
} from "~/components/FilePreviewModal";
import { resolvePreviewSrc } from "~/components/CertPreviewImg";
2026-07-08 14:44:41 +08:00
import {
fetchStaffCertDetail,
fetchStaffCertListByPersonnelId,
} from "~/api/staffCertificate";
2026-07-07 17:13:33 +08:00
import { fetchOrgPersonnelDetail } from "~/utils/qualFiling/personnelHelper";
2026-07-15 16:13:07 +08:00
import { QUALIFICATION_INDUSTRY_OPTIONS_MAP, TITLE_LEVEL_MAP } from "~/enumerate/enterpriseOptions";
2026-06-26 17:28:06 +08:00
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);
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-07-07 09:07:19 +08:00
const loadCertList = requestCertListRef.current;
2026-06-26 17:28:06 +08:00
Promise.all([
2026-07-08 14:44:41 +08:00
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) => {
2026-07-08 14:44:41 +08:00
console.warn("[StaffViewModal] load certificates failed:", err);
return [];
})
2026-06-26 17:28:06 +08:00
: Promise.resolve([]),
2026-07-08 14:44:41 +08:00
])
.then(([detailRes, certList]) => {
if (!cancelled) {
setInfo(detailRes?.data || {});
setCertificates(Array.isArray(certList) ? certList : []);
}
})
.finally(() => {
if (!cancelled) {
setDetailLoading(false);
}
});
2026-06-26 17:28:06 +08:00
return () => {
cancelled = true;
};
}, [open, currentId]);
2026-06-26 17:28:06 +08:00
const openCertPreview = async (record) => {
setCertPreview(record);
setCertPreviewInfo(null);
if (typeof requestCertDetailsRef.current !== "function") {
2026-06-26 17:28:06 +08:00
setCertPreviewInfo(record);
return;
}
setCertPreviewLoading(true);
try {
2026-07-07 09:07:19 +08:00
const res = await requestCertDetailsRef.current({ id: record.id });
2026-06-26 17:28:06 +08:00
setCertPreviewInfo(res?.data || record);
2026-07-07 09:07:19 +08:00
} catch (err) {
console.warn("[StaffViewModal] load cert detail failed:", err);
setCertPreviewInfo(record);
} finally {
2026-06-26 17:28:06 +08:00
setCertPreviewLoading(false);
}
};
2026-07-08 14:44:41 +08:00
const proofMaterialUrl = info.proofMaterialUrl
? JSON.parse(info.proofMaterialUrl)
: [];
2026-07-07 15:27:25 +08:00
2026-07-31 16:59:40 +08:00
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>
);
});
};
2026-06-26 17:28:06 +08:00
const certPreviewFiles = certPreviewInfo?.certImgs?.length
? certPreviewInfo.certImgs
: certPreviewInfo?.certImgFiles || [];
const certPreviewUrl = resolvePreviewSrc(certPreviewFiles[0]);
2026-07-08 14:44:41 +08:00
const certPreviewName =
certPreviewFiles[0]?.fileName ||
certPreviewFiles[0]?.name ||
certPreviewInfo?.certName;
2026-06-26 17:28:06 +08:00
return (
<>
<Modal
open={open}
2026-07-06 18:06:49 +08:00
destroyOnHidden
2026-06-26 17:28:06 +08:00
title="查看"
width={900}
loading={detailLoading}
cancelText="关闭"
okButtonProps={{ style: { display: "none" } }}
onCancel={onCancel}
>
<Descriptions bordered column={2} labelStyle={{ width: 120 }}>
2026-07-14 18:31:33 +08:00
<Descriptions.Item label="姓名">{info.userName}</Descriptions.Item>
2026-07-08 14:44:41 +08:00
<Descriptions.Item label="性别">
{GENDER_MAP[info.genderCode]}
</Descriptions.Item>
<Descriptions.Item label="出生日期">
{info.birthDate}
</Descriptions.Item>
2026-07-10 16:37:58 +08:00
<Descriptions.Item label="参加工作时间">
{info.joinWorkDate}
</Descriptions.Item>
2026-06-26 17:28:06 +08:00
<Descriptions.Item label="账号">{info.account}</Descriptions.Item>
<Descriptions.Item label="部门">{info.deptName}</Descriptions.Item>
2026-07-07 15:27:25 +08:00
<Descriptions.Item label="岗位">{info.postName}</Descriptions.Item>
2026-07-08 14:44:41 +08:00
<Descriptions.Item label="资质范围">
2026-07-15 16:13:07 +08:00
{QUALIFICATION_INDUSTRY_OPTIONS_MAP[info.qualScope] || info.qualScope || "-"}
2026-07-08 14:44:41 +08:00
</Descriptions.Item>
<Descriptions.Item label="学历类型">
{info.educationTypeName || "-"}
</Descriptions.Item>
<Descriptions.Item label="学历层次">
{info.educationLevelName || "-"}
</Descriptions.Item>
2026-07-31 16:59:40 +08:00
<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,
},
]}
/>
) : (
"-"
)}
2026-07-08 14:44:41 +08:00
</Descriptions.Item>
2026-06-26 17:28:06 +08:00
<Descriptions.Item label="是否注册安全工程师">
{REGISTER_ENGINEER_MAP[info.registerEngineerFlag] ?? "-"}
</Descriptions.Item>
2026-07-15 14:26:05 +08:00
<Descriptions.Item label="是否技术负责人">
{info.technicalDirectorFlag === true ? "是" : info.technicalDirectorFlag === false ? "否" : "-"}
</Descriptions.Item>
<Descriptions.Item label="是否过程控制负责人">
{info.processControlLeaderFlag === true ? "是" : info.processControlLeaderFlag === false ? "否" : "-"}
</Descriptions.Item>
2026-07-08 14:44:41 +08:00
<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>
2026-07-31 16:59:40 +08:00
2026-07-08 14:44:41 +08:00
<Descriptions.Item
label="出版学术专著、专利、获奖、发表学术论文等"
span={2}
>
2026-06-26 17:28:06 +08:00
{info.publications || "-"}
</Descriptions.Item>
<Descriptions.Item label="自我申报的专业能力及认定方式" span={2}>
{info.abilityDeclaration || "-"}
</Descriptions.Item>
<Descriptions.Item label="主要学习工作经历" span={2}>
{info.workExperience || "-"}
</Descriptions.Item>
2026-07-31 16:59:40 +08:00
<Descriptions.Item label="学历证明材料" span={2}>
2026-07-08 14:44:41 +08:00
{proofMaterialUrl.length
? proofMaterialUrl.map((item) => {
2026-07-09 09:23:22 +08:00
const isImage =
/\.(jpg|jpeg|png|gif|webp|bmp|svg)(\?.*)?$/i.test(item.url);
2026-07-08 14:44:41 +08:00
return (
<div key={item.id}>
{isImage ? (
<Image
src={item.url}
style={{ maxWidth: 200, cursor: "pointer" }}
2026-07-09 09:23:22 +08:00
preview={{
mask: item.fileName || item.name || "预览",
}}
2026-07-08 14:44:41 +08:00
/>
) : (
2026-07-09 09:23:22 +08:00
<Button
type="link"
onClick={() => window.open(item.url)}
>
2026-07-08 14:44:41 +08:00
{item.fileName || item.name || item.url}
</Button>
)}
</div>
);
})
: "-"}
2026-06-26 17:28:06 +08:00
</Descriptions.Item>
</Descriptions>
2026-07-08 14:44:41 +08:00
<div style={{ marginTop: 16, marginBottom: 8, fontWeight: 600 }}>
证照信息
</div>
2026-06-26 17:28:06 +08:00
<Table
size="small"
bordered
rowKey="id"
pagination={false}
dataSource={certificates}
locale={{ emptyText: "暂无证照信息" }}
columns={[
2026-07-16 18:14:16 +08:00
{ title: "证照名称", dataIndex: "certName",ellipsis: true },
{ title: "证书编号", dataIndex: "certNo",ellipsis: true },
{ title: "证书类别", dataIndex: "certTypeName",ellipsis: true },
{ title: "发证机关", dataIndex: "issueOrg",ellipsis: true },
2026-06-26 17:28:06 +08:00
{ title: "有效开始日期", dataIndex: "validStartDate", width: 120 },
{ title: "有效结束日期", dataIndex: "validEndDate", width: 120 },
{
title: "操作",
width: 90,
render: (_, record) => (
2026-07-08 14:44:41 +08:00
<Button
type="link"
size="small"
onClick={() => openCertPreview(record)}
>
查看证书
</Button>
2026-06-26 17:28:06 +08:00
),
},
]}
/>
</Modal>
<FilePreviewModal
open={!!attachmentPreview}
title="附件预览"
fileName={attachmentPreview?.fileName}
url={attachmentPreview?.url}
onCancel={() => setAttachmentPreview(null)}
/>
<Modal
open={!!certPreview}
2026-07-06 18:06:49 +08:00
destroyOnHidden
2026-06-26 17:28:06 +08:00
title="证书预览"
width={720}
loading={certPreviewLoading}
cancelText="关闭"
okText={certPreviewUrl ? "新窗口打开" : "关闭"}
2026-07-08 14:44:41 +08:00
okButtonProps={{
style: certPreviewUrl ? undefined : { display: "none" },
}}
2026-06-26 17:28:06 +08:00
onCancel={() => {
setCertPreview(null);
setCertPreviewInfo(null);
}}
onOk={() => {
if (certPreviewUrl) {
window.open(certPreviewUrl, "_blank");
}
setCertPreview(null);
setCertPreviewInfo(null);
}}
>
{certPreviewInfo && (
<div>
2026-07-08 14:44:41 +08:00
<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="证书类别">
2026-07-09 09:23:22 +08:00
{certPreviewInfo.certTypeName || "-"}
2026-07-08 14:44:41 +08:00
</Descriptions.Item>
<Descriptions.Item label="发证机关">
{certPreviewInfo.issueOrg || "-"}
</Descriptions.Item>
<Descriptions.Item label="有效开始日期">
{certPreviewInfo.validStartDate || "-"}
</Descriptions.Item>
<Descriptions.Item label="有效结束日期">
{certPreviewInfo.validEndDate || "-"}
</Descriptions.Item>
2026-07-09 09:23:22 +08:00
<Descriptions.Item label="查看证书">
{certPreviewInfo.certAttachmentUrl &&
2026-07-15 16:13:07 +08:00
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>
);
})}
2026-07-09 09:23:22 +08:00
</Descriptions.Item>
2026-06-26 17:28:06 +08:00
</Descriptions>
</div>
)}
</Modal>
</>
);
2026-07-08 14:44:41 +08:00
}