feat(admin): 报读记录班级选择、已报读禁用与班级跳转
- 班级名称列改为班级下拉(选项禁用已报读班级),保存时映射 className+classId 双字段 - 已有关联的班级名渲染为可点击链接跳转班级详情
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -30,6 +30,7 @@ export interface EnrollmentRecord {
|
||||
courseCategory: string;
|
||||
classType: string;
|
||||
className?: string;
|
||||
classId?: number | null;
|
||||
headTeacher?: string;
|
||||
subjectTeacher?: string;
|
||||
startDate?: string;
|
||||
|
||||
Reference in New Issue
Block a user