代码提交
parent
c31b85c4c1
commit
de5a1a4dc6
|
|
@ -10,10 +10,10 @@ module.exports = {
|
|||
javaGitBranch: "dev",
|
||||
// 本地联调 safetyEval-service(context-path: /safetyEval,默认端口 8095)
|
||||
// 可通过环境变量覆盖: SAFETY_EVAL_API_HOST=http://192.168.x.x:8095
|
||||
// API_HOST: "https://gbs-gateway.qhdsafety.com",
|
||||
API_HOST: "http://localhost:80",
|
||||
|
||||
// API_HOST: "http://192.168.0.134",
|
||||
API_HOST: "http://192.168.0.150", //太浅
|
||||
// API_HOST: "http://192.168.0.150", //太浅
|
||||
// API_HOST: "http://192.168.0.152",
|
||||
//API_HOST: "http://192.168.0.103", //huwei
|
||||
},
|
||||
|
|
|
|||
|
|
@ -8,6 +8,12 @@ export const staffInfoList = declareRequest(
|
|||
"staffInfoList: [] | res.data || [] & staffInfoTotal: 0 | res.total || 0",
|
||||
);
|
||||
|
||||
export const staffInfoDeptPersonCount = declareRequest(
|
||||
"staffInfoLoading",
|
||||
"Get > /safetyEval/org-personnel/deptPersonCount",
|
||||
"staffInfoDeptPersonCount: [] | res.data || []",
|
||||
);
|
||||
|
||||
export const staffInfoGet = declareRequest(
|
||||
"staffInfoLoading",
|
||||
"Get > /safetyEval/org-personnel/get",
|
||||
|
|
|
|||
|
|
@ -12,13 +12,14 @@ import {
|
|||
Select,
|
||||
Space,
|
||||
Table,
|
||||
Tree,
|
||||
TreeSelect,
|
||||
Upload,
|
||||
Tag,
|
||||
} from "antd";
|
||||
import { InboxOutlined } from "@ant-design/icons";
|
||||
import { Get } from "@cqsjjb/jjb-common-lib/http";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import dayjs from "dayjs";
|
||||
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
||||
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
|
||||
|
|
@ -49,11 +50,13 @@ import { idCardRule, mobileRule } from "~/utils/validators";
|
|||
import AttachmentUpload from "~/components/AttachmentUpload";
|
||||
import StaffViewModal from "~/components/StaffViewModal";
|
||||
import StaffResumeModal from "~/components/StaffResumeModal";
|
||||
import { asId } from "~/utils/enterpriseInfo/idUtil";
|
||||
import "./index.less";
|
||||
|
||||
const { router } = tools;
|
||||
const API_HOST = window.process?.env?.app?.API_HOST || "";
|
||||
const evalCertLevelMap = CERT_LEVEL_LABEL_BY_TYPE.evaluator;
|
||||
const ALL_DEPT_KEY = "ALL";
|
||||
|
||||
function PersonnelInfoPage(props) {
|
||||
const [formModalOpen, setFormModalOpen] = useState(false);
|
||||
|
|
@ -65,6 +68,11 @@ function PersonnelInfoPage(props) {
|
|||
const [searchForm] = Form.useForm();
|
||||
const [deptOptions, setDeptOptions] = useState([]);
|
||||
const [positionOptions, setPositionOptions] = useState([]);
|
||||
const [treeData, setTreeData] = useState([]);
|
||||
const [deptCountMap, setDeptCountMap] = useState({});
|
||||
const [selectedDeptId, setSelectedDeptId] = useState(
|
||||
asId(router.query.deptId) || ALL_DEPT_KEY,
|
||||
);
|
||||
const { staffInfo, orgDepartmentTree, orgDepartment } = props;
|
||||
const { orgDepartmentTreeData } = orgDepartment;
|
||||
const {
|
||||
|
|
@ -73,8 +81,36 @@ function PersonnelInfoPage(props) {
|
|||
staffInfoLoading: loading,
|
||||
} = staffInfo || {};
|
||||
|
||||
const loadDeptPersonCount = async () => {
|
||||
try {
|
||||
const res = await props.staffInfoDeptPersonCount?.();
|
||||
const map = {};
|
||||
(res?.data || []).forEach((item) => {
|
||||
const id = asId(item.deptId);
|
||||
if (id) {
|
||||
map[id] = Number(item.personCount) || 0;
|
||||
}
|
||||
});
|
||||
setDeptCountMap(map);
|
||||
} catch (err) {
|
||||
console.warn("[PersonnelInfo] load deptPersonCount failed:", err);
|
||||
setDeptCountMap({});
|
||||
}
|
||||
};
|
||||
|
||||
const loadTree = async () => {
|
||||
try {
|
||||
const res = await orgDepartmentTree();
|
||||
setTreeData(res?.data || []);
|
||||
} catch (err) {
|
||||
console.warn("[PersonnelInfo] loadTree failed:", err);
|
||||
setTreeData([]);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
orgDepartmentTree();
|
||||
loadTree();
|
||||
loadDeptPersonCount();
|
||||
let cancelled = false;
|
||||
(async () => {
|
||||
try {
|
||||
|
|
@ -100,20 +136,62 @@ function PersonnelInfoPage(props) {
|
|||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
searchForm.setFieldsValue(router.query);
|
||||
const { deptId: _ignored, ...rest } = router.query || {};
|
||||
searchForm.setFieldsValue(rest);
|
||||
handleSearch();
|
||||
}, []);
|
||||
|
||||
const handleSearch = () => {
|
||||
props.staffInfoList({ ...router.query });
|
||||
const deptId =
|
||||
selectedDeptId && selectedDeptId !== ALL_DEPT_KEY
|
||||
? asId(selectedDeptId)
|
||||
: undefined;
|
||||
props.staffInfoList({
|
||||
...router.query,
|
||||
deptId,
|
||||
});
|
||||
};
|
||||
|
||||
const goCertificate = (id, staffName) => {
|
||||
props.history.push(
|
||||
`Certificate?staffId=${id}&staffName=${encodeURIComponent(staffName || "")}`,
|
||||
);
|
||||
const refreshListAndCount = () => {
|
||||
handleSearch();
|
||||
loadDeptPersonCount();
|
||||
};
|
||||
|
||||
const applyDeptFilter = (deptId) => {
|
||||
const nextId = deptId || ALL_DEPT_KEY;
|
||||
setSelectedDeptId(nextId);
|
||||
const queryDeptId = nextId === ALL_DEPT_KEY ? undefined : asId(nextId);
|
||||
router.query = {
|
||||
...router.query,
|
||||
deptId: queryDeptId,
|
||||
current: 1,
|
||||
};
|
||||
props.staffInfoList({
|
||||
...router.query,
|
||||
deptId: queryDeptId,
|
||||
});
|
||||
};
|
||||
|
||||
const displayTree = useMemo(
|
||||
() => [
|
||||
{
|
||||
id: ALL_DEPT_KEY,
|
||||
deptName: "全部人员",
|
||||
children: treeData,
|
||||
},
|
||||
],
|
||||
[treeData],
|
||||
);
|
||||
|
||||
const totalPersonCount = useMemo(
|
||||
() =>
|
||||
Object.values(deptCountMap).reduce(
|
||||
(sum, n) => sum + (Number(n) || 0),
|
||||
0,
|
||||
),
|
||||
[deptCountMap],
|
||||
);
|
||||
|
||||
const onDelete = (id) => {
|
||||
Modal.confirm({
|
||||
title: "提示",
|
||||
|
|
@ -124,7 +202,7 @@ function PersonnelInfoPage(props) {
|
|||
const res = await props.staffInfoRemove({ id });
|
||||
if (res?.success !== false) {
|
||||
message.success("删除成功");
|
||||
handleSearch();
|
||||
refreshListAndCount();
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
@ -160,166 +238,187 @@ function PersonnelInfoPage(props) {
|
|||
</Space>
|
||||
}
|
||||
>
|
||||
<SearchForm
|
||||
style={{ marginBottom: 24 }}
|
||||
form={searchForm}
|
||||
loading={loading}
|
||||
formLine={[
|
||||
<Form.Item
|
||||
key="userName"
|
||||
name="userName"
|
||||
rules={[{ max: 50, message: "用户名称不能超过50个字符" }]}
|
||||
>
|
||||
<ControlWrapper.Input
|
||||
label="用户名称"
|
||||
placeholder="请输入用户名称"
|
||||
allowClear
|
||||
maxLength={50}
|
||||
/>
|
||||
</Form.Item>,
|
||||
<Form.Item key="deptId" name="deptId">
|
||||
<ControlWrapper.TreeSelect
|
||||
placeholder="请选择部门"
|
||||
treeData={orgDepartmentTreeData}
|
||||
allowClear
|
||||
fieldNames={{
|
||||
label: "deptName",
|
||||
value: "id",
|
||||
key: "id",
|
||||
}}
|
||||
treeDefaultExpandAll
|
||||
showSearch
|
||||
></ControlWrapper.TreeSelect>
|
||||
</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>,
|
||||
]}
|
||||
onReset={(value) => {
|
||||
router.query = { ...value, current: 1, size: 10 };
|
||||
handleSearch();
|
||||
}}
|
||||
onFinish={(value) => {
|
||||
router.query = { ...value, current: 1, size: 10 };
|
||||
handleSearch();
|
||||
}}
|
||||
/>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={[
|
||||
{ title: "姓名", dataIndex: "userName", ellipsis: true, width: 150 },
|
||||
{ title: "账号", dataIndex: "account" },
|
||||
{ title: "部门", dataIndex: "deptName" },
|
||||
{ title: "岗位", dataIndex: "postName" },
|
||||
<div className="personnel-info-layout">
|
||||
<div className="personnel-info-tree">
|
||||
<div className="personnel-info-tree-title">组织机构</div>
|
||||
<Tree
|
||||
key={treeData.length ? "dept-tree-loaded" : "dept-tree-empty"}
|
||||
selectedKeys={[asId(selectedDeptId) || ALL_DEPT_KEY]}
|
||||
treeData={displayTree}
|
||||
defaultExpandAll
|
||||
fieldNames={{ title: "deptName", key: "id", children: "children" }}
|
||||
titleRender={(node) => {
|
||||
if (node.id === ALL_DEPT_KEY) {
|
||||
return `全部人员 (${totalPersonCount}人)`;
|
||||
}
|
||||
const count = deptCountMap[asId(node.id)] ?? 0;
|
||||
return `${node.deptName || ""} (${count}人)`;
|
||||
}}
|
||||
onSelect={(keys, { node }) => {
|
||||
if (!keys?.length) {
|
||||
return;
|
||||
}
|
||||
applyDeptFilter(node.id);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="personnel-info-main">
|
||||
<SearchForm
|
||||
style={{ marginBottom: 24 }}
|
||||
form={searchForm}
|
||||
loading={loading}
|
||||
formLine={[
|
||||
<Form.Item
|
||||
key="userName"
|
||||
name="userName"
|
||||
rules={[{ max: 50, message: "用户名称不能超过50个字符" }]}
|
||||
>
|
||||
<ControlWrapper.Input
|
||||
label="用户名称"
|
||||
placeholder="请输入用户名称"
|
||||
allowClear
|
||||
maxLength={50}
|
||||
/>
|
||||
</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>,
|
||||
]}
|
||||
onReset={(value) => {
|
||||
const deptId =
|
||||
selectedDeptId && selectedDeptId !== ALL_DEPT_KEY
|
||||
? asId(selectedDeptId)
|
||||
: undefined;
|
||||
router.query = { ...value, deptId, current: 1, size: 10 };
|
||||
handleSearch();
|
||||
}}
|
||||
onFinish={(value) => {
|
||||
const deptId =
|
||||
selectedDeptId && selectedDeptId !== ALL_DEPT_KEY
|
||||
? asId(selectedDeptId)
|
||||
: undefined;
|
||||
router.query = { ...value, deptId, current: 1, size: 10 };
|
||||
handleSearch();
|
||||
}}
|
||||
/>
|
||||
<Table
|
||||
rowKey="id"
|
||||
columns={[
|
||||
{ title: "姓名", dataIndex: "userName", ellipsis: true, width: 150 },
|
||||
{ title: "账号", dataIndex: "account" },
|
||||
{ title: "部门", dataIndex: "deptName" },
|
||||
{ title: "岗位", dataIndex: "postName" },
|
||||
|
||||
{
|
||||
title: "专业能力",
|
||||
dataIndex: "capabilityAssessment",
|
||||
render: (list) => {
|
||||
if (!Array.isArray(list) || !list.length) return "-";
|
||||
return list
|
||||
.map((item) => CAPABILITY_MAP[item.professionalCapabilityCode])
|
||||
.join("、");
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "证照名称",
|
||||
dataIndex: "personnelCertFilingPageCOList",
|
||||
width: 280,
|
||||
render: (_, record) => {
|
||||
const arr = [];
|
||||
if(record.registeredSafetyEngineerCert){
|
||||
arr.push(<Tag color="blue">{TITLE_LEVEL_MAP[record.registeredSafetyEngineerCert.certLevel]}注册安全工程师</Tag>);
|
||||
}
|
||||
if(record.evaluatorCert){
|
||||
arr.push(<Tag color="green">{evalCertLevelMap[record.evaluatorCert.certLevel]}注册安全评价师</Tag>);
|
||||
}
|
||||
return <Space size={4}>{arr}</Space>;
|
||||
{
|
||||
title: "专业能力",
|
||||
dataIndex: "capabilityAssessment",
|
||||
render: (list) => {
|
||||
if (!Array.isArray(list) || !list.length) return "-";
|
||||
return list
|
||||
.map((item) => CAPABILITY_MAP[item.professionalCapabilityCode])
|
||||
.join("、");
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 200,
|
||||
fixed: "right",
|
||||
render: (_, record) => (
|
||||
<TableAction>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setCurrentId(record.id);
|
||||
setViewModalOpen(true);
|
||||
}}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setCurrentId(record.id);
|
||||
setResumeModalOpen(true);
|
||||
}}
|
||||
>
|
||||
查看简历
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setCurrentId(record.id);
|
||||
setFormModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
{
|
||||
title: "证照名称",
|
||||
dataIndex: "personnelCertFilingPageCOList",
|
||||
width: 280,
|
||||
render: (_, record) => {
|
||||
const arr = [];
|
||||
if(record.registeredSafetyEngineerCert){
|
||||
arr.push(<Tag color="blue">{TITLE_LEVEL_MAP[record.registeredSafetyEngineerCert.certLevel]}注册安全工程师</Tag>);
|
||||
}
|
||||
if(record.evaluatorCert){
|
||||
arr.push(<Tag color="green">{evalCertLevelMap[record.evaluatorCert.certLevel]}注册安全评价师</Tag>);
|
||||
}
|
||||
return <Space size={4}>{arr}</Space>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: "操作",
|
||||
width: 200,
|
||||
fixed: "right",
|
||||
render: (_, record) => (
|
||||
<TableAction>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setCurrentId(record.id);
|
||||
setViewModalOpen(true);
|
||||
}}
|
||||
>
|
||||
查看
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setCurrentId(record.id);
|
||||
setResumeModalOpen(true);
|
||||
}}
|
||||
>
|
||||
查看简历
|
||||
</Button>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setCurrentId(record.id);
|
||||
setFormModalOpen(true);
|
||||
}}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => onResetPassword(record.id)}
|
||||
>
|
||||
重置密码
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => onDelete(record.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</TableAction>
|
||||
),
|
||||
},
|
||||
]}
|
||||
dataSource={dataSource}
|
||||
scroll={{ y: props.scrollY, x: 1300 }}
|
||||
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();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => onResetPassword(record.id)}
|
||||
>
|
||||
重置密码
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => onDelete(record.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</TableAction>
|
||||
),
|
||||
},
|
||||
]}
|
||||
dataSource={dataSource}
|
||||
scroll={{ y: props.scrollY, x: 1300 }}
|
||||
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();
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{formModalOpen && (
|
||||
<StaffFormModal
|
||||
|
|
@ -337,7 +436,7 @@ function PersonnelInfoPage(props) {
|
|||
setFormModalOpen(false);
|
||||
setCurrentId("");
|
||||
}}
|
||||
onSuccess={handleSearch}
|
||||
onSuccess={refreshListAndCount}
|
||||
/>
|
||||
)}
|
||||
{viewModalOpen && (
|
||||
|
|
@ -364,7 +463,7 @@ function PersonnelInfoPage(props) {
|
|||
<StaffImportModal
|
||||
open={importModalOpen}
|
||||
onCancel={() => setImportModalOpen(false)}
|
||||
onSuccess={handleSearch}
|
||||
onSuccess={refreshListAndCount}
|
||||
/>
|
||||
)}
|
||||
{resetPasswordModalOpen && (
|
||||
|
|
|
|||
|
|
@ -1,3 +1,28 @@
|
|||
.personnel-info-layout {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
min-height: 480px;
|
||||
}
|
||||
|
||||
.personnel-info-tree {
|
||||
width: 260px;
|
||||
flex-shrink: 0;
|
||||
border-right: 1px solid #f0f0f0;
|
||||
padding-right: 16px;
|
||||
overflow: auto;
|
||||
|
||||
&-title {
|
||||
font-weight: 500;
|
||||
margin-bottom: 12px;
|
||||
color: rgba(0, 0, 0, 0.85);
|
||||
}
|
||||
}
|
||||
|
||||
.personnel-info-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.register-engineer-section {
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 8px;
|
||||
|
|
|
|||
Loading…
Reference in New Issue