feat: 重构各业务模块管理页面与服务
This commit is contained in:
@@ -1,16 +1,21 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import { Alert, Button, Card, Descriptions, Empty, Space, Spin, Table, Tag, Tooltip } from 'antd';
|
||||
import type { ColumnsType } from 'antd/es/table';
|
||||
import { ArrowLeftOutlined, EyeOutlined } from '@ant-design/icons';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { useNavigate, useParams } from 'react-router';
|
||||
import api from '../../api';
|
||||
import EditableCell from '../../components/EditableCell';
|
||||
import { useViewSensitive } from '../../hooks/useViewSensitive';
|
||||
import { maskPhone } from '../../utils/sensitive';
|
||||
import { message } from '../../ui/app-message';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import { useApiMutation } from '../../hooks/useApiMutation';
|
||||
import { validateResponse } from '../../utils/validate';
|
||||
import { examDetailSchema } from '../../api/schemas';
|
||||
import { usePermission } from '../../hooks/usePermission';
|
||||
import type { ExamItem } from './types';
|
||||
import './style.css';
|
||||
import { getErrorMessage } from '../../utils/error';
|
||||
|
||||
interface ScoreRow {
|
||||
id: number;
|
||||
@@ -50,30 +55,38 @@ const PhoneCell: React.FC<{ row: ScoreRow }> = ({ row }) => {
|
||||
const ExamDetailPage: React.FC = () => {
|
||||
const { id } = useParams();
|
||||
const navigate = useNavigate();
|
||||
const [detail, setDetail] = useState<ExamDetail | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
const load = useCallback(async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
setDetail(await api.get<ExamDetail>(`/exams/${id}`));
|
||||
} catch (error) {
|
||||
message.error((error as { message?: string })?.message || '加载考试失败');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [id]);
|
||||
const { data: detail, isLoading, isFetching } = useQuery<ExamDetail | null>({
|
||||
queryKey: ['exams', 'detail', id],
|
||||
queryFn: async () => {
|
||||
try {
|
||||
return validateResponse<ExamDetail>(
|
||||
examDetailSchema,
|
||||
await api.get<ExamDetail>(`/exams/${id}`),
|
||||
);
|
||||
} catch (error) {
|
||||
message.error(getErrorMessage(error, '加载考试失败'));
|
||||
return null;
|
||||
}
|
||||
},
|
||||
});
|
||||
const loading = isLoading || isFetching;
|
||||
|
||||
useEffect(() => {
|
||||
void load();
|
||||
}, [load]);
|
||||
const saveScoreMutation = useApiMutation(
|
||||
async ({ rowId, score }: { rowId: number; score: number | null }) =>
|
||||
api.put(`/exams/${id}/scores/${rowId}`, { score }),
|
||||
{ invalidate: [['exams', 'detail', id]] },
|
||||
);
|
||||
const saveScore = useCallback(
|
||||
async (row: ScoreRow, value: number | undefined) => {
|
||||
await api.put(`/exams/${id}/scores/${row.id}`, { score: value ?? null });
|
||||
message.success('成绩已保存');
|
||||
await load();
|
||||
try {
|
||||
await saveScoreMutation.mutateAsync({ rowId: row.id, score: value ?? null });
|
||||
message.success('成绩已保存');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
},
|
||||
[id, load],
|
||||
[saveScoreMutation],
|
||||
);
|
||||
|
||||
const columns = useMemo<ColumnsType<ScoreRow>>(() => {
|
||||
|
||||
Reference in New Issue
Block a user