feat(admin): 报读记录班级选择、已报读禁用与班级跳转

- 班级名称列改为班级下拉(选项禁用已报读班级),保存时映射 className+classId 双字段
- 已有关联的班级名渲染为可点击链接跳转班级详情
This commit is contained in:
2026-08-11 15:18:25 +08:00
parent 3e3c05b8bc
commit 0a685c8ea6
2 changed files with 43 additions and 7 deletions

View File

@@ -2,6 +2,7 @@ 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 { useNavigate } from 'react-router';
import { useQuery } from '@tanstack/react-query';
import dayjs from 'dayjs';
import api from '../../api';
@@ -47,6 +48,7 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
studentId,
}) => {
const { modal } = App.useApp();
const navigate = useNavigate();
const { hasPermission } = usePermission();
const canPurgeArchive = hasPermission('archive:purge');
const [modalOpen, setModalOpen] = useState(false);
@@ -60,8 +62,8 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
{ invalidate: [['archive', studentId]] },
);
const saveEnrollmentCellMutation = useApiMutation(
async ({ id, field, value }: { id: number; field: string; value: unknown }) =>
api.put(`/archive/enrollments/${id}`, { [field]: value }),
async ({ id, payload }: { id: number; payload: Record<string, unknown> }) =>
api.put(`/archive/enrollments/${id}`, payload),
{ invalidate: [['archive', studentId]] },
);
const purgeEnrollmentMutation = useApiMutation(
@@ -73,7 +75,6 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
data: classes = [],
} = useQuery<ClassOption[]>({
queryKey: queryKeys.classes.list({ archived: false }),
enabled: modalOpen,
queryFn: async () => {
try {
return await api.get<ClassOption[]>('/classes', { params: { isArchived: false } });
@@ -88,6 +89,11 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
[data],
);
const enrolledClassIds = useMemo(
() => new Set(data.map((e) => e.classId).filter((id): id is number => id != null)),
[data],
);
const {
data: teachers = [],
} = useQuery<ClassTeacher[]>({
@@ -138,6 +144,7 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
const selectedClass = classes.find((c) => c.id === classId);
await addEnrollmentMutation.mutateAsync({
...rest,
classId: classId ?? undefined,
className: selectedClass?.name,
startDate: values.startDate?.format('YYYY-MM-DD'),
endDate: values.endDate?.format('YYYY-MM-DD'),
@@ -154,7 +161,20 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
const saveCell = async (record: EnrollmentRecord, field: string, value: unknown) => {
try {
await saveEnrollmentCellMutation.mutateAsync({ id: record.id, field, value });
if (field === ENROLLMENT_FIELDS.className) {
// 单元格编辑提交的是选中班级 id含撤销回退的空值映射回 className+classId 双字段
const classId = value === '' || value === null || value === undefined ? null : Number(value);
const cls = classId != null ? classes.find((c) => c.id === classId) : undefined;
await saveEnrollmentCellMutation.mutateAsync({
id: record.id,
payload: { className: cls?.name ?? '', classId },
});
} else {
await saveEnrollmentCellMutation.mutateAsync({
id: record.id,
payload: { [field]: value },
});
}
message.success('报读记录已保存');
} catch {
// 错误提示由 useApiMutation 统一处理
@@ -202,8 +222,23 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
title: '班级名称',
dataIndex: 'className',
render: (v: string, r) => (
<EditableArchiveCell value={v} field={ENROLLMENT_FIELDS.className} record={r} onSave={saveCell}>
{v || '-'}
<EditableArchiveCell
value={r.classId ?? null}
field={ENROLLMENT_FIELDS.className}
record={r}
editor="select"
options={classes.map((c) => ({
value: c.id,
label: c.name,
disabled: enrolledClassIds.has(c.id) && c.id !== r.classId,
}))}
onSave={saveCell}
>
{r.classId != null ? (
<a onClick={() => navigate(`/classes/${r.classId}`)}>{v || '-'}</a>
) : (
v || '-'
)}
</EditableArchiveCell>
),
},
@@ -340,7 +375,7 @@ export const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> =
options={classes.map((c) => ({
value: c.id,
label: enrolledClassNames.has(c.name) ? `${c.name}(已报读)` : c.name,
disabled: enrolledClassNames.has(c.name),
disabled: enrolledClassNames.has(c.name) || enrolledClassIds.has(c.id),
}))}
/>
</Form.Item>

View File

@@ -30,6 +30,7 @@ export interface EnrollmentRecord {
courseCategory: string;
classType: string;
className?: string;
classId?: number | null;
headTeacher?: string;
subjectTeacher?: string;
startDate?: string;