feat: 支持表格单元格快捷编辑
This commit is contained in:
@@ -36,6 +36,7 @@ import api from '../../api';
|
||||
import { maskPhone, maskIdNumber } from '../../utils/sensitive';
|
||||
import { useViewSensitive } from '../../hooks/useViewSensitive';
|
||||
import { message } from '../../ui/app-message';
|
||||
import EditableCell from '../EditableCell';
|
||||
|
||||
// ---- Types ----
|
||||
|
||||
@@ -409,20 +410,131 @@ const EnrollmentsTab: React.FC<TabProps & { data: EnrollmentRecord[] }> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const saveCell = async (record: EnrollmentRecord, field: string, value: unknown) => {
|
||||
await api.put(`/archive/enrollments/${record.id}`, { [field]: value });
|
||||
message.success('报读记录已保存');
|
||||
onRefresh();
|
||||
};
|
||||
|
||||
const columns: ColumnsType<EnrollmentRecord> = [
|
||||
{ title: '课程类别', dataIndex: 'courseCategory', render: getCourseCategoryLabel },
|
||||
{ title: '班型', dataIndex: 'classType', render: getClassTypeLabel },
|
||||
{ title: '班级名称', dataIndex: 'className', render: (v: string) => v || '-' },
|
||||
{ title: '班主任', dataIndex: 'headTeacher', render: (v: string) => v || '-' },
|
||||
{ title: '任课教师', dataIndex: 'subjectTeacher', render: (v: string) => v || '-' },
|
||||
{ title: '开始日期', dataIndex: 'startDate', render: (v: string) => v || '-' },
|
||||
{ title: '结束日期', dataIndex: 'endDate', render: (v: string) => v || '-' },
|
||||
{
|
||||
title: '课程类别',
|
||||
dataIndex: 'courseCategory',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="select"
|
||||
options={COURSE_CATEGORY_OPTIONS}
|
||||
permission="student:edit"
|
||||
required
|
||||
onSave={(next) => saveCell(r, 'courseCategory', next)}
|
||||
>
|
||||
{getCourseCategoryLabel(v)}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '班型',
|
||||
dataIndex: 'classType',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="select"
|
||||
options={CLASS_TYPE_OPTIONS}
|
||||
permission="student:edit"
|
||||
required
|
||||
onSave={(next) => saveCell(r, 'classType', next)}
|
||||
>
|
||||
{getClassTypeLabel(v)}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '班级名称',
|
||||
dataIndex: 'className',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'className', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '班主任',
|
||||
dataIndex: 'headTeacher',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'headTeacher', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '任课教师',
|
||||
dataIndex: 'subjectTeacher',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'subjectTeacher', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '开始日期',
|
||||
dataIndex: 'startDate',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="date"
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'startDate', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '结束日期',
|
||||
dataIndex: 'endDate',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="date"
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'endDate', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
dataIndex: 'status',
|
||||
render: (v: string) => {
|
||||
render: (v: string, r) => {
|
||||
const status = getEnrollmentStatus(v);
|
||||
return <Tag color={status.color}>{status.text}</Tag>;
|
||||
return (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="select"
|
||||
options={Object.entries(ENROLLMENT_STATUS_MAP).map(([value, item]) => ({
|
||||
value,
|
||||
label: item.text,
|
||||
}))}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'status', next)}
|
||||
>
|
||||
<Tag color={status.color}>{status.text}</Tag>
|
||||
</EditableCell>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
@@ -520,34 +632,137 @@ const ExamScoresTab: React.FC<
|
||||
}
|
||||
};
|
||||
|
||||
const saveCell = async (record: ExamScoreRecord, field: string, value: unknown) => {
|
||||
await api.put(`/archive/exam-scores/${record.id}`, { [field]: value });
|
||||
message.success('考试成绩已保存');
|
||||
onRefresh();
|
||||
};
|
||||
|
||||
const columns: ColumnsType<ExamScoreRecord> = [
|
||||
{
|
||||
title: '考试类型',
|
||||
dataIndex: 'examType',
|
||||
render: (v: string) => EXAM_TYPE_OPTIONS.find((o) => o.value === v)?.label || v,
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="select"
|
||||
options={EXAM_TYPE_OPTIONS}
|
||||
permission="student:edit"
|
||||
required
|
||||
onSave={(next) => saveCell(r, 'examType', next)}
|
||||
>
|
||||
{EXAM_TYPE_OPTIONS.find((o) => o.value === v)?.label || v}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '考试名称',
|
||||
dataIndex: 'examName',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'examName', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '科目',
|
||||
dataIndex: 'subject',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
required
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'subject', next)}
|
||||
>
|
||||
{v}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '成绩',
|
||||
dataIndex: 'score',
|
||||
render: (v: number, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="number"
|
||||
min={0}
|
||||
required
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'score', next)}
|
||||
>
|
||||
{v}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{ title: '考试名称', dataIndex: 'examName', render: (v: string) => v || '-' },
|
||||
{ title: '科目', dataIndex: 'subject' },
|
||||
{ title: '成绩', dataIndex: 'score' },
|
||||
{
|
||||
title: '班级均分',
|
||||
dataIndex: 'classAvg',
|
||||
render: (v: number | undefined) => (v !== undefined ? v : '-'),
|
||||
render: (v: number | undefined, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="number"
|
||||
min={0}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'classAvg', next)}
|
||||
>
|
||||
{v !== undefined ? v : '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '排名',
|
||||
dataIndex: 'rank',
|
||||
render: (v: number | undefined) => (v !== undefined ? v : '-'),
|
||||
render: (v: number | undefined, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="number"
|
||||
min={1}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'rank', next)}
|
||||
>
|
||||
{v !== undefined ? v : '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '考试日期',
|
||||
dataIndex: 'examDate',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="date"
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'examDate', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{ title: '考试日期', dataIndex: 'examDate', render: (v: string) => v || '-' },
|
||||
{
|
||||
title: '关联报读',
|
||||
dataIndex: 'enrollmentId',
|
||||
render: (v: number | undefined) => {
|
||||
if (v === undefined) return '-';
|
||||
const enr = enrollments.find((e) => e.id === v);
|
||||
return enr ? formatEnrollmentDisplayName(enr) : String(v);
|
||||
},
|
||||
render: (v: number | undefined, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="select"
|
||||
options={enrollments.map((item) => ({
|
||||
value: item.id,
|
||||
label: formatEnrollmentDisplayName(item),
|
||||
}))}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'enrollmentId', next)}
|
||||
>
|
||||
{(() => {
|
||||
if (v === undefined) return '-';
|
||||
const enr = enrollments.find((e) => e.id === v);
|
||||
return enr ? formatEnrollmentDisplayName(enr) : String(v);
|
||||
})()}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -656,16 +871,87 @@ const LearningTab: React.FC<TabProps & { data: LearningRecord[] }> = ({
|
||||
}
|
||||
};
|
||||
|
||||
const saveCell = async (record: LearningRecord, field: string, value: unknown) => {
|
||||
await api.put(`/archive/learning-records/${record.id}`, { [field]: value });
|
||||
message.success('学情记录已保存');
|
||||
onRefresh();
|
||||
};
|
||||
|
||||
const columns: ColumnsType<LearningRecord> = [
|
||||
{ title: '记录日期', dataIndex: 'recordDate' },
|
||||
{
|
||||
title: '记录日期',
|
||||
dataIndex: 'recordDate',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="date"
|
||||
permission="student:edit"
|
||||
required
|
||||
onSave={(next) => saveCell(r, 'recordDate', next)}
|
||||
>
|
||||
{v}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '记录类型',
|
||||
dataIndex: 'recordType',
|
||||
render: (v: string) => RECORD_TYPE_OPTIONS.find((o) => o.value === v)?.label || v,
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="select"
|
||||
options={RECORD_TYPE_OPTIONS}
|
||||
permission="student:edit"
|
||||
required
|
||||
onSave={(next) => saveCell(r, 'recordType', next)}
|
||||
>
|
||||
{RECORD_TYPE_OPTIONS.find((o) => o.value === v)?.label || v}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '内容',
|
||||
dataIndex: 'content',
|
||||
ellipsis: true,
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="textarea"
|
||||
permission="student:edit"
|
||||
required
|
||||
onSave={(next) => saveCell(r, 'content', next)}
|
||||
>
|
||||
{v}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '跟进方式',
|
||||
dataIndex: 'followUpMethod',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'followUpMethod', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '下一步计划',
|
||||
dataIndex: 'nextStep',
|
||||
render: (v: string, r) => (
|
||||
<EditableCell
|
||||
value={v}
|
||||
editor="textarea"
|
||||
permission="student:edit"
|
||||
onSave={(next) => saveCell(r, 'nextStep', next)}
|
||||
>
|
||||
{v || '-'}
|
||||
</EditableCell>
|
||||
),
|
||||
},
|
||||
{ title: '内容', dataIndex: 'content', ellipsis: true },
|
||||
{ title: '跟进方式', dataIndex: 'followUpMethod', render: (v: string) => v || '-' },
|
||||
{ title: '下一步计划', dataIndex: 'nextStep', render: (v: string) => v || '-' },
|
||||
];
|
||||
|
||||
return (
|
||||
|
||||
Reference in New Issue
Block a user