feat: 重构各业务模块管理页面与服务

This commit is contained in:
2026-08-05 17:12:00 +08:00
parent 80e6fccf05
commit fd39e1686a
163 changed files with 18409 additions and 13449 deletions

View File

@@ -1,4 +1,8 @@
import React, { useEffect, useState, useCallback, useMemo } from 'react';
import React, { useState, useCallback, useMemo } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useApiMutation } from '../../hooks/useApiMutation';
import { validateResponse } from '../../utils/validate';
import { teacherListSchema } from '../../api/schemas';
import { Table, Input, Modal, Form, Select, DatePicker, Tag, Space } from 'antd';
import { EditOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
@@ -53,9 +57,6 @@ const DEFAULT_PAGE_SIZE = 20;
const TeachersPage: React.FC = () => {
const { hasPermission } = usePermission();
const canEditTeachers = hasPermission('teacher:edit');
const [data, setData] = useState<TeacherRow[]>([]);
const [loading, setLoading] = useState(false);
const [total, setTotal] = useState(0);
const [page, setPage] = useState(1);
const [pageSize, setPageSize] = useState(DEFAULT_PAGE_SIZE);
const [search, setSearch] = useState('');
@@ -63,43 +64,62 @@ const TeachersPage: React.FC = () => {
const [form] = Form.useForm<ProfileFormValues>();
const [saving, setSaving] = useState(false);
const fetchData = useCallback(async () => {
setLoading(true);
try {
const res = await api.get<TeacherListResponse>('/rbac/teachers', {
params: { search: search || undefined, page, pageSize },
});
setData(res.list);
setTotal(res.total);
} catch {
// silent
}
setLoading(false);
}, [page, pageSize, search]);
const {
data: fetchResult = { list: [], total: 0 },
isLoading,
isFetching,
} = useQuery<TeacherListResponse>({
queryKey: ['rbac', 'teachers', page, pageSize, search],
queryFn: async () => {
try {
return validateResponse<TeacherListResponse>(
teacherListSchema,
await api.get<TeacherListResponse>('/rbac/teachers', {
params: { search: search || undefined, page, pageSize },
}),
);
} catch {
return { list: [], total: 0 };
}
},
});
const data = fetchResult.list;
const total = fetchResult.total;
const loading = isLoading || isFetching;
useEffect(() => {
fetchData();
}, [fetchData]);
const saveProfileMutation = useApiMutation(
async ({
id,
values,
}: {
id: number;
values: { subjects: string[]; joinedAt?: string; qualifications?: string };
}) => api.put(`/rbac/teachers/${id}/profile`, values),
{ invalidate: [['rbac', 'teachers']] },
);
const saveProfileCellMutation = useApiMutation(
async ({ record, field, value }: { record: TeacherRow; field: string; value: unknown }) =>
api.put(`/rbac/teachers/${record.id}/profile`, { [field]: value }),
{ invalidate: [['rbac', 'teachers']] },
);
const handleSaveProfile = async () => {
const values = await form.validateFields();
if (!profileModal) return;
setSaving(true);
try {
await api.put(`/rbac/teachers/${profileModal.id}/profile`, {
subjects: values.subjects || [],
joinedAt: values.joinedAt?.format('YYYY-MM-DD'),
qualifications: values.qualifications,
await saveProfileMutation.mutateAsync({
id: profileModal.id,
values: {
subjects: values.subjects || [],
joinedAt: values.joinedAt?.format('YYYY-MM-DD'),
qualifications: values.qualifications,
},
});
message.success('已更新');
setProfileModal(null);
fetchData();
} catch (e: unknown) {
let msg = '更新失败';
if (e !== null && typeof e === 'object' && 'message' in e) {
msg = String(e.message);
}
message.error(msg);
} catch {
// 错误提示由 useApiMutation 统一处理
} finally {
setSaving(false);
}
@@ -107,11 +127,14 @@ const TeachersPage: React.FC = () => {
const saveProfileCell = useCallback(
async (record: TeacherRow, field: string, value: unknown) => {
await api.put(`/rbac/teachers/${record.id}/profile`, { [field]: value });
message.success('已保存');
await fetchData();
try {
await saveProfileCellMutation.mutateAsync({ record, field, value });
message.success('已保存');
} catch {
// 错误提示由 useApiMutation 统一处理
}
},
[fetchData],
[saveProfileCellMutation],
);
const columns = useMemo(