safety-eval-service-frontend/src/pages/Container/EnterpriseInfo/DepartmentPosition/index.js

486 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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";
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 { NS_ORG_DEPARTMENT, NS_ORG_POSITION, NS_STAFF_INFO } from "~/enumerate/namespace";
import { asId, sameId } from "~/utils/enterpriseInfo/idUtil";
import { safeListRequest, safeRequest } from "~/utils";
function findDeptNode(nodes = [], id) {
for (const node of nodes) {
if (sameId(node.id, id)) {
return node;
}
if (node.children?.length) {
const found = findDeptNode(node.children, id);
if (found) {
return found;
}
}
}
return null;
}
function toSelectedDept(node) {
if (!node) {
return null;
}
return {
id: asId(node.id),
deptName: node.deptName || node.title,
leaderAccount: node.leaderAccount,
leaderName: node.leaderName,
deptLevel: node.deptLevel,
};
}
function DepartmentPositionPage(props) {
const [selectedDept, setSelectedDept] = useState(null);
const [treeData, setTreeData] = useState([]);
const [deptModalOpen, setDeptModalOpen] = useState(false);
const [positionModalOpen, setPositionModalOpen] = useState(false);
const [currentId, setCurrentId] = useState("");
const [filterKeyword, setFilterKeyword] = useState("");
const [staffOptions, setStaffOptions] = useState([]);
const [positionForm] = Form.useForm();
const [modalForm] = Form.useForm();
useEffect(() => {
(async () => {
try {
const res = await props.staffInfoList?.({ pageIndex: 1, pageSize: 500 });
setStaffOptions((res?.data || []).map((s) => ({
label: `${s.userName ?? s.staffName}${s.account}`,
value: s.account,
staffName: s.userName ?? s.staffName,
})));
}
catch (err) {
console.warn("[DepartmentPosition] load staff options failed:", err);
}
})();
}, []);
const { tableProps: positionTableProps, getData: getPositionData } = useTable(
safeListRequest(props.orgPositionList),
{
form: positionForm,
transform: (formData) => ({
...formData,
eqDeptId: asId(selectedDept?.id),
}),
},
);
const loadTree = async () => {
try {
const res = await props.orgDepartmentTree();
const normalizeTree = (nodes = []) => nodes.map((node) => ({
...node,
title: node.title || node.deptName,
deptName: node.deptName || node.title,
children: node.children ? normalizeTree(node.children) : [],
}));
const tree = normalizeTree(res?.data || []);
setTreeData(tree);
if (!selectedDept && tree.length) {
setSelectedDept(toSelectedDept(tree[0]));
}
}
catch (err) {
console.warn("[DepartmentPosition] loadTree failed:", err);
setTreeData([]);
}
};
useEffect(() => {
loadTree();
}, []);
useEffect(() => {
if (selectedDept?.id) {
getPositionData();
}
}, [selectedDept?.id]);
const filteredTree = useMemo(() => {
if (!filterKeyword) {
return treeData;
}
const filterNodes = (nodes) => {
return nodes
.map((node) => {
const children = node.children ? filterNodes(node.children) : [];
const match = (node.deptName || node.title)?.includes(filterKeyword);
if (match || children.length) {
return { ...node, children };
}
return null;
})
.filter(Boolean);
};
return filterNodes(treeData);
}, [treeData, filterKeyword]);
const onDeleteDept = (id) => {
Modal.confirm({
title: "提示",
content: "数据将会删除,您是否确认删除?",
okText: "是",
cancelText: "否",
onOk: async () => {
const res = await props.orgDepartmentRemove({ id });
if (res?.success !== false) {
message.success("删除成功");
if (sameId(selectedDept?.id, id)) {
setSelectedDept(null);
}
loadTree();
}
},
});
};
const onDeletePosition = (id) => {
Modal.confirm({
title: "提示",
content: "数据将会删除,您是否确认删除?",
okText: "是",
cancelText: "否",
onOk: async () => {
const res = await props.orgPositionRemove({ id });
if (res?.success !== false) {
message.success("删除成功");
getPositionData();
}
},
});
};
const closeDeptModal = () => {
modalForm.resetFields();
setCurrentId("");
setDeptModalOpen(false);
};
const closePositionModal = () => {
modalForm.resetFields();
setCurrentId("");
setPositionModalOpen(false);
};
const openDeptModal = (record) => {
setCurrentId(asId(record?.id) || "");
setDeptModalOpen(true);
};
const openPositionModal = (record) => {
if (!selectedDept?.id) {
message.warning("请先选择部门");
return;
}
setCurrentId(asId(record?.id) || "");
modalForm.resetFields();
if (record) {
modalForm.setFieldsValue({
...record,
deptId: asId(record.deptId || selectedDept.id),
deptName: record.deptName || selectedDept.deptName,
});
}
else {
modalForm.setFieldsValue({ deptId: asId(selectedDept.id), deptName: selectedDept.deptName });
}
setPositionModalOpen(true);
};
const submitPosition = async (values) => {
const request = currentId ? props.orgPositionEdit : props.orgPositionAdd;
const payload = { ...values };
if (currentId) {
payload.id = currentId;
}
payload.deptId = asId(values.deptId || selectedDept?.id);
const res = await request(payload);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
closePositionModal();
getPositionData();
}
else {
message.error(res?.message || "操作失败");
}
};
return (
<PageLayout title="部门岗位管理">
<div style={{ display: "flex", gap: 16, minHeight: 480 }}>
<div style={{ width: 260, borderRight: "1px solid #f0f0f0", paddingRight: 16, flexShrink: 0 }}>
<Space direction="vertical" style={{ width: "100%", marginBottom: 12 }}>
<Button type="primary" icon={<AddIcon />} block onClick={() => openDeptModal()}>
新增部门
</Button>
<Input.Search
placeholder="输入关键字进行过滤"
allowClear
onChange={(e) => setFilterKeyword(e.target.value)}
/>
</Space>
<Tree
selectedKeys={selectedDept?.id ? [asId(selectedDept.id)] : []}
treeData={filteredTree}
fieldNames={{ title: "deptName", key: "id", children: "children" }}
onSelect={(_, { node }) => {
setSelectedDept(toSelectedDept(node));
}}
/>
</div>
<div style={{ flex: 1, minWidth: 0 }}>
{selectedDept?.id ? (
<>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }}>
<span>
当前部门
<strong>{selectedDept.deptName}</strong>
</span>
<Space>
<Button onClick={() => openDeptModal(selectedDept)}>编辑部门</Button>
<Button danger onClick={() => onDeleteDept(selectedDept.id)}>删除部门</Button>
</Space>
</div>
<Search
form={positionForm}
options={[{ name: "likePositionName", label: "岗位名称", placeholder: "请输入岗位名称" }]}
onFinish={getPositionData}
/>
<Table
toolBarRender={() => (
<Button type="primary" icon={<AddIcon />} onClick={() => openPositionModal()}>
新增岗位
</Button>
)}
columns={[
{ title: "名称", dataIndex: "positionName" },
{ title: "职责", dataIndex: "dutyDesc" },
{ title: "备注", dataIndex: "remark" },
{
title: "操作",
width: 160,
render: (_, record) => (
<TableAction>
<Button type="link" onClick={() => openPositionModal(record)}>编辑</Button>
<Button danger type="link" onClick={() => onDeletePosition(record.id)}>删除</Button>
</TableAction>
),
},
]}
{...positionTableProps}
/>
</>
) : (
<Empty description="请在左侧选择部门,查看该部门下的岗位" />
)}
</div>
</div>
{deptModalOpen && (
<DeptFormModal
open={deptModalOpen}
currentId={currentId}
staffOptions={staffOptions}
treeData={treeData}
requestAdd={props.orgDepartmentAdd}
requestEdit={props.orgDepartmentEdit}
requestDetails={props.orgDepartmentGet}
onCancel={closeDeptModal}
onSuccess={loadTree}
/>
)}
{positionModalOpen && (
<Modal
open={positionModalOpen}
destroyOnHidden
title={currentId ? "编辑岗位" : "添加岗位"}
width={560}
onCancel={closePositionModal}
onOk={modalForm.submit}
>
<Form form={modalForm} layout="vertical" onFinish={submitPosition}>
<Form.Item name="deptId" hidden>
<Input />
</Form.Item>
<Form.Item name="deptName" label="所属部门">
<Input disabled />
</Form.Item>
<Form.Item
name="positionName"
label="岗位名称"
rules={[{ required: true, message: "请输入岗位名称" }]}
>
<Input placeholder="请输入岗位名称" />
</Form.Item>
<Form.Item
name="dutyDesc"
label="岗位职责"
rules={[{ required: true, message: "请输入岗位职责" }]}
>
<Input.TextArea rows={3} placeholder="请输入岗位职责" showCount maxLength={500} />
</Form.Item>
<Form.Item name="remark" label="备注">
<Input.TextArea rows={2} placeholder="请输入备注" showCount maxLength={500} />
</Form.Item>
</Form>
</Modal>
)}
</PageLayout>
);
}
function resolveLeaderAccount(dept, options) {
if (dept?.leaderAccount) {
return dept.leaderAccount;
}
if (dept?.leaderName) {
const matched = options.find((item) => item.staffName === dept.leaderName);
return matched?.value;
}
return undefined;
}
function DeptFormModal({
open,
currentId,
staffOptions,
treeData,
requestAdd,
requestEdit,
requestDetails,
onCancel,
onSuccess,
}) {
const [form] = Form.useForm();
const [submitting, setSubmitting] = useState(false);
const [detailLoading, setDetailLoading] = useState(false);
const staffOptionsRef = useRef(staffOptions);
const treeDataRef = useRef(treeData);
const requestDetailsRef = useRef(requestDetails);
staffOptionsRef.current = staffOptions;
treeDataRef.current = treeData;
requestDetailsRef.current = requestDetails;
useEffect(() => {
if (!open) {
return;
}
form.resetFields();
if (!currentId) {
return;
}
let cancelled = false;
setDetailLoading(true);
const loadDetail = async () => {
let dept = null;
const getDetails = requestDetailsRef.current;
if (typeof getDetails === "function") {
const res = await safeRequest(getDetails, { id: currentId });
dept = res?.data || null;
}
if (!dept) {
dept = findDeptNode(treeDataRef.current, currentId);
}
if (!cancelled && dept) {
form.setFieldsValue({
...dept,
leaderAccount: resolveLeaderAccount(dept, staffOptionsRef.current),
leaderName: dept.leaderName,
});
}
};
loadDetail().finally(() => {
if (!cancelled) {
setDetailLoading(false);
}
});
return () => {
cancelled = true;
};
}, [open, currentId]);
const onLeaderChange = (account) => {
const staff = staffOptions.find((s) => s.value === account);
form.setFieldValue("leaderName", staff?.staffName || "");
};
const handleSubmit = async (values) => {
try {
setSubmitting(true);
const request = currentId ? requestEdit : requestAdd;
const payload = { ...values };
if (currentId) {
payload.id = currentId;
}
const res = await request(payload);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
form.resetFields();
onCancel();
await onSuccess();
}
else {
message.error(res?.message || "操作失败");
}
}
finally {
setSubmitting(false);
}
};
return (
<Modal
open={open}
destroyOnHidden
title={currentId ? "编辑部门" : "添加部门"}
width={560}
loading={detailLoading}
confirmLoading={submitting}
onCancel={onCancel}
onOk={form.submit}
>
<Form form={form} layout="vertical" onFinish={handleSubmit}>
<Form.Item
name="deptName"
label="部门名称"
rules={[{ required: true, message: "请输入部门名称" }]}
>
<Input placeholder="请输入部门名称" />
</Form.Item>
<Form.Item name="leaderAccount" label="负责人">
<Select
allowClear
showSearch
placeholder="请选择负责人"
options={staffOptions}
optionFilterProp="label"
onChange={onLeaderChange}
/>
</Form.Item>
<Form.Item name="leaderName" hidden>
<Input />
</Form.Item>
<Form.Item name="deptLevel" label="部门级别">
<Input placeholder="请输入部门级别" />
</Form.Item>
</Form>
</Modal>
);
}
export default Connect([NS_ORG_DEPARTMENT, NS_ORG_POSITION, NS_STAFF_INFO], true)(DepartmentPositionPage);