fix: fix the wrong way to determine form type (#2787)

* fix: depulicate action should load data as depulicate fields config

* fix: use formBlockType

* Revert "fix: depulicate action should load data as depulicate fields config"

This reverts commit 5acb6a1c7046ae8a89243edc4529a5201a9a8f14.

* Revert "fix: should allow to set default value in relationship form block (#2777)"

This reverts commit 0140e623a6.

* fix: should not lazy load data of association fields when creating form

* fix: fix subTable

* test: add test

* test: add test

---------

Co-authored-by: Rain <958414905@qq.com>
This commit is contained in:
katherinehhh 2023-10-11 14:24:31 +08:00 committed by GitHub
parent af32a4e59a
commit 87a8b2e844
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 454 additions and 101 deletions

View File

@ -0,0 +1,368 @@
import { expect, test } from '@nocobase/test/client';
const config = {
pageSchema: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Page',
properties: {
k24lhmzq1u2: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'BlockInitializers',
properties: {
zp24b63pujf: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
e8t2mwjafsr: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
'9x571vayogf': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'TableBlockProvider',
'x-acl-action': 'users:list',
'x-decorator-props': {
collection: 'users',
resource: 'users',
action: 'list',
params: {
pageSize: 20,
},
rowKey: 'id',
showIndex: true,
dragSort: false,
disableTemplate: false,
},
'x-designer': 'TableBlockDesigner',
'x-component': 'CardItem',
'x-filter-targets': [],
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'TableActionInitializers',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 'var(--nb-spacing)',
},
},
'x-uid': 't2drja2eg9p',
'x-async': false,
'x-index': 1,
},
edacvuffzln: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'array',
'x-initializer': 'TableColumnInitializers',
'x-component': 'TableV2',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useProps: '{{ useTableBlockProps }}',
},
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Actions") }}',
'x-action-column': 'actions',
'x-decorator': 'TableV2.Column.ActionBar',
'x-component': 'TableV2.Column',
'x-designer': 'TableV2.ActionColumnDesigner',
'x-initializer': 'TableActionColumnInitializers',
properties: {
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-decorator': 'DndContext',
'x-component': 'Space',
'x-component-props': {
split: '|',
},
properties: {
'2j97snwh4cx': {
'x-uid': 'il7gzvmxtzu',
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-action': 'duplicate',
'x-acl-action': 'create',
title: '{{ t("Duplicate") }}',
'x-designer': 'Action.Designer',
'x-component': 'Action.Link',
'x-decorator': 'ACLActionProvider',
'x-component-props': {
openMode: 'drawer',
component: 'DuplicateAction',
duplicateMode: 'continueduplicate',
duplicateFields: ['nickname'],
duplicateCollection: 'users',
},
'x-designer-props': {
linkageAction: true,
},
properties: {
drawer: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{ t("Duplicate") }}',
'x-component': 'Action.Container',
'x-component-props': {
className: 'nb-action-popup',
},
properties: {
tabs: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Tabs',
'x-component-props': {},
'x-initializer': 'TabPaneInitializers',
properties: {
tab1: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
title: '{{t("Duplicate")}}',
'x-component': 'Tabs.TabPane',
'x-designer': 'Tabs.Designer',
'x-component-props': {},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'CreateFormBlockInitializers',
properties: {
qvyfk3a9a2g: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
t8ooaf8oxm0: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
jo3yh728qkn: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-acl-action-props': {
skipScopeCheck: true,
},
'x-acl-action': 'users:create',
'x-decorator': 'FormBlockProvider',
'x-decorator-props': {
resource: 'users',
collection: 'users',
},
'x-designer': 'FormV2.Designer',
'x-component': 'CardItem',
'x-component-props': {},
properties: {
'5o8dowro5r1': {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'FormV2',
'x-component-props': {
useProps: '{{ useFormBlockProps }}',
},
properties: {
grid: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid',
'x-initializer': 'FormItemInitializers',
properties: {
h191s1ni4am: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Row',
properties: {
twb640jbhp3: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-component': 'Grid.Col',
properties: {
nickname: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
'x-designer': 'FormItem.Designer',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field':
'users.nickname',
'x-component-props': {},
'x-uid': 'pcw1toh7uvj',
'x-async': false,
'x-index': 1,
},
roles: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'string',
'x-designer': 'FormItem.Designer',
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'users.roles',
'x-component-props': {},
'x-uid': '197m7f208wz',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'bbkl81w4h5e',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '1blkzui6noh',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'pcpeajmpx9k',
'x-async': false,
'x-index': 1,
},
actions: {
_isJSONSchemaObject: true,
version: '2.0',
type: 'void',
'x-initializer': 'CreateFormActionInitializers',
'x-component': 'ActionBar',
'x-component-props': {
layout: 'one-column',
style: {
marginTop: 24,
},
},
'x-uid': 'y3gbelepat8',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'rmu7uywdppl',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kpfthrvsd4l',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'drns72r222r',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'iongy3crgdb',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'zw6jjtof2vn',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'hm4kv8gn4me',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'pztq2aledmj',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'qr43qja4p46',
'x-async': false,
'x-index': 1,
},
},
'x-async': false,
'x-index': 1,
},
},
'x-uid': '2w1e5m7at3v',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'uranm958u5v',
'x-async': false,
'x-index': 1,
},
},
'x-uid': '38j1kvvsdtp',
'x-async': false,
'x-index': 2,
},
},
'x-uid': 'k4isiobw2ab',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'vr305f4itlf',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 't70enk0z07b',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'rjmnr89y4f6',
'x-async': false,
'x-index': 1,
},
},
'x-uid': 'kd7dz5gvdv6',
'x-async': true,
'x-index': 1,
},
};
// fix https://nocobase.height.app/T-2187
test('BUG: in the Duplicate mode, the Roles field should not have a value after clicking it because it is not selected', async ({
page,
mockPage,
}) => {
await mockPage(config).goto();
await page.getByText('Duplicate').click();
await expect(page.getByTestId('users.nickname-field').getByRole('textbox')).toHaveValue('Super Admin');
await expect(page.getByText('AdminMemberRoot')).not.toBeVisible();
});

View File

@ -13,7 +13,7 @@ import { FormActiveFieldsProvider } from './hooks';
export const FormBlockContext = createContext<any>({});
const InternalFormBlockProvider = (props) => {
const { action, readPretty, params, association } = props;
const { action, readPretty, params } = props;
const field = useField();
const form = useMemo(
() =>
@ -35,6 +35,8 @@ const InternalFormBlockProvider = (props) => {
params,
action,
form,
// update 表示是表单编辑区块create 表示是表单新增区块
type: action === 'get' ? 'update' : 'create',
field,
service,
resource,
@ -42,46 +44,28 @@ const InternalFormBlockProvider = (props) => {
formBlockRef,
}}
>
{association ? associationBlock() : normalBlock()}
{readPretty ? (
<RecordProvider parent={isEmpty(record?.__parent) ? record : record?.__parent} record={service?.data?.data}>
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
</RecordProvider>
) : (
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
)}
</FormBlockContext.Provider>
);
};
function normalBlock(): React.ReactNode {
return readPretty ? (
<RecordProvider parent={isEmpty(record?.__parent) ? record : record?.__parent} record={service?.data?.data}>
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
</RecordProvider>
) : (
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
);
}
// 这里的 Form 区块是新增区块,所以其字段是可以设置默认值的。又因为是否可以设置默认值是由 record 是否为空来决定的,
// 所以在这里需要将 record 设置为空这样就可以设置默认值了。相关代码https://github.com/nocobase/nocobase/blob/fa3127e467bcb3a2425eedfb06fc110cc6eb7f1e/packages/core/client/src/schema-settings/hooks/useIsAllowToSetDefaultValue.tsx#L117-L122
//
// 如何添加关系区块的 Form 区块:
// 1. 点击 Table 行的查看按钮;
// 2. 弹出的 Drawer 中可以添加关系区块;
// 3. 选择对多字段,点击 Form 区块;
function associationBlock(): React.ReactNode {
return readPretty ? (
<RecordProvider parent={isEmpty(record?.__parent) ? record : record?.__parent} record={service?.data?.data}>
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
</RecordProvider>
) : (
<RecordProvider parent={isEmpty(record?.__parent) ? record : record?.__parent} record={{}}>
<div ref={formBlockRef}>
<RenderChildrenWithDataTemplates form={form} />
</div>
</RecordProvider>
);
}
/**
* update create
* @returns
*/
export const useFormBlockType = () => {
const ctx = useFormBlockContext() || {};
return { type: ctx.type } as { type: 'update' | 'create' };
};
export const useIsEmptyRecord = () => {

View File

@ -6,14 +6,14 @@ import { RecursionField, observer, useFieldSchema } from '@formily/react';
import { action } from '@formily/reactive';
import { each } from '@formily/shared';
import { Button, Card, Divider, Tooltip } from 'antd';
import _ from 'lodash';
import React, { useCallback, useContext } from 'react';
import { useTranslation } from 'react-i18next';
import { FormActiveFieldsProvider } from '../../../block-provider';
import { RecordIndexProvider, RecordProvider, useRecord } from '../../../record-provider';
import { RecordIndexProvider, RecordProvider } from '../../../record-provider';
import { isPatternDisabled, isSystemField } from '../../../schema-settings';
import {
DefaultValueProvider,
IsAllowToSetDefaultValueParams,
interfacesOfUnsupportedDefaultValue,
} from '../../../schema-settings/hooks/useIsAllowToSetDefaultValue';
import { LocalVariablesProvider } from '../../../variables/hooks/useLocalVariables';
@ -33,40 +33,39 @@ export const Nester = (props) => {
const ToOneNester = (props) => {
const { field } = useAssociationFieldContext<ArrayField>();
const record = useRecord();
// 保存原始数据,用于判断是否允许设置默认值
const recordRef = React.useRef(_.omit(record, '__parent'));
const isAllowToSetDefaultValue = useCallback(({ form, fieldSchema, collectionField, getInterface }) => {
if (!collectionField) {
if (process.env.NODE_ENV !== 'production') {
console.error(`collectionField should not be ${collectionField}`);
const isAllowToSetDefaultValue = useCallback(
({ form, fieldSchema, collectionField, getInterface, formBlockType }: IsAllowToSetDefaultValueParams) => {
if (!collectionField) {
if (process.env.NODE_ENV !== 'production') {
console.error(`collectionField should not be ${collectionField}`);
}
return false;
}
return false;
}
// 当 Field component 不是下列组件时,不允许设置默认值
if (
collectionField.target &&
fieldSchema['x-component-props']?.mode &&
!['Picker', 'Select'].includes(fieldSchema['x-component-props'].mode)
) {
return false;
}
// 当 Field component 不是下列组件时,不允许设置默认值
if (
collectionField.target &&
fieldSchema['x-component-props']?.mode &&
!['Picker', 'Select'].includes(fieldSchema['x-component-props'].mode)
) {
return false;
}
// hasOne 和 belongsTo 类型的字段只能有一个值,不会新增值,所以在编辑状态下不允许设置默认值
if (!_.isEmpty(recordRef.current)) {
return false;
}
// hasOne 和 belongsTo 类型的字段只能有一个值,不会新增值,所以在编辑状态下不允许设置默认值
if (formBlockType === 'update') {
return false;
}
return (
!form?.readPretty &&
!isPatternDisabled(fieldSchema) &&
!interfacesOfUnsupportedDefaultValue.includes(collectionField?.interface) &&
!isSystemField(collectionField, getInterface)
);
}, []);
return (
!form?.readPretty &&
!isPatternDisabled(fieldSchema) &&
!interfacesOfUnsupportedDefaultValue.includes(collectionField?.interface) &&
!isSystemField(collectionField, getInterface)
);
},
[],
);
return (
<FormActiveFieldsProvider name="nester">

View File

@ -27,8 +27,8 @@ import { HTMLEncode } from '../input/shared';
import { useColorFields } from '../table-v2/Table.Column.Designer';
import { FilterFormDesigner } from './FormItem.FilterFormDesigner';
import { useEnsureOperatorsValid } from './SchemaSettingOptions';
import useLazyLoadAssociationFieldOfForm from './hooks/useLazyLoadAssociationFieldOfForm';
import useLazyLoadAssociationFieldOfSubForm from './hooks/useLazyLoadAssociationFieldOfSubForm';
import useLazyLoadDisplayAssociationFieldsOfForm from './hooks/useLazyLoadAssociationFieldOfForm';
import useLazyLoadAssociationFieldOfForm from './hooks/useLazyLoadAssociationFieldOfSubForm';
import useParseDefaultValue from './hooks/useParseDefaultValue';
export const FormItem: any = observer(
@ -46,8 +46,8 @@ export const FormItem: any = observer(
// 需要放在注冊完变量之后
useParseDefaultValue();
useLazyLoadDisplayAssociationFieldsOfForm();
useLazyLoadAssociationFieldOfForm();
useLazyLoadAssociationFieldOfSubForm();
useEffect(() => {
addActiveFieldName?.(schema.name as string);

View File

@ -7,8 +7,11 @@ import { transformVariableValue } from '../../../../variables/utils/transformVar
/**
* Form
*
* - Display association fields
* -
*/
const useLazyLoadAssociationFieldOfForm = () => {
const useLazyLoadDisplayAssociationFieldsOfForm = () => {
const { name } = useCollection();
const { getCollectionJoinField } = useCollectionManager();
const form = useForm();
@ -47,4 +50,4 @@ const useLazyLoadAssociationFieldOfForm = () => {
}, [schemaName.includes('.') ? form.values[schemaName.split('.')[0]] : null]);
};
export default useLazyLoadAssociationFieldOfForm;
export default useLazyLoadDisplayAssociationFieldsOfForm;

View File

@ -2,16 +2,19 @@ import { Field } from '@formily/core';
import { useField, useFieldSchema } from '@formily/react';
import _ from 'lodash';
import { useEffect } from 'react';
import { useRecord } from '../../../..';
import { useFormBlockType, useRecord } from '../../../..';
import { useCollection, useCollectionManager } from '../../../../collection-manager';
import { useFlag } from '../../../../flag-provider';
import { useVariables } from '../../../../variables';
import { transformVariableValue } from '../../../../variables/utils/transformVariableValue';
/**
* subForm
* Form
*
* - record
* -
*/
const useLazyLoadAssociationFieldOfSubForm = () => {
const useLazyLoadAssociationFieldOfForm = () => {
const { name } = useCollection();
const { getCollectionJoinField } = useCollectionManager();
const record = useRecord();
@ -19,11 +22,12 @@ const useLazyLoadAssociationFieldOfSubForm = () => {
const variables = useVariables();
const field = useField<Field>();
const { isInAssignFieldValues } = useFlag() || {};
const { type: formBlockType } = useFormBlockType();
const schemaName = fieldSchema.name.toString();
useEffect(() => {
if (isInAssignFieldValues) {
if (isInAssignFieldValues || formBlockType === 'create') {
return;
}
@ -59,4 +63,4 @@ const useLazyLoadAssociationFieldOfSubForm = () => {
}, [field, name, record, schemaName, isInAssignFieldValues]);
};
export default useLazyLoadAssociationFieldOfSubForm;
export default useLazyLoadAssociationFieldOfForm;

View File

@ -166,9 +166,7 @@ export const useSubTableSpecialCase = ({ field }) => {
field.value = [{}];
// 因为默认值的解析是异步的,所以下面的代码会优先于默认值的设置,这样就防止了设置完默认值后又被清空的问题
setTimeout(() => {
if (JSON.stringify(field.value) === '[{}]') {
field.value = value;
}
field.value = value;
});
}
}, []);

View File

@ -84,9 +84,7 @@ export const linkageMergeAction = async ({
// 2. TODO: 需要把里面解析变量的逻辑删除,因为在上一步已经解析过了
result = evaluate(exp, { ...scope, now: () => new Date().toString(), ...expScope });
} catch (error) {
if (process.env.NODE_ENV !== 'production') {
throw error;
}
console.error(error);
}
} else if (value?.mode === 'constant') {
result = value?.value || value;

View File

@ -1,5 +1,5 @@
import { FormOutlined } from '@ant-design/icons';
import React from 'react';
import { FormOutlined } from '@ant-design/icons';
import { useSchemaTemplateManager } from '../../schema-templates';
import { SchemaInitializer } from '../SchemaInitializer';

View File

@ -1,8 +1,13 @@
import { Form } from '@formily/core';
import { Schema, useFieldSchema } from '@formily/react';
import _ from 'lodash';
import React, { useContext, useMemo } from 'react';
import { CollectionFieldOptions, useCollection, useCollectionManager, useFormBlockContext, useRecord } from '../..';
import {
CollectionFieldOptions,
useCollection,
useCollectionManager,
useFormBlockContext,
useFormBlockType,
} from '../..';
import { isPatternDisabled, isSystemField } from '../SchemaSettings';
interface DefaultValueProviderProps {
@ -10,15 +15,12 @@ interface DefaultValueProviderProps {
children: React.ReactNode;
}
interface IsAllowToSetDefaultValueParams {
export interface IsAllowToSetDefaultValueParams {
collectionField: CollectionFieldOptions;
getInterface: (name: string) => any;
formBlockType?: 'update' | 'create';
form: Form;
fieldSchema: Schema<any, any, any, any, any, any, any, any, any>;
/**
* `useRecord`
*/
record: Record<string, any>;
isSubTableColumn?: boolean;
}
@ -26,7 +28,6 @@ interface Props {
form?: Form;
fieldSchema?: Schema<any, any, any, any, any, any, any, any, any>;
collectionField?: CollectionFieldOptions;
record?: Record<string, any>;
}
const DefaultValueContext = React.createContext<Omit<DefaultValueProviderProps, 'children'>>(null);
@ -41,12 +42,12 @@ export const DefaultValueProvider = (props: DefaultValueProviderProps) => {
return <DefaultValueContext.Provider value={value}>{props.children}</DefaultValueContext.Provider>;
};
const useIsAllowToSetDefaultValue = ({ form, fieldSchema, collectionField, record }: Props = {}) => {
const useIsAllowToSetDefaultValue = ({ form, fieldSchema, collectionField }: Props = {}) => {
const { getInterface, getCollectionJoinField } = useCollectionManager();
const { getField } = useCollection();
const { form: innerForm } = useFormBlockContext();
const innerFieldSchema = useFieldSchema();
const innerRecord = useRecord();
const { type } = useFormBlockType();
const { isAllowToSetDefaultValue = _isAllowToSetDefaultValue } = useContext(DefaultValueContext) || {};
const innerCollectionField =
@ -58,8 +59,8 @@ const useIsAllowToSetDefaultValue = ({ form, fieldSchema, collectionField, recor
collectionField: collectionField || innerCollectionField,
getInterface,
form: form || innerForm,
formBlockType: type,
fieldSchema: fieldSchema || innerFieldSchema,
record: record || innerRecord,
isSubTableColumn,
});
},
@ -86,9 +87,9 @@ export const interfacesOfUnsupportedDefaultValue = [
function _isAllowToSetDefaultValue({
collectionField,
getInterface,
formBlockType,
form,
fieldSchema,
record,
isSubTableColumn,
}: IsAllowToSetDefaultValueParams) {
if (isSubTableColumn) {
@ -114,10 +115,8 @@ function _isAllowToSetDefaultValue({
return false;
}
record = _.omit(record, '__parent');
// 表单编辑状态下,不允许设置默认值
if (!_.isEmpty(record)) {
if (formBlockType === 'update') {
return false;
}