fix(admin): 补全 React hooks 依赖并修复页面稳定性

- 各页面 useCallback/useMemo 依赖补全(modal/mutation/setter 等),避免闭包过期
- ECharts 使用 optionRef、MainLayout 缓存菜单转换函数、main.tsx 增加挂载点校验
- 学生同步结果改用 recordsCount 展示
This commit is contained in:
2026-08-05 21:14:08 +08:00
parent ab4765cee5
commit 1a1e90c72f
21 changed files with 439 additions and 341 deletions

View File

@@ -44,16 +44,19 @@ const UsersPage: React.FC = () => {
const [saving, setSaving] = useState(false);
const [showArchived, setShowArchived] = useState(false);
const handleOpenProfile = async (record: any) => {
setProfileUser(record);
try {
const res = await api.get<UserProfileResponse>(`/rbac/users/${record.id}/profile`);
profileForm.setFieldsValue(userProfileResponseToFormValues(res));
} catch {
profileForm.setFieldsValue({});
}
setProfileModalOpen(true);
};
const handleOpenProfile = useCallback(
async (record: any) => {
setProfileUser(record);
try {
const res = await api.get<UserProfileResponse>(`/rbac/users/${record.id}/profile`);
profileForm.setFieldsValue(userProfileResponseToFormValues(res));
} catch {
profileForm.setFieldsValue({});
}
setProfileModalOpen(true);
},
[profileForm],
);
const handleProfileSubmit = async () => {
setSaving(true);
@@ -133,15 +136,18 @@ const UsersPage: React.FC = () => {
setModalOpen(true);
};
const handleEdit = (record: any) => {
setEditing(record);
form.setFieldsValue({
username: record.username,
name: record.name,
roleIds: record.roles?.map((r: any) => r.id) || [],
});
setModalOpen(true);
};
const handleEdit = useCallback(
(record: any) => {
setEditing(record);
form.setFieldsValue({
username: record.username,
name: record.name,
roleIds: record.roles?.map((r: any) => r.id) || [],
});
setModalOpen(true);
},
[form],
);
const handleSubmit = async () => {
setSaving(true);
@@ -161,39 +167,48 @@ const UsersPage: React.FC = () => {
setSaving(false);
}
};
const handleArchive = async (id: number, archive: boolean) => {
try {
await archiveMutation.mutateAsync({ id, archive });
message.success(archive ? '已归档' : '已恢复');
} catch {
// 错误提示由 useApiMutation 统一处理
}
};
const handleArchive = useCallback(
async (id: number, archive: boolean) => {
try {
await archiveMutation.mutateAsync({ id, archive });
message.success(archive ? '已归档' : '已恢复');
} catch {
// 错误提示由 useApiMutation 统一处理
}
},
[archiveMutation],
);
const handlePurge = (record: any) => {
modal.confirm({
title: `永久删除账号「${record.name || record.username}」?`,
content:
'删除后不可恢复,关联学生、任教、排课或考勤操作时将无法删除;角色绑定、通知和 AI 会话将被清除,操作日志保留。确定继续?',
okText: '永久删除',
okButtonProps: { danger: true },
cancelText: '取消',
onOk: async () => {
try {
await purgeMutation.mutateAsync(record.id);
message.success('已永久删除(不可恢复)');
} catch {
// 错误提示由 useApiMutation 统一处理
}
},
});
};
const handlePurge = useCallback(
(record: any) => {
modal.confirm({
title: `永久删除账号「${record.name || record.username}」?`,
content:
'删除后不可恢复,关联学生、任教、排课或考勤操作时将无法删除;角色绑定、通知和 AI 会话将被清除,操作日志保留。确定继续?',
okText: '永久删除',
okButtonProps: { danger: true },
cancelText: '取消',
onOk: async () => {
try {
await purgeMutation.mutateAsync(record.id);
message.success('已永久删除(不可恢复)');
} catch {
// 错误提示由 useApiMutation 统一处理
}
},
});
},
[modal, purgeMutation],
);
const handleResetPwd = (record: any) => {
setResetTarget(record);
pwdForm.resetFields();
setPwdModalOpen(true);
};
const handleResetPwd = useCallback(
(record: any) => {
setResetTarget(record);
pwdForm.resetFields();
setPwdModalOpen(true);
},
[pwdForm],
);
const handlePwdSubmit = async () => {
setSaving(true);
@@ -353,7 +368,16 @@ const UsersPage: React.FC = () => {
),
},
],
[roles, saveCell, canPurgeUser, handlePurge],
[
roles,
saveCell,
canPurgeUser,
handlePurge,
handleOpenProfile,
handleEdit,
handleArchive,
handleResetPwd,
],
);
return (