feat: 支持表格单元格快捷编辑
Some checks failed
CI 检查 / lint (pull_request) Has been cancelled
CI 检查 / typecheck (pull_request) Has been cancelled
CI 检查 / test (pull_request) Has been cancelled

This commit is contained in:
2026-07-21 14:17:08 +08:00
parent fe0b71a0a9
commit a585fd42d5
17 changed files with 2197 additions and 303 deletions

View File

@@ -33,6 +33,7 @@ import {
import api from '../../api';
import PermissionButton from '../../components/PermissionButton';
import StudentProfileContent from '../../components/StudentProfileContent';
import EditableCell from '../../components/EditableCell';
import { maskIdNumber, maskPhone } from '../../utils/sensitive';
import { message } from '../../ui/app-message';
@@ -170,7 +171,14 @@ const StudentsPage: React.FC = () => {
message.error(err?.message || '加载失败,请稍后重试');
}
setLoading(false);
}, [searchName, showArchived, filterStatus, filterOrganizationId, filterClassId, filterTeacherId]);
}, [
searchName,
showArchived,
filterStatus,
filterOrganizationId,
filterClassId,
filterTeacherId,
]);
useEffect(() => {
fetchData();
@@ -213,6 +221,15 @@ const StudentsPage: React.FC = () => {
}
};
const saveCell = useCallback(
async (record: any, field: string, value: unknown) => {
await api.put(`/students/${record.id}`, { [field]: value });
message.success('已保存');
await fetchData();
},
[fetchData],
);
const handleArchive = async (id: number) => {
try {
await api.delete(`/students/${id}`);
@@ -376,9 +393,15 @@ const StudentsPage: React.FC = () => {
dataIndex: 'name',
width: 120,
render: (v: string, record: any) => (
<Button type="link" size="small" onClick={() => openDrawer(record.id)}>
<EditableCell
value={v}
required
permission="student:edit"
disabled={record.status === 'archived'}
onSave={(next) => saveCell(record, 'name', next)}
>
{v}
</Button>
</EditableCell>
),
},
{
@@ -407,7 +430,16 @@ const StudentsPage: React.FC = () => {
title: '学号',
dataIndex: 'studentNo',
width: 120,
render: (v: string) => v || '-',
render: (v: string, record: any) => (
<EditableCell
value={v}
permission="student:edit"
disabled={record.status === 'archived'}
onSave={(next) => saveCell(record, 'studentNo', next)}
>
{v || '-'}
</EditableCell>
),
},
{
title: '身份证',
@@ -431,8 +463,36 @@ const StudentsPage: React.FC = () => {
);
},
},
{ title: '民族', dataIndex: 'ethnicity', width: 90 },
{ title: '紧急联系人', dataIndex: 'emergencyContact', width: 100 },
{
title: '民族',
dataIndex: 'ethnicity',
width: 90,
render: (v: string, record: any) => (
<EditableCell
value={v}
permission="student:edit"
disabled={record.status === 'archived'}
onSave={(next) => saveCell(record, 'ethnicity', next)}
>
{v || '-'}
</EditableCell>
),
},
{
title: '紧急联系人',
dataIndex: 'emergencyContact',
width: 100,
render: (v: string, record: any) => (
<EditableCell
value={v}
permission="student:edit"
disabled={record.status === 'archived'}
onSave={(next) => saveCell(record, 'emergencyContact', next)}
>
{v || '-'}
</EditableCell>
),
},
{
title: '紧急联系人电话',
dataIndex: 'emergencyPhone',
@@ -459,30 +519,68 @@ const StudentsPage: React.FC = () => {
title: '所属机构',
dataIndex: 'organization',
width: 100,
render: (organization: { name?: string } | null) =>
organization?.name ? (
<Tag
color="purple"
style={{ maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis' }}
>
{organization.name}
</Tag>
) : (
'-'
),
render: (organization: { name?: string } | null, record: any) => (
<EditableCell
value={record.organizationId}
editor="select"
options={organizations.map((item) => ({ value: item.id, label: item.name }))}
permission="student:edit"
disabled={record.status === 'archived'}
required
onSave={(next) => saveCell(record, 'organizationId', next)}
>
{organization?.name ? (
<Tag
color="purple"
style={{ maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis' }}
>
{organization.name}
</Tag>
) : (
'-'
)}
</EditableCell>
),
},
{
title: '负责人',
dataIndex: 'supervisor',
width: 100,
render: (v: string, record: any) => (
<EditableCell
value={v}
permission="student:edit"
disabled={record.status === 'archived'}
onSave={(next) => saveCell(record, 'supervisor', next)}
>
{v || '-'}
</EditableCell>
),
},
{ title: '负责人', dataIndex: 'supervisor', width: 100 },
{
title: '状态',
dataIndex: 'status',
width: 80,
render: (s: string) => (
<Tag
color={statusMap[s]?.color}
style={{ maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis' }}
render: (s: string, record: any) => (
<EditableCell
value={s}
editor="select"
options={[
{ value: 'active', label: '在读' },
{ value: 'graduated', label: '已毕业' },
{ value: 'withdrawn', label: '已退训' },
]}
permission="student:edit"
disabled={record.status === 'archived'}
onSave={(next) => saveCell(record, 'status', next)}
>
{statusMap[s]?.text || s}
</Tag>
<Tag
color={statusMap[s]?.color}
style={{ maxWidth: '100%', overflow: 'hidden', textOverflow: 'ellipsis' }}
>
{statusMap[s]?.text || s}
</Tag>
</EditableCell>
),
},
{
@@ -547,7 +645,7 @@ const StudentsPage: React.FC = () => {
),
},
],
[handleViewSensitive, openDrawer, showArchived, organizations],
[handleViewSensitive, openDrawer, showArchived, organizations, saveCell],
);
return (