代码提交

dev-1.3
zhanglei 2026-08-10 10:18:38 +08:00
parent c31b85c4c1
commit de5a1a4dc6
4 changed files with 300 additions and 170 deletions

View File

@ -10,10 +10,10 @@ module.exports = {
javaGitBranch: "dev",
// 本地联调 safetyEval-servicecontext-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
},

View File

@ -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",

View File

@ -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,6 +238,31 @@ function PersonnelInfoPage(props) {
</Space>
}
>
<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}
@ -177,20 +280,6 @@ function PersonnelInfoPage(props) {
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="岗位"
@ -207,11 +296,19 @@ function PersonnelInfoPage(props) {
</Form.Item>,
]}
onReset={(value) => {
router.query = { ...value, current: 1, size: 10 };
const deptId =
selectedDeptId && selectedDeptId !== ALL_DEPT_KEY
? asId(selectedDeptId)
: undefined;
router.query = { ...value, deptId, current: 1, size: 10 };
handleSearch();
}}
onFinish={(value) => {
router.query = { ...value, current: 1, size: 10 };
const deptId =
selectedDeptId && selectedDeptId !== ALL_DEPT_KEY
? asId(selectedDeptId)
: undefined;
router.query = { ...value, deptId, current: 1, size: 10 };
handleSearch();
}}
/>
@ -320,6 +417,8 @@ function PersonnelInfoPage(props) {
},
}}
/>
</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 && (

View File

@ -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;