Files
gongxue-base/apps/admin/src/hooks/useDirtyGuard.ts
wangziqi a32c0a0731 fix(admin): 前端功能与安全修复(角色勾选/月视图新建/附件预览/考勤 late 等)
由 OCR(open-codereview.ai,deepseek-v4-flash)审查驱动修复:
- Roles 多组勾选、Schedules 月视图新建、useDirtyGuard 稳定引用
- JinshujuMatchModal success:false、Attendance store 订阅、EditableCell Enter、DynamicChart 空数据、渲染期 ref
- AttachmentsTab 附件预览不再 window.open(防存储型 XSS)+ 请求乱序防护
- 考勤 late 计入出勤率与主状态

Reviewed-by: OCR (open-codereview.ai)
2026-08-09 21:29:54 +08:00

54 lines
1.8 KiB
TypeScript

import { useCallback, useMemo, useRef } from 'react';
import { App } from 'antd';
import type { FormInstance } from 'antd';
import equal from 'fast-deep-equal';
/**
* 弹窗「未保存内容」保护:关闭弹窗时若表单值已被修改,先确认再关闭,
* 避免用户误关丢失已填内容。
*
* 用法:
* const { confirmClose, snapshot } = useDirtyGuard(form);
* // 打开弹窗(或编辑回填后)时调用一次 snapshot() 记录初始值
* const openEdit = () => { form.setFieldsValue(record); snapshot(); setOpen(true); };
* // Modal 的 onCancel 改用确认关闭
* <Modal onCancel={() => confirmClose(() => setOpen(false))} ...>
*/
export function useDirtyGuard(form: FormInstance) {
const { modal } = App.useApp();
const pristineRef = useRef<unknown>(null);
/** 记录当前表单值为「未修改」基准;打开弹窗/回填后调用 */
const snapshot = useCallback(() => {
pristineRef.current = form.getFieldsValue();
}, [form]);
/** 表单是否有未保存修改(与 snapshot 时对比) */
const isDirty = useCallback(() => {
return !equal(form.getFieldsValue(), pristineRef.current);
}, [form]);
const confirmClose = useCallback(
(close: () => void) => {
if (!isDirty()) {
close();
return;
}
modal.confirm({
title: '放弃未保存的修改?',
content: '当前表单有未保存的内容,关闭后修改将丢失。',
okText: '放弃修改',
okButtonProps: { danger: true },
cancelText: '继续编辑',
onOk: close,
});
},
[isDirty, modal],
);
// 用 useMemo 稳定返回对象引用,避免消费方 useEffect 依赖每次渲染都变化
return useMemo(() => ({ confirmClose, snapshot, isDirty }), [confirmClose, snapshot, isDirty]);
}
export default useDirtyGuard;