2026-07-08 09:37:21 +08:00
|
|
|
|
import { PlusOutlined } from "@ant-design/icons";
|
2026-06-23 18:07:30 +08:00
|
|
|
|
import { Connect } from "@cqsjjb/jjb-dva-runtime";
|
2026-07-08 09:37:21 +08:00
|
|
|
|
import {
|
|
|
|
|
|
Button,
|
|
|
|
|
|
Empty,
|
|
|
|
|
|
Form,
|
|
|
|
|
|
Input,
|
|
|
|
|
|
message,
|
|
|
|
|
|
Modal,
|
|
|
|
|
|
Select,
|
|
|
|
|
|
Space,
|
|
|
|
|
|
Tree,
|
|
|
|
|
|
Table,
|
|
|
|
|
|
TreeSelect,
|
|
|
|
|
|
} from "antd";
|
|
|
|
|
|
import { AntdTableFuncControl } from "@cqsjjb/jjb-common-decorator/antd";
|
2026-07-07 09:07:19 +08:00
|
|
|
|
import TableAction from "@cqsjjb/jjb-react-admin-component/TableAction";
|
2026-06-23 18:07:30 +08:00
|
|
|
|
import { useEffect, useMemo, useRef, useState } from "react";
|
2026-06-29 16:45:58 +08:00
|
|
|
|
import PageLayout from "@cqsjjb/jjb-react-admin-component/PageLayout";
|
2026-07-08 09:37:21 +08:00
|
|
|
|
import SearchForm from "@cqsjjb/jjb-react-admin-component/SearchForm";
|
|
|
|
|
|
|
|
|
|
|
|
import {
|
|
|
|
|
|
NS_ORG_DEPARTMENT,
|
|
|
|
|
|
NS_ORG_POSITION,
|
|
|
|
|
|
NS_STAFF_INFO,
|
|
|
|
|
|
} from "~/enumerate/namespace";
|
2026-07-07 17:13:33 +08:00
|
|
|
|
import { asId, sameId } from "~/utils/enterpriseInfo/idUtil";
|
2026-06-23 18:07:30 +08:00
|
|
|
|
|
|
|
|
|
|
function findDeptNode(nodes = [], id) {
|
|
|
|
|
|
for (const node of nodes) {
|
2026-06-25 16:30:37 +08:00
|
|
|
|
if (sameId(node.id, id)) {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
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 {
|
2026-06-25 16:30:37 +08:00
|
|
|
|
id: asId(node.id),
|
2026-06-23 18:07:30 +08:00
|
|
|
|
deptName: node.deptName || node.title,
|
2026-07-08 09:37:21 +08:00
|
|
|
|
managerAccount: node.managerAccount,
|
|
|
|
|
|
managerName: node.managerName,
|
|
|
|
|
|
deptLevelName: node.deptLevelName,
|
2026-06-23 18:07:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function DepartmentPositionPage(props) {
|
2026-07-08 14:44:41 +08:00
|
|
|
|
const { orgPosition } = props;
|
|
|
|
|
|
const { orgPositionLoading } = orgPosition;
|
2026-06-23 18:07:30 +08:00
|
|
|
|
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();
|
2026-07-08 09:37:21 +08:00
|
|
|
|
const [dataSource, setDataSource] = useState([]);
|
|
|
|
|
|
const [total, setTotal] = useState(0);
|
|
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
|
|
const [pageIndex, setPageIndex] = useState(1);
|
|
|
|
|
|
const [pageSize, setPageSize] = useState(10);
|
2026-06-23 18:07:30 +08:00
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
(async () => {
|
|
|
|
|
|
try {
|
2026-07-08 09:37:21 +08:00
|
|
|
|
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) {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
console.warn("[DepartmentPosition] load staff options failed:", err);
|
|
|
|
|
|
}
|
|
|
|
|
|
})();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
2026-07-08 09:37:21 +08:00
|
|
|
|
const fetchPositionData = async (page = 1, size = 10) => {
|
|
|
|
|
|
if (!selectedDept?.id) return;
|
|
|
|
|
|
setLoading(true);
|
|
|
|
|
|
try {
|
|
|
|
|
|
const formData = positionForm.getFieldsValue();
|
|
|
|
|
|
const res = await props.orgPositionList({
|
2026-06-23 18:07:30 +08:00
|
|
|
|
...formData,
|
2026-07-08 09:37:21 +08:00
|
|
|
|
pageIndex: page,
|
|
|
|
|
|
pageSize: size,
|
|
|
|
|
|
deptId: asId(selectedDept.id),
|
|
|
|
|
|
});
|
|
|
|
|
|
setDataSource(res?.data || []);
|
|
|
|
|
|
setTotal(res?.totalCount || 0);
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
setDataSource([]);
|
|
|
|
|
|
setTotal(0);
|
|
|
|
|
|
} finally {
|
|
|
|
|
|
setLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-06-23 18:07:30 +08:00
|
|
|
|
|
|
|
|
|
|
const loadTree = async () => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const res = await props.orgDepartmentTree();
|
2026-07-08 09:37:21 +08:00
|
|
|
|
const tree = res?.data || [];
|
2026-06-23 18:07:30 +08:00
|
|
|
|
setTreeData(tree);
|
2026-07-08 09:37:21 +08:00
|
|
|
|
if (tree.length) {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
setSelectedDept(toSelectedDept(tree[0]));
|
|
|
|
|
|
}
|
2026-07-08 09:37:21 +08:00
|
|
|
|
} catch (err) {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
console.warn("[DepartmentPosition] loadTree failed:", err);
|
|
|
|
|
|
setTreeData([]);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
loadTree();
|
|
|
|
|
|
}, []);
|
|
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
|
if (selectedDept?.id) {
|
2026-07-08 09:37:21 +08:00
|
|
|
|
fetchPositionData(1, 10);
|
2026-06-23 18:07:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
}, [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 () => {
|
2026-07-08 09:37:21 +08:00
|
|
|
|
const res = await props.orgDepartmentRemove({ data: id });
|
2026-06-23 18:07:30 +08:00
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
message.success("删除成功");
|
2026-06-25 16:30:37 +08:00
|
|
|
|
if (sameId(selectedDept?.id, id)) {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
setSelectedDept(null);
|
|
|
|
|
|
}
|
|
|
|
|
|
loadTree();
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const onDeletePosition = (id) => {
|
|
|
|
|
|
Modal.confirm({
|
|
|
|
|
|
title: "提示",
|
|
|
|
|
|
content: "数据将会删除,您是否确认删除?",
|
|
|
|
|
|
okText: "是",
|
|
|
|
|
|
cancelText: "否",
|
|
|
|
|
|
onOk: async () => {
|
2026-07-08 12:59:15 +08:00
|
|
|
|
const res = await props.orgPositionRemove({ data: id });
|
2026-06-23 18:07:30 +08:00
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
message.success("删除成功");
|
2026-07-08 09:37:21 +08:00
|
|
|
|
fetchPositionData(1, 10);
|
2026-06-23 18:07:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const closeDeptModal = () => {
|
|
|
|
|
|
modalForm.resetFields();
|
|
|
|
|
|
setCurrentId("");
|
|
|
|
|
|
setDeptModalOpen(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const closePositionModal = () => {
|
|
|
|
|
|
modalForm.resetFields();
|
|
|
|
|
|
setCurrentId("");
|
|
|
|
|
|
setPositionModalOpen(false);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const openDeptModal = (record) => {
|
2026-06-25 16:30:37 +08:00
|
|
|
|
setCurrentId(asId(record?.id) || "");
|
2026-06-23 18:07:30 +08:00
|
|
|
|
setDeptModalOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const openPositionModal = (record) => {
|
|
|
|
|
|
if (!selectedDept?.id) {
|
|
|
|
|
|
message.warning("请先选择部门");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
2026-06-25 16:30:37 +08:00
|
|
|
|
setCurrentId(asId(record?.id) || "");
|
2026-06-23 18:07:30 +08:00
|
|
|
|
modalForm.resetFields();
|
|
|
|
|
|
if (record) {
|
|
|
|
|
|
modalForm.setFieldsValue({
|
|
|
|
|
|
...record,
|
2026-06-25 16:30:37 +08:00
|
|
|
|
deptId: asId(record.deptId || selectedDept.id),
|
2026-06-23 18:07:30 +08:00
|
|
|
|
deptName: record.deptName || selectedDept.deptName,
|
|
|
|
|
|
});
|
2026-07-08 09:37:21 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
modalForm.setFieldsValue({
|
|
|
|
|
|
deptId: asId(selectedDept.id),
|
|
|
|
|
|
deptName: selectedDept.deptName,
|
|
|
|
|
|
});
|
2026-06-23 18:07:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
setPositionModalOpen(true);
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
const submitPosition = async (values) => {
|
|
|
|
|
|
const request = currentId ? props.orgPositionEdit : props.orgPositionAdd;
|
|
|
|
|
|
const payload = { ...values };
|
|
|
|
|
|
if (currentId) {
|
|
|
|
|
|
payload.id = currentId;
|
|
|
|
|
|
}
|
2026-06-25 16:30:37 +08:00
|
|
|
|
payload.deptId = asId(values.deptId || selectedDept?.id);
|
2026-06-23 18:07:30 +08:00
|
|
|
|
const res = await request(payload);
|
|
|
|
|
|
if (res?.success !== false) {
|
|
|
|
|
|
message.success(currentId ? "编辑成功" : "添加成功");
|
|
|
|
|
|
closePositionModal();
|
2026-07-08 09:37:21 +08:00
|
|
|
|
fetchPositionData(pageIndex, pageSize);
|
|
|
|
|
|
} else {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
message.error(res?.message || "操作失败");
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
return (
|
2026-06-29 16:45:58 +08:00
|
|
|
|
<PageLayout title="部门岗位管理">
|
2026-06-23 18:07:30 +08:00
|
|
|
|
<div style={{ display: "flex", gap: 16, minHeight: 480 }}>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
width: 260,
|
|
|
|
|
|
borderRight: "1px solid #f0f0f0",
|
|
|
|
|
|
paddingRight: 16,
|
|
|
|
|
|
flexShrink: 0,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Space
|
|
|
|
|
|
direction="vertical"
|
|
|
|
|
|
style={{ width: "100%", marginBottom: 12 }}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
|
|
block
|
|
|
|
|
|
onClick={() => openDeptModal()}
|
|
|
|
|
|
>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
新增部门
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Input.Search
|
|
|
|
|
|
placeholder="输入关键字进行过滤"
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
onChange={(e) => setFilterKeyword(e.target.value)}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Space>
|
|
|
|
|
|
<Tree
|
2026-06-25 16:30:37 +08:00
|
|
|
|
selectedKeys={selectedDept?.id ? [asId(selectedDept.id)] : []}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
treeData={filteredTree}
|
|
|
|
|
|
fieldNames={{ title: "deptName", key: "id", children: "children" }}
|
|
|
|
|
|
onSelect={(_, { node }) => {
|
|
|
|
|
|
setSelectedDept(toSelectedDept(node));
|
|
|
|
|
|
}}
|
|
|
|
|
|
/>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div style={{ flex: 1, minWidth: 0 }}>
|
|
|
|
|
|
{selectedDept?.id ? (
|
|
|
|
|
|
<>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<div
|
|
|
|
|
|
style={{
|
|
|
|
|
|
display: "flex",
|
|
|
|
|
|
justifyContent: "space-between",
|
|
|
|
|
|
alignItems: "center",
|
|
|
|
|
|
marginBottom: 16,
|
|
|
|
|
|
}}
|
|
|
|
|
|
>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
<span>
|
|
|
|
|
|
当前部门:
|
|
|
|
|
|
<strong>{selectedDept.deptName}</strong>
|
|
|
|
|
|
</span>
|
|
|
|
|
|
<Space>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<Button onClick={() => openDeptModal(selectedDept)}>
|
|
|
|
|
|
编辑部门
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button danger onClick={() => onDeleteDept(selectedDept.id)}>
|
|
|
|
|
|
删除部门
|
|
|
|
|
|
</Button>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
</Space>
|
|
|
|
|
|
</div>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<SearchForm
|
2026-06-23 18:07:30 +08:00
|
|
|
|
form={positionForm}
|
2026-07-08 09:37:21 +08:00
|
|
|
|
loading={loading}
|
|
|
|
|
|
formLine={[
|
|
|
|
|
|
<Form.Item key="positionName" name="positionName">
|
|
|
|
|
|
<Input allowClear placeholder="请输入岗位名称" />
|
|
|
|
|
|
</Form.Item>,
|
|
|
|
|
|
]}
|
|
|
|
|
|
onFinish={() => fetchPositionData(1, 10)}
|
2026-07-08 14:44:41 +08:00
|
|
|
|
onReset={() => fetchPositionData(1, 10)}
|
2026-07-08 09:37:21 +08:00
|
|
|
|
style={{ marginBottom: 16 }}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
/>
|
|
|
|
|
|
<Table
|
2026-07-08 09:37:21 +08:00
|
|
|
|
footer={() => (
|
|
|
|
|
|
<Button
|
|
|
|
|
|
type="primary"
|
|
|
|
|
|
icon={<PlusOutlined />}
|
|
|
|
|
|
onClick={() => openPositionModal()}
|
|
|
|
|
|
>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
新增岗位
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
)}
|
|
|
|
|
|
columns={[
|
|
|
|
|
|
{ title: "名称", dataIndex: "positionName" },
|
|
|
|
|
|
{ title: "职责", dataIndex: "dutyDesc" },
|
|
|
|
|
|
{ title: "备注", dataIndex: "remark" },
|
|
|
|
|
|
{
|
|
|
|
|
|
title: "操作",
|
|
|
|
|
|
width: 160,
|
|
|
|
|
|
render: (_, record) => (
|
2026-07-07 09:07:19 +08:00
|
|
|
|
<TableAction>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<Button
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
onClick={() => openPositionModal(record)}
|
|
|
|
|
|
>
|
|
|
|
|
|
编辑
|
|
|
|
|
|
</Button>
|
|
|
|
|
|
<Button
|
|
|
|
|
|
danger
|
|
|
|
|
|
type="link"
|
|
|
|
|
|
onClick={() => onDeletePosition(record.id)}
|
|
|
|
|
|
>
|
|
|
|
|
|
删除
|
|
|
|
|
|
</Button>
|
2026-07-07 09:07:19 +08:00
|
|
|
|
</TableAction>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
),
|
|
|
|
|
|
},
|
|
|
|
|
|
]}
|
2026-07-08 09:37:21 +08:00
|
|
|
|
dataSource={dataSource}
|
|
|
|
|
|
loading={loading}
|
|
|
|
|
|
pagination={{
|
|
|
|
|
|
current: pageIndex,
|
|
|
|
|
|
pageSize,
|
|
|
|
|
|
total,
|
|
|
|
|
|
showSizeChanger: true,
|
|
|
|
|
|
showTotal: (t) => `共 ${t} 条`,
|
|
|
|
|
|
}}
|
|
|
|
|
|
scroll={{ y: props.scrollY }}
|
|
|
|
|
|
onChange={(pag) => {
|
|
|
|
|
|
setPageIndex(pag.current);
|
|
|
|
|
|
setPageSize(pag.pageSize);
|
|
|
|
|
|
fetchPositionData(pag.current, pag.pageSize);
|
|
|
|
|
|
}}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</>
|
|
|
|
|
|
) : (
|
|
|
|
|
|
<Empty description="请在左侧选择部门,查看该部门下的岗位" />
|
|
|
|
|
|
)}
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
{deptModalOpen && (
|
|
|
|
|
|
<DeptFormModal
|
|
|
|
|
|
open={deptModalOpen}
|
|
|
|
|
|
currentId={currentId}
|
2026-07-08 09:37:21 +08:00
|
|
|
|
selectedDeptId={selectedDept?.id}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
staffOptions={staffOptions}
|
|
|
|
|
|
treeData={treeData}
|
|
|
|
|
|
requestAdd={props.orgDepartmentAdd}
|
|
|
|
|
|
requestEdit={props.orgDepartmentEdit}
|
|
|
|
|
|
requestDetails={props.orgDepartmentGet}
|
|
|
|
|
|
onCancel={closeDeptModal}
|
|
|
|
|
|
onSuccess={loadTree}
|
|
|
|
|
|
/>
|
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
|
|
{positionModalOpen && (
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
open={positionModalOpen}
|
2026-07-06 18:06:49 +08:00
|
|
|
|
destroyOnHidden
|
2026-06-23 18:07:30 +08:00
|
|
|
|
title={currentId ? "编辑岗位" : "添加岗位"}
|
|
|
|
|
|
width={560}
|
2026-07-08 12:59:15 +08:00
|
|
|
|
confirmLoading={orgPositionLoading}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
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: "请输入岗位职责" }]}
|
|
|
|
|
|
>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<Input.TextArea
|
|
|
|
|
|
rows={3}
|
|
|
|
|
|
placeholder="请输入岗位职责"
|
|
|
|
|
|
showCount
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
/>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
<Form.Item name="remark" label="备注">
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<Input.TextArea
|
|
|
|
|
|
rows={2}
|
|
|
|
|
|
placeholder="请输入备注"
|
|
|
|
|
|
showCount
|
|
|
|
|
|
maxLength={500}
|
|
|
|
|
|
/>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
)}
|
2026-06-29 16:45:58 +08:00
|
|
|
|
</PageLayout>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-08 09:37:21 +08:00
|
|
|
|
function resolveManagerAccount(dept, options) {
|
|
|
|
|
|
if (dept?.managerAccount) {
|
|
|
|
|
|
return dept.managerAccount;
|
2026-06-23 18:07:30 +08:00
|
|
|
|
}
|
2026-07-08 09:37:21 +08:00
|
|
|
|
if (dept?.managerName) {
|
|
|
|
|
|
const matched = options.find((item) => item.staffName === dept.managerName);
|
2026-06-23 18:07:30 +08:00
|
|
|
|
return matched?.value;
|
|
|
|
|
|
}
|
|
|
|
|
|
return undefined;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function DeptFormModal({
|
|
|
|
|
|
open,
|
|
|
|
|
|
currentId,
|
|
|
|
|
|
staffOptions,
|
|
|
|
|
|
treeData,
|
|
|
|
|
|
requestAdd,
|
|
|
|
|
|
requestEdit,
|
|
|
|
|
|
requestDetails,
|
|
|
|
|
|
onCancel,
|
|
|
|
|
|
onSuccess,
|
2026-07-08 09:37:21 +08:00
|
|
|
|
selectedDeptId,
|
2026-06-23 18:07:30 +08:00
|
|
|
|
}) {
|
|
|
|
|
|
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") {
|
2026-07-08 09:37:21 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const res = await getDetails({ id: currentId });
|
|
|
|
|
|
dept = res?.data || null;
|
|
|
|
|
|
} catch {
|
|
|
|
|
|
dept = null;
|
|
|
|
|
|
}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
}
|
|
|
|
|
|
if (!dept) {
|
|
|
|
|
|
dept = findDeptNode(treeDataRef.current, currentId);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (!cancelled && dept) {
|
|
|
|
|
|
form.setFieldsValue({
|
|
|
|
|
|
...dept,
|
2026-07-08 09:37:21 +08:00
|
|
|
|
managerAccount: resolveManagerAccount(dept, staffOptionsRef.current),
|
|
|
|
|
|
managerName: dept.managerName,
|
2026-06-23 18:07:30 +08:00
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
loadDetail().finally(() => {
|
|
|
|
|
|
if (!cancelled) {
|
|
|
|
|
|
setDetailLoading(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
return () => {
|
|
|
|
|
|
cancelled = true;
|
|
|
|
|
|
};
|
|
|
|
|
|
}, [open, currentId]);
|
|
|
|
|
|
|
2026-07-08 09:37:21 +08:00
|
|
|
|
const onManagerChange = (account) => {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
const staff = staffOptions.find((s) => s.value === account);
|
2026-07-08 09:37:21 +08:00
|
|
|
|
form.setFieldValue("managerName", staff?.staffName || "");
|
2026-06-23 18:07:30 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
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();
|
2026-07-08 09:37:21 +08:00
|
|
|
|
} else {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
message.error(res?.message || "操作失败");
|
|
|
|
|
|
}
|
2026-07-08 09:37:21 +08:00
|
|
|
|
} finally {
|
2026-06-23 18:07:30 +08:00
|
|
|
|
setSubmitting(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
2026-07-08 14:44:41 +08:00
|
|
|
|
|
2026-06-23 18:07:30 +08:00
|
|
|
|
return (
|
|
|
|
|
|
<Modal
|
|
|
|
|
|
open={open}
|
2026-07-06 18:06:49 +08:00
|
|
|
|
destroyOnHidden
|
2026-06-23 18:07:30 +08:00
|
|
|
|
title={currentId ? "编辑部门" : "添加部门"}
|
|
|
|
|
|
width={560}
|
|
|
|
|
|
loading={detailLoading}
|
|
|
|
|
|
confirmLoading={submitting}
|
|
|
|
|
|
onCancel={onCancel}
|
|
|
|
|
|
onOk={form.submit}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
2026-07-08 14:44:41 +08:00
|
|
|
|
{!currentId && (
|
|
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="parentId"
|
|
|
|
|
|
label="上级部门"
|
|
|
|
|
|
initialValue={selectedDeptId}
|
|
|
|
|
|
>
|
|
|
|
|
|
<TreeSelect
|
|
|
|
|
|
treeData={treeData}
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
fieldNames={{
|
|
|
|
|
|
label: "deptName",
|
|
|
|
|
|
value: "id",
|
|
|
|
|
|
children: "children",
|
|
|
|
|
|
}}
|
|
|
|
|
|
showSearch
|
|
|
|
|
|
placeholder="请选择上级部门"
|
|
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
)}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
<Form.Item
|
|
|
|
|
|
name="deptName"
|
|
|
|
|
|
label="部门名称"
|
|
|
|
|
|
rules={[{ required: true, message: "请输入部门名称" }]}
|
|
|
|
|
|
>
|
|
|
|
|
|
<Input placeholder="请输入部门名称" />
|
|
|
|
|
|
</Form.Item>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<Form.Item name="managerAccount" label="负责人">
|
2026-06-23 18:07:30 +08:00
|
|
|
|
<Select
|
|
|
|
|
|
allowClear
|
|
|
|
|
|
showSearch
|
|
|
|
|
|
placeholder="请选择负责人"
|
|
|
|
|
|
options={staffOptions}
|
|
|
|
|
|
optionFilterProp="label"
|
2026-07-08 09:37:21 +08:00
|
|
|
|
onChange={onManagerChange}
|
2026-06-23 18:07:30 +08:00
|
|
|
|
/>
|
|
|
|
|
|
</Form.Item>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<Form.Item name="managerName" hidden>
|
2026-06-23 18:07:30 +08:00
|
|
|
|
<Input />
|
|
|
|
|
|
</Form.Item>
|
2026-07-08 09:37:21 +08:00
|
|
|
|
<Form.Item name="deptLevelName" label="部门级别">
|
2026-06-23 18:07:30 +08:00
|
|
|
|
<Input placeholder="请输入部门级别" />
|
|
|
|
|
|
</Form.Item>
|
|
|
|
|
|
</Form>
|
|
|
|
|
|
</Modal>
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-08 09:37:21 +08:00
|
|
|
|
export default Connect(
|
|
|
|
|
|
[NS_ORG_DEPARTMENT, NS_ORG_POSITION, NS_STAFF_INFO],
|
|
|
|
|
|
true,
|
|
|
|
|
|
)(AntdTableFuncControl(DepartmentPositionPage));
|