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

486 lines
14 KiB
JavaScript
Raw Normal View History

2026-06-23 18:07:30 +08:00
import { Connect } from "@cqsjjb/jjb-dva-runtime";
import { Button, Empty, Form, Input, message, Modal, Row, Col, Select, Space, Tree } from "antd";
import { useEffect, useMemo, useRef, useState } from "react";
import AddIcon from "zy-react-library/components/Icon/AddIcon";
import Page from "zy-react-library/components/Page";
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 { safeListRequest, safeRequest } from "~/utils";
import PageHeader from "../components/PageHeader";
function findDeptNode(nodes = [], id) {
for (const node of nodes) {
if (String(node.id) === String(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: 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.staffName}${s.account}`,
value: s.account,
staffName: 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: selectedDept?.id != null ? Number(selectedDept.id) : undefined,
}),
},
);
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 (String(selectedDept?.id) === String(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(record?.id || "");
setDeptModalOpen(true);
};
const openPositionModal = (record) => {
if (!selectedDept?.id) {
message.warning("请先选择部门");
return;
}
setCurrentId(record?.id || "");
modalForm.resetFields();
if (record) {
modalForm.setFieldsValue({
...record,
deptId: record.deptId || selectedDept.id,
deptName: record.deptName || selectedDept.deptName,
});
}
else {
modalForm.setFieldsValue({ deptId: 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 = values.deptId || selectedDept?.id;
const res = await request(payload);
if (res?.success !== false) {
message.success(currentId ? "编辑成功" : "添加成功");
closePositionModal();
getPositionData();
}
else {
message.error(res?.message || "操作失败");
}
};
return (
<Page isShowAllAction={false}>
<PageHeader 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 ? [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) => (
<Space>
<Button type="link" onClick={() => openPositionModal(record)}>编辑</Button>
<Button danger type="link" onClick={() => onDeletePosition(record.id)}>删除</Button>
</Space>
),
},
]}
{...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}
destroyOnClose
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>
)}
</Page>
);
}
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}
destroyOnClose
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);