代码提交-员工信息管理新增功能,查看简历,下载简历
parent
a388bd3401
commit
6f90fc66df
File diff suppressed because it is too large
Load Diff
|
|
@ -33,6 +33,8 @@
|
||||||
"dayjs": "^1.11.7",
|
"dayjs": "^1.11.7",
|
||||||
"echarts": "^6.1.0",
|
"echarts": "^6.1.0",
|
||||||
"history": "^4.10.1",
|
"history": "^4.10.1",
|
||||||
|
"html2canvas": "^1.4.1",
|
||||||
|
"jspdf": "^2.5.2",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|
|
||||||
2171
pnpm-lock.yaml
2171
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,270 @@
|
||||||
|
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,59 @@
|
||||||
|
.staff-resume-modal {
|
||||||
|
.staff-resume-title {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #1f1f1f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.staff-resume-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
table-layout: fixed;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #262626;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
border: 1px solid #bfbfbf;
|
||||||
|
padding: 10px 12px;
|
||||||
|
vertical-align: middle;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-label {
|
||||||
|
width: 18%;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #595959;
|
||||||
|
font-weight: 400;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-value {
|
||||||
|
width: 32%;
|
||||||
|
background: #fff;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-label-wide {
|
||||||
|
width: 22%;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: #595959;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-value-full {
|
||||||
|
background: #fff;
|
||||||
|
text-align: left;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
line-height: 1.7;
|
||||||
|
min-height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.resume-link {
|
||||||
|
color: #1677ff;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -49,6 +49,7 @@ import { getBirthDateFromIdCard } from "~/utils";
|
||||||
import { idCardRule, mobileRule } from "~/utils/validators";
|
import { idCardRule, mobileRule } from "~/utils/validators";
|
||||||
import AttachmentUpload from "~/components/AttachmentUpload";
|
import AttachmentUpload from "~/components/AttachmentUpload";
|
||||||
import StaffViewModal from "~/components/StaffViewModal";
|
import StaffViewModal from "~/components/StaffViewModal";
|
||||||
|
import StaffResumeModal from "~/components/StaffResumeModal";
|
||||||
import "./index.less";
|
import "./index.less";
|
||||||
|
|
||||||
const { router } = tools;
|
const { router } = tools;
|
||||||
|
|
@ -58,6 +59,7 @@ const evalCertLevelMap = CERT_LEVEL_LABEL_BY_TYPE.evaluator;
|
||||||
function PersonnelInfoPage(props) {
|
function PersonnelInfoPage(props) {
|
||||||
const [formModalOpen, setFormModalOpen] = useState(false);
|
const [formModalOpen, setFormModalOpen] = useState(false);
|
||||||
const [viewModalOpen, setViewModalOpen] = useState(false);
|
const [viewModalOpen, setViewModalOpen] = useState(false);
|
||||||
|
const [resumeModalOpen, setResumeModalOpen] = useState(false);
|
||||||
const [importModalOpen, setImportModalOpen] = useState(false);
|
const [importModalOpen, setImportModalOpen] = useState(false);
|
||||||
const [resetPasswordModalOpen, setResetPasswordModalOpen] = useState(false);
|
const [resetPasswordModalOpen, setResetPasswordModalOpen] = useState(false);
|
||||||
const [currentId, setCurrentId] = useState("");
|
const [currentId, setCurrentId] = useState("");
|
||||||
|
|
@ -263,6 +265,16 @@ function PersonnelInfoPage(props) {
|
||||||
>
|
>
|
||||||
查看
|
查看
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="link"
|
||||||
|
size="small"
|
||||||
|
onClick={() => {
|
||||||
|
setCurrentId(record.id);
|
||||||
|
setResumeModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
查看简历
|
||||||
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="link"
|
type="link"
|
||||||
size="small"
|
size="small"
|
||||||
|
|
@ -339,6 +351,16 @@ function PersonnelInfoPage(props) {
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{resumeModalOpen && (
|
||||||
|
<StaffResumeModal
|
||||||
|
open={resumeModalOpen}
|
||||||
|
currentId={currentId}
|
||||||
|
onCancel={() => {
|
||||||
|
setResumeModalOpen(false);
|
||||||
|
setCurrentId("");
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
{importModalOpen && (
|
{importModalOpen && (
|
||||||
<StaffImportModal
|
<StaffImportModal
|
||||||
open={importModalOpen}
|
open={importModalOpen}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue