feat(admin): 弹窗未保存内容保护与空状态引导扩展
- 新增 useDirtyGuard hook:关闭弹窗时若表单被修改,先确认再关闭 - 接入 17 个弹窗:账号/密码/档案、角色、教室、宿舍/床位/柜子、 考勤机、机构、班级、教师、考试、排课、入住/退宿/换房、费用 (子组件弹窗用 useEffect 在回填后快照,时序正确) - 学生列表空状态带「添加学生」引导动作 - 排课创建成功提示「同步钉钉」,考试创建成功提示「去详情录成绩」 aislop scan: 5 引擎 0 issues
This commit is contained in:
@@ -40,7 +40,9 @@ import { validateResponse } from '../../utils/validate';
|
||||
import { classOptionsSchema, examsSchema } from '../../api/schemas';
|
||||
import { getErrorMessage } from '../../utils/error';
|
||||
import { QueryErrorState } from '../../components/QueryState';
|
||||
import { NextStepHint } from '../../components/NextStepHint';
|
||||
import { useVisibleRefetch } from '../../hooks/usePageVisible';
|
||||
import { useDirtyGuard } from '../../hooks/useDirtyGuard';
|
||||
|
||||
const ExamsPage: React.FC = () => {
|
||||
const { modal } = App.useApp();
|
||||
@@ -48,6 +50,7 @@ const ExamsPage: React.FC = () => {
|
||||
const { hasPermission } = usePermission();
|
||||
const canPurgeExam = hasPermission('exam:purge');
|
||||
const [form] = Form.useForm<ExamFormValues>();
|
||||
const examFormGuard = useDirtyGuard(form);
|
||||
const [batchLoading, setBatchLoading] = useState(false);
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [modalOpen, setModalOpen] = useState(false);
|
||||
@@ -56,6 +59,8 @@ const ExamsPage: React.FC = () => {
|
||||
const [classId, setClassId] = useState<number>();
|
||||
const [showArchived, setShowArchived] = useState(false);
|
||||
const [selectedExamIds, setSelectedExamIds] = useState<number[]>([]);
|
||||
// 考试创建成功后的「下一步:去详情录成绩」引导
|
||||
const [examCreatedId, setExamCreatedId] = useState<number | null>(null);
|
||||
const [debouncedFilters] = useDebounceValue(
|
||||
{ keyword, examType, classId, showArchived },
|
||||
200,
|
||||
@@ -152,6 +157,7 @@ const ExamsPage: React.FC = () => {
|
||||
const openCreate = () => {
|
||||
form.resetFields();
|
||||
form.setFieldValue('examDate', dayjs());
|
||||
examFormGuard.snapshot();
|
||||
setModalOpen(true);
|
||||
};
|
||||
|
||||
@@ -160,9 +166,10 @@ const ExamsPage: React.FC = () => {
|
||||
const values = await form.validateFields();
|
||||
setSaving(true);
|
||||
const payload = { ...values, examDate: values.examDate.format('YYYY-MM-DD') };
|
||||
await saveMutation.mutateAsync(payload);
|
||||
const created = (await saveMutation.mutateAsync(payload)) as { id?: number };
|
||||
message.success('考试已创建');
|
||||
setModalOpen(false);
|
||||
if (created?.id != null) setExamCreatedId(created.id);
|
||||
} catch {
|
||||
// 校验错误静默,接口错误由 useApiMutation 统一提示
|
||||
} finally {
|
||||
@@ -255,6 +262,20 @@ const ExamsPage: React.FC = () => {
|
||||
|
||||
return (
|
||||
<div className="exam-page">
|
||||
{examCreatedId !== null && (
|
||||
<NextStepHint
|
||||
title="考试已创建"
|
||||
description="接下来可以在考试详情中添加学生名单、录入成绩。"
|
||||
action={{
|
||||
label: '去考试详情',
|
||||
onClick: () => {
|
||||
navigate(`/exams/${examCreatedId}`);
|
||||
setExamCreatedId(null);
|
||||
},
|
||||
}}
|
||||
onClose={() => setExamCreatedId(null)}
|
||||
/>
|
||||
)}
|
||||
<div className="exam-toolbar">
|
||||
<Space wrap>
|
||||
<Input
|
||||
@@ -461,7 +482,7 @@ const ExamsPage: React.FC = () => {
|
||||
saving={saving}
|
||||
form={form}
|
||||
classes={classes}
|
||||
onCancel={() => setModalOpen(false)}
|
||||
onCancel={() => examFormGuard.confirmClose(() => setModalOpen(false))}
|
||||
onSubmit={() => void submit()}
|
||||
/>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user