666 lines
20 KiB
JavaScript
666 lines
20 KiB
JavaScript
import { Permission } from "@cqsjjb/jjb-common-decorator/permission";
|
||
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
||
import { Button, Form, message, Modal, Space } from "antd";
|
||
import { useEffect, useRef, useState } from "react";
|
||
import FormBuilder from "zy-react-library/components/FormBuilder";
|
||
import AddIcon from "zy-react-library/components/Icon/AddIcon";
|
||
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
||
import Search from "zy-react-library/components/Search";
|
||
import DictionarySelect from "zy-react-library/components/Select/Dictionary";
|
||
import PersonnelSelect from "zy-react-library/components/Select/Personnel/Gwj";
|
||
import Table from "zy-react-library/components/Table";
|
||
import TooltipPreviewImg from "zy-react-library/components/TooltipPreviewImg";
|
||
import Upload from "zy-react-library/components/Upload";
|
||
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
|
||
import { UPLOAD_FILE_TYPE_ENUM } from "zy-react-library/enum/uploadFile/gwj";
|
||
import useDeleteFile from "zy-react-library/hooks/useDeleteFile";
|
||
import useGetFile from "zy-react-library/hooks/useGetFile";
|
||
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
|
||
import useGetUserInfo from "zy-react-library/hooks/useGetUserInfo";
|
||
import useTable from "zy-react-library/hooks/useTable";
|
||
import useUploadFile from "zy-react-library/hooks/useUploadFile";
|
||
import { NS_USER_CERTIFICATE } from "~/enumerate/namespace";
|
||
import { useDebounce } from "~/utils";
|
||
import { useLocation } from 'react-router-dom';
|
||
|
||
const JOB_STATUS = {
|
||
1: "在职",
|
||
0: "离职",
|
||
2: "信息变更中",
|
||
3: "未入职",
|
||
4: "实习生",
|
||
5: "实习结束",
|
||
6: "退休",
|
||
7: "劳务派遣",
|
||
8: "劳务派遣结束",
|
||
11: "入职待审核",
|
||
10: "离职待审核",
|
||
};
|
||
|
||
function List({ props, certificatePhotoType, displayType, personnelType, permissionAdd, permissionEdit, permissionView, permissionDel, dictionaryType }) {
|
||
const [addModalOpen, setAddModalOpen] = useState(false);
|
||
const [currentId, setCurrentId] = useState("");
|
||
const queryParams = useGetUrlQuery();
|
||
const { loading: getFileLoading, getFile } = useGetFile();
|
||
const [form] = Form.useForm();
|
||
const { tableProps, getData } = useTable(props["userCertificateList"], {
|
||
form,
|
||
transform: (formData) => {
|
||
return {
|
||
...formData,
|
||
eqCorpinfoId: queryParams["corpinfoId"],
|
||
eqType: personnelType,
|
||
};
|
||
},
|
||
});
|
||
const onDelete = async (row) => {
|
||
props["projectHasUser"]({ eqUserId: row.userId }).then((res) => {
|
||
console.log(res);
|
||
if (res.data && res.data.length > 0) {
|
||
const arr = [];
|
||
const userArr = [];
|
||
|
||
res.data.forEach((item) => {
|
||
arr.push(item.projectName);
|
||
userArr.push(item.userName);
|
||
});
|
||
|
||
Modal.confirm({
|
||
title: "提示",
|
||
content: (
|
||
<div>
|
||
<div>
|
||
【
|
||
{[...new Set(userArr)].join(",")}
|
||
】有正在进行的项目,包含【
|
||
{
|
||
[...new Set(arr)].join(",")
|
||
}
|
||
】确定删除吗?
|
||
</div>
|
||
<div style={{ fontSize: 14, color: "red" }}>删除可能会对正在进行的项目造成异常状态,请谨慎操作。</div>
|
||
</div>
|
||
),
|
||
onOk: () => {
|
||
props["userCertificateRemove"]({
|
||
id: row.id,
|
||
}).then((res) => {
|
||
if (res.success) {
|
||
message.success("删除成功");
|
||
getData();
|
||
}
|
||
});
|
||
},
|
||
});
|
||
}
|
||
else {
|
||
Modal.confirm({
|
||
title: "提示",
|
||
content: "确定删除吗?",
|
||
onOk: () => {
|
||
props["userCertificateRemove"]({
|
||
id: row.id,
|
||
}).then((res) => {
|
||
if (res.success) {
|
||
message.success("删除成功");
|
||
getData();
|
||
}
|
||
});
|
||
},
|
||
});
|
||
}
|
||
});
|
||
};
|
||
const [fileCache, setFileCache] = useState({});
|
||
const [loadingKeys, setLoadingKeys] = useState(new Set());
|
||
const pendingLoadIdsRef = useRef(new Set());
|
||
const requestQueueRef = useRef([]);
|
||
const activeRequestsRef = useRef(0);
|
||
const MAX_CONCURRENT_REQUESTS = 3; // 最大并发请求数
|
||
|
||
const processQueue = () => {
|
||
while (
|
||
requestQueueRef.current.length > 0
|
||
&& activeRequestsRef.current < MAX_CONCURRENT_REQUESTS
|
||
) {
|
||
const { id, resolve, reject } = requestQueueRef.current.shift();
|
||
activeRequestsRef.current++;
|
||
|
||
getFile({
|
||
eqType: UPLOAD_FILE_TYPE_ENUM[certificatePhotoType],
|
||
eqForeignKey: id,
|
||
})
|
||
.then((res) => {
|
||
setFileCache(prev => ({
|
||
...prev,
|
||
[id]: res || [],
|
||
}));
|
||
resolve(res);
|
||
})
|
||
.catch((err) => {
|
||
console.error("图片加载失败:", err);
|
||
reject(err);
|
||
})
|
||
.finally(() => {
|
||
activeRequestsRef.current--;
|
||
setLoadingKeys((prev) => {
|
||
const newSet = new Set(prev);
|
||
newSet.delete(id);
|
||
return newSet;
|
||
});
|
||
processQueue();
|
||
});
|
||
}
|
||
};
|
||
|
||
const loadFileForRecord = (userQualificationinfoId) => {
|
||
if (!userQualificationinfoId)
|
||
return Promise.resolve();
|
||
if (fileCache[userQualificationinfoId])
|
||
return Promise.resolve();
|
||
if (loadingKeys.has(userQualificationinfoId))
|
||
return Promise.resolve();
|
||
if (pendingLoadIdsRef.current.has(userQualificationinfoId))
|
||
return Promise.resolve();
|
||
|
||
pendingLoadIdsRef.current.add(userQualificationinfoId);
|
||
setLoadingKeys(prev => new Set([...prev, userQualificationinfoId]));
|
||
|
||
return new Promise((resolve, reject) => {
|
||
requestQueueRef.current.push({ id: userQualificationinfoId, resolve, reject });
|
||
processQueue();
|
||
}).finally(() => {
|
||
pendingLoadIdsRef.current.delete(userQualificationinfoId);
|
||
});
|
||
};
|
||
|
||
// 清除伪类
|
||
useEffect(() => {
|
||
if (displayType === "View") {
|
||
const style = document.createElement("style");
|
||
style.innerHTML = `
|
||
.search-layout::after {
|
||
content: none !important;
|
||
display: none !important;
|
||
}
|
||
`;
|
||
document.head.appendChild(style);
|
||
// 清理函数,在组件卸载时移除样式
|
||
return () => {
|
||
document.head.removeChild(style);
|
||
};
|
||
}
|
||
}, []);
|
||
|
||
// 缓存数据变化时清空图片缓存
|
||
useEffect(() => {
|
||
if (tableProps.dataSource) {
|
||
const currentIds = new Set(tableProps.dataSource.map(item => item.userCertificateId).filter(Boolean));
|
||
setFileCache((prev) => {
|
||
const newCache = {};
|
||
Object.keys(prev).forEach((id) => {
|
||
if (currentIds.has(id)) {
|
||
newCache[id] = prev[id];
|
||
}
|
||
});
|
||
return newCache;
|
||
});
|
||
}
|
||
}, [tableProps.dataSource]);
|
||
|
||
// 记录已经触发过加载的 ID,避免 render 重复触发
|
||
const triggeredLoadIdsRef = useRef(new Set());
|
||
useEffect(() => {
|
||
if (tableProps.dataSource) {
|
||
tableProps.dataSource.forEach((record) => {
|
||
const id = record.userCertificateId;
|
||
if (id && !triggeredLoadIdsRef.current.has(id)) {
|
||
triggeredLoadIdsRef.current.add(id);
|
||
loadFileForRecord(id);
|
||
}
|
||
});
|
||
}
|
||
// 组件卸载或数据源变化时重置
|
||
return () => {
|
||
triggeredLoadIdsRef.current.clear();
|
||
};
|
||
}, [tableProps.dataSource]);
|
||
|
||
return (
|
||
<PageLayout>
|
||
|
||
<Search
|
||
form={form}
|
||
options={[
|
||
{
|
||
name: "likeUserName",
|
||
label: "姓名",
|
||
},
|
||
{
|
||
name: "likePostName",
|
||
label: "岗位名称",
|
||
},
|
||
]}
|
||
onFinish={getData}
|
||
/>
|
||
<Table
|
||
loding={getFileLoading}
|
||
options={displayType !== "View"}
|
||
toolBarRender={() => (
|
||
<>
|
||
{
|
||
(displayType !== "View" && props.permission(permissionAdd))
|
||
&& (
|
||
<Button
|
||
type="primary"
|
||
icon={<AddIcon />}
|
||
onClick={() => {
|
||
setAddModalOpen(true);
|
||
}}
|
||
>
|
||
新增
|
||
</Button>
|
||
)
|
||
}
|
||
</>
|
||
)}
|
||
columns={[
|
||
{
|
||
title: "姓名",
|
||
dataIndex: "name",
|
||
},
|
||
{
|
||
title: "证书名称",
|
||
dataIndex: "certificateName",
|
||
},
|
||
{
|
||
title: "证书编号",
|
||
dataIndex: "certificateCode",
|
||
},
|
||
{
|
||
title: "岗位名称",
|
||
dataIndex: "postName",
|
||
},
|
||
{
|
||
title: "有效期至",
|
||
dataIndex: "certificateNo",
|
||
width: 280,
|
||
render: (_, record) =>
|
||
<div>{`${record.certificateDateStart ?? ""} - ${record.certificateDateEnd ?? ""}`}</div>,
|
||
},
|
||
{
|
||
title: "就职状态",
|
||
dataIndex: "certificateNo",
|
||
render: (_, record) => (
|
||
<div>{JOB_STATUS[record.employmentStatus] || "-"}</div>
|
||
|
||
),
|
||
},
|
||
{
|
||
title: "图片",
|
||
dataIndex: "name",
|
||
render: (_, record) => {
|
||
const id = record.userCertificateId;
|
||
const files = fileCache[id] || [];
|
||
|
||
if (!files.length)
|
||
return <span>无</span>;
|
||
return <TooltipPreviewImg files={files} />;
|
||
},
|
||
},
|
||
{
|
||
title: "操作",
|
||
width: 200,
|
||
render: (_, record) => (
|
||
<Space>
|
||
{
|
||
props.permission(permissionView)
|
||
&& (
|
||
<Button
|
||
type="link"
|
||
onClick={() => props.history.push(`./View?id=${record.id}`)}
|
||
>
|
||
查看
|
||
</Button>
|
||
|
||
)
|
||
}
|
||
{
|
||
(displayType !== "View" && props.permission(permissionEdit))
|
||
&& (
|
||
<Button
|
||
type="link"
|
||
onClick={() => {
|
||
setAddModalOpen(true);
|
||
setCurrentId(record.id);
|
||
}}
|
||
>
|
||
编辑
|
||
</Button>
|
||
)
|
||
}
|
||
|
||
{
|
||
(displayType !== "View" && props.permission(permissionDel))
|
||
&& (
|
||
<Button
|
||
danger
|
||
type="link"
|
||
onClick={() => onDelete(record)}
|
||
>
|
||
删除
|
||
</Button>
|
||
)
|
||
}
|
||
</Space>
|
||
),
|
||
},
|
||
]}
|
||
{...tableProps}
|
||
/>
|
||
|
||
{addModalOpen && (
|
||
<AddModal
|
||
open={addModalOpen}
|
||
loding={props.userCertificate.userCertificateLoading}
|
||
getData={getData}
|
||
currentId={currentId}
|
||
requestAdd={props["userCertificateAdd"]}
|
||
requestEdit={props["userCertificateEdit"]}
|
||
requestDetails={props["userCertificateInfo"]}
|
||
userCertificateIsExistCertNo={props["userCertificateIsExistCertNo"]}
|
||
getUserlistAll={props["getUserlistAll"]}
|
||
certificatePhotoType={certificatePhotoType}
|
||
personnelType={personnelType}
|
||
dictionaryType={dictionaryType}
|
||
onCancel={() => {
|
||
setAddModalOpen(false);
|
||
setCurrentId("");
|
||
}}
|
||
onSuccess={(userQualificationinfoId) => {
|
||
// 清除该记录的图片缓存,强制下次 render 时重新加载
|
||
setFileCache((prev) => {
|
||
const newCache = { ...prev };
|
||
delete newCache[userQualificationinfoId];
|
||
return newCache;
|
||
});
|
||
}}
|
||
/>
|
||
)}
|
||
|
||
</PageLayout>
|
||
);
|
||
}
|
||
function AddModalComponent(props) {
|
||
const [form] = Form.useForm();
|
||
const [userQualificationinfoId, setUserQualificationinfoId] = useState("");
|
||
const [userObj, setUserObj] = useState({});
|
||
const { loading: deleteFileLoading, deleteFile } = useDeleteFile();
|
||
const { loading: uploadFileLoading, uploadFile } = useUploadFile();
|
||
const { loading: getFileLoading, getFile } = useGetFile();
|
||
const { getUserInfo } = useGetUserInfo();
|
||
const [deleteCardImageFiles, setDeleteCardImageFiles] = useState([]);
|
||
const [CertificateCodeValue, setPertificateCodeValue] = useState(null);
|
||
const debouncedCertificateCodeValue = useDebounce(CertificateCodeValue, 100);
|
||
const [personnelData, setPersonnelData] = useState([]);
|
||
const [isSubmit, setIsSubmit] = useState(true);
|
||
const location = useLocation();
|
||
const getUserInfoFun = async () => {
|
||
const userInfo = await getUserInfo();
|
||
setUserObj(userInfo);
|
||
};
|
||
|
||
const getUserlistFun = async () => {
|
||
props.getUserlistAll({menuPath:"/certificate"+location.pathname}).then(res => {
|
||
if(res.data) {
|
||
res.data.forEach(item => {
|
||
item.bianma=item.id;
|
||
})
|
||
setPersonnelData(res.data)
|
||
}
|
||
})
|
||
};
|
||
|
||
useEffect(() => {
|
||
if (props.currentId) {
|
||
const fetchData = async () => {
|
||
const { data } = await props.requestDetails({
|
||
id: props.currentId,
|
||
});
|
||
const certificateImgs = await getFile({
|
||
eqType: UPLOAD_FILE_TYPE_ENUM[props.certificatePhotoType],
|
||
eqForeignKey: data.userCertificateId,
|
||
});
|
||
data.certificateDate = [data.certificateDateStart, data.certificateDateEnd];
|
||
data.certificateImgs = certificateImgs;
|
||
form.setFieldsValue(data);
|
||
setUserQualificationinfoId(data.userCertificateId);
|
||
};
|
||
fetchData();
|
||
}
|
||
getUserInfoFun();
|
||
getUserlistFun()
|
||
}, [props.currentId]);
|
||
const onCancel = () => {
|
||
form.resetFields();
|
||
props.onCancel();
|
||
};
|
||
const onSubmit = async (values) => {
|
||
if (!isSubmit) {
|
||
form.setFields([
|
||
{
|
||
name: "certificateCode",
|
||
errors: ["证书编号重复"],
|
||
},
|
||
]);
|
||
return;
|
||
}
|
||
if (values.certificateImgs.length !== 2) {
|
||
message.error("证照照片必须上传正反面两张");
|
||
return;
|
||
}
|
||
|
||
const maxSizeInBytes = 10 * 1024 * 1024;
|
||
|
||
const isOverSize = values.certificateImgs.some(file => file.size > maxSizeInBytes);
|
||
|
||
if (isOverSize) {
|
||
message.error("选择的图片不能大于10MB");
|
||
return;
|
||
}
|
||
|
||
await deleteFile({
|
||
single: false,
|
||
files: deleteCardImageFiles,
|
||
});
|
||
const { id } = await uploadFile({
|
||
single: false,
|
||
files: values.certificateImgs,
|
||
params: {
|
||
type: UPLOAD_FILE_TYPE_ENUM[props.certificatePhotoType],
|
||
foreignKey: userQualificationinfoId,
|
||
},
|
||
});
|
||
values.certificateDateStart = values.certificateDate[0];
|
||
values.certificateDateEnd = values.certificateDate[1];
|
||
values.type = props.personnelType;
|
||
const personnelTypeMap = {
|
||
tezhongzuoye: "特种作业人员",
|
||
tzsbczry: "特种设备操作人员",
|
||
zyfzr: "主要负责人",
|
||
aqscglry: "安全生产管理人员",
|
||
};
|
||
|
||
values.typeName = personnelTypeMap[props.personnelType];
|
||
if (props.currentId) {
|
||
values.id = props.currentId;
|
||
values.userCertificateId = userQualificationinfoId;
|
||
await props.requestEdit(values).then((res) => {
|
||
if (res.success) {
|
||
onCancel();
|
||
props.getData();
|
||
props.onSuccess(userQualificationinfoId);
|
||
message.success("编辑成功");
|
||
}
|
||
});
|
||
}
|
||
else {
|
||
values.userCertificateId = id;
|
||
await props.requestAdd(values).then((res) => {
|
||
if (res.success) {
|
||
onCancel();
|
||
props.getData();
|
||
props.onSuccess(userQualificationinfoId);
|
||
message.success("新增成功");
|
||
}
|
||
});
|
||
}
|
||
};
|
||
// 校验重复
|
||
useEffect(() => {
|
||
if (!debouncedCertificateCodeValue) {
|
||
form.setFields([
|
||
{
|
||
name: "certificateCode",
|
||
errors: [],
|
||
},
|
||
]);
|
||
return;
|
||
}
|
||
props["userCertificateIsExistCertNo"]({
|
||
certNo: debouncedCertificateCodeValue,
|
||
id: props.currentId ?? "",
|
||
}).then((res) => {
|
||
if (res.data) {
|
||
form.setFields([
|
||
{
|
||
name: "certificateCode",
|
||
errors: ["证书编号重复"],
|
||
},
|
||
]);
|
||
setIsSubmit(false);
|
||
}
|
||
else {
|
||
setIsSubmit(true);
|
||
}
|
||
});
|
||
}, [debouncedCertificateCodeValue]);
|
||
const onValuesChange = (changedValues) => {
|
||
if ("certificateCode" in changedValues) {
|
||
setPertificateCodeValue(changedValues.certificateCode ?? "");
|
||
}
|
||
};
|
||
return (
|
||
<Modal
|
||
open={props.open}
|
||
maskClosable={false}
|
||
title={props.currentId ? "编辑" : "新增"}
|
||
width={800}
|
||
|
||
confirmLoading={
|
||
deleteFileLoading || uploadFileLoading || getFileLoading || props.loding
|
||
}
|
||
onOk={form.submit}
|
||
onCancel={onCancel}
|
||
>
|
||
<FormBuilder
|
||
form={form}
|
||
onValuesChange={onValuesChange}
|
||
span={24}
|
||
values={{
|
||
securityFlag: 0,
|
||
}}
|
||
options={[
|
||
{
|
||
name: "userId",
|
||
label: "选择人员",
|
||
render: FORM_ITEM_RENDER_ENUM.SELECT,
|
||
items:personnelData
|
||
},
|
||
{ name: "userName", label: "人员名称", onlyForLabel: true },
|
||
{
|
||
name: "certificateName",
|
||
label: "证书名称",
|
||
},
|
||
{
|
||
name: "certificateCode",
|
||
label: "证书编号",
|
||
},
|
||
|
||
{
|
||
name: "postId",
|
||
label: "岗位名称",
|
||
render: (
|
||
<DictionarySelect
|
||
dictValue={props.dictionaryType}
|
||
onGetLabel={label => form.setFieldValue("postName", label)}
|
||
/>
|
||
),
|
||
},
|
||
{ name: "postName", label: "岗位名称", onlyForLabel: true },
|
||
{
|
||
name: "issuingAuthority",
|
||
label: "发证机构",
|
||
},
|
||
|
||
{
|
||
name: "dateIssue",
|
||
label: "发证日期",
|
||
render: FORM_ITEM_RENDER_ENUM.DATE,
|
||
},
|
||
{
|
||
name: "certificateDate",
|
||
label: "有效期",
|
||
render: FORM_ITEM_RENDER_ENUM.DATE_RANGE,
|
||
rules: [
|
||
{
|
||
validator: (_, value) => {
|
||
const allEmptyStrings = Array.isArray(value) && value.every(item => item === "");
|
||
if (allEmptyStrings) {
|
||
return Promise.reject(new Error("请选择有效期"));
|
||
}
|
||
return Promise.resolve();
|
||
},
|
||
},
|
||
],
|
||
},
|
||
{
|
||
name: "certificateImgs",
|
||
label: "证照照片",
|
||
render: (
|
||
<Upload
|
||
|
||
maxCount={2}
|
||
onGetRemoveFile={(file) => {
|
||
setDeleteCardImageFiles([...deleteCardImageFiles, file]);
|
||
}}
|
||
tipContent={(
|
||
<div
|
||
style={{
|
||
lineHeight: 1.6,
|
||
color: "red",
|
||
fontSize: 12,
|
||
}}
|
||
>
|
||
<div>
|
||
温馨提示:用户要上传证照照片正反面(证照照片数量是2张)单张大小不超过10MB,支持jpg、jpeg、png格式。
|
||
</div>
|
||
</div>
|
||
)}
|
||
/>
|
||
),
|
||
},
|
||
]}
|
||
labelCol={{
|
||
span: 10,
|
||
}}
|
||
showActionButtons={false}
|
||
onFinish={onSubmit}
|
||
/>
|
||
</Modal>
|
||
);
|
||
}
|
||
const AddModal = AddModalComponent;
|
||
|
||
export default Connect([NS_USER_CERTIFICATE], true)(Permission(List));
|