feat(admin): 报读班型支持班级选择、自动带出与已报读识别

This commit is contained in:
2026-08-10 17:29:48 +08:00
parent d7d1bfaab1
commit b946713b61

View File

@@ -1,8 +1,11 @@
import React, { useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { App, Button, DatePicker, Form, Input, Modal, Select, Table, Tag } from 'antd';
import type { ColumnsType } from 'antd/es/table';
import { PlusOutlined } from '@ant-design/icons';
import { useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import api from '../../api';
import { queryKeys } from '../../api/queryKeys';
import { message } from '../../ui/app-message';
import { useApiMutation } from '../../hooks/useApiMutation';
import { usePermission } from '../../hooks/usePermission';
@@ -22,6 +25,23 @@ const ENROLLMENT_FIELDS = {
status: 'status',
} as const;
interface ClassOption {
id: number;
name: string;
classType: string;
startDate?: string | null;
endDate?: string | null;
isArchived: boolean;
}
interface ClassTeacher {
id: number;
userId: number;
name?: string | null;
roleType: string;
subject?: string | null;
}
export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> = ({
data,
studentId,
@@ -32,6 +52,7 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
const [modalOpen, setModalOpen] = useState(false);
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
const [selectedClassId, setSelectedClassId] = useState<number>();
const addEnrollmentMutation = useApiMutation(
async (payload: Record<string, unknown>) =>
@@ -48,12 +69,76 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
{ invalidate: [['archive', studentId]] },
);
const {
data: classes = [],
} = useQuery<ClassOption[]>({
queryKey: queryKeys.classes.list({ archived: false }),
enabled: modalOpen,
queryFn: async () => {
try {
return await api.get<ClassOption[]>('/classes', { params: { isArchived: false } });
} catch {
return [];
}
},
});
const enrolledClassNames = useMemo(
() => new Set(data.map((e) => e.className).filter((name): name is string => Boolean(name))),
[data],
);
const {
data: teachers = [],
} = useQuery<ClassTeacher[]>({
queryKey: ['classes', 'detail', 'teachers', selectedClassId],
enabled: modalOpen && !!selectedClassId,
queryFn: async () => {
try {
return await api.get<ClassTeacher[]>(`/classes/${selectedClassId}/teachers`);
} catch {
return [];
}
},
});
useEffect(() => {
const headTeacher = teachers.find((t) => t.roleType === 'head_teacher')?.name || '';
const subjectNames = Array.from(
new Set(
teachers
.filter((t) => t.roleType === 'subject_teacher')
.map((t) => t.name)
.filter((name): name is string => Boolean(name)),
),
);
form.setFieldsValue({ headTeacher, subjectTeacher: subjectNames.join('、') });
}, [modalOpen, selectedClassId, teachers, form]);
const handleClassChange = (classId: number) => {
setSelectedClassId(classId);
const cls = classes.find((c) => c.id === classId);
if (!cls) return;
const categoryOption = COURSE_CATEGORY_OPTIONS.find((o) => o.value === cls.classType);
form.setFieldsValue({
...(categoryOption ? { courseCategory: categoryOption.value } : {}),
// 切班时始终重置日期:新班级无起止日期时清空上一班级自动带出的值
startDate: cls.startDate ? dayjs(cls.startDate) : undefined,
endDate: cls.endDate ? dayjs(cls.endDate) : undefined,
headTeacher: '',
subjectTeacher: '',
});
};
const handleAdd = async () => {
try {
const values = await form.validateFields();
setSaving(true);
const { classId, ...rest } = values;
const selectedClass = classes.find((c) => c.id === classId);
await addEnrollmentMutation.mutateAsync({
...values,
...rest,
className: selectedClass?.name,
startDate: values.startDate?.format('YYYY-MM-DD'),
endDate: values.endDate?.format('YYYY-MM-DD'),
});
@@ -199,6 +284,7 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
type="primary"
onClick={() => {
form.resetFields();
setSelectedClassId(undefined);
setModalOpen(true);
}}
style={{ marginBottom: 16 }}
@@ -220,7 +306,10 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
title="添加报读记录"
open={modalOpen && hasPermission('student:edit')}
onOk={hasPermission('student:edit') ? handleAdd : undefined}
onCancel={() => setModalOpen(false)}
onCancel={() => {
setSelectedClassId(undefined);
setModalOpen(false);
}}
confirmLoading={saving}
>
<Form form={form} layout="vertical">
@@ -238,8 +327,22 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
>
<Select options={CLASS_TYPE_OPTIONS} placeholder="请选择" />
</Form.Item>
<Form.Item name="className" label="班级名称">
<Input placeholder="如2024届冲刺班" />
<Form.Item
name="classId"
label="班级名称"
extra="选择班级后自动带出课程类别、日期与教师信息"
>
<Select
showSearch
optionFilterProp="label"
placeholder="搜索并选择班级"
onChange={handleClassChange}
options={classes.map((c) => ({
value: c.id,
label: enrolledClassNames.has(c.name) ? `${c.name}(已报读)` : c.name,
disabled: enrolledClassNames.has(c.name),
}))}
/>
</Form.Item>
<Form.Item name="headTeacher" label="班主任">
<Input placeholder="班主任姓名" />