import React, { useEffect, useState, useCallback } from 'react'; import { Tree, Card, Button, Modal, Form, Input, Select, InputNumber, Table, Row, Col, Popconfirm, Space, message, Tag, Descriptions, Empty, Spin, } from 'antd'; import { PlusOutlined, EditOutlined, DeleteOutlined, } from '@ant-design/icons'; import type { DataNode } from 'antd/es/tree'; import api from '../../api'; import PermissionButton from '../../components/PermissionButton'; interface DepartmentItem { id: number; name: string; parentId: number | null; type: string; sortOrder: number; status: string; children?: DepartmentItem[]; } interface UserInfo { id: number; username: string; name: string; isActive: boolean; } interface DeptMember { id: number; userId: number; departmentId: number; isDefault: boolean; user: UserInfo; } function departmentToTreeNode(dept: DepartmentItem): DataNode { return { key: String(dept.id), title: dept.name, children: dept.children?.map(departmentToTreeNode), }; } const TYPE_LABELS: Record = { campus: '校区', department: '部门', }; const TYPE_COLORS: Record = { campus: 'blue', department: 'green', }; const DepartmentsPage: React.FC = () => { const [treeData, setTreeData] = useState([]); const [flatDepts, setFlatDepts] = useState([]); const [selectedId, setSelectedId] = useState(null); const [selectedDept, setSelectedDept] = useState(null); const [members, setMembers] = useState([]); const [membersLoading, setMembersLoading] = useState(false); const [treeLoading, setTreeLoading] = useState(false); const [modalOpen, setModalOpen] = useState(false); const [editing, setEditing] = useState(null); const [form] = Form.useForm(); const fetchTree = useCallback(async () => { setTreeLoading(true); try { const data = await api.get('/departments/tree') as unknown as DepartmentItem[]; const flat = await api.get('/departments') as unknown as DepartmentItem[]; setFlatDepts(flat); setTreeData(data.map(departmentToTreeNode)); } catch { message.error('加载部门树失败'); } finally { setTreeLoading(false); } }, []); useEffect(() => { fetchTree(); }, [fetchTree]); const fetchDetail = useCallback(async (id: number) => { try { const dept = await api.get(`/departments/${id}`) as unknown as DepartmentItem; setSelectedDept(dept); setMembersLoading(true); const users = await api.get(`/departments/${id}/users`) as unknown as DeptMember[]; setMembers(users); } catch { message.error('加载部门详情失败'); } finally { setMembersLoading(false); } }, []); const handleSelect = useCallback( (keys: React.Key[]) => { if (keys.length === 0) { setSelectedId(null); setSelectedDept(null); setMembers([]); return; } const id = String(keys[0]); setSelectedId(id); fetchDetail(Number(id)); }, [fetchDetail], ); const handleAdd = useCallback(() => { setEditing(null); form.resetFields(); if (selectedId) { form.setFieldsValue({ parentId: Number(selectedId) }); } setModalOpen(true); }, [selectedId, form]); const handleAddChild = useCallback(() => { if (!selectedDept) return; setEditing(null); form.resetFields(); form.setFieldsValue({ parentId: selectedDept.id }); setModalOpen(true); }, [selectedDept, form]); const handleEdit = useCallback(() => { if (!selectedDept) return; setEditing(selectedDept); form.setFieldsValue({ name: selectedDept.name, parentId: selectedDept.parentId, type: selectedDept.type, sortOrder: selectedDept.sortOrder, }); setModalOpen(true); }, [selectedDept, form]); const handleSubmit = useCallback(async () => { try { const values = await form.validateFields(); const payload = { name: values.name, parentId: values.parentId || null, type: values.type || 'department', sortOrder: values.sortOrder ?? 0, }; if (editing) { await api.put(`/departments/${editing.id}`, payload); message.success('更新成功'); } else { await api.post('/departments', payload); message.success('创建成功'); } setModalOpen(false); await fetchTree(); if (editing && selectedId) { fetchDetail(editing.id); } } catch { // form validation error: do nothing } }, [editing, fetchTree, fetchDetail, selectedId, form]); const handleDelete = useCallback(async () => { if (!selectedDept) return; try { await api.delete(`/departments/${selectedDept.id}`); message.success('删除成功'); setSelectedId(null); setSelectedDept(null); setMembers([]); await fetchTree(); } catch (err: unknown) { const msg = err && typeof err === 'object' && 'message' in err ? (err as { message: string }).message : '删除失败'; message.error(msg); } }, [selectedDept, fetchTree]); const memberColumns = [ { title: 'ID', dataIndex: 'userId', key: 'userId', width: 60 }, { title: '用户名', dataIndex: ['user', 'username'], key: 'username' }, { title: '姓名', dataIndex: ['user', 'name'], key: 'name' }, { title: '默认部门', dataIndex: 'isDefault', key: 'isDefault', width: 80, render: (v: boolean) => (v ? : null), }, ]; return (

组织架构管理

} onClick={handleAdd} > 新增
{treeLoading ? ( ) : ( )} {selectedDept ? ( {selectedDept.name} {TYPE_LABELS[selectedDept.type] || selectedDept.type} } extra={ } onClick={handleEdit} > 编辑 } onClick={handleAddChild} > 新增子部门 0 ? '该部门下存在子部门,可能无法删除' : undefined} onConfirm={handleDelete} okText="确定" cancelText="取消" > } > {selectedDept.id} {TYPE_LABELS[selectedDept.type] || selectedDept.type} {selectedDept.sortOrder} {selectedDept.parentId ?? '无(顶级)'} {selectedDept.status} ) : ( )} setModalOpen(false)} destroyOnHidden >
); }; export default DepartmentsPage;