From 9eec7c34be31aa4c4b0e9a5d131955cc6211718f Mon Sep 17 00:00:00 2001 From: wangziqi Date: Mon, 6 Jul 2026 15:36:52 +0800 Subject: [PATCH] feat: add teacher management page with profile editing --- apps/admin/src/App.tsx | 9 + apps/admin/src/pages/Teachers/index.tsx | 236 ++++++++++++++++++++++++ 2 files changed, 245 insertions(+) create mode 100644 apps/admin/src/pages/Teachers/index.tsx diff --git a/apps/admin/src/App.tsx b/apps/admin/src/App.tsx index ffe3303..7069219 100644 --- a/apps/admin/src/App.tsx +++ b/apps/admin/src/App.tsx @@ -13,6 +13,7 @@ import BillsPage from './pages/Bills'; import RoomVisualPage from './pages/RoomVisual'; import OperationLogsPage from './pages/OperationLogs'; import UsersPage from './pages/Users'; +import TeachersPage from './pages/Teachers'; import DepositsPage from './pages/Deposits'; import ClassroomsPage from './pages/Classrooms'; import ClassesPage from './pages/Classes'; @@ -173,6 +174,14 @@ const App: React.FC = () => { } /> + + + + } + /> = { + super_admin: '超管', + teacher: '老师', + class_teacher: '班主任', + dormitory_supervisor: '宿管', + institution_head: '机构负责人', +}; + +const ROLE_TYPE_LABELS: Record = { + subject_teacher: '任课教师', + head_teacher: '班主任', + life_teacher: '生活老师', + academic_teacher: '教务老师', +}; + +const PAGE_SIZE = 20; + +const TeachersPage: React.FC = () => { + const [data, setData] = useState([]); + const [loading, setLoading] = useState(false); + const [total, setTotal] = useState(0); + const [page, setPage] = useState(1); + const [search, setSearch] = useState(''); + const [profileModal, setProfileModal] = useState(null); + const [form] = Form.useForm(); + + const fetchData = useCallback(async () => { + setLoading(true); + try { + const res = await api.get('/rbac/teachers', { + params: { search: search || undefined, page, pageSize: PAGE_SIZE }, + }); + setData(res.list); + setTotal(res.total); + } catch { + // silent + } + setLoading(false); + }, [page, search]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + const handleSaveProfile = async () => { + const values = await form.validateFields(); + if (!profileModal) return; + try { + await api.put(`/rbac/teachers/${profileModal.id}/profile`, { + subjects: values.subjects || [], + joinedAt: values.joinedAt?.format('YYYY-MM-DD'), + qualifications: values.qualifications, + }); + message.success('已更新'); + setProfileModal(null); + fetchData(); + } catch (e: unknown) { + let msg = '更新失败'; + if (e !== null && typeof e === 'object' && 'message' in e) { + msg = String(e.message); + } + message.error(msg); + } + }; + + const columns = [ + { title: '姓名', dataIndex: 'name', key: 'name', width: 100 }, + { title: '用户名', dataIndex: 'username', key: 'username', width: 130 }, + { + title: '角色', + dataIndex: 'roles', + key: 'roles', + width: 220, + render: (roles: TeacherRow['roles']) => + roles.map((r) => {ROLE_LABELS[r.code] || r.name}), + }, + { + title: '任课班级', + dataIndex: 'classAssignments', + key: 'classes', + width: 200, + render: (ca: TeacherRow['classAssignments']) => + ca?.length + ? ca.map((a, i) => ( + + {a.className || '-'} + {a.subject ? ` (${a.subject})` : ''} + + )) + : '-', + }, + { + title: '科目', + dataIndex: 'profile', + key: 'subjects', + width: 130, + render: (p: TeacherRow['profile']) => p?.subjects?.join('、') || '-', + }, + { + title: '入职日期', + dataIndex: 'profile', + key: 'joinedAt', + width: 110, + render: (p: TeacherRow['profile']) => p?.joinedAt || '-', + }, + { + title: '状态', + dataIndex: 'isActive', + key: 'status', + width: 80, + render: (v: boolean) => {v ? '在职' : '停用'}, + }, + { + title: '最后登录', + dataIndex: 'lastLoginAt', + key: 'login', + width: 160, + render: (v: string) => (v ? dayjs(v).format('YYYY-MM-DD HH:mm') : '-'), + }, + { + title: '操作', + key: 'actions', + width: 80, + render: (_: unknown, r: TeacherRow) => ( + + ), + }, + ]; + + return ( +
+

教师管理

+ + { + setSearch(v); + setPage(1); + }} + style={{ width: 220 }} + /> + + `共 ${t} 人`, + }} + expandable={{ + rowExpandable: (r) => (r.classAssignments || []).length > 0, + expandedRowRender: (r) => + r.classAssignments?.length ? ( + + {r.classAssignments.map((a, i) => ( + + {ROLE_TYPE_LABELS[a.roleType] || a.roleType}: {a.className} + {a.subject ? ` — ${a.subject}` : ''} + + ))} + + ) : null, + }} + /> + setProfileModal(null)} + okText="保存" + > +
+ +