feat(admin): 报读班型支持班级选择、自动带出与已报读识别
This commit is contained in:
@@ -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="班主任姓名" />
|
||||
|
||||
Reference in New Issue
Block a user