fix(ActionDrawer): improve footer schema handling (#7414)

* fix(ActionDrawer): improve footer schema handling

* fix(ActionDrawer): avoid UI refresh issues by removing fieldSchema dependency

* fix(MobilePicker, MobileDatePicker): remove contentEditable attribute from div elements

* refactor(CreateRecordAction): optimize addNewerSchema handling
This commit is contained in:
Zeke Zhang
2025-08-21 13:44:37 +08:00
committed by GitHub
parent 255f2126fa
commit 121bdcd60b
6 changed files with 63 additions and 65 deletions
@@ -7,15 +7,15 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { observer, useField, useFieldSchema } from '@formily/react';
import React, { useState } from 'react';
import { observer, Schema, useField, useFieldSchema } from '@formily/react';
import React, { useMemo, useState } from 'react';
import { CollectionProvider_deprecated, useCollectionManager_deprecated } from '../../../../collection-manager';
import { NocoBaseRecursionField } from '../../../../formily/NocoBaseRecursionField';
import { CreateAction } from '../../../../schema-initializer/components';
import { ActionContextProvider, useActionContext } from '../../action';
import { TabsContextProvider } from '../../tabs/context';
import { useAssociationFieldContext, useInsertSchema } from '../hooks';
import schema from '../schema';
import { TabsContextProvider } from '../../tabs/context';
export const CreateRecordAction = observer(
(props) => {
@@ -31,12 +31,27 @@ export const CreateRecordAction = observer(
const [currentDataSource, setCurrentDataSource] = useState(targetCollection?.dataSource);
const [formValueChanged, setFormValueChanged] = useState(false);
const addNewerSchema = useMemo(() => {
return fieldSchema.reduceProperties((buf, s) => {
if (s['x-component'] === 'AssociationField.AddNewer') {
Object.keys(s.parent.properties).forEach((key) => {
if (key !== s.name) {
s.parent.removeProperty(key);
}
});
return s.parent;
}
return buf;
}) as Schema;
}, [fieldSchema]);
const addbuttonClick = (collectionData) => {
insertAddNewer(schema.AddNewer);
setVisibleAddNewer(true);
setCurrentCollection(collectionData.name);
setCurrentDataSource(collectionData.dataSource);
};
return (
<CollectionProvider_deprecated name={collectionField?.target}>
<CreateAction {...props} onClick={(arg) => addbuttonClick(arg)} />
@@ -51,14 +66,7 @@ export const CreateRecordAction = observer(
>
<CollectionProvider_deprecated name={currentCollection} dataSource={currentDataSource}>
<TabsContextProvider>
<NocoBaseRecursionField
onlyRenderProperties
basePath={field.address}
schema={fieldSchema}
filterProperties={(s) => {
return s['x-component'] === 'AssociationField.AddNewer';
}}
/>
<NocoBaseRecursionField basePath={field.address} schema={addNewerSchema} />
</TabsContextProvider>
</CollectionProvider_deprecated>
</ActionContextProvider>
@@ -7,7 +7,7 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import { ISchema, observer, useField, useFieldSchema } from '@formily/react';
import { ISchema, observer, Schema, useField, useFieldSchema } from '@formily/react';
import {
Action,
FlagProvider,
@@ -111,6 +111,12 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
const { visible, setVisible } = useActionContext();
const { visiblePopup } = usePopupContainer(visible);
// 克隆的目的是为了把底部按钮的 schema 去掉,避免重复渲染。
// 不使用 filterProperties 的原因是防止 Iphone 中出现卡死的问题,具体原因未知。
const clonedFieldSchema = useMemo(() => {
return new Schema(fieldSchema.toJSON());
}, []); // 不需要依赖 fieldSchema,不然会导致在弹窗中添加区块时不刷新 UI
// this schema need to add padding in the content area of the popup
const isSpecialSchema = isChangePasswordSchema(fieldSchema) || isEditProfileSchema(fieldSchema);
@@ -118,19 +124,15 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
const specialStyle = isSpecialSchema ? { backgroundColor: 'white' } : {};
const footerSchema = fieldSchema.reduceProperties((buf, s) => {
if (s['x-component'] === footerNodeName) {
return s;
}
return buf;
});
const filterProperties = useCallback(
(s) => {
return s['x-component'] !== footerNodeName;
},
[footerNodeName],
);
const footerSchema = useMemo(() => {
return clonedFieldSchema.reduceProperties((buf, s) => {
if (s['x-component'] === footerNodeName) {
s.parent.removeProperty(s.name); // 移除掉底部按钮区域的 schema
return s;
}
return buf;
}) as Schema;
}, [clonedFieldSchema, footerNodeName]);
const title = field.title || '';
@@ -140,27 +142,15 @@ export const ActionDrawerUsedInMobile: any = observer((props: { footerNodeName?:
const popupContent = isSpecialSchema ? (
<div style={{ padding: 12, ...specialStyle }}>
<SchemaComponent basePath={field.address} schema={fieldSchema} filterProperties={filterProperties} />
<SchemaComponent basePath={field.address} schema={clonedFieldSchema} />
</div>
) : (
<SchemaComponent
basePath={field.address}
schema={fieldSchema}
onlyRenderProperties
filterProperties={filterProperties}
/>
<SchemaComponent basePath={field.address} schema={clonedFieldSchema} />
);
const footerContent = footerSchema ? (
<div className="nb-mobile-action-drawer-footer" style={isSpecialSchema ? specialStyle : null}>
<NocoBaseRecursionField
basePath={field.address}
schema={fieldSchema}
onlyRenderProperties
filterProperties={(s) => {
return s['x-component'] === footerNodeName;
}}
/>
<NocoBaseRecursionField basePath={field.address} schema={footerSchema} />
</div>
) : null;
@@ -7,25 +7,25 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React, { useState, useCallback, useEffect, useRef } from 'react';
import { DatePicker, Picker } from 'antd-mobile';
import { Space, Select } from 'antd';
import {
mapDatePicker,
DatePicker as NBDatePicker,
useDatePickerContext,
useCompile,
inferPickerType,
TimePicker as NBTimePicker,
mapTimeFormat,
useVariables,
isVariable,
useLocalVariables,
} from '@nocobase/client';
import { autorun } from '@formily/reactive';
import dayjs from 'dayjs';
import { connect, mapProps, mapReadPretty, useField, useFieldSchema } from '@formily/react';
import { autorun } from '@formily/reactive';
import {
inferPickerType,
isVariable,
mapDatePicker,
mapTimeFormat,
DatePicker as NBDatePicker,
TimePicker as NBTimePicker,
useCompile,
useDatePickerContext,
useLocalVariables,
useVariables,
} from '@nocobase/client';
import { getPickerFormat } from '@nocobase/utils/client';
import { Select, Space } from 'antd';
import { DatePicker, Picker } from 'antd-mobile';
import dayjs from 'dayjs';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
function getPrecision(timeFormat: string): 'hour' | 'minute' | 'second' {
@@ -133,7 +133,7 @@ const MobileDateTimePicker = connect(
}, []);
return (
<>
<div contentEditable="false" onClick={() => !disabled && setVisible(true)}>
<div onClick={() => !disabled && setVisible(true)}>
<NBDatePicker
onClick={() => setVisible(true)}
value={value}
@@ -326,7 +326,7 @@ const MobileTimePicker: ComposedMobileTimePicker = connect(
};
return (
<div contentEditable="false" onClick={() => setVisible(true)}>
<div onClick={() => setVisible(true)}>
<NBTimePicker {...props} style={{ pointerEvents: 'none' }} />
<Picker onConfirm={handleTimeChange} columns={timeData} visible={visible} />
</div>
@@ -342,4 +342,4 @@ MobileDateFilterWithPicker.displayName = 'MobileDateFilterWithPicker';
MobileTimePicker.displayName = 'MobileTimePicker';
MobileTimePicker.RangePicker = NBTimePicker.RangePicker;
export { MobileDateTimePicker, MobileRangePicker, MobileDateFilterWithPicker, MobileTimePicker };
export { MobileDateFilterWithPicker, MobileDateTimePicker, MobileRangePicker, MobileTimePicker };
@@ -37,7 +37,7 @@ const MobilePicker = connect(
return (
<>
<div contentEditable="false" onClick={() => !disabled && setVisible(true)}>
<div onClick={() => !disabled && setVisible(true)}>
<Select
disabled={disabled}
value={value}
@@ -73,7 +73,7 @@ const MobileDateTimePicker = connect(
}, []);
return (
<>
<div contentEditable="false" onClick={() => setVisible(true)}>
<div onClick={() => setVisible(true)}>
<NBDatePicker
onClick={() => setVisible(true)}
value={value}
@@ -7,10 +7,10 @@
* For more information, please refer to: https://www.nocobase.com/agreement.
*/
import React, { useEffect, useMemo, useState } from 'react';
import { Button, CheckList, Popup, SearchBar } from 'antd-mobile';
import { connect, mapProps } from '@formily/react';
import { Select } from '@nocobase/client';
import { Button, CheckList, Popup, SearchBar } from 'antd-mobile';
import React, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
const MobilePicker = connect(
@@ -38,7 +38,7 @@ const MobilePicker = connect(
return (
<>
<div contentEditable="false" onClick={() => setVisible(true)}>
<div onClick={() => setVisible(true)}>
<Select
placeholder={t('Select')}
value={value}