import React, { useEffect, useState, useCallback } from 'react'; import { Table, Input, Button, Modal, Form, Select, DatePicker, Tag, Space, message } from 'antd'; import { EditOutlined } from '@ant-design/icons'; import dayjs from 'dayjs'; import api from '../../api'; interface TeacherRow { id: number; username: string; name: string; isActive: boolean; profile: { subjects?: string[]; joinedAt?: string; qualifications?: string } | null; lastLoginAt: string; roles: { code: string; name: string }[]; classAssignments: { roleType: string; subject: string; className: string | null }[]; } interface TeacherListResponse { list: TeacherRow[]; total: number; } interface ProfileFormValues { subjects: string[]; joinedAt: dayjs.Dayjs | null; qualifications: string; } const ROLE_LABELS: Record = { 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="保存" >