import React, { useState } from 'react'; import { App, Button, DatePicker, Form, Input, InputNumber, Modal, Select, Table } from 'antd'; import type { ColumnsType } from 'antd/es/table'; import { PlusOutlined } from '@ant-design/icons'; import api from '../../api'; import { message } from '../../ui/app-message'; import { useApiMutation } from '../../hooks/useApiMutation'; import { usePermission } from '../../hooks/usePermission'; import PermissionButton from '../PermissionButton'; import { EditableArchiveCell } from './EditableArchiveCell'; import { EXAM_TYPE_OPTIONS, formatEnrollmentDisplayName, getClassTypeLabel } from './shared'; import type { EnrollmentRecord, ExamScoreRecord, TabProps } from './shared'; const EXAM_SCORE_FIELDS = { examType: 'examType', examName: 'examName', subject: 'subject', score: 'score', classAvg: 'classAvg', rank: 'rank', examDate: 'examDate', enrollmentId: 'enrollmentId', } as const; export const ExamScoresTab: React.FC< TabProps & { data: ExamScoreRecord[]; enrollments: EnrollmentRecord[] } > = ({ data, studentId, enrollments }) => { const { modal } = App.useApp(); const { hasPermission } = usePermission(); const canPurgeArchive = hasPermission('archive:purge'); const [modalOpen, setModalOpen] = useState(false); const [form] = Form.useForm(); const [saving, setSaving] = useState(false); const addExamScoreMutation = useApiMutation( async (payload: Record) => api.post(`/archive/${studentId}/exam-scores`, payload), { invalidate: [['archive', studentId]] }, ); const saveExamScoreCellMutation = useApiMutation( async ({ id, field, value }: { id: number; field: string; value: unknown }) => api.put(`/archive/exam-scores/${id}`, { [field]: value }), { invalidate: [['archive', studentId]] }, ); const purgeExamScoreMutation = useApiMutation( async (id: number) => api.delete(`/archive/exam-scores/${id}/permanent`), { invalidate: [['archive', studentId]] }, ); const handleAdd = async () => { try { const values = await form.validateFields(); setSaving(true); await addExamScoreMutation.mutateAsync({ ...values, examDate: values.examDate?.format('YYYY-MM-DD'), }); message.success('考试成绩已添加'); setModalOpen(false); form.resetFields(); } catch { // 错误提示由 useApiMutation 统一处理 } finally { setSaving(false); } }; const saveCell = async (record: ExamScoreRecord, field: string, value: unknown) => { try { await saveExamScoreCellMutation.mutateAsync({ id: record.id, field, value }); message.success('考试成绩已保存'); } catch { // 错误提示由 useApiMutation 统一处理 } }; const handlePurge = (record: ExamScoreRecord) => { modal.confirm({ title: `永久删除考试成绩(${record.examName || record.subject || `记录${record.id}`})?`, content: '删除后不可恢复,成绩记录将被物理删除。确定继续?', okText: '永久删除', okButtonProps: { danger: true }, cancelText: '取消', onOk: async () => { try { await purgeExamScoreMutation.mutateAsync(record.id); message.success('已永久删除(不可恢复)'); } catch { // 错误提示由 useApiMutation 统一处理 } }, }); }; const columns: ColumnsType = [ { title: '考试类型', dataIndex: 'examType', render: (v: string, r) => ( {EXAM_TYPE_OPTIONS.find((o) => o.value === v)?.label || v} ), }, { title: '考试名称', dataIndex: 'examName', render: (v: string, r) => ( {v || '-'} ), }, { title: '科目', dataIndex: 'subject', render: (v: string, r) => ( {v} ), }, { title: '成绩', dataIndex: 'score', render: (v: number | null, r) => ( {v ?? '-'} ), }, { title: '班级均分', dataIndex: 'classAvg', render: (v: number | undefined, r) => ( {v !== undefined ? v : '-'} ), }, { title: '排名', dataIndex: 'rank', render: (v: number | undefined, r) => ( {v !== undefined ? v : '-'} ), }, { title: '考试日期', dataIndex: 'examDate', render: (v: string, r) => ( {v || '-'} ), }, { title: '关联报读', dataIndex: 'enrollmentId', render: (v: number | undefined, r) => ( ({ value: item.id, label: formatEnrollmentDisplayName(item), }))} onSave={saveCell}> {(() => { if (r.examId) return r.exam?.class?.name || '-'; if (v === undefined) return '-'; const enr = enrollments.find((e) => e.id === v); return enr ? formatEnrollmentDisplayName(enr) : String(v); })()} ), }, { title: '操作', render: (_: unknown, r: ExamScoreRecord) => r.status === 'archived' && canPurgeArchive ? ( ) : null, }, ]; return (
} type="primary" onClick={() => { form.resetFields(); setModalOpen(true); }} style={{ marginBottom: 16 }} > 添加考试成绩 columns={columns} dataSource={data} rowKey="id" rowClassName={(record) => (record.status === 'archived' ? 'archived-row' : '')} pagination={{ defaultPageSize: 15, showSizeChanger: true, pageSizeOptions: [15, 30, 50], }} /> setModalOpen(false)} confirmLoading={saving} >