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

@@ -21,6 +21,7 @@ import { useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
import api from '../../api';
import PermissionButton from '../../components/PermissionButton';
import EditableCell from '../../components/EditableCell';
import { message } from '../../ui/app-message';
// ---- Types ----
@@ -167,6 +168,15 @@ const ClassesPage: React.FC = () => {
}
};
const saveCell = useCallback(
async (record: ClassItem, field: string, value: unknown) => {
await api.put(`/classes/${record.id}`, { [field]: value });
message.success('已保存');
await fetchData();
},
[fetchData],
);
const columns: ColumnsType<ClassItem> = useMemo(
() => [
{
@@ -174,33 +184,103 @@ const ClassesPage: React.FC = () => {
dataIndex: 'name',
width: 120,
sorter: (a, b) => a.name.localeCompare(b.name),
render: (v: string, r: ClassItem) => (
<EditableCell
value={v}
required
permission="class:edit"
disabled={r.isArchived}
onSave={(next) => saveCell(r, 'name', next)}
>
{v}
</EditableCell>
),
},
{
title: '编码',
dataIndex: 'code',
width: 140,
render: (v: string, r: ClassItem) => (
<EditableCell
value={v}
required
permission="class:edit"
disabled={r.isArchived}
onSave={(next) => saveCell(r, 'code', next)}
>
{v}
</EditableCell>
),
},
{ title: '编码', dataIndex: 'code', width: 140 },
{
title: '班型',
dataIndex: 'classType',
width: 100,
render: (v: string) => <Tag>{TYPE_MAP[v] || v}</Tag>,
render: (v: string, r: ClassItem) => (
<EditableCell
value={v}
editor="select"
options={Object.entries(TYPE_MAP).map(([value, label]) => ({ value, label }))}
permission="class:edit"
disabled={r.isArchived}
onSave={(next) => saveCell(r, 'classType', next)}
>
<Tag>{TYPE_MAP[v] || v}</Tag>
</EditableCell>
),
},
{
title: '开班日期',
dataIndex: 'startDate',
width: 110,
render: (v: string | null) => v || '-',
render: (v: string | null, r: ClassItem) => (
<EditableCell
value={v}
editor="date"
permission="class:edit"
disabled={r.isArchived}
onSave={(next) => saveCell(r, 'startDate', next)}
>
{v || '-'}
</EditableCell>
),
},
{
title: '学员',
width: 100,
render: (_: unknown, r: ClassItem) => `${r.studentCount || 0}/${r.maxStudents || '-'}`,
render: (_: unknown, r: ClassItem) => (
<EditableCell
value={r.maxStudents}
editor="number"
min={0}
permission="class:edit"
disabled={r.isArchived}
onSave={(next) => saveCell(r, 'maxStudents', next)}
>{`${r.studentCount || 0}/${r.maxStudents || '-'}`}</EditableCell>
),
},
{
title: '状态',
dataIndex: 'status',
width: 100,
render: (v: string) => {
const cfg = STATUS_MAP[v] || { color: 'default', text: v };
return <Tag color={cfg.color}>{cfg.text}</Tag>;
},
render: (v: string, r: ClassItem) => (
<EditableCell
value={v}
editor="select"
options={Object.entries(STATUS_MAP).map(([value, item]) => ({
value,
label: item.text,
}))}
permission="class:edit"
disabled={r.isArchived}
onSave={(next) => saveCell(r, 'status', next)}
>
{(() => {
const cfg = STATUS_MAP[v] || { color: 'default', text: v };
return <Tag color={cfg.color}>{cfg.text}</Tag>;
})()}
</EditableCell>
),
},
{
title: '操作',
@@ -237,7 +317,7 @@ const ClassesPage: React.FC = () => {
),
},
],
[],
[saveCell],
);
return (