index.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. import { fetchDeleteDept } from '@/services/dept';
  2. import { ReloadOutlined } from '@ant-design/icons';
  3. import { PageContainer, ProCard } from '@ant-design/pro-components';
  4. import { useIntl } from '@umijs/max';
  5. import { Button, Empty, Popconfirm, Space, Spin, Tooltip, Tree, message } from 'antd';
  6. import type { DataNode } from 'antd/es/tree';
  7. import { DeptForm } from './components/Form';
  8. import { DeptUserTable } from './components/UserTable';
  9. import { useDeptPage } from './hooks/useDeptPage';
  10. export default function DeptPage() {
  11. const intl = useIntl();
  12. const {
  13. loading,
  14. treeData,
  15. selectedDeptId,
  16. setSelectedDeptId,
  17. selectedDeptIds,
  18. selectedDeptName,
  19. filteredUsers,
  20. noDeptUsers,
  21. loadData,
  22. productRolesBase,
  23. } = useDeptPage();
  24. const handleDelete = async (id: number) => {
  25. const res = await fetchDeleteDept({ id });
  26. if (!res.success) return;
  27. message.success('删除成功');
  28. if (selectedDeptId === id) setSelectedDeptId(undefined);
  29. loadData();
  30. };
  31. const renderTitle = (node: API.DeptItem) => (
  32. <Space>
  33. <span>{node.name}</span>
  34. <Space onClick={(e) => e.stopPropagation()}>
  35. <DeptForm
  36. mode="edit"
  37. initialValues={node}
  38. onSuccess={loadData}
  39. trigger={<a className="text-xs">编辑</a>}
  40. />
  41. <DeptForm
  42. mode="add"
  43. initialValues={{ parentId: node.id }}
  44. onSuccess={loadData}
  45. trigger={<a className="text-xs">新建子部门</a>}
  46. />
  47. <Popconfirm title={`确认删除「${node.name}」?`} onConfirm={() => handleDelete(node.id)}>
  48. <a className="text-xs text-(--ant-color-error)">删除</a>
  49. </Popconfirm>
  50. </Space>
  51. </Space>
  52. );
  53. const renderNodes = (items: API.DeptItem[]): DataNode[] =>
  54. items.map((item) => ({
  55. key: item.id,
  56. title: renderTitle(item),
  57. children: item.children ? renderNodes(item.children) : undefined,
  58. }));
  59. return (
  60. <PageContainer title={intl.formatMessage({ id: 'admin.dept.title' })}>
  61. <ProCard>
  62. <div className="flex gap-4">
  63. <div className="flex flex-col">
  64. <div className="flex gap-2 items-center py-4">
  65. <DeptForm
  66. mode="add"
  67. initialValues={{ parentId: 0 }}
  68. onSuccess={loadData}
  69. trigger={
  70. <Button type="primary">
  71. {intl.formatMessage({ id: 'admin.dept.createRoot' })}
  72. </Button>
  73. }
  74. />
  75. <Tooltip title="刷新">
  76. <span
  77. className="cursor-pointer text-(--ant-color-text) hover:text-(--ant-color-primary) transition-colors"
  78. onClick={loadData}
  79. >
  80. <ReloadOutlined style={{ fontSize: 16 }} />
  81. </span>
  82. </Tooltip>
  83. </div>
  84. <div className="w-80 shrink-0 bg-(--ant-color-fill-quaternary) rounded-lg p-2">
  85. <Spin spinning={loading}>
  86. {treeData.length > 0 && (
  87. <Tree
  88. className="bg-transparent!"
  89. treeData={renderNodes(treeData)}
  90. defaultExpandAll
  91. showLine
  92. height={window.innerHeight - 400}
  93. selectedKeys={selectedDeptId ? [selectedDeptId] : []}
  94. onSelect={(keys) => setSelectedDeptId(keys[0] as number | undefined)}
  95. />
  96. )}
  97. </Spin>
  98. </div>
  99. </div>
  100. <div className="flex-1 min-w-0">
  101. {selectedDeptIds ? (
  102. <DeptUserTable
  103. users={filteredUsers}
  104. productRolesBase={productRolesBase}
  105. deptId={selectedDeptId!}
  106. deptName={selectedDeptName ?? ''}
  107. noDeptUsers={noDeptUsers}
  108. onMemberChange={loadData}
  109. />
  110. ) : (
  111. <Empty description="请在左侧选择部门" className="py-16" />
  112. )}
  113. </div>
  114. </div>
  115. </ProCard>
  116. </PageContainer>
  117. );
  118. }