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

271 lines
8.8 KiB
JavaScript
Raw Normal View History

import { Button, Modal, message } from "antd";
import { useEffect, useRef, useState } from "react";
import html2canvas from "html2canvas";
import { jsPDF } from "jspdf";
import { fetchOrgPersonnelDetail } from "~/utils/qualFiling/personnelHelper";
import { ABILITY_SOURCE_MAP } from "~/enumerate/enterpriseOptions";
import {
CERT_LEVEL_LABEL_BY_TYPE,
CAPABILITY_MAP,
} from "~/enumerate/constant";
import "./index.less";
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 resumeRef = useRef(null);
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 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 () => {
if (!resumeRef.current) return;
setDownloading(true);
try {
const canvas = await html2canvas(resumeRef.current, {
scale: 2,
useCORS: true,
backgroundColor: "#ffffff",
logging: false,
});
const imgData = canvas.toDataURL("image/jpeg", 0.95);
const pdf = new jsPDF({
orientation: "portrait",
unit: "mm",
format: "a4",
});
const pageWidth = pdf.internal.pageSize.getWidth();
const pageHeight = pdf.internal.pageSize.getHeight();
const margin = 10;
const contentWidth = pageWidth - margin * 2;
const contentHeight = (canvas.height * contentWidth) / canvas.width;
let heightLeft = contentHeight;
let position = margin;
pdf.addImage(imgData, "JPEG", margin, position, contentWidth, contentHeight);
heightLeft -= pageHeight - margin * 2;
while (heightLeft > 0) {
position = margin - (contentHeight - heightLeft);
pdf.addPage();
pdf.addImage(imgData, "JPEG", margin, position, contentWidth, contentHeight);
heightLeft -= pageHeight - margin * 2;
}
const fileName = `安全评价师简历表_${info.userName || currentId || ""}.pdf`;
pdf.save(fileName);
message.success("简历下载成功");
} catch (err) {
console.warn("[StaffResumeModal] download pdf failed:", err);
message.error("简历下载失败");
} finally {
setDownloading(false);
}
};
return (
<Modal
open={open}
destroyOnHidden
title={null}
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 ref={resumeRef} style={{ background: "#fff", padding: 8 }}>
<div className="staff-resume-title">安全评价师简历表</div>
<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>
</tbody>
</table>
</div>
</Modal>
);
}