refactor(web): 系统设置模块页面表单一致性重构
- 新增 useCrudDrawer hook 封装 CRUD Drawer 通用模式(状态管理/提交/错误处理) - 新增 useListData hook 封装非分页列表数据获取 - 11 个页面统一迁移到 DrawerForm + 共享 hooks,消除重复代码 - 错误处理统一使用 useApiRequest.execute(),移除内联 try-catch - Modal 全部替换为 DrawerForm,保持 UI 一致性 - 净减少 ~1300 行代码(858 增 / 2136 删)
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
import { useEffect, useState, useCallback } from 'react';
|
||||
import { useState, useCallback, useEffect, useRef } from 'react';
|
||||
import {
|
||||
Tree,
|
||||
Button,
|
||||
Space,
|
||||
Modal,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Table,
|
||||
Popconfirm,
|
||||
message,
|
||||
Empty,
|
||||
Tag,
|
||||
} from 'antd';
|
||||
@@ -21,7 +19,9 @@ import {
|
||||
} from '@ant-design/icons';
|
||||
import type { DataNode } from 'antd/es/tree';
|
||||
import { useThemeMode } from '../hooks/useThemeMode';
|
||||
import { handleApiError } from '../api/client';
|
||||
import { DrawerForm } from '../components/DrawerForm';
|
||||
import { useCrudDrawer } from '../hooks/useCrudDrawer';
|
||||
import { useApiRequest } from '../hooks/useApiRequest';
|
||||
import {
|
||||
listOrgTree,
|
||||
createOrg,
|
||||
@@ -40,6 +40,7 @@ import {
|
||||
|
||||
export default function Organizations() {
|
||||
const isDark = useThemeMode();
|
||||
const { execute } = useApiRequest();
|
||||
|
||||
const cardStyle = {
|
||||
background: isDark ? '#111827' : '#FFFFFF',
|
||||
@@ -50,7 +51,6 @@ export default function Organizations() {
|
||||
// --- Org tree state ---
|
||||
const [orgTree, setOrgTree] = useState<OrganizationInfo[]>([]);
|
||||
const [selectedOrg, setSelectedOrg] = useState<OrganizationInfo | null>(null);
|
||||
const [, setLoading] = useState(false);
|
||||
|
||||
// --- Department tree state ---
|
||||
const [deptTree, setDeptTree] = useState<DepartmentInfo[]>([]);
|
||||
@@ -59,41 +59,44 @@ export default function Organizations() {
|
||||
// --- Position list state ---
|
||||
const [positions, setPositions] = useState<PositionInfo[]>([]);
|
||||
|
||||
// --- Modal state ---
|
||||
const [orgModalOpen, setOrgModalOpen] = useState(false);
|
||||
const [deptModalOpen, setDeptModalOpen] = useState(false);
|
||||
const [positionModalOpen, setPositionModalOpen] = useState(false);
|
||||
const [editOrg, setEditOrg] = useState<OrganizationInfo | null>(null);
|
||||
|
||||
const [orgForm] = Form.useForm();
|
||||
const [deptForm] = Form.useForm();
|
||||
const [positionForm] = Form.useForm();
|
||||
// --- Ref for drawer onSuccess callback (avoids before-declaration issue) ---
|
||||
const refreshOrgTreeRef = useRef<() => void>(() => {});
|
||||
|
||||
// --- Fetch org tree ---
|
||||
const fetchOrgTree = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const tree = await listOrgTree();
|
||||
setOrgTree(tree);
|
||||
if (selectedOrg) {
|
||||
const stillExists = findOrgInTree(tree, selectedOrg.id);
|
||||
if (!stillExists) {
|
||||
setSelectedOrg(null);
|
||||
setDeptTree([]);
|
||||
setPositions([]);
|
||||
}
|
||||
if (!stillExists) { setSelectedOrg(null); setDeptTree([]); setPositions([]); }
|
||||
}
|
||||
} catch {
|
||||
message.error('加载组织树失败');
|
||||
}
|
||||
setLoading(false);
|
||||
} catch { /* silent */ }
|
||||
}, [selectedOrg]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchOrgTree();
|
||||
}, [fetchOrgTree]);
|
||||
refreshOrgTreeRef.current = () => { fetchOrgTree(); };
|
||||
|
||||
// --- Fetch dept tree when org selected ---
|
||||
useEffect(() => { fetchOrgTree(); }, [fetchOrgTree]);
|
||||
|
||||
// --- Dept Drawer ---
|
||||
const [deptDrawerOpen, setDeptDrawerOpen] = useState(false);
|
||||
|
||||
// --- Position Drawer ---
|
||||
const [positionDrawerOpen, setPositionDrawerOpen] = useState(false);
|
||||
|
||||
// --- Org Drawer (uses ref to avoid before-declaration) ---
|
||||
const orgDrawer = useCrudDrawer<OrganizationInfo>({
|
||||
getId: (r) => r.id,
|
||||
onCreate: async (values) => {
|
||||
await createOrg({ ...(values as { name: string; code?: string; sort_order?: number }), parent_id: selectedOrg?.id });
|
||||
},
|
||||
onUpdate: async (id, values) => {
|
||||
await updateOrg(id, values as { name: string; code?: string; sort_order?: number; version: number });
|
||||
},
|
||||
onSuccess: () => { refreshOrgTreeRef.current(); },
|
||||
});
|
||||
|
||||
// --- Fetch dept tree ---
|
||||
const fetchDeptTree = useCallback(async () => {
|
||||
if (!selectedOrg) return;
|
||||
try {
|
||||
@@ -101,209 +104,87 @@ export default function Organizations() {
|
||||
setDeptTree(tree);
|
||||
if (selectedDept) {
|
||||
const stillExists = findDeptInTree(tree, selectedDept.id);
|
||||
if (!stillExists) {
|
||||
setSelectedDept(null);
|
||||
setPositions([]);
|
||||
}
|
||||
if (!stillExists) { setSelectedDept(null); setPositions([]); }
|
||||
}
|
||||
} catch {
|
||||
message.error('加载部门树失败');
|
||||
}
|
||||
} catch { /* silent */ }
|
||||
}, [selectedOrg, selectedDept]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchDeptTree();
|
||||
}, [fetchDeptTree]);
|
||||
useEffect(() => { fetchDeptTree(); }, [fetchDeptTree]);
|
||||
|
||||
// --- Fetch positions when dept selected ---
|
||||
// --- Fetch positions ---
|
||||
const fetchPositions = useCallback(async () => {
|
||||
if (!selectedDept) return;
|
||||
try {
|
||||
const list = await listPositions(selectedDept.id);
|
||||
setPositions(list);
|
||||
} catch {
|
||||
message.error('加载岗位列表失败');
|
||||
}
|
||||
setPositions(await listPositions(selectedDept.id));
|
||||
} catch { /* silent */ }
|
||||
}, [selectedDept]);
|
||||
|
||||
useEffect(() => {
|
||||
fetchPositions();
|
||||
}, [fetchPositions]);
|
||||
useEffect(() => { fetchPositions(); }, [fetchPositions]);
|
||||
|
||||
// --- Org handlers ---
|
||||
const handleCreateOrg = async (values: {
|
||||
name: string;
|
||||
code?: string;
|
||||
sort_order?: number;
|
||||
}) => {
|
||||
try {
|
||||
if (editOrg) {
|
||||
await updateOrg(editOrg.id, {
|
||||
name: values.name,
|
||||
code: values.code,
|
||||
sort_order: values.sort_order,
|
||||
version: editOrg.version,
|
||||
});
|
||||
message.success('组织更新成功');
|
||||
} else {
|
||||
await createOrg({
|
||||
name: values.name,
|
||||
code: values.code,
|
||||
parent_id: selectedOrg?.id,
|
||||
sort_order: values.sort_order,
|
||||
});
|
||||
message.success('组织创建成功');
|
||||
}
|
||||
setOrgModalOpen(false);
|
||||
setEditOrg(null);
|
||||
orgForm.resetFields();
|
||||
fetchOrgTree();
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err, '操作失败');
|
||||
}
|
||||
};
|
||||
|
||||
const handleDeleteOrg = async (id: string) => {
|
||||
try {
|
||||
await deleteOrg(id);
|
||||
message.success('组织已删除');
|
||||
setSelectedOrg(null);
|
||||
setDeptTree([]);
|
||||
setPositions([]);
|
||||
fetchOrgTree();
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err, '删除失败');
|
||||
}
|
||||
await execute(() => deleteOrg(id), '组织已删除');
|
||||
setSelectedOrg(null); setDeptTree([]); setPositions([]);
|
||||
fetchOrgTree();
|
||||
};
|
||||
|
||||
// --- Dept handlers ---
|
||||
const handleCreateDept = async (values: {
|
||||
name: string;
|
||||
code?: string;
|
||||
sort_order?: number;
|
||||
}) => {
|
||||
const handleCreateDept = async (values: Record<string, unknown>) => {
|
||||
if (!selectedOrg) return;
|
||||
try {
|
||||
await createDept(selectedOrg.id, {
|
||||
name: values.name,
|
||||
code: values.code,
|
||||
parent_id: selectedDept?.id,
|
||||
sort_order: values.sort_order,
|
||||
});
|
||||
message.success('部门创建成功');
|
||||
setDeptModalOpen(false);
|
||||
deptForm.resetFields();
|
||||
fetchDeptTree();
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err, '操作失败');
|
||||
}
|
||||
await execute(() => createDept(selectedOrg.id, {
|
||||
name: values.name as string, code: values.code as string | undefined,
|
||||
parent_id: selectedDept?.id, sort_order: values.sort_order as number | undefined,
|
||||
}), '部门创建成功');
|
||||
setDeptDrawerOpen(false);
|
||||
fetchDeptTree();
|
||||
};
|
||||
|
||||
const handleDeleteDept = async (id: string) => {
|
||||
try {
|
||||
await deleteDept(id);
|
||||
message.success('部门已删除');
|
||||
setSelectedDept(null);
|
||||
setPositions([]);
|
||||
fetchDeptTree();
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err, '删除失败');
|
||||
}
|
||||
await execute(() => deleteDept(id), '部门已删除');
|
||||
setSelectedDept(null); setPositions([]);
|
||||
fetchDeptTree();
|
||||
};
|
||||
|
||||
// --- Position handlers ---
|
||||
const handleCreatePosition = async (values: {
|
||||
name: string;
|
||||
code?: string;
|
||||
level?: number;
|
||||
sort_order?: number;
|
||||
}) => {
|
||||
const handleCreatePosition = async (values: Record<string, unknown>) => {
|
||||
if (!selectedDept) return;
|
||||
try {
|
||||
await createPosition(selectedDept.id, {
|
||||
name: values.name,
|
||||
code: values.code,
|
||||
level: values.level,
|
||||
sort_order: values.sort_order,
|
||||
});
|
||||
message.success('岗位创建成功');
|
||||
setPositionModalOpen(false);
|
||||
positionForm.resetFields();
|
||||
fetchPositions();
|
||||
} catch (err: unknown) {
|
||||
handleApiError(err, '操作失败');
|
||||
}
|
||||
await execute(() => createPosition(selectedDept.id, {
|
||||
name: values.name as string, code: values.code as string | undefined,
|
||||
level: values.level as number | undefined, sort_order: values.sort_order as number | undefined,
|
||||
}), '岗位创建成功');
|
||||
setPositionDrawerOpen(false);
|
||||
fetchPositions();
|
||||
};
|
||||
|
||||
const handleDeletePosition = async (id: string) => {
|
||||
try {
|
||||
await deletePosition(id);
|
||||
message.success('岗位已删除');
|
||||
fetchPositions();
|
||||
} catch {
|
||||
message.error('删除失败');
|
||||
}
|
||||
await execute(() => deletePosition(id), '岗位已删除');
|
||||
fetchPositions();
|
||||
};
|
||||
|
||||
// --- Tree node converters ---
|
||||
const convertOrgTree = (items: OrganizationInfo[]): DataNode[] =>
|
||||
items.map((item) => ({
|
||||
key: item.id,
|
||||
title: (
|
||||
<span>
|
||||
{item.name}{' '}
|
||||
{item.code && <Tag style={{
|
||||
marginLeft: 4,
|
||||
background: isDark ? '#0f172a' : '#eff6ff',
|
||||
border: 'none',
|
||||
color: '#2563eb',
|
||||
fontSize: 11,
|
||||
}}>{item.code}</Tag>}
|
||||
</span>
|
||||
),
|
||||
title: <span>{item.name} {item.code && <Tag style={{ marginLeft: 4, background: isDark ? '#0f172a' : '#eff6ff', border: 'none', color: '#2563eb', fontSize: 11 }}>{item.code}</Tag>}</span>,
|
||||
children: convertOrgTree(item.children),
|
||||
}));
|
||||
|
||||
const convertDeptTree = (items: DepartmentInfo[]): DataNode[] =>
|
||||
items.map((item) => ({
|
||||
key: item.id,
|
||||
title: (
|
||||
<span>
|
||||
{item.name}{' '}
|
||||
{item.code && <Tag style={{
|
||||
marginLeft: 4,
|
||||
background: isDark ? '#0f172a' : '#ECFDF5',
|
||||
border: 'none',
|
||||
color: '#059669',
|
||||
fontSize: 11,
|
||||
}}>{item.code}</Tag>}
|
||||
</span>
|
||||
),
|
||||
title: <span>{item.name} {item.code && <Tag style={{ marginLeft: 4, background: isDark ? '#0f172a' : '#ECFDF5', border: 'none', color: '#059669', fontSize: 11 }}>{item.code}</Tag>}</span>,
|
||||
children: convertDeptTree(item.children),
|
||||
}));
|
||||
|
||||
const onSelectOrg = (selectedKeys: React.Key[]) => {
|
||||
if (selectedKeys.length === 0) {
|
||||
setSelectedOrg(null);
|
||||
setDeptTree([]);
|
||||
setSelectedDept(null);
|
||||
setPositions([]);
|
||||
return;
|
||||
}
|
||||
const org = findOrgInTree(orgTree, selectedKeys[0] as string);
|
||||
setSelectedOrg(org);
|
||||
setSelectedDept(null);
|
||||
setPositions([]);
|
||||
if (selectedKeys.length === 0) { setSelectedOrg(null); setDeptTree([]); setSelectedDept(null); setPositions([]); return; }
|
||||
setSelectedOrg(findOrgInTree(orgTree, selectedKeys[0] as string));
|
||||
setSelectedDept(null); setPositions([]);
|
||||
};
|
||||
|
||||
const onSelectDept = (selectedKeys: React.Key[]) => {
|
||||
if (selectedKeys.length === 0) {
|
||||
setSelectedDept(null);
|
||||
setPositions([]);
|
||||
return;
|
||||
}
|
||||
const dept = findDeptInTree(deptTree, selectedKeys[0] as string);
|
||||
setSelectedDept(dept);
|
||||
if (selectedKeys.length === 0) { setSelectedDept(null); setPositions([]); return; }
|
||||
setSelectedDept(findDeptInTree(deptTree, selectedKeys[0] as string));
|
||||
};
|
||||
|
||||
const positionColumns = [
|
||||
@@ -312,16 +193,10 @@ export default function Organizations() {
|
||||
{ title: '级别', dataIndex: 'level', key: 'level' },
|
||||
{ title: '排序', dataIndex: 'sort_order', key: 'sort_order' },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
title: '操作', key: 'actions',
|
||||
render: (_: unknown, record: PositionInfo) => (
|
||||
<Popconfirm
|
||||
title="确定删除此岗位?"
|
||||
onConfirm={() => handleDeletePosition(record.id)}
|
||||
>
|
||||
<Button size="small" type="text" danger icon={<DeleteOutlined />}>
|
||||
删除
|
||||
</Button>
|
||||
<Popconfirm title="确定删除此岗位?" onConfirm={() => handleDeletePosition(record.id)}>
|
||||
<Button size="small" type="text" danger icon={<DeleteOutlined />}>删除</Button>
|
||||
</Popconfirm>
|
||||
),
|
||||
},
|
||||
@@ -329,60 +204,29 @@ export default function Organizations() {
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* 页面标题 */}
|
||||
<div className="erp-page-header">
|
||||
<div>
|
||||
<h4>
|
||||
<ApartmentOutlined style={{ marginRight: 8, color: '#2563eb' }} />
|
||||
组织架构管理
|
||||
</h4>
|
||||
<h4><ApartmentOutlined style={{ marginRight: 8, color: '#2563eb' }} />组织架构管理</h4>
|
||||
<div className="erp-page-subtitle">管理组织、部门和岗位的层级结构</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 三栏布局 */}
|
||||
<div style={{ display: 'flex', gap: 16, minHeight: 500 }}>
|
||||
{/* 左栏:组织树 */}
|
||||
<div style={{ width: 300, flexShrink: 0, ...cardStyle, overflow: 'hidden' }}>
|
||||
<div style={{
|
||||
padding: '14px 20px',
|
||||
borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '14px 20px', borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
}}>
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>组织</span>
|
||||
<Space size={4}>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
setEditOrg(null);
|
||||
orgForm.resetFields();
|
||||
setOrgModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Button size="small" type="text" icon={<PlusOutlined />} onClick={() => orgDrawer.openCreate()} />
|
||||
{selectedOrg && (
|
||||
<>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => {
|
||||
setEditOrg(selectedOrg);
|
||||
orgForm.setFieldsValue({
|
||||
name: selectedOrg.name,
|
||||
code: selectedOrg.code,
|
||||
sort_order: selectedOrg.sort_order,
|
||||
});
|
||||
setOrgModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Popconfirm
|
||||
title="确定删除此组织?"
|
||||
onConfirm={() => handleDeleteOrg(selectedOrg.id)}
|
||||
>
|
||||
<Button size="small" type="text" icon={<EditOutlined />} onClick={() => orgDrawer.openEdit(selectedOrg, (r) => ({
|
||||
name: r.name, code: r.code, sort_order: r.sort_order,
|
||||
}))} />
|
||||
<Popconfirm title="确定删除此组织?" onConfirm={() => handleDeleteOrg(selectedOrg.id)}>
|
||||
<Button size="small" type="text" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
</>
|
||||
@@ -391,47 +235,23 @@ export default function Organizations() {
|
||||
</div>
|
||||
<div style={{ padding: 12 }}>
|
||||
{orgTree.length > 0 ? (
|
||||
<Tree
|
||||
showLine
|
||||
defaultExpandAll
|
||||
treeData={convertOrgTree(orgTree)}
|
||||
onSelect={onSelectOrg}
|
||||
selectedKeys={selectedOrg ? [selectedOrg.id] : []}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="暂无组织" />
|
||||
)}
|
||||
<Tree showLine defaultExpandAll treeData={convertOrgTree(orgTree)} onSelect={onSelectOrg} selectedKeys={selectedOrg ? [selectedOrg.id] : []} />
|
||||
) : <Empty description="暂无组织" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 中栏:部门树 */}
|
||||
<div style={{ width: 300, flexShrink: 0, ...cardStyle, overflow: 'hidden' }}>
|
||||
<div style={{
|
||||
padding: '14px 20px',
|
||||
borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '14px 20px', borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
}}>
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>
|
||||
{selectedOrg ? `${selectedOrg.name} · 部门` : '部门'}
|
||||
</span>
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>{selectedOrg ? `${selectedOrg.name} · 部门` : '部门'}</span>
|
||||
{selectedOrg && (
|
||||
<Space size={4}>
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
deptForm.resetFields();
|
||||
setDeptModalOpen(true);
|
||||
}}
|
||||
/>
|
||||
<Button size="small" type="text" icon={<PlusOutlined />} onClick={() => setDeptDrawerOpen(true)} />
|
||||
{selectedDept && (
|
||||
<Popconfirm
|
||||
title="确定删除此部门?"
|
||||
onConfirm={() => handleDeleteDept(selectedDept.id)}
|
||||
>
|
||||
<Popconfirm title="确定删除此部门?" onConfirm={() => handleDeleteDept(selectedDept.id)}>
|
||||
<Button size="small" type="text" danger icon={<DeleteOutlined />} />
|
||||
</Popconfirm>
|
||||
)}
|
||||
@@ -441,157 +261,98 @@ export default function Organizations() {
|
||||
<div style={{ padding: 12 }}>
|
||||
{selectedOrg ? (
|
||||
deptTree.length > 0 ? (
|
||||
<Tree
|
||||
showLine
|
||||
defaultExpandAll
|
||||
treeData={convertDeptTree(deptTree)}
|
||||
onSelect={onSelectDept}
|
||||
selectedKeys={selectedDept ? [selectedDept.id] : []}
|
||||
/>
|
||||
) : (
|
||||
<Empty description="暂无部门" />
|
||||
)
|
||||
) : (
|
||||
<Empty description="请先选择组织" />
|
||||
)}
|
||||
<Tree showLine defaultExpandAll treeData={convertDeptTree(deptTree)} onSelect={onSelectDept} selectedKeys={selectedDept ? [selectedDept.id] : []} />
|
||||
) : <Empty description="暂无部门" />
|
||||
) : <Empty description="请先选择组织" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* 右栏:岗位表 */}
|
||||
<div style={{ flex: 1, ...cardStyle, overflow: 'hidden' }}>
|
||||
<div style={{
|
||||
padding: '14px 20px',
|
||||
borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '14px 20px', borderBottom: `1px solid ${isDark ? '#0f172a' : '#f8fafc'}`,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'space-between',
|
||||
}}>
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>
|
||||
{selectedDept ? `${selectedDept.name} · 岗位` : '岗位'}
|
||||
</span>
|
||||
<span style={{ fontWeight: 600, fontSize: 14 }}>{selectedDept ? `${selectedDept.name} · 岗位` : '岗位'}</span>
|
||||
{selectedDept && (
|
||||
<Button
|
||||
size="small"
|
||||
type="text"
|
||||
icon={<PlusOutlined />}
|
||||
onClick={() => {
|
||||
positionForm.resetFields();
|
||||
setPositionModalOpen(true);
|
||||
}}
|
||||
>
|
||||
新建岗位
|
||||
</Button>
|
||||
<Button size="small" type="text" icon={<PlusOutlined />} onClick={() => setPositionDrawerOpen(true)}>新建岗位</Button>
|
||||
)}
|
||||
</div>
|
||||
<div style={{ padding: '0 4px' }}>
|
||||
{selectedDept ? (
|
||||
<Table
|
||||
columns={positionColumns}
|
||||
dataSource={positions}
|
||||
rowKey="id"
|
||||
size="small"
|
||||
pagination={false}
|
||||
/>
|
||||
) : (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Empty description="请先选择部门" />
|
||||
</div>
|
||||
)}
|
||||
<Table columns={positionColumns} dataSource={positions} rowKey="id" size="small" pagination={false} />
|
||||
) : <div style={{ padding: 24 }}><Empty description="请先选择部门" /></div>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Org Modal */}
|
||||
<Modal
|
||||
title={editOrg ? '编辑组织' : selectedOrg ? `在 ${selectedOrg.name} 下新建子组织` : '新建根组织'}
|
||||
open={orgModalOpen}
|
||||
onCancel={() => {
|
||||
setOrgModalOpen(false);
|
||||
setEditOrg(null);
|
||||
}}
|
||||
onOk={() => orgForm.submit()}
|
||||
{/* Org Drawer */}
|
||||
<DrawerForm
|
||||
title={orgDrawer.editingRecord ? '编辑组织' : selectedOrg ? `在 ${selectedOrg.name} 下新建子组织` : '新建根组织'}
|
||||
open={orgDrawer.open}
|
||||
onClose={orgDrawer.close}
|
||||
onSubmit={orgDrawer.handleSubmit}
|
||||
initialValues={orgDrawer.initialValues}
|
||||
loading={orgDrawer.loading}
|
||||
width={480}
|
||||
columns={1}
|
||||
>
|
||||
<Form form={orgForm} onFinish={handleCreateOrg} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="名称"
|
||||
rules={[{ required: true, message: '请输入组织名称' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="code" label="编码">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="sort_order" label="排序" initialValue={0}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入组织名称' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="code" label="编码"><Input /></Form.Item>
|
||||
<Form.Item name="sort_order" label="排序" initialValue={0}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</DrawerForm>
|
||||
|
||||
{/* Dept Modal */}
|
||||
<Modal
|
||||
title={
|
||||
selectedDept
|
||||
? `在 ${selectedDept.name} 下新建子部门`
|
||||
: `在 ${selectedOrg?.name} 下新建部门`
|
||||
}
|
||||
open={deptModalOpen}
|
||||
onCancel={() => setDeptModalOpen(false)}
|
||||
onOk={() => deptForm.submit()}
|
||||
{/* Dept Drawer */}
|
||||
<DrawerForm
|
||||
title={selectedDept ? `在 ${selectedDept.name} 下新建子部门` : `在 ${selectedOrg?.name} 下新建部门`}
|
||||
open={deptDrawerOpen}
|
||||
onClose={() => setDeptDrawerOpen(false)}
|
||||
onSubmit={handleCreateDept}
|
||||
initialValues={{ sort_order: 0 }}
|
||||
loading={false}
|
||||
width={480}
|
||||
columns={1}
|
||||
>
|
||||
<Form form={deptForm} onFinish={handleCreateDept} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="名称"
|
||||
rules={[{ required: true, message: '请输入部门名称' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="code" label="编码">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="sort_order" label="排序" initialValue={0}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Form.Item name="name" label="名称" rules={[{ required: true, message: '请输入部门名称' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="code" label="编码"><Input /></Form.Item>
|
||||
<Form.Item name="sort_order" label="排序" initialValue={0}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</DrawerForm>
|
||||
|
||||
{/* Position Modal */}
|
||||
<Modal
|
||||
{/* Position Drawer */}
|
||||
<DrawerForm
|
||||
title={`在 ${selectedDept?.name} 下新建岗位`}
|
||||
open={positionModalOpen}
|
||||
onCancel={() => setPositionModalOpen(false)}
|
||||
onOk={() => positionForm.submit()}
|
||||
open={positionDrawerOpen}
|
||||
onClose={() => setPositionDrawerOpen(false)}
|
||||
onSubmit={handleCreatePosition}
|
||||
initialValues={{ level: 1, sort_order: 0 }}
|
||||
loading={false}
|
||||
width={480}
|
||||
columns={1}
|
||||
>
|
||||
<Form form={positionForm} onFinish={handleCreatePosition} layout="vertical" style={{ marginTop: 16 }}>
|
||||
<Form.Item
|
||||
name="name"
|
||||
label="岗位名称"
|
||||
rules={[{ required: true, message: '请输入岗位名称' }]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="code" label="编码">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="level" label="级别" initialValue={1}>
|
||||
<InputNumber min={1} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="sort_order" label="排序" initialValue={0}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Form.Item name="name" label="岗位名称" rules={[{ required: true, message: '请输入岗位名称' }]}>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item name="code" label="编码"><Input /></Form.Item>
|
||||
<Form.Item name="level" label="级别" initialValue={1}>
|
||||
<InputNumber min={1} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
<Form.Item name="sort_order" label="排序" initialValue={0}>
|
||||
<InputNumber min={0} style={{ width: '100%' }} />
|
||||
</Form.Item>
|
||||
</DrawerForm>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// --- Helpers ---
|
||||
|
||||
function findOrgInTree(
|
||||
tree: OrganizationInfo[],
|
||||
id: string,
|
||||
): OrganizationInfo | null {
|
||||
function findOrgInTree(tree: OrganizationInfo[], id: string): OrganizationInfo | null {
|
||||
for (const item of tree) {
|
||||
if (item.id === id) return item;
|
||||
const found = findOrgInTree(item.children, id);
|
||||
@@ -600,10 +361,7 @@ function findOrgInTree(
|
||||
return null;
|
||||
}
|
||||
|
||||
function findDeptInTree(
|
||||
tree: DepartmentInfo[],
|
||||
id: string,
|
||||
): DepartmentInfo | null {
|
||||
function findDeptInTree(tree: DepartmentInfo[], id: string): DepartmentInfo | null {
|
||||
for (const item of tree) {
|
||||
if (item.id === id) return item;
|
||||
const found = findDeptInTree(item.children, id);
|
||||
|
||||
Reference in New Issue
Block a user