fix(admin): 补全 React hooks 依赖并修复页面稳定性
- 各页面 useCallback/useMemo 依赖补全(modal/mutation/setter 等),避免闭包过期 - ECharts 使用 optionRef、MainLayout 缓存菜单转换函数、main.tsx 增加挂载点校验 - 学生同步结果改用 recordsCount 展示
This commit is contained in:
@@ -93,10 +93,10 @@ const StudentsPage: React.FC = () => {
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [jinshujuOpen, setJinshujuOpen] = useState(false);
|
||||
|
||||
const openDrawer = (studentId: number) => {
|
||||
const openDrawer = useCallback((studentId: number) => {
|
||||
setDrawerStudentId(studentId);
|
||||
setDrawerOpen(true);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const logCreateRef = React.useRef(hasPermission('log:create'));
|
||||
const sensitiveModalRef = React.useRef<ReturnType<typeof modal.confirm> | null>(null);
|
||||
@@ -112,38 +112,41 @@ const StudentsPage: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleViewSensitive = (studentId: number, field: string, value: string) => {
|
||||
if (!logCreateRef.current) return;
|
||||
sensitiveModalRef.current = modal.confirm({
|
||||
title: '查看敏感信息',
|
||||
content: `您即将查看 "${field}" 的完整信息。此操作将被记录。`,
|
||||
okText: '确认查看',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
if (!logCreateRef.current) return;
|
||||
try {
|
||||
await api.post('/operation-logs/audit', {
|
||||
module: '学生管理',
|
||||
action: '查看敏感信息',
|
||||
targetId: studentId,
|
||||
targetType: 'student',
|
||||
detail: `查看${field}`,
|
||||
});
|
||||
modal.info({
|
||||
title: field,
|
||||
content: value,
|
||||
okText: '关闭',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('审计日志记录失败', e);
|
||||
message.error('审计日志记录失败,请稍后重试');
|
||||
}
|
||||
},
|
||||
afterClose: () => {
|
||||
sensitiveModalRef.current = null;
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleViewSensitive = useCallback(
|
||||
(studentId: number, field: string, value: string) => {
|
||||
if (!logCreateRef.current) return;
|
||||
sensitiveModalRef.current = modal.confirm({
|
||||
title: '查看敏感信息',
|
||||
content: `您即将查看 "${field}" 的完整信息。此操作将被记录。`,
|
||||
okText: '确认查看',
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
if (!logCreateRef.current) return;
|
||||
try {
|
||||
await api.post('/operation-logs/audit', {
|
||||
module: '学生管理',
|
||||
action: '查看敏感信息',
|
||||
targetId: studentId,
|
||||
targetType: 'student',
|
||||
detail: `查看${field}`,
|
||||
});
|
||||
modal.info({
|
||||
title: field,
|
||||
content: value,
|
||||
okText: '关闭',
|
||||
});
|
||||
} catch (e) {
|
||||
console.error('审计日志记录失败', e);
|
||||
message.error('审计日志记录失败,请稍后重试');
|
||||
}
|
||||
},
|
||||
afterClose: () => {
|
||||
sensitiveModalRef.current = null;
|
||||
},
|
||||
});
|
||||
},
|
||||
[modal],
|
||||
);
|
||||
|
||||
const {
|
||||
data = [],
|
||||
@@ -329,41 +332,50 @@ const StudentsPage: React.FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleArchive = async (id: number) => {
|
||||
try {
|
||||
await archiveMutation.mutateAsync(id);
|
||||
message.success('已归档');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
};
|
||||
const handleArchive = useCallback(
|
||||
async (id: number) => {
|
||||
try {
|
||||
await archiveMutation.mutateAsync(id);
|
||||
message.success('已归档');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
},
|
||||
[archiveMutation],
|
||||
);
|
||||
|
||||
const handleRestore = async (id: number) => {
|
||||
try {
|
||||
await restoreMutation.mutateAsync(id);
|
||||
message.success('已恢复');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
};
|
||||
const handleRestore = useCallback(
|
||||
async (id: number) => {
|
||||
try {
|
||||
await restoreMutation.mutateAsync(id);
|
||||
message.success('已恢复');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
},
|
||||
[restoreMutation],
|
||||
);
|
||||
|
||||
const handlePurge = (id: number, name: string) => {
|
||||
modal.confirm({
|
||||
title: `永久删除学生「${name}」?`,
|
||||
content: '删除后不可恢复,该学生及其关联数据将无法找回。确定继续?',
|
||||
okText: '永久删除',
|
||||
okButtonProps: { danger: true },
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await purgeMutation.mutateAsync(id);
|
||||
message.success('已永久删除(不可恢复)');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
const handlePurge = useCallback(
|
||||
(id: number, name: string) => {
|
||||
modal.confirm({
|
||||
title: `永久删除学生「${name}」?`,
|
||||
content: '删除后不可恢复,该学生及其关联数据将无法找回。确定继续?',
|
||||
okText: '永久删除',
|
||||
okButtonProps: { danger: true },
|
||||
cancelText: '取消',
|
||||
onOk: async () => {
|
||||
try {
|
||||
await purgeMutation.mutateAsync(id);
|
||||
message.success('已永久删除(不可恢复)');
|
||||
} catch {
|
||||
// 错误提示由 useApiMutation 统一处理
|
||||
}
|
||||
},
|
||||
});
|
||||
},
|
||||
[modal, purgeMutation],
|
||||
);
|
||||
|
||||
const handleBatchDelete = async () => {
|
||||
if (batchLoading) return;
|
||||
@@ -450,7 +462,9 @@ const StudentsPage: React.FC = () => {
|
||||
if (log?.status === 'partial') {
|
||||
message.warning(log.errorMessage || '钉钉同步完成,但有数据需要人工处理');
|
||||
} else {
|
||||
message.success(log?.errorMessage || `钉钉同步完成,共处理 ${res.synced} 条`);
|
||||
message.success(
|
||||
log?.errorMessage || `钉钉同步完成,共处理 ${log?.recordsCount ?? res.synced} 条`,
|
||||
);
|
||||
}
|
||||
void queryClient.invalidateQueries({ queryKey: ['students'] });
|
||||
} catch (e: unknown) {
|
||||
@@ -506,6 +520,10 @@ const StudentsPage: React.FC = () => {
|
||||
saveCell,
|
||||
handleViewSensitive,
|
||||
form,
|
||||
openDrawer,
|
||||
handleArchive,
|
||||
handleRestore,
|
||||
handlePurge,
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user