diff --git a/packages/core/client/src/locale/en_US.ts b/packages/core/client/src/locale/en_US.ts index d9266ac1fb..3e736627c8 100644 --- a/packages/core/client/src/locale/en_US.ts +++ b/packages/core/client/src/locale/en_US.ts @@ -285,4 +285,7 @@ export default { // workflows 'Execution History': 'History', 'Parallel branch': 'Branch', + + 'Confirm': 'Confirm', + 'Form value changed tip': 'Are you sure to exit that Data unsaved', } diff --git a/packages/core/client/src/locale/zh_CN.ts b/packages/core/client/src/locale/zh_CN.ts index e2d56da31b..b351953436 100644 --- a/packages/core/client/src/locale/zh_CN.ts +++ b/packages/core/client/src/locale/zh_CN.ts @@ -477,5 +477,7 @@ export default { 'Please select collection first': '请先选择数据表', 'Only update records matching conditions': '只更新满足条件的数据', 'Fields that are not assigned a value will be set to the default value, and those that do not have a default value are set to null.': '未被赋值的字段将被设置为默认值,没有默认值的设置为空值。', + 'Confirm': '确认', + 'Form value changed tip': '数据更新未保存,确定要退出吗?', 'Dragging': '拖拽中', } diff --git a/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx b/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx index da9bef2ce0..3a42479749 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.Drawer.tsx @@ -1,15 +1,17 @@ import { css } from '@emotion/css'; import { observer, RecursionField, useField, useFieldSchema } from '@formily/react'; -import { Drawer } from 'antd'; +import { Drawer, Modal as AntdModal } from 'antd'; import classNames from 'classnames'; import React from 'react'; import { createPortal } from 'react-dom'; +import { useTranslation } from 'react-i18next'; import { useActionContext } from './hooks'; import { ComposedActionDrawer } from './types'; export const ActionDrawer: ComposedActionDrawer = observer((props) => { const { footerNodeName = 'Action.Drawer.Footer', ...others } = props; - const { visible, setVisible } = useActionContext(); + const { t } = useTranslation(); + const { visible, setVisible, formValueChanged, setFormValueChanged } = useActionContext(); const schema = useFieldSchema(); const field = useField(); const footerSchema = schema.reduceProperties((buf, s) => { @@ -18,6 +20,21 @@ export const ActionDrawer: ComposedActionDrawer = observer((props) => { } return buf; }); + + const closeHandler = () => { + if (!formValueChanged) { + setVisible(false); + return; + } + AntdModal.confirm({ + title: t('Confirm'), + content: t('Form value changed tip'), + async onOk() { + setFormValueChanged(false); + setVisible(false); + }, + }); + }; return ( <> {createPortal( @@ -32,7 +49,7 @@ export const ActionDrawer: ComposedActionDrawer = observer((props) => { {...others} destroyOnClose visible={visible} - onClose={() => setVisible(false)} + onClose={closeHandler} className={classNames( others.className, css` diff --git a/packages/core/client/src/schema-component/antd/action/Action.tsx b/packages/core/client/src/schema-component/antd/action/Action.tsx index 604fc6fe80..082abc6c78 100644 --- a/packages/core/client/src/schema-component/antd/action/Action.tsx +++ b/packages/core/client/src/schema-component/antd/action/Action.tsx @@ -80,6 +80,7 @@ export const Action: ComposedAction = observer((props: any) => { } = props; const { onClick } = useProps(props); const [visible, setVisible] = useState(false); + const [formValueChanged, setFormValueChanged] = useState(false); const Designer = useDesigner(); const field = useField(); const { run } = useAction(); @@ -117,7 +118,17 @@ export const Action: ComposedAction = observer((props: any) => { ); return ( - + {popover && } {!popover && renderButton()} {!popover && props.children} diff --git a/packages/core/client/src/schema-component/antd/action/context.tsx b/packages/core/client/src/schema-component/antd/action/context.tsx index 82135c4a3b..7c0ea0af1e 100644 --- a/packages/core/client/src/schema-component/antd/action/context.tsx +++ b/packages/core/client/src/schema-component/antd/action/context.tsx @@ -8,4 +8,6 @@ export interface ActionContextProps { setVisible?: (v: boolean) => void; openMode?: 'drawer' | 'modal' | 'page'; containerRefKey?: string; + formValueChanged?: boolean; + setFormValueChanged?: (v: boolean) => void; } diff --git a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx index ef1d5b6dfc..3f9e3c07b1 100644 --- a/packages/core/client/src/schema-component/antd/form-v2/Form.tsx +++ b/packages/core/client/src/schema-component/antd/form-v2/Form.tsx @@ -1,8 +1,10 @@ import { FormLayout } from '@formily/antd'; -import { createForm, Field } from '@formily/core'; +import { createForm, Field, onFormInputChange } from '@formily/core'; import { FieldContext, FormContext, observer, RecursionField, useField, useFieldSchema } from '@formily/react'; +import { uid } from '@nocobase/utils'; import { Spin } from 'antd'; -import React, { useMemo } from 'react'; +import React, { useEffect, useMemo } from 'react'; +import { useActionContext } from '..'; import { useAttach, useComponent } from '../..'; import { useProps } from '../../hooks/useProps'; @@ -53,13 +55,37 @@ const FormDecorator: React.FC = (props) => { }; const WithForm = (props) => { + const { form } = props; const fieldSchema = useFieldSchema(); + const { setFormValueChanged } = useActionContext(); + useEffect(() => { + const id = uid(); + form.addEffects(id, () => { + onFormInputChange((form) => { + setFormValueChanged(true); + }); + }); + return () => { + form.removeEffects(id); + }; + }, []); return fieldSchema['x-decorator'] === 'Form' ? : ; }; const WithoutForm = (props) => { const fieldSchema = useFieldSchema(); - const form = useMemo(() => createForm(), []); + const { setFormValueChanged } = useActionContext(); + const form = useMemo( + () => + createForm({ + effects() { + onFormInputChange((form) => { + setFormValueChanged(true); + }); + }, + }), + [], + ); return fieldSchema['x-decorator'] === 'Form' ? ( ) : (