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

277 lines
9.3 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 { Button, Modal, message } from "antd";
import { useEffect, useRef, useState } from "react";
import { fetchOrgPersonnelDetail } from "~/utils/qualFiling/personnelHelper";
import { fillDocxTemplate } from "~/utils/fillDocxTemplate";
import { ABILITY_SOURCE_MAP } from "~/enumerate/enterpriseOptions";
import {
CERT_LEVEL_LABEL_BY_TYPE,
CAPABILITY_MAP,
} from "~/enumerate/constant";
import "./index.less";
const RESUME_TEMPLATE_URL = "https://test-dragon-yf-pub.oss-cn-hangzhou.aliyuncs.com/jjb/6a87b61be4b063db1579e790.docx";
const GENDER_MAP = { 1: "男", 2: "女" };
const parseFileList = (raw) => {
if (!raw) return [];
if (Array.isArray(raw)) return raw;
try {
const parsed = JSON.parse(raw);
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
};
const maskIdCard = (val) => {
if (!val || String(val).length < 8) return val || "-";
const s = String(val);
return `${s.slice(0, 14)}****`;
};
const maskPhone = (val) => {
if (!val || String(val).length < 7) return val || "-";
const s = String(val);
return `${s.slice(0, 3)}****${s.slice(-4)}`;
};
const display = (val) => {
if (val === 0) return "0";
if (val == null || val === "") return "-";
return val;
};
export default function StaffResumeModal({
open,
currentId,
requestDetails = fetchOrgPersonnelDetail,
onCancel,
}) {
const [info, setInfo] = useState({});
const [detailLoading, setDetailLoading] = useState(false);
const [downloading, setDownloading] = useState(false);
const requestDetailsRef = useRef(requestDetails);
requestDetailsRef.current = requestDetails;
useEffect(() => {
if (!open || !currentId) {
setInfo({});
return;
}
let cancelled = false;
setDetailLoading(true);
Promise.resolve(requestDetailsRef.current({ id: currentId }))
.catch((err) => {
console.warn("[StaffResumeModal] load detail failed:", err);
return { data: {} };
})
.then((detailRes) => {
if (!cancelled) {
setInfo(detailRes?.data || {});
}
})
.finally(() => {
if (!cancelled) {
setDetailLoading(false);
}
});
return () => {
cancelled = true;
};
}, [open, currentId]);
const evalCert = info.evaluatorCert;
const evalLevelMap = CERT_LEVEL_LABEL_BY_TYPE.evaluator;
const evalLevelName = evalLevelMap[evalCert?.certLevel] || "";
const qualificationText =
evalLevelName || evalCert?.certNo
? [evalLevelName, evalCert?.certNo].filter(Boolean).join(" / ")
: "-";
const regCert = info.registeredSafetyEngineerCert;
const regCertFiles = parseFileList(regCert?.certAttachmentUrl);
const isZyEducation = info.educationTypeName === "在职教育";
const educationText = [info.educationTypeName, info.educationLevelName]
.filter(Boolean)
.join("") || "-";
const schoolMajorText = [info.graduateSchool, info.basicDisciplineMajorName]
.filter(Boolean)
.join(" / ") || "-";
const capabilityText = Array.isArray(info.capabilityAssessment)
? info.capabilityAssessment
.map((item) => {
const name =
CAPABILITY_MAP[item.professionalCapabilityCode] ||
item.professionalCapabilityCode ||
"";
const source =
ABILITY_SOURCE_MAP[item.professionalCapabilitySourceCode] ||
item.professionalCapabilitySourceCode ||
"";
if (!name && !source) return "";
return [name, source].filter(Boolean).join(" / ");
})
.filter(Boolean)
.join("") || "-"
: "-";
const handleDownload = async () => {
setDownloading(true);
try {
const data = {
userName: display(info.userName),
gender: GENDER_MAP[info.genderCode] || "-",
birthDate: display(info.birthDate),
currentAddress: display(info.currentAddress),
officeAddress: display(info.officeAddress),
qualificationText,
certNo: display(regCert?.certNo),
idCardNo: maskIdCard(info.idCardNo),
phone: maskPhone(info.account),
// 全日制填 education/schoolMajor在职教育填 ZYeducation/ZYschoolMajor
education: isZyEducation ? "" : info.educationLevelName,
schoolMajor: isZyEducation ? "" : schoolMajorText,
ZYeducation: isZyEducation ? info.educationLevelName : "",
ZYschoolMajor: isZyEducation ? schoolMajorText : "",
publications: info.publications || "无",
capability: capabilityText,
certFiles:
regCertFiles
.map(
(item) =>
item.fileName || item.name || "查看注册安全工程师资格证",
)
.join("\n") || "-",
workExperience: display(info.workExperience),
};
await fillDocxTemplate(
RESUME_TEMPLATE_URL,
data,
`安全评价师简历表_${info.userName || currentId || ""}.docx`,
{ download: true },
);
message.success("简历下载成功");
} catch (err) {
console.warn("[StaffResumeModal] download word failed:", err);
message.error("简历下载失败");
} finally {
setDownloading(false);
}
};
return (
<Modal
open={open}
destroyOnHidden
title={'安全评价师简历表'}
width={860}
loading={detailLoading}
className="staff-resume-modal"
onCancel={onCancel}
footer={[
<Button key="download" type="primary" loading={downloading} onClick={handleDownload}>
下载简历
</Button>,
<Button key="close" onClick={onCancel}>
关闭
</Button>,
]}
>
<div style={{ background: "#fff", padding: 8 }}>
<table className="staff-resume-table">
<tbody>
<tr>
<td className="resume-label">姓名</td>
<td className="resume-value">{display(info.userName)}</td>
<td className="resume-label">性别</td>
<td className="resume-value">
{GENDER_MAP[info.genderCode] || "-"}
</td>
</tr>
<tr>
<td className="resume-label">出生日期</td>
<td className="resume-value">{display(info.birthDate)}</td>
<td className="resume-label">现住址</td>
<td className="resume-value">{display(info.currentAddress)}</td>
</tr>
<tr>
<td className="resume-label">办公地址</td>
<td className="resume-value">{display(info.officeAddress)}</td>
<td className="resume-label">职业资格等级及证书号码</td>
<td className="resume-value">{qualificationText}</td>
</tr>
<tr>
<td className="resume-label">注册安全工程师证书编号</td>
<td className="resume-value">{display(regCert?.certNo)}</td>
<td className="resume-label">身份证件号码</td>
<td className="resume-value">{maskIdCard(info.idCardNo)}</td>
</tr>
<tr>
<td className="resume-label">联系电话</td>
<td className="resume-value">{maskPhone(info.account)}</td>
<td className="resume-label">学历学位</td>
<td className="resume-value">{educationText}</td>
</tr>
<tr>
<td className="resume-label">毕业院校及专业</td>
<td className="resume-value">{schoolMajorText}</td>
<td className="resume-label">
出版学术专著专利科技发明及获奖情况
</td>
<td className="resume-value">
{info.publications ? info.publications : "无"}
</td>
</tr>
<tr>
<td className="resume-label-wide">
自我申报的专业能力及认定方式
</td>
<td className="resume-value-full" colSpan={3}>
{capabilityText}
</td>
</tr>
<tr>
<td className="resume-label-wide">申报专业能力证明材料</td>
<td className="resume-value-full" colSpan={3}>
{regCertFiles.length
? regCertFiles.map((item) => (
<div key={item.uid || item.url}>
<a
className="resume-link"
href={item.url}
target="_blank"
rel="noreferrer"
>
{item.fileName || item.name || "查看注册安全工程师资格证"}
</a>
</div>
))
: "-"}
</td>
</tr>
<tr>
<td className="resume-label-wide">主要学习工作经历</td>
<td className="resume-value-full" colSpan={3}>
{display(info.workExperience)}
</td>
</tr>
<tr>
<td className="resume-label">本人签字</td>
<td className="resume-sign"></td>
<td className="resume-label">从业机构确认</td>
<td className="resume-sign"></td>
</tr>
</tbody>
</table>
</div>
</Modal>
);
}