dev_1.0.1
tangjie 2026-07-07 09:07:19 +08:00
parent 0c9e2d62d3
commit 950980d48b
10 changed files with 927 additions and 741 deletions

View File

@ -1,55 +1,53 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import {
fromPageResponse,
fromSingleResponse,
fromStaffCertForm,
toPageQuery,
toStaffCertForm,
} from "../enterpriseInfo/adapter";
import { apiGet, apiPost, apiPostDelete, safeAction, safePageResult } from "../enterpriseInfo/http";
import { Get } from "@cqsjjb/jjb-common-lib/http";
export const staffCertificateList = declareRequest("staffCertificateLoading", safePageResult(async (params) => {
const query = toPageQuery(params, {
eqStaffId: "personnelId",
likeCertNo: "certNo",
});
const res = await apiGet("/safetyEval/org-personnel-cert/page", query);
return fromPageResponse(res, toStaffCertForm);
}));
export const staffCertificateList = declareRequest(
"staffCertificateLoading",
"Get > /safetyEval/org-personnel-cert/page",
"staffCertificateList: [] | res.data || [] & staffCertificateTotal: 0 | res.data?.total || 0",
);
export const staffCertificateInfo = declareRequest("staffCertificateLoading", safeAction(async (params) => {
const res = await apiGet("/safetyEval/org-personnel-cert/get", { id: params.id });
return fromSingleResponse(res, toStaffCertForm);
}));
export const staffCertificateInfo = declareRequest(
"staffCertificateLoading",
"Get > /safetyEval/org-personnel-cert/get",
"staffCertificateDetail: {} | res.data || {}",
);
export const staffCertificateAdd = declareRequest("staffCertificateLoading", safeAction(async (values) => {
const res = await apiPost("/safetyEval/org-personnel-cert/save", fromStaffCertForm(values));
return fromSingleResponse(res, toStaffCertForm);
}));
export const staffCertificateAdd = declareRequest(
"staffCertificateLoading",
"Post > @/safetyEval/org-personnel-cert/save",
);
export const staffCertificateEdit = declareRequest("staffCertificateLoading", safeAction(async (values) => {
const res = await apiPost("/safetyEval/org-personnel-cert/modify", fromStaffCertForm(values));
return fromSingleResponse(res, toStaffCertForm);
}));
export const staffCertificateEdit = declareRequest(
"staffCertificateLoading",
"Post > @/safetyEval/org-personnel-cert/modify",
);
export const staffCertificateRemove = declareRequest("staffCertificateLoading", safeAction(async ({ id }) => {
return apiPostDelete("/safetyEval/org-personnel-cert/delete", id);
}));
export const staffCertificateRemove = declareRequest(
"staffCertificateLoading",
"Post > @/safetyEval/org-personnel-cert/delete",
);
/** StaffViewModal 直接调用的辅助函数 — 获取人员下所有证书列表 */
export async function fetchStaffCertListByPersonnelId(personnelId) {
const query = toPageQuery({
pageIndex: 1,
pageSize: 999,
eqStaffId: personnelId,
}, {
eqStaffId: "personnelId",
});
const res = await apiGet("/safetyEval/org-personnel-cert/page", query);
const page = fromPageResponse(res, toStaffCertForm);
return page?.data || [];
try {
const res = await Get("/safetyEval/org-personnel-cert/page", {
current: 1,
size: 999,
personnelId,
});
return res?.data || [];
} catch {
return [];
}
}
/** StaffViewModal 直接调用的辅助函数 — 获取证书详情 */
export async function fetchStaffCertDetail({ id }) {
const res = await apiGet("/safetyEval/org-personnel-cert/get", { id });
return fromSingleResponse(res, toStaffCertForm);
}
try {
const res = await Get("/safetyEval/org-personnel-cert/get", { id });
return res?.data || null;
} catch {
return null;
}
}

View File

@ -1,42 +1,29 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import {
fromPageResponse,
fromResignAuditForm,
fromSingleResponse,
toChangeLogForm,
toPageQuery,
toResignApplyForm,
toStaffChangeSummary,
} from "../enterpriseInfo/adapter";
import { apiGet, apiPost, apiPostDelete, safeAction, safePageResult } from "../enterpriseInfo/http";
export const staffChangeLogStaffList = declareRequest("staffChangeLogLoading", safePageResult(async (params) => {
const query = toPageQuery(params, {
likeAccount: "account",
likeStaffName: "userName",
employmentStatus: "employmentStatusCode",
resignAuditStatus: "resignAuditStatus",
});
const res = await apiGet("/safetyEval/org-personnel/page", query);
return fromPageResponse(res, toStaffChangeSummary);
}));
export const staffChangeLogStaffList = declareRequest(
"staffChangeLogLoading",
"Get > /safetyEval/org-personnel/page",
"staffChangeLogStaffList: [] | res.data || [] & staffChangeLogStaffTotal: 0 | res.total || 0",
);
export const staffChangeLogList = declareRequest("staffChangeLogLoading", safePageResult(async (params) => {
const query = toPageQuery(params, { eqStaffId: "personnelId" });
const res = await apiGet("/safetyEval/org-personnel-change/page", query);
return fromPageResponse(res, toChangeLogForm);
}));
export const staffChangeLogList = declareRequest(
"staffChangeLogLoading",
"Get > /safetyEval/org-personnel-change/page",
"staffChangeLogChangeList: [] | res.data || [] & staffChangeLogChangeTotal: 0 | res.total || 0",
);
export const staffChangeLogRemove = declareRequest("staffChangeLogLoading", safeAction(async ({ id }) => {
return apiPostDelete("/safetyEval/org-personnel-change/delete", id);
}));
export const staffChangeLogRemove = declareRequest(
"staffChangeLogLoading",
"Post > @/safetyEval/org-personnel-change/delete",
);
export const staffResignationAudit = declareRequest("staffChangeLogLoading", safeAction(async (params) => {
const res = await apiPost("/safetyEval/org-resign-apply/modify", fromResignAuditForm(params));
return fromSingleResponse(res, toResignApplyForm);
}));
export const staffResignationAudit = declareRequest(
"staffChangeLogLoading",
"Post > @/safetyEval/org-resign-apply/modify",
);
export const staffResignationInfo = declareRequest("staffChangeLogLoading", safeAction(async (params) => {
const res = await apiGet("/safetyEval/org-resign-apply/get", { id: params.id });
return fromSingleResponse(res, toResignApplyForm);
}));
export const staffResignationInfo = declareRequest(
"staffChangeLogLoading",
"Get > /safetyEval/org-resign-apply/get",
"staffResignationDetail: {} | res.data || {}",
);

View File

@ -1,43 +1,33 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import {
fromPageResponse,
fromSingleResponse,
fromStaffForm,
toPageQuery,
toStaffForm,
} from "../enterpriseInfo/adapter";
import { apiGet, apiPost, apiPostDelete, safeAction, safePageResult } from "../enterpriseInfo/http";
export const staffInfoList = declareRequest("staffInfoLoading", safePageResult(async (params) => {
const query = toPageQuery(params, {
likeStaffName: "userName",
eqDeptId: "deptId",
eqPositionId: "postId",
});
const res = await apiGet("/safetyEval/org-personnel/page", query);
return fromPageResponse(res, toStaffForm);
}));
export const staffInfoList = declareRequest(
"staffInfoLoading",
"Get > /safetyEval/org-personnel/page",
"staffInfoList: [] | res.data || [] & staffInfoTotal: 0 | res.data?.total || 0",
);
export const staffInfoGet = declareRequest("staffInfoLoading", safeAction(async (params) => {
const res = await apiGet("/safetyEval/org-personnel/get", { id: params.id });
return fromSingleResponse(res, toStaffForm);
}));
export const staffInfoGet = declareRequest(
"staffInfoLoading",
"Get > /safetyEval/org-personnel/get",
"staffInfoDetail: {} | res.data || {}",
);
export const staffInfoAdd = declareRequest("staffInfoLoading", safeAction(async (values) => {
const res = await apiPost("/safetyEval/org-personnel/save", fromStaffForm(values));
return fromSingleResponse(res, toStaffForm);
}));
export const staffInfoAdd = declareRequest(
"staffInfoLoading",
"Post > @/safetyEval/org-personnel/save",
);
export const staffInfoEdit = declareRequest("staffInfoLoading", safeAction(async (values) => {
const res = await apiPost("/safetyEval/org-personnel/modify", fromStaffForm(values));
return fromSingleResponse(res, toStaffForm);
}));
export const staffInfoEdit = declareRequest(
"staffInfoLoading",
"Post > @/safetyEval/org-personnel/modify",
);
export const staffInfoRemove = declareRequest("staffInfoLoading", safeAction(async ({ id }) => {
return apiPostDelete("/safetyEval/org-personnel/delete", id);
}));
export const staffInfoRemove = declareRequest(
"staffInfoLoading",
"Post > @/safetyEval/org-personnel/delete",
);
/** 后端暂未提供重置密码接口 */
export const staffInfoResetPassword = declareRequest("staffInfoLoading", safeAction(async ({ id }) => {
return apiPost(`/safetyEval/org-personnel/reset-password?id=${id}`);
}));
export const staffInfoResetPassword = declareRequest(
"staffInfoLoading",
"Get > /safetyEval/org-personnel/reset-password",
);

View File

@ -1,28 +1,18 @@
import { declareRequest } from "@cqsjjb/jjb-dva-runtime";
import {
fromPageResponse,
fromResignApplyForm,
fromSingleResponse,
toPageQuery,
toResignApplyForm,
} from "../enterpriseInfo/adapter";
import { apiGet, apiPost, safeAction, safePageResult } from "../enterpriseInfo/http";
export const staffResignationApplyList = declareRequest("staffResignationApplyLoading", safePageResult(async (params) => {
const query = toPageQuery(params, {
likeStaffName: "applicantName",
auditStatus: "auditStatusCode",
});
const res = await apiGet("/safetyEval/org-resign-apply/page", query);
return fromPageResponse(res, toResignApplyForm);
}));
export const staffResignationApplyList = declareRequest(
"staffResignationApplyLoading",
"Get > /safetyEval/org-resign-apply/page",
"staffResignationApplyList: [] | res.data || [] & staffResignationApplyTotal: 0 | res.total || 0",
);
export const staffResignationApplyInfo = declareRequest("staffResignationApplyLoading", safeAction(async (params) => {
const res = await apiGet("/safetyEval/org-resign-apply/get", { id: params.id });
return fromSingleResponse(res, toResignApplyForm);
}));
export const staffResignationApplyInfo = declareRequest(
"staffResignationApplyLoading",
"Get > /safetyEval/org-resign-apply/get",
"resignApplyDetail: {} | res.data || {}",
);
export const staffResignationApplyAdd = declareRequest("staffResignationApplyLoading", safeAction(async (values) => {
const res = await apiPost("/safetyEval/org-resign-apply/save", fromResignApplyForm(values));
return fromSingleResponse(res, toResignApplyForm);
}));
export const staffResignationApplyAdd = declareRequest(
"staffResignationApplyLoading",
"Post > @/safetyEval/org-resign-apply/save",
);

View File

@ -1,5 +1,6 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Empty, Form, Input, message, Modal, Row, Col, Select, Space, Tree } from "antd";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { useEffect, useMemo, useRef, useState } from "react";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
@ -271,10 +272,10 @@ function DepartmentPositionPage(props) {
title: "操作",
width: 160,
render: (_, record) => (
<Space>
<TableAction>
<Button type="link" onClick={() => openPositionModal(record)}>编辑</Button>
<Button danger type="link" onClick={() => onDeletePosition(record.id)}>删除</Button>
</Space>
</TableAction>
),
},
]}

View File

@ -1,18 +1,30 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Descriptions, Form, Input, message, Modal, Select, Space, Typography } from "antd";
import { Button, Descriptions, Form, Input, message, Modal, Select, Space, Table, Tag, Typography } from "antd";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { useEffect, useState } from "react";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import useTable from "zy-react-library/hooks/useTable";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
import { NS_STAFF_CHANGE_LOG, NS_STAFF_INFO } from "~/enumerate/namespace";
import { safeListRequest, safeRequest } from "~/utils";
import { CHANGE_COUNT_STYLE, formSelectField, getChangeCount, getEmploymentStatusLabel, getResignAuditStatusLabel } from "~/utils/enterpriseForm";
import {
CHANGE_COUNT_STYLE,
RESIGN_AUDIT_STATUS_LABEL,
AUDIT_STATUS_COLOR,
} from "~/utils/enterpriseForm";
const { router } = tools;
const EMPLOYMENT_STATUS = { 1: "在职", 2: "离职" };
const RESIGN_AUDIT_STATUS = { 0: "未审核", 1: "已审核", 2: "已退回" };
const SEARCH_COL = { xs: 24, sm: 12, md: 8, lg: 6 };
const EMPLOYMENT_STATUS_OPTIONS = [
{ value: 1, label: "在职" },
{ value: 2, label: "离职" },
];
const RESIGN_AUDIT_OPTIONS = Object.entries(RESIGN_AUDIT_STATUS_LABEL).map(([value, label]) => ({
value: Number(value),
label,
}));
function PersonnelChangePage(props) {
const [viewMode, setViewMode] = useState("list");
@ -22,27 +34,34 @@ function PersonnelChangePage(props) {
const [resignInfo, setResignInfo] = useState({});
const [rejectReason, setRejectReason] = useState("");
const [searchForm] = Form.useForm();
const [logForm] = Form.useForm();
const { tableProps, getData } = useTable(safeListRequest(props.staffChangeLogStaffList), {
form: searchForm,
});
const { staffChangeLog } = props;
const {
staffChangeLogStaffList: staffList,
staffChangeLogStaffTotal: staffTotal,
staffChangeLogLoading: loading,
staffChangeLogChangeList: changeList,
staffChangeLogChangeTotal: changeTotal,
} = staffChangeLog || {};
const { tableProps: logTableProps, getData: getLogData } = useTable(
safeListRequest(props.staffChangeLogList),
{
form: logForm,
transform: () => ({ eqStaffId: selectedStaff?.staffId }),
},
);
const handleSearch = () => {
props.staffChangeLogStaffList({ ...router.query });
};
useEffect(() => {
getData();
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
const handleLogSearch = () => {
if (selectedStaff?.staffId) {
props.staffChangeLogList({ eqStaffId: selectedStaff.staffId });
}
};
useEffect(() => {
if (viewMode === "log" && selectedStaff?.staffId) {
getLogData();
handleLogSearch();
}
}, [viewMode, selectedStaff?.staffId]);
@ -56,9 +75,8 @@ function PersonnelChangePage(props) {
const res = await props.staffInfoRemove?.({ id: record.staffId || record.id });
if (res?.success !== false) {
message.success("删除成功");
getData();
}
else {
handleSearch();
} else {
message.error(res?.message || "删除失败");
}
},
@ -67,26 +85,23 @@ function PersonnelChangePage(props) {
const openResignAudit = async (record) => {
try {
const res = await safeRequest(props.staffResignationInfo, { id: record.resignApplyId });
setResignInfo(res?.data || {});
const res = await props.staffResignationInfo({ id: record.resignApplyId });
const data = res?.data || {};
setResignInfo(data);
setSelectedStaff(record);
setRejectReason("");
setAuditModalOpen(true);
}
catch (err) {
console.warn("[PersonnelChange] openResignAudit failed:", err);
} catch {
message.error("获取离职申请详情失败");
}
};
const openResignView = async (record) => {
try {
const res = await safeRequest(props.staffResignationInfo, { id: record.resignApplyId });
const res = await props.staffResignationInfo({ id: record.resignApplyId });
setResignInfo(res?.data || {});
setViewResignOpen(true);
}
catch (err) {
console.warn("[PersonnelChange] openResignView failed:", err);
} catch {
message.error("获取离职申请详情失败");
}
};
@ -99,39 +114,121 @@ function PersonnelChangePage(props) {
try {
const res = await props.staffResignationAudit({
id: resignInfo.id,
auditStatus: passed ? 1 : 2,
auditStatusCode: passed ? 1 : 2,
auditStatusName: passed ? "已审核" : "已退回",
rejectReason: passed ? undefined : rejectReason,
});
if (res?.success !== false) {
message.success(passed ? "审核通过" : "已退回");
setAuditModalOpen(false);
getData();
}
else {
handleSearch();
} else {
message.error(res?.message || "审核操作失败");
}
}
catch (err) {
console.warn("[PersonnelChange] submitAudit failed:", err);
} catch {
message.error("审核操作失败");
}
};
const staffColumns = [
{ title: "账户", dataIndex: "account", width: 130 },
{ title: "姓名", dataIndex: "userName", width: 100 },
{ title: "部门", dataIndex: "deptName", width: 120 },
{
title: "信息变更数",
dataIndex: "changeCount",
width: 110,
render: (_, record) => {
const n = Number(record.changeCount ?? 0);
const style = n > 0 ? CHANGE_COUNT_STYLE.active : CHANGE_COUNT_STYLE.zero;
if (n <= 0) {
return <span style={style}>{n}</span>;
}
return (
<Typography.Link
style={style}
onClick={() => {
setSelectedStaff(record);
setViewMode("log");
}}
>
{n}
</Typography.Link>
);
},
},
{
title: "就职状态",
dataIndex: "employmentStatusCode",
width: 90,
render: (_, record) => {
const code = record.employmentStatusCode ?? record.employmentStatus;
return <Tag color={code === 2 ? "error" : "success"}>{code === 2 ? "离职" : "在职"}</Tag>;
},
},
{
title: "离职申请审核状态",
dataIndex: "resignAuditStatus",
width: 140,
render: (_, record) => {
const code = record.resignAuditStatus ?? record.auditStatus ?? record.auditStatusCode;
const label = RESIGN_AUDIT_STATUS_LABEL[code] ?? "-";
return <Tag color={AUDIT_STATUS_COLOR[code]}>{label}</Tag>;
},
},
{
title: "操作",
width: 200,
fixed: "right",
render: (_, record) => (
<TableAction>
<Button type="link" size="small" onClick={() => openResignView(record)}>
查看
</Button>
{record.resignApplyId && Number(record.resignAuditStatus) === 0 && (
<Button type="link" size="small" onClick={() => openResignAudit(record)}>
离职审核
</Button>
)}
<Button danger type="link" size="small" onClick={() => onDelete(record)}>
删除
</Button>
</TableAction>
),
},
];
const logColumns = [
{ title: "变更事项", dataIndex: "changeItem", width: 200 },
{ title: "变更时间", dataIndex: "changeTime", width: 160 },
{ title: "操作人", dataIndex: "createBy", width: 100 },
];
if (viewMode === "log") {
return (
<PageLayout title="人员变更次数">
<Button style={{ marginBottom: 16 }} onClick={() => setViewMode("list")}>返回</Button>
<div style={{ marginBottom: 8 }}>
人员
{selectedStaff?.staffName}
</div>
<Button style={{ marginBottom: 16 }} onClick={() => setViewMode("list")}>
返回
</Button>
<div style={{ marginBottom: 8 }}>人员{selectedStaff?.staffName || selectedStaff?.userName}</div>
<Table
columns={[
{ title: "变更事项", dataIndex: "changeItem" },
{ title: "变更时间", dataIndex: "changeTime" },
{ title: "操作人", dataIndex: "createBy" },
]}
{...logTableProps}
rowKey="id"
columns={logColumns}
dataSource={changeList}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total: changeTotal,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: router.query.current || 1,
pageSize: router.query.size || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleLogSearch();
},
}}
/>
</PageLayout>
);
@ -148,82 +245,60 @@ function PersonnelChangePage(props) {
</div>
}
>
<Search
<SearchForm
style={{ marginBottom: 24 }}
form={searchForm}
options={[
{ name: "likeAccount", label: "账户", placeholder: "请输入账户", colProps: SEARCH_COL },
{ name: "likeStaffName", label: "姓名", placeholder: "请输入姓名", colProps: SEARCH_COL },
formSelectField(
"employmentStatus",
"就职状态",
Object.entries(EMPLOYMENT_STATUS).map(([value, label]) => ({ label, value: Number(value) })),
{ colProps: SEARCH_COL },
),
formSelectField(
"resignAuditStatus",
"复核状态",
Object.entries(RESIGN_AUDIT_STATUS).map(([value, label]) => ({ label, value: Number(value) })),
{ colProps: SEARCH_COL, placeholder: "请选择复核状态" },
),
loading={loading}
formLine={[
<Form.Item key="account" name="account">
<ControlWrapper.Input label="账户" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="userName" name="userName">
<ControlWrapper.Input label="姓名" placeholder="请输入" allowClear />
</Form.Item>,
<Form.Item key="employmentStatusCode" name="employmentStatusCode">
<ControlWrapper.Select label="就职状态" placeholder="请选择" allowClear style={{ width: "100%" }}>
{EMPLOYMENT_STATUS_OPTIONS.map((o) => (
<Select.Option key={o.value} value={o.value}>{o.label}</Select.Option>
))}
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="resignAuditStatus" name="resignAuditStatus">
<ControlWrapper.Select label="复核状态" placeholder="请选择" allowClear style={{ width: "100%" }}>
{RESIGN_AUDIT_OPTIONS.map((o) => (
<Select.Option key={o.value} value={o.value}>{o.label}</Select.Option>
))}
</ControlWrapper.Select>
</Form.Item>,
]}
onFinish={getData}
onReset={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
onFinish={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
/>
<Table
columns={[
{ title: "账户", dataIndex: "account", width: 130 },
{ title: "姓名", dataIndex: "staffName", width: 100 },
{ title: "部门", dataIndex: "deptName", width: 120 },
{
title: "信息变更数",
dataIndex: "changeCount",
width: 110,
render: (_, record) => {
const n = getChangeCount(record);
const style = n > 0 ? CHANGE_COUNT_STYLE.active : CHANGE_COUNT_STYLE.zero;
if (n <= 0) {
return <span style={style}>{n}</span>;
}
return (
<Typography.Link
style={style}
onClick={() => {
setSelectedStaff(record);
setViewMode("log");
}}
>
{n}
</Typography.Link>
);
},
rowKey={(record) => record.id || record.staffId}
columns={staffColumns}
dataSource={staffList}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total: staffTotal,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: router.query.current || 1,
pageSize: router.query.size || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleSearch();
},
{
title: "就职状态",
dataIndex: "employmentStatusName",
width: 90,
render: (_, record) => getEmploymentStatusLabel(record),
},
{
title: "离职申请审核状态",
dataIndex: "resignAuditStatus",
width: 140,
render: (_, record) => getResignAuditStatusLabel(record),
},
{
title: "操作",
width: 220,
fixed: "right",
render: (_, record) => (
<Space>
<Button type="link" style={{ padding: 0 }} onClick={() => openResignView(record)}>查看</Button>
{record.resignApplyId && Number(record.resignAuditStatus) === 0 && (
<Button type="link" style={{ padding: 0 }} onClick={() => openResignAudit(record)}>离职审核</Button>
)}
<Button danger type="link" style={{ padding: 0 }} onClick={() => onDelete(record)}>删除</Button>
</Space>
),
},
]}
{...tableProps}
}}
/>
<Modal
@ -231,20 +306,20 @@ function PersonnelChangePage(props) {
destroyOnHidden
title="审核离职申请"
width={720}
footer={(
footer={
<Space>
<Button onClick={() => setAuditModalOpen(false)}>取消</Button>
<Button danger onClick={() => submitAudit(false)}>退回</Button>
<Button type="primary" onClick={() => submitAudit(true)}>通过</Button>
</Space>
)}
}
onCancel={() => setAuditModalOpen(false)}
>
<Descriptions bordered column={1} labelStyle={{ width: 120 }}>
<Descriptions.Item label="申请人">{resignInfo.applicantName}</Descriptions.Item>
<Descriptions.Item label="申请时间">{resignInfo.applyTime}</Descriptions.Item>
<Descriptions.Item label="预计离职日期">{resignInfo.expectedLeaveDate}</Descriptions.Item>
<Descriptions.Item label="离职原因">{resignInfo.leaveReason}</Descriptions.Item>
<Descriptions.Item label="预计离职日期">{resignInfo.expectedResignDate}</Descriptions.Item>
<Descriptions.Item label="离职原因">{resignInfo.resignReason}</Descriptions.Item>
<Descriptions.Item label="备注">{resignInfo.remark}</Descriptions.Item>
</Descriptions>
<Form.Item label="退回原因" style={{ marginTop: 16 }}>
@ -264,8 +339,8 @@ function PersonnelChangePage(props) {
<Descriptions bordered column={1} labelStyle={{ width: 120 }}>
<Descriptions.Item label="申请人">{resignInfo.applicantName}</Descriptions.Item>
<Descriptions.Item label="申请时间">{resignInfo.applyTime}</Descriptions.Item>
<Descriptions.Item label="预计离职日期">{resignInfo.expectedLeaveDate}</Descriptions.Item>
<Descriptions.Item label="离职原因">{resignInfo.leaveReason}</Descriptions.Item>
<Descriptions.Item label="预计离职日期">{resignInfo.expectedResignDate}</Descriptions.Item>
<Descriptions.Item label="离职原因">{resignInfo.resignReason}</Descriptions.Item>
<Descriptions.Item label="备注">{resignInfo.remark}</Descriptions.Item>
</Descriptions>
</Modal>
@ -273,4 +348,4 @@ function PersonnelChangePage(props) {
);
}
export default Connect([NS_STAFF_CHANGE_LOG, NS_STAFF_INFO], true)(PersonnelChangePage);
export default Connect([NS_STAFF_CHANGE_LOG, NS_STAFF_INFO], true)(AntdTableFuncControl(PersonnelChangePage));

View File

@ -1,45 +1,42 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Descriptions, Form, message, Modal, Space } from "antd";
import { Button, DatePicker, Descriptions, Form, Input, message, Modal, Select, Table, Upload } from "antd";
import { UploadOutlined } from "@ant-design/icons";
import { useEffect, 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 CertPreviewImg from "~/components/CertPreviewImg";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import Upload from "zy-react-library/components/Upload";
import { FORM_ITEM_RENDER_ENUM } from "zy-react-library/enum/formItemRender";
import useGetFile from "zy-react-library/hooks/useGetFile";
import useGetUrlQuery from "zy-react-library/hooks/useGetUrlQuery";
import useTable from "zy-react-library/hooks/useTable";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
import { NS_STAFF_CERTIFICATE } from "~/enumerate/namespace";
import { safeGetFiles, safeListRequest, safeRequest } from "~/utils";
import { mockUploadFileList, resolveUploadFileId } from "~/utils/mockUpload";
import CertPreviewImg from "~/components/CertPreviewImg";
import { dateRangeRule } from "~/utils/validators";
const { router } = tools;
function StaffCertificatePage(props) {
const query = useGetUrlQuery();
const staffId = query.staffId;
const staffName = query.staffName;
const urlParams = Object.fromEntries(new URLSearchParams(window.location.search));
const staffId = urlParams.staffId;
const staffName = urlParams.staffName ? decodeURIComponent(urlParams.staffName) : "";
const [addModalOpen, setAddModalOpen] = useState(false);
const [viewModalOpen, setViewModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
const [form] = Form.useForm();
const { tableProps, getData } = useTable(safeListRequest(props.staffCertificateList), {
form,
transform: (formData) => ({
...formData,
eqStaffId: staffId,
}),
});
const [searchForm] = Form.useForm();
const { staffCertificate } = props;
const {
staffCertificateList: dataSource,
staffCertificateTotal: total,
staffCertificateLoading: loading,
} = staffCertificate || {};
useEffect(() => {
if (staffId) {
getData();
}
}, [staffId]);
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
const handleSearch = () => {
props.staffCertificateList({ ...router.query, personnelId: staffId });
};
const goBack = () => {
window.location.href = window.location.pathname.replace(/\/Certificate.*$/, "/List");
@ -55,7 +52,7 @@ function StaffCertificatePage(props) {
const res = await props.staffCertificateRemove({ id });
if (res?.success !== false) {
message.success("删除成功");
getData();
handleSearch();
}
},
});
@ -64,28 +61,32 @@ function StaffCertificatePage(props) {
return (
<PageLayout title={`人员证书 - ${staffName || ""}`}>
<Button style={{ marginBottom: 16 }} onClick={goBack}>返回</Button>
<Search
form={form}
options={[
{ name: "likeCertNo", label: "证书编号", placeholder: "请输入证书编号" },
{ name: "certCategory", label: "证书类别" },
{ name: "certWorkCategory", label: "证书作业类别" },
<SearchForm
style={{ marginBottom: 24 }}
form={searchForm}
loading={loading}
formLine={[
<Form.Item key="certNo" name="certNo">
<ControlWrapper.Input label="证书编号" placeholder="请输入证书编号" allowClear />
</Form.Item>,
<Form.Item key="certCategory" name="certCategory">
<ControlWrapper.Input label="证书类别" placeholder="请输入证书类别" allowClear />
</Form.Item>,
<Form.Item key="certWorkCategory" name="certWorkCategory">
<ControlWrapper.Input label="证书作业类别" placeholder="请输入证书作业类别" allowClear />
</Form.Item>,
]}
onFinish={getData}
onReset={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
onFinish={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
/>
<Table
toolBarRender={() => (
<Button
type="primary"
icon={<AddIcon />}
onClick={() => {
setCurrentId("");
setAddModalOpen(true);
}}
>
添加证书
</Button>
)}
rowKey="id"
columns={[
{ title: "证书类型", dataIndex: "certCategory" },
{ title: "证书作业类别", dataIndex: "certWorkCategory" },
@ -94,19 +95,33 @@ function StaffCertificatePage(props) {
title: "操作",
width: 200,
render: (_, record) => (
<Space>
<Button type="link" onClick={() => { setCurrentId(record.id); setViewModalOpen(true); }}>
<TableAction>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setViewModalOpen(true); }}>
查看
</Button>
<Button type="link" onClick={() => { setCurrentId(record.id); setAddModalOpen(true); }}>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setAddModalOpen(true); }}>
编辑
</Button>
<Button danger type="link" onClick={() => onDelete(record.id)}>删除</Button>
</Space>
<Button danger type="link" size="small" onClick={() => onDelete(record.id)}>删除</Button>
</TableAction>
),
},
]}
{...tableProps}
dataSource={dataSource}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: Number(router.query.current) || 1,
pageSize: Number(router.query.size) || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleSearch();
},
}}
/>
{addModalOpen && (
@ -114,21 +129,21 @@ function StaffCertificatePage(props) {
open={addModalOpen}
staffId={staffId}
currentId={currentId}
requestAdd={props.staffCertificateAdd}
requestEdit={props.staffCertificateEdit}
requestDetails={props.staffCertificateInfo}
staffCertificateAdd={props.staffCertificateAdd}
staffCertificateEdit={props.staffCertificateEdit}
staffCertificateInfo={props.staffCertificateInfo}
onCancel={() => {
setAddModalOpen(false);
setCurrentId("");
}}
onSuccess={getData}
onSuccess={handleSearch}
/>
)}
{viewModalOpen && (
<ViewModal
open={viewModalOpen}
currentId={currentId}
requestDetails={props.staffCertificateInfo}
staffCertificateInfo={props.staffCertificateInfo}
onCancel={() => {
setViewModalOpen(false);
setCurrentId("");
@ -140,83 +155,91 @@ function StaffCertificatePage(props) {
}
function CertModal({
open, staffId, currentId, requestAdd, requestEdit, requestDetails, onCancel, onSuccess,
open, staffId, currentId, staffCertificateAdd, staffCertificateEdit, staffCertificateInfo, onCancel, onSuccess,
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const { getFile } = useGetFile();
const [uploadFileList, setUploadFileList] = useState([]);
useEffect(() => {
if (!open) {
return;
}
if (!open) return;
if (!currentId) {
form.resetFields();
form.setFieldsValue({ certImgs: mockUploadFileList("证书附件.jpg") });
setUploadFileList([]);
return;
}
let cancelled = false;
(async () => {
setDetailLoading(true);
try {
const res = await safeRequest(requestDetails, { id: currentId });
if (cancelled || !res?.data) {
return;
}
const res = await staffCertificateInfo({ id: currentId });
if (cancelled || !res?.data) return;
const data = { ...res.data };
data.validDate = [data.validStartDate, data.validEndDate];
data.certImgs = data.certImgFiles?.length ? data.certImgFiles : mockUploadFileList("证书附件.jpg");
// 将 validStartDate/validEndDate 合并为 validDateRangePicker
if (data.validStartDate || data.validEndDate) {
data.validDate = [data.validStartDate, data.validEndDate];
}
// 处理证书附件 fileList
let fileList = [];
if (Array.isArray(data.certImgFiles)) {
fileList = data.certImgFiles.map((f, i) => ({
uid: f.uid || `-${i}`,
name: f.fileName || f.name || `证书附件${i + 1}`,
status: "done",
url: f.url,
}));
} else if (data.certAttachmentUrl) {
fileList = data.certAttachmentUrl.split(",").filter(Boolean).map((url, i) => ({
uid: `-${i}`,
name: url.split("/").pop() || `证书附件${i + 1}`,
status: "done",
url,
}));
}
data.certAttachmentUrl = fileList;
setUploadFileList(fileList);
form.setFieldsValue(data);
setDetailLoading(false);
const files = await safeGetFiles(
getFile,
{ eqType: "staff_cert", eqForeignKey: data.id },
data.certImgFiles || [],
);
if (!cancelled && files.length) {
form.setFieldValue("certImgs", files);
}
}
catch (err) {
} catch (err) {
console.warn("[StaffCertificate] load detail failed:", err);
}
finally {
if (!cancelled) {
setDetailLoading(false);
}
} finally {
if (!cancelled) setDetailLoading(false);
}
})();
return () => {
cancelled = true;
};
return () => { cancelled = true; };
}, [open, currentId]);
const handleCancel = () => {
form.resetFields();
setUploadFileList([]);
onCancel();
};
const handleSubmit = async (values) => {
try {
setSubmitting(true);
values.validStartDate = values.validDate?.[0];
values.validEndDate = values.validDate?.[1];
values.staffId = staffId;
values.certAttachmentUrl = resolveUploadFileId(values.certImgs);
const request = currentId ? requestEdit : requestAdd;
if (currentId) {
values.id = currentId;
}
const res = await request(values);
const payload = {
...values,
personnelId: staffId,
validStartDate: values.validDate?.[0],
validEndDate: values.validDate?.[1],
certAttachmentUrl: Array.isArray(values.certAttachmentUrl)
? values.certAttachmentUrl.map((f) => f.url || f.response?.url).filter(Boolean).join(",")
: "",
};
delete payload.validDate;
delete payload.certImgFiles;
if (currentId) payload.id = currentId;
const request = currentId ? staffCertificateEdit : staffCertificateAdd;
const res = await request(payload);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
handleCancel();
onSuccess();
} else {
message.error(res?.message || "操作失败");
}
}
finally {
} finally {
setSubmitting(false);
}
};
@ -232,46 +255,60 @@ function CertModal({
onCancel={handleCancel}
onOk={form.submit}
>
<FormBuilder
form={form}
span={24}
showActionButtons={false}
onFinish={handleSubmit}
options={[
{ name: "certName", label: "证照名称", rules: [{ required: true, message: "请输入证照名称" }] },
{ name: "certCategory", label: "证书类别", rules: [{ required: true, message: "请选择证书类别" }] },
{ name: "certWorkCategory", label: "证书作业类别" },
{ name: "certNo", label: "证书编号", rules: [{ required: true, message: "请输入证书编号" }] },
{ name: "issueOrg", label: "发证机关", rules: [{ required: true, message: "请输入发证机关" }] },
{
name: "validDate",
label: "证书有效开始/结束日期",
render: FORM_ITEM_RENDER_ENUM.DATE_RANGE,
rules: [{ required: true, message: "请选择证书有效期" }, dateRangeRule()],
},
{ name: "reviewDate", label: "复合日期", render: FORM_ITEM_RENDER_ENUM.DATE },
{
name: "certImgs",
label: "证书附件",
render: (
<Upload
maxCount={3}
onGetRemoveFile={(file) => setDeleteFiles(prev => [...prev, file])}
/>
),
},
]}
labelCol={{ span: 24 }}
wrapperCol={{ span: 24 }}
/>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item name="certName" label="证照名称" rules={[{ required: true, message: "请输入证照名称" }]}>
<Input placeholder="请输入证照名称" />
</Form.Item>
<Form.Item name="certCategory" label="证书类别" rules={[{ required: true, message: "请选择证书类别" }]}>
<Input placeholder="请输入证书类别" />
</Form.Item>
<Form.Item name="certWorkCategory" label="证书作业类别">
<Input placeholder="请输入证书作业类别" />
</Form.Item>
<Form.Item name="certNo" label="证书编号" rules={[{ required: true, message: "请输入证书编号" }]}>
<Input placeholder="请输入证书编号" />
</Form.Item>
<Form.Item name="issueOrg" label="发证机关" rules={[{ required: true, message: "请输入发证机关" }]}>
<Input placeholder="请输入发证机关" />
</Form.Item>
<Form.Item
name="validDate"
label="证书有效开始/结束日期"
rules={[{ required: true, message: "请选择证书有效期" }, dateRangeRule()]}
>
<DatePicker.RangePicker style={{ width: "100%" }} />
</Form.Item>
<Form.Item name="reviewDate" label="复合日期">
<DatePicker style={{ width: "100%" }} />
</Form.Item>
<Form.Item
name="certAttachmentUrl"
label="证书附件"
valuePropName="fileList"
getValueFromEvent={(e) => {
if (Array.isArray(e)) return e;
return e?.fileList;
}}
>
<Upload
action="/api/upload"
maxCount={3}
fileList={uploadFileList}
onChange={(info) => {
setUploadFileList(info.fileList);
}}
>
<Button icon={<UploadOutlined />}>上传文件</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
);
}
function ViewModal({ open, currentId, requestDetails, onCancel }) {
function ViewModal({ open, currentId, staffCertificateInfo, onCancel }) {
const [info, setInfo] = useState({});
const [detailLoading, setDetailLoading] = useState(false);
const { getFile } = useGetFile();
useEffect(() => {
if (!open || !currentId) {
@ -282,36 +319,29 @@ function ViewModal({ open, currentId, requestDetails, onCancel }) {
(async () => {
setDetailLoading(true);
try {
const res = await safeRequest(requestDetails, { id: currentId });
if (cancelled || !res?.data) {
return;
}
const res = await staffCertificateInfo({ id: currentId });
if (cancelled || !res?.data) return;
const data = { ...res.data };
data.certImgs = data.certImgFiles || [];
// 处理 certImgFiles
if (Array.isArray(data.certImgFiles)) {
data.certDisplayFiles = data.certImgFiles;
} else if (data.certAttachmentUrl) {
data.certDisplayFiles = data.certAttachmentUrl.split(",").filter(Boolean).map((url, i) => ({
uid: `-${i}`,
fileName: url.split("/").pop() || `证书附件${i + 1}`,
url,
}));
} else {
data.certDisplayFiles = [];
}
setInfo(data);
setDetailLoading(false);
const files = await safeGetFiles(
getFile,
{ eqType: "staff_cert", eqForeignKey: data.id },
data.certImgFiles || [],
);
if (!cancelled && files.length) {
setInfo((prev) => ({ ...prev, certImgs: files }));
}
}
catch (err) {
} catch (err) {
console.warn("[StaffCertificate] load view failed:", err);
}
finally {
if (!cancelled) {
setDetailLoading(false);
}
} finally {
if (!cancelled) setDetailLoading(false);
}
})();
return () => {
cancelled = true;
};
return () => { cancelled = true; };
}, [open, currentId]);
return (
@ -333,10 +363,10 @@ function ViewModal({ open, currentId, requestDetails, onCancel }) {
<Descriptions.Item label="证书有效开始日期">{info.validStartDate}</Descriptions.Item>
<Descriptions.Item label="证书有效结束日期">{info.validEndDate}</Descriptions.Item>
<Descriptions.Item label="复合日期">{info.reviewDate}</Descriptions.Item>
<Descriptions.Item label="证书图片"><CertPreviewImg files={info.certImgs} /></Descriptions.Item>
<Descriptions.Item label="证书图片"><CertPreviewImg files={info.certDisplayFiles} /></Descriptions.Item>
</Descriptions>
</Modal>
);
}
export default Connect([NS_STAFF_CERTIFICATE], true)(StaffCertificatePage);
export default Connect([NS_STAFF_CERTIFICATE], true)(AntdTableFuncControl(StaffCertificatePage));

View File

@ -1,13 +1,16 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, DatePicker, Form, Input, message, Modal, Row, Col, Select, Space } from "antd";
import { Button, DatePicker, Form, Input, message, Modal, Row, Col, Select, Table, Upload } from "antd";
import { UploadOutlined } from "@ant-design/icons";
import { useEffect, useRef, useState } from "react";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
import dayjs from "dayjs";
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
import Search from "zy-react-library/components/Search";
import Table from "zy-react-library/components/Table";
import Upload from "zy-react-library/components/Upload";
import useTable from "zy-react-library/hooks/useTable";
import { NS_ORG_DEPARTMENT, NS_ORG_POSITION, NS_STAFF_INFO } from "~/enumerate/namespace";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
import { Get } from "@cqsjjb/jjb-common-lib/http";
import { NS_STAFF_INFO } from "~/enumerate/namespace";
import {
EDUCATION_LEVEL_OPTIONS,
EDUCATION_TYPE_OPTIONS,
@ -16,21 +19,11 @@ import {
QUALIFICATION_INDUSTRY_OPTIONS,
REGISTER_ENGINEER_OPTIONS,
} from "~/enumerate/enterpriseOptions";
import { ensureOrgContext, fetchOrgDepartmentPage, fetchOrgPositionPage } from "~/api/enterpriseInfo/orgBootstrap";
import { asId, sameId } from "~/api/enterpriseInfo/idUtil";
import { getBirthDateFromIdCard, safeListRequest, safeRequest } from "~/utils";
import { toDayjs } from "~/utils/dateFormat";
import { formSelectField } from "~/utils/enterpriseForm";
import { getBirthDateFromIdCard } from "~/utils";
import { idCardRule, mobileRule } from "~/utils/validators";
import StaffViewModal from "../StaffViewModal";
function mapPositionOptions(list = []) {
return list.map((p) => ({
label: p.positionName,
value: asId(p.id),
deptId: asId(p.deptId),
}));
}
const { router } = tools;
function PersonnelInfoPage(props) {
const [formModalOpen, setFormModalOpen] = useState(false);
@ -39,43 +32,47 @@ function PersonnelInfoPage(props) {
const [searchForm] = Form.useForm();
const [deptOptions, setDeptOptions] = useState([]);
const [positionOptions, setPositionOptions] = useState([]);
const { staffInfo } = props;
const {
staffInfoList: dataSource,
staffInfoTotal: total,
staffInfoLoading: loading,
} = staffInfo || {};
useEffect(() => {
let cancelled = false;
(async () => {
try {
await ensureOrgContext();
const [deptRes, posRes] = await Promise.all([
fetchOrgDepartmentPage({ pageIndex: 1, pageSize: 200 }),
fetchOrgPositionPage({ pageIndex: 1, pageSize: 200 }),
Get("/safetyEval/org-department/page", { current: 1, size: 200 }),
Get("/safetyEval/org-position/page", { current: 1, size: 200 }),
]);
if (cancelled) {
return;
}
setDeptOptions((deptRes?.data || []).map((d) => ({ label: d.deptName, value: asId(d.id) })));
setPositionOptions(mapPositionOptions(posRes?.data));
}
catch (err) {
if (cancelled) return;
setDeptOptions((deptRes?.data || []).map((d) => ({ label: d.deptName, value: d.id })));
setPositionOptions((posRes?.data || []).map((p) => ({
label: p.positionName,
value: p.id,
deptId: p.deptId,
})));
} catch (err) {
console.warn("[PersonnelInfo] load dept/position options failed:", err);
}
})();
return () => {
cancelled = true;
};
return () => { cancelled = true; };
}, []);
const { tableProps, getData } = useTable(safeListRequest(props.staffInfoList), {
form: searchForm,
transform: (formData) => ({
...formData,
eqDeptId: formData.deptId,
eqPositionId: formData.positionId,
}),
});
useEffect(() => {
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
const goCertificate = (staffId, staffName) => {
const handleSearch = () => {
props.staffInfoList({ ...router.query });
};
const goCertificate = (id, staffName) => {
const base = window.location.pathname.replace(/\/List.*$/, "");
window.location.href = `${base}/Certificate?staffId=${staffId}&staffName=${encodeURIComponent(staffName || "")}`;
window.location.href = `${base}/Certificate?staffId=${id}&staffName=${encodeURIComponent(staffName || "")}`;
};
const onDelete = (id) => {
@ -88,7 +85,7 @@ function PersonnelInfoPage(props) {
const res = await props.staffInfoRemove({ id });
if (res?.success !== false) {
message.success("删除成功");
getData();
handleSearch();
}
},
});
@ -120,30 +117,42 @@ function PersonnelInfoPage(props) {
</div>
}
>
<Search
<SearchForm
style={{ marginBottom: 24 }}
form={searchForm}
options={[
{ name: "likeStaffName", label: "用户名称", placeholder: "请输入用户名称" },
formSelectField("deptId", "部门", deptOptions),
formSelectField("positionId", "岗位", positionOptions),
loading={loading}
formLine={[
<Form.Item key="userName" name="userName">
<ControlWrapper.Input label="用户名称" placeholder="请输入用户名称" allowClear />
</Form.Item>,
<Form.Item key="deptId" name="deptId">
<ControlWrapper.Select label="部门" placeholder="请选择部门" allowClear style={{ width: "100%" }}>
{deptOptions.map((d) => (
<Select.Option key={d.value} value={d.value}>{d.label}</Select.Option>
))}
</ControlWrapper.Select>
</Form.Item>,
<Form.Item key="postId" name="postId">
<ControlWrapper.Select label="岗位" placeholder="请选择岗位" allowClear style={{ width: "100%" }}>
{positionOptions.map((p) => (
<Select.Option key={p.value} value={p.value}>{p.label}</Select.Option>
))}
</ControlWrapper.Select>
</Form.Item>,
]}
onFinish={getData}
onReset={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
onFinish={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
/>
<Table
toolBarRender={() => (
<Button
type="primary"
icon={<AddIcon />}
onClick={() => {
setCurrentId("");
setFormModalOpen(true);
}}
>
新增人员
</Button>
)}
rowKey="id"
columns={[
{ title: "用户名称", dataIndex: "staffName" },
{ title: "用户名称", dataIndex: "userName" },
{ title: "账号", dataIndex: "account" },
{ title: "部门", dataIndex: "deptName" },
{ title: "岗位", dataIndex: "positionName" },
@ -157,38 +166,53 @@ function PersonnelInfoPage(props) {
title: "操作",
width: 320,
render: (_, record) => (
<Space wrap>
<Button type="link" onClick={() => { setCurrentId(asId(record.id)); setViewModalOpen(true); }}>
<TableAction>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setViewModalOpen(true); }}>
查看
</Button>
<Button type="link" onClick={() => { setCurrentId(asId(record.id)); setFormModalOpen(true); }}>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); setFormModalOpen(true); }}>
编辑
</Button>
<Button type="link" onClick={() => goCertificate(asId(record.id), record.staffName)}>
<Button type="link" size="small" onClick={() => { setCurrentId(record.id); goCertificate(record.id, record.userName); }}>
证书
</Button>
<Button type="link" onClick={() => onResetPassword(asId(record.id))}>重置密码</Button>
<Button danger type="link" onClick={() => onDelete(asId(record.id))}>删除</Button>
</Space>
<Button type="link" size="small" onClick={() => onResetPassword(record.id)}>重置密码</Button>
<Button danger type="link" size="small" onClick={() => onDelete(record.id)}>删除</Button>
</TableAction>
),
},
]}
{...tableProps}
dataSource={dataSource}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: Number(router.query.current) || 1,
pageSize: Number(router.query.size) || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleSearch();
},
}}
/>
{formModalOpen && (
<StaffFormModal
open={formModalOpen}
currentId={currentId}
requestAdd={props.staffInfoAdd}
requestEdit={props.staffInfoEdit}
requestDetails={props.staffInfoGet}
staffInfoGet={props.staffInfoGet}
staffInfoAdd={props.staffInfoAdd}
staffInfoEdit={props.staffInfoEdit}
deptOptions={deptOptions}
positionOptions={positionOptions}
onCancel={() => {
setFormModalOpen(false);
setCurrentId("");
}}
onSuccess={getData}
onSuccess={handleSearch}
/>
)}
{viewModalOpen && (
@ -207,60 +231,41 @@ function PersonnelInfoPage(props) {
}
function StaffFormModal({
open, currentId, requestAdd, requestEdit, requestDetails, deptOptions, onCancel, onSuccess,
open, currentId, staffInfoGet, staffInfoAdd, staffInfoEdit, deptOptions, positionOptions, onCancel, onSuccess,
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const [deptPositionOptions, setDeptPositionOptions] = useState([]);
const [positionLoading, setPositionLoading] = useState(false);
const [uploadFileList, setUploadFileList] = useState([]);
const watchedDeptId = Form.useWatch("deptId", form);
const wasOpenRef = useRef(false);
const inflightDeptRef = useRef(null);
const requestDetailsRef = useRef(requestDetails);
requestDetailsRef.current = requestDetails;
const loadPositionsByDept = async (deptId, currentStaff = null) => {
const id = asId(deptId);
const loadPositionsByDept = async (deptId) => {
const id = deptId;
if (!id) {
setDeptPositionOptions([]);
return [];
}
const key = id;
if (inflightDeptRef.current === key) {
return deptPositionOptions;
}
inflightDeptRef.current = key;
if (inflightDeptRef.current === id) return deptPositionOptions;
inflightDeptRef.current = id;
setPositionLoading(true);
try {
let res = await fetchOrgPositionPage({
pageIndex: 1,
pageSize: 200,
eqDeptId: id,
});
let options = mapPositionOptions(res?.data);
// 兼容历史岗位 dept_id 因 Number() 精度丢失与部门 id 不完全一致
if (!options.length) {
const allRes = await fetchOrgPositionPage({ pageIndex: 1, pageSize: 500 });
options = mapPositionOptions(allRes?.data).filter((p) => sameId(p.deptId, id));
}
const positionId = asId(currentStaff?.positionId);
const positionName = currentStaff?.positionName;
if (positionId && positionName && !options.some((item) => sameId(item.value, positionId))) {
options = [{ label: positionName, value: positionId, deptId: id }, ...options];
}
const res = await Get("/safetyEval/org-position/page", { current: 1, size: 200, deptId: id });
const options = (res?.data || []).map((p) => ({
label: p.positionName,
value: p.id,
}));
setDeptPositionOptions(options);
return options;
}
catch (err) {
} catch (err) {
console.warn("[PersonnelInfo] load positions by dept failed:", err);
setDeptPositionOptions([]);
return [];
}
finally {
if (inflightDeptRef.current === key) {
inflightDeptRef.current = null;
}
} finally {
if (inflightDeptRef.current === id) inflightDeptRef.current = null;
setPositionLoading(false);
}
};
@ -270,51 +275,53 @@ function StaffFormModal({
if (!currentId) {
form.resetFields();
setDeptPositionOptions([]);
setUploadFileList([]);
form.setFieldsValue({
personType: "基础人员",
registerEngineerFlag: 2,
});
}
}
if (!open) {
setDeptPositionOptions([]);
setUploadFileList([]);
}
wasOpenRef.current = open;
}, [open, currentId, form]);
useEffect(() => {
if (!open || !currentId) {
return;
}
if (!open || !currentId) return;
let cancelled = false;
setDetailLoading(true);
safeRequest(requestDetailsRef.current, { id: currentId }).then(async (res) => {
if (!cancelled && res?.data) {
const data = res.data;
await loadPositionsByDept(data.deptId, data);
if (!cancelled) {
form.setFieldsValue({
...data,
deptId: asId(data.deptId),
positionId: asId(data.positionId),
birthDate: toDayjs(data.birthDate),
proofMaterials: data.proofMaterials?.length ? data.proofMaterials : [],
});
}
}
staffInfoGet({ id: currentId }).then((res) => {
if (cancelled || !res?.data) return;
const data = { ...res.data };
loadPositionsByDept(data.deptId);
if (cancelled) return;
const setFields = {
...data,
};
if (data.birthDate) setFields.birthDate = dayjs(data.birthDate);
const fileList = data.proofMaterialUrl
? data.proofMaterialUrl.split(",").filter(Boolean).map((url, i) => ({
uid: `-${i}`,
name: url.split("/").pop() || `附件${i + 1}`,
status: "done",
url,
}))
: [];
setFields.proofMaterialUrl = fileList;
setUploadFileList(fileList);
form.setFieldsValue(setFields);
}).finally(() => {
if (!cancelled) {
setDetailLoading(false);
}
if (!cancelled) setDetailLoading(false);
});
return () => {
cancelled = true;
};
return () => { cancelled = true; };
}, [open, currentId]);
const handleCancel = () => {
form.resetFields();
setDeptPositionOptions([]);
setUploadFileList([]);
onCancel();
};
@ -322,11 +329,11 @@ function StaffFormModal({
if ("idCardNo" in changed && changed.idCardNo) {
const birth = getBirthDateFromIdCard(changed.idCardNo);
if (birth) {
form.setFieldValue("birthDate", toDayjs(birth));
form.setFieldValue("birthDate", dayjs(birth));
}
}
if ("deptId" in changed) {
form.setFieldValue("positionId", undefined);
form.setFieldValue("postId", undefined);
loadPositionsByDept(changed.deptId);
}
};
@ -334,21 +341,37 @@ function StaffFormModal({
const handleSubmit = async (values) => {
try {
setSubmitting(true);
const request = currentId ? requestEdit : requestAdd;
if (currentId) {
values.id = currentId;
const payload = {
...values,
genderName: values.genderCode === 1 ? "男" : values.genderCode === 2 ? "女" : undefined,
personTypeCode: values.personTypeName,
personTypeName: values.personTypeName,
professionalLevelCode: values.professionalLevelName,
professionalLevelName: values.professionalLevelName,
educationTypeCode: values.educationTypeName,
educationTypeName: values.educationTypeName,
educationLevelCode: values.educationLevelName,
educationLevelName: values.educationLevelName,
proofMaterialUrl: Array.isArray(values.proofMaterialUrl)
? values.proofMaterialUrl.map((f) => f.url || f.response?.url).filter(Boolean).join(",")
: "",
};
if (payload.birthDate && typeof payload.birthDate !== "string") {
payload.birthDate = payload.birthDate.format("YYYY-MM-DD");
}
const res = await request(values);
if (currentId) payload.id = currentId;
delete payload.proofMaterials;
const request = currentId ? staffInfoEdit : staffInfoAdd;
const res = await request(payload);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
handleCancel();
onSuccess();
}
else {
} else {
message.error(res?.message || "操作失败");
}
}
finally {
} finally {
setSubmitting(false);
}
};
@ -372,12 +395,12 @@ function StaffFormModal({
>
<Row gutter={16}>
<Col span={12}>
<Form.Item name="staffName" label="姓名" rules={[{ required: true, message: "请输入姓名" }]}>
<Form.Item name="userName" label="姓名" rules={[{ required: true, message: "请输入姓名" }]}>
<Input placeholder="请输入姓名" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="gender" label="性别" rules={[{ required: true, message: "请选择性别" }]}>
<Form.Item name="genderCode" label="性别" rules={[{ required: true, message: "请选择性别" }]}>
<Select
placeholder="请选择性别"
options={[{ label: "男", value: 1 }, { label: "女", value: 2 }]}
@ -400,7 +423,7 @@ function StaffFormModal({
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="positionId" label="岗位" rules={[{ required: true, message: "请选择岗位" }]}>
<Form.Item name="postId" label="岗位" rules={[{ required: true, message: "请选择岗位" }]}>
<Select
placeholder="请选择岗位"
options={deptPositionOptions}
@ -418,7 +441,7 @@ function StaffFormModal({
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="personType" label="人员类型" initialValue="基础人员">
<Form.Item name="personTypeName" label="人员类型">
<Select placeholder="请选择人员类型" options={PERSON_TYPE_OPTIONS} />
</Form.Item>
</Col>
@ -428,7 +451,7 @@ function StaffFormModal({
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="professionalLevel" label="职业等级">
<Form.Item name="professionalLevelName" label="职业等级">
<Select placeholder="请选择职业等级" allowClear options={PROFESSIONAL_LEVEL_OPTIONS} />
</Form.Item>
</Col>
@ -438,12 +461,12 @@ function StaffFormModal({
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="educationType" label="学历类型">
<Form.Item name="educationTypeName" label="学历类型">
<Select placeholder="请选择学历类型" allowClear options={EDUCATION_TYPE_OPTIONS} />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="educationLevel" label="学历层次">
<Form.Item name="educationLevelName" label="学历层次">
<Select placeholder="请选择学历层次" allowClear options={EDUCATION_LEVEL_OPTIONS} />
</Form.Item>
</Col>
@ -473,12 +496,24 @@ function StaffFormModal({
</Form.Item>
</Col>
<Col span={24}>
<Form.Item name="proofMaterials" label="申报专业能力证明材料">
<Upload maxCount={5} />
<Form.Item name="proofMaterialUrl" label="申报专业能力证明材料" valuePropName="fileList" getValueFromEvent={(e) => {
if (Array.isArray(e)) return e;
return e?.fileList;
}}>
<Upload
action="/api/upload"
maxCount={5}
fileList={uploadFileList}
onChange={(info) => {
setUploadFileList(info.fileList);
}}
>
<Button icon={<UploadOutlined />}>上传文件</Button>
</Upload>
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="homeAddress" label="现住地址">
<Form.Item name="currentAddress" label="现住地址">
<Input placeholder="请输入现住地址" />
</Form.Item>
</Col>
@ -497,15 +532,10 @@ function StaffFormModal({
<Input placeholder="请输入专业" />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item name="joinWorkDate" label="参加工作日期">
<DatePicker placeholder="请选择参加工作日期" />
</Form.Item>
</Col>
</Row>
</Form>
</Modal>
);
}
export default Connect([NS_STAFF_INFO, NS_ORG_DEPARTMENT, NS_ORG_POSITION], true)(PersonnelInfoPage);
export default Connect([NS_STAFF_INFO], true)(AntdTableFuncControl(PersonnelInfoPage));

View File

@ -1,10 +1,9 @@
import { Button, Descriptions, Modal, Space, Table } from "antd";
import { Button, Descriptions, Modal, Table } from "antd";
import { useEffect, useRef, useState } from "react";
import FilePreviewModal, { FilePreviewContent } from "~/components/FilePreviewModal";
import { resolvePreviewSrc } from "~/components/CertPreviewImg";
import { fetchStaffCertDetail, fetchStaffCertListByPersonnelId } from "~/api/staffCertificate";
import { fetchOrgPersonnelDetail } from "~/api/qualFiling/personnelHelper";
import { safeRequest } from "~/utils";
const GENDER_MAP = { 1: "男", 2: "女" };
const REGISTER_ENGINEER_MAP = { 1: "是", 2: "否" };
@ -39,9 +38,13 @@ export default function StaffViewModal({
}
let cancelled = false;
setDetailLoading(true);
const loadCertList = requestCertListRef.current;
Promise.all([
safeRequest(requestDetailsRef.current, { id: currentId }),
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) => {
console.warn("[StaffViewModal] load certificates failed:", err);
@ -72,10 +75,12 @@ export default function StaffViewModal({
}
setCertPreviewLoading(true);
try {
const res = await safeRequest(requestCertDetailsRef.current, { id: record.id });
const res = await requestCertDetailsRef.current({ id: record.id });
setCertPreviewInfo(res?.data || record);
}
finally {
} catch (err) {
console.warn("[StaffViewModal] load cert detail failed:", err);
setCertPreviewInfo(record);
} finally {
setCertPreviewLoading(false);
}
};
@ -133,11 +138,11 @@ export default function StaffViewModal({
</Descriptions.Item>
<Descriptions.Item label="申报专业能力证明材料" span={2}>
{proofFiles.length ? (
<Space direction="vertical" size={4}>
<div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
{proofFiles.map((file, index) => {
const fileName = file.name || file.fileName || `专业能力证明${index + 1}.pdf`;
return (
<Space key={`${fileName}-${index}`}>
<div key={`${fileName}-${index}`} style={{ display: "flex", alignItems: "center", gap: 8 }}>
<Button
type="link"
size="small"
@ -147,10 +152,10 @@ export default function StaffViewModal({
查看附件
</Button>
<span>{fileName}</span>
</Space>
</div>
);
})}
</Space>
</div>
) : "-"}
</Descriptions.Item>
</Descriptions>
@ -226,4 +231,4 @@ export default function StaffViewModal({
</Modal>
</>
);
}
}

View File

@ -1,20 +1,35 @@
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, DatePicker, Descriptions, Form, Input, message, Modal, Select } from "antd";
import {
Button,
DatePicker,
Descriptions,
Form,
Input,
message,
Modal,
Select,
Table,
Tag,
Upload,
} from "antd";
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
import { UploadOutlined } from "@ant-design/icons";
import { useEffect, useState } from "react";
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 Table from "zy-react-library/components/Table";
import Upload from "zy-react-library/components/Upload";
import useTable from "zy-react-library/hooks/useTable";
import { asId } from "~/api/enterpriseInfo/idUtil";
import { NS_STAFF_INFO, NS_STAFF_RESIGNATION_APPLY } from "~/enumerate/namespace";
import { safeListRequest, safeRequest } from "~/utils";
import { formSelectField, getResignAuditStatusLabel } from "~/utils/enterpriseForm";
import { DEFAULT_UPLOAD_FILE_URL, mockUploadFileList, resolveUploadFileId } from "~/utils/mockUpload";
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
import ControlWrapper from "@cqsjjb/jjb-react-admin-component/ControlWrapper";
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
import { tools } from "@cqsjjb/jjb-common-lib";
import {
NS_STAFF_INFO,
NS_STAFF_RESIGNATION_APPLY,
} from "~/enumerate/namespace";
import {
RESIGN_AUDIT_STATUS_LABEL,
AUDIT_STATUS_COLOR,
} from "~/utils/enterpriseForm";
const RESIGN_AUDIT_STATUS = { 0: "未审核", 1: "已审核", 2: "已退回" };
const { router } = tools;
function ResignationApplyPage(props) {
const [addModalOpen, setAddModalOpen] = useState(false);
@ -22,27 +37,39 @@ function ResignationApplyPage(props) {
const [currentId, setCurrentId] = useState("");
const [staffOptions, setStaffOptions] = useState([]);
const [searchForm] = Form.useForm();
const { staffResignationApply } = props;
const {
staffResignationApplyList: dataSource,
staffResignationApplyTotal: total,
staffResignationApplyLoading: loading,
} = staffResignationApply || {};
const { tableProps, getData } = useTable(safeListRequest(props.staffResignationApplyList), { form: searchForm });
const handleSearch = () => {
props.staffResignationApplyList({ ...router.query });
};
useEffect(() => {
getData();
searchForm.setFieldsValue(router.query);
handleSearch();
}, []);
useEffect(() => {
let cancelled = false;
props.staffInfoList?.({ pageIndex: 1, pageSize: 500 }).then((res) => {
if (!cancelled) {
setStaffOptions((res?.data || []).map((s) => ({
label: `${s.staffName}${s.account}`,
value: asId(s.id),
staffName: s.staffName,
account: s.account,
})));
}
}).catch((err) => {
console.warn("[ResignationApply] load staff options failed:", err);
});
props
.staffInfoList?.({ current: 1, size: 500 })
.then((res) => {
if (!cancelled) {
setStaffOptions(
(res?.data || []).map((s) => ({
label: `${s.userName ?? s.staffName}${s.account}`,
value: s.id,
staffName: s.userName ?? s.staffName,
account: s.account,
})),
);
}
})
.catch(() => {});
return () => {
cancelled = true;
};
@ -58,82 +85,126 @@ function ResignationApplyPage(props) {
</div>
</div>
}
extra={
<Button
type="primary"
style={{ marginTop: 16 }}
onClick={() => {
setCurrentId("");
setAddModalOpen(true);
}}
>
新增离职申请
</Button>
}
>
<Search
<SearchForm
style={{ marginBottom: 24 }}
form={searchForm}
options={[
{ name: "likeStaffName", label: "姓名", placeholder: "请输入姓名", colProps: { xs: 24, sm: 12, md: 8, lg: 6 } },
formSelectField(
"auditStatus",
"离职申请审核状态",
Object.entries(RESIGN_AUDIT_STATUS).map(([value, label]) => ({ label, value: Number(value) })),
{ colProps: { xs: 24, sm: 12, md: 8, lg: 8 }, placeholder: "请选择审核状态" },
),
loading={loading}
formLine={[
<Form.Item key="applicantName" name="applicantName">
<ControlWrapper.Input
label="姓名"
placeholder="请输入"
allowClear
/>
</Form.Item>,
<Form.Item key="auditStatusCode" name="auditStatusCode">
<ControlWrapper.Select
label="离职申请审核状态"
placeholder="请选择"
allowClear
style={{ width: "100%" }}
>
{Object.entries(RESIGN_AUDIT_STATUS_LABEL).map(
([value, label]) => (
<Select.Option key={value} value={Number(value)}>
{label}
</Select.Option>
),
)}
</ControlWrapper.Select>
</Form.Item>,
]}
onFinish={getData}
onReset={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
onFinish={() => {
router.query = { ...router.query, current: 1, size: 10 };
handleSearch();
}}
/>
<Table
toolBarRender={() => (
<Button
type="primary"
icon={<AddIcon />}
onClick={() => {
setCurrentId("");
setAddModalOpen(true);
}}
>
新增离职申请
</Button>
)}
rowKey="id"
columns={[
{ title: "账户", dataIndex: "account" },
{ title: "姓名", dataIndex: "applicantName" },
{ title: "部门", dataIndex: "deptName" },
{ title: "申请时间", dataIndex: "applyTime" },
{ title: "账户", dataIndex: "account", width: 140 },
{ title: "姓名", dataIndex: "applicantName", width: 100 },
{ title: "部门", dataIndex: "deptName", width: 160 },
{ title: "申请时间", dataIndex: "applyTime", width: 160 },
{
title: "离职申请审核状态",
dataIndex: "auditStatus",
render: (_, record) => getResignAuditStatusLabel(record),
dataIndex: "auditStatusCode",
width: 140,
render: (_, record) => {
const code = record.auditStatusCode ?? record.auditStatus;
const label = RESIGN_AUDIT_STATUS_LABEL[code] ?? "-";
return <Tag color={AUDIT_STATUS_COLOR[code]}>{label}</Tag>;
},
},
{
title: "操作",
width: 100,
width: 80,
render: (_, record) => (
<Button
type="link"
onClick={() => {
setCurrentId(asId(record.id));
setViewModalOpen(true);
}}
>
查看
</Button>
<TableAction>
<Button
type="link"
size="small"
onClick={() => {
setCurrentId(record.id);
setViewModalOpen(true);
}}
>
查看
</Button>
</TableAction>
),
},
]}
{...tableProps}
dataSource={dataSource}
scroll={{ y: props.scrollY }}
loading={loading}
pagination={{
total,
showSizeChanger: true,
showQuickJumper: true,
showTotal: (t) => `${t}`,
current: router.query.current || 1,
pageSize: router.query.size || 10,
onChange: (page, pageSize) => {
router.query = { ...router.query, current: page, size: pageSize };
handleSearch();
},
}}
/>
{addModalOpen && (
<AddModal
open={addModalOpen}
staffOptions={staffOptions}
requestAdd={props.staffResignationApplyAdd}
onCancel={() => setAddModalOpen(false)}
onSuccess={getData}
/>
)}
{viewModalOpen && (
<ViewModal
open={viewModalOpen}
currentId={currentId}
requestDetails={props.staffResignationApplyInfo}
onCancel={() => {
setViewModalOpen(false);
setCurrentId("");
}}
/>
)}
<AddModal
open={addModalOpen}
staffOptions={staffOptions}
requestAdd={props.staffResignationApplyAdd}
onCancel={() => setAddModalOpen(false)}
onSuccess={handleSearch}
/>
<ViewModal
open={viewModalOpen}
currentId={currentId}
requestDetail={props.staffResignationApplyInfo}
onCancel={() => {
setViewModalOpen(false);
setCurrentId("");
}}
/>
</PageLayout>
);
}
@ -141,36 +212,33 @@ function ResignationApplyPage(props) {
function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [uploading, setUploading] = useState(false);
const handleCancel = () => {
form.resetFields();
onCancel();
};
const onApplicantChange = (personnelId) => {
const staff = staffOptions.find((s) => String(s.value) === String(personnelId));
form.setFieldsValue({
applicantName: staff?.staffName || "",
});
};
const handleSubmit = async (values) => {
const handleSubmit = async () => {
try {
const values = await form.validateFields();
setSubmitting(true);
const staff = staffOptions.find((s) => String(s.value) === String(values.personnelId));
values.applicantName = staff?.staffName || values.applicantName;
values.attachmentId = resolveUploadFileId(values.resignReport);
values.applyTime =
values.applyTime?.format?.("YYYY-MM-DD HH:mm:ss") || values.applyTime;
values.expectedResignDate =
values.expectedResignDate?.format?.("YYYY-MM-DD") ||
values.expectedResignDate;
const res = await requestAdd(values);
if (res?.success !== false) {
message.success("提交成功");
handleCancel();
onSuccess();
}
else {
} else {
message.error(res?.message || "提交失败");
}
}
finally {
} catch {
// validation error
} finally {
setSubmitting(false);
}
};
@ -183,9 +251,9 @@ function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
width={720}
confirmLoading={submitting}
onCancel={handleCancel}
onOk={form.submit}
onOk={handleSubmit}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form form={form} layout="vertical">
<Form.Item
name="personnelId"
label="申请人"
@ -196,12 +264,8 @@ function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
placeholder="请选择申请人"
options={staffOptions}
optionFilterProp="label"
onChange={onApplicantChange}
/>
</Form.Item>
<Form.Item name="applicantName" hidden>
<Input />
</Form.Item>
<Form.Item
name="applyTime"
label="申请时间"
@ -210,14 +274,14 @@ function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
<DatePicker showTime style={{ width: "100%" }} />
</Form.Item>
<Form.Item
name="expectedLeaveDate"
name="expectedResignDate"
label="预计离职日期"
rules={[{ required: true, message: "请选择预计离职日期" }]}
>
<DatePicker style={{ width: "100%" }} />
</Form.Item>
<Form.Item
name="leaveReason"
name="resignReason"
label="离职原因"
rules={[{ required: true, message: "请输入离职原因" }]}
>
@ -227,25 +291,40 @@ function AddModal({ open, staffOptions, requestAdd, onCancel, onSuccess }) {
<Input.TextArea rows={2} placeholder="请输入备注" />
</Form.Item>
<Form.Item
name="resignReport"
name="reportFileUrl"
label="离职通知报告"
rules={[{ required: true, message: "请上传离职通知报告" }]}
valuePropName="fileList"
getValueFromEvent={(e) => e?.fileList}
initialValue={mockUploadFileList("离职通知报告.pdf")}
>
<Upload
accept=".pdf"
action={`${window.process.env.app.API_HOST}/safetyEval/file/upload`}
showUploadList={false}
maxCount={1}
tip="联调阶段默认通过,使用固定文件地址"
/>
accept=".pdf"
onChange={(info) => {
if (info.file.status === "uploading") {
setUploading(true);
} else {
setUploading(false);
if (info.file.status === "done") {
const data = info.file.response?.data;
if (data) {
form.setFieldsValue({ reportFileUrl: data.url || data });
}
}
}
}}
>
<Button loading={uploading} icon={<UploadOutlined />}>
上传文件
</Button>
</Upload>
</Form.Item>
</Form>
</Modal>
);
}
function ViewModal({ open, currentId, requestDetails, onCancel }) {
function ViewModal({ open, currentId, requestDetail, onCancel }) {
const [info, setInfo] = useState({});
const [detailLoading, setDetailLoading] = useState(false);
@ -256,21 +335,13 @@ function ViewModal({ open, currentId, requestDetails, onCancel }) {
}
let cancelled = false;
setDetailLoading(true);
safeRequest(requestDetails, { id: currentId }).then((res) => {
if (!cancelled) {
const data = res?.data || {};
setInfo({
...data,
resignReport: data.attachmentId
? [{ name: "离职通知报告.pdf", fileName: "离职通知报告.pdf", url: data.attachmentId || DEFAULT_UPLOAD_FILE_URL }]
: [],
});
}
}).finally(() => {
if (!cancelled) {
setDetailLoading(false);
}
});
requestDetail({ id: currentId })
.then((res) => {
if (!cancelled) setInfo(res?.data || {});
})
.finally(() => {
if (!cancelled) setDetailLoading(false);
});
return () => {
cancelled = true;
};
@ -288,17 +359,26 @@ function ViewModal({ open, currentId, requestDetails, onCancel }) {
onCancel={onCancel}
>
<Descriptions bordered column={1} labelStyle={{ width: 120 }}>
<Descriptions.Item label="申请人">{info.applicantName}</Descriptions.Item>
<Descriptions.Item label="申请人">
{info.applicantName}
</Descriptions.Item>
<Descriptions.Item label="申请时间">{info.applyTime}</Descriptions.Item>
<Descriptions.Item label="预计离职日期">{info.expectedLeaveDate}</Descriptions.Item>
<Descriptions.Item label="离职原因">{info.leaveReason}</Descriptions.Item>
<Descriptions.Item label="预计离职日期">
{info.expectedResignDate}
</Descriptions.Item>
<Descriptions.Item label="离职原因">
{info.resignReason}
</Descriptions.Item>
<Descriptions.Item label="备注">{info.remark}</Descriptions.Item>
<Descriptions.Item label="离职通知报告">
{info.resignReport?.[0]?.fileName || info.resignReport?.[0]?.name || "-"}
{info.reportFileUrl || "-"}
</Descriptions.Item>
</Descriptions>
</Modal>
);
}
export default Connect([NS_STAFF_RESIGNATION_APPLY, NS_STAFF_INFO], true)(ResignationApplyPage);
export default Connect(
[NS_STAFF_RESIGNATION_APPLY, NS_STAFF_INFO],
true,
)(AntdTableFuncControl(ResignationApplyPage));