From 9e88972ba63078cd837e684dd231371c7d3e2b5b Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Sat, 9 Mar 2024 18:59:56 +0800 Subject: [PATCH] fix(Form): retain field's default value after submitting the form (#3665) * fix(Form): should not be clear default value after submit * test: make e2e more stable --- .../client/src/block-provider/hooks/index.ts | 26 ++- .../form-create/schemaInitializer.test.ts | 35 ++- .../form-create/schemaSettings.test.ts | 6 +- .../__e2e__/form-create/templatesOfBug.ts | 210 ++++++++++++++++++ .../antd/association-field/SubTable.tsx | 1 - .../antd/association-field/util.ts | 6 +- 6 files changed, 274 insertions(+), 10 deletions(-) diff --git a/packages/core/client/src/block-provider/hooks/index.ts b/packages/core/client/src/block-provider/hooks/index.ts index 7f2aefc6c..abcdcc27c 100644 --- a/packages/core/client/src/block-provider/hooks/index.ts +++ b/packages/core/client/src/block-provider/hooks/index.ts @@ -29,6 +29,9 @@ import { transformVariableValue } from '../../variables/utils/transformVariableV import { useBlockRequestContext, useFilterByTk, useParamsFromRecord } from '../BlockProvider'; import { useDetailsBlockContext } from '../DetailsBlockProvider'; import { TableFieldResource } from '../TableFieldProvider'; +import { Field, Form } from '@formily/core'; +import { untracked } from '@formily/reactive'; +import { isSubMode } from '../../schema-component/antd/association-field/util'; export * from './useFormActiveFields'; export * from './useParsedFilter'; @@ -222,14 +225,14 @@ export const useCreateActionProps = () => { __parent?.service?.refresh?.(); if (!onSuccess?.successMessage) { message.success(t('Saved successfully')); - await form.reset(undefined, { forceClear: true }); + await resetFormCorrectly(form); return; } if (onSuccess?.manualClose) { modal.success({ title: compile(onSuccess?.successMessage), onOk: async () => { - await form.reset(undefined, { forceClear: true }); + await resetFormCorrectly(form); if (onSuccess?.redirecting && onSuccess?.redirectTo) { if (isURL(onSuccess.redirectTo)) { window.location.href = onSuccess.redirectTo; @@ -241,7 +244,7 @@ export const useCreateActionProps = () => { }); } else { message.success(compile(onSuccess?.successMessage)); - await form.reset(undefined, { forceClear: true }); + await resetFormCorrectly(form); if (onSuccess?.redirecting && onSuccess?.redirectTo) { if (isURL(onSuccess.redirectTo)) { window.location.href = onSuccess.redirectTo; @@ -1343,3 +1346,20 @@ function getTargetField(obj) { const result = keys.slice(0, index); return result; } + +/** + * 之所以不直接使用 form.reset() 是因为其无法将子表格重置为空 + * 主要用于修复这个问题:https://nocobase.height.app/T-3106 + * @param form + */ +async function resetFormCorrectly(form: Form) { + untracked(() => { + Object.keys(form.fields).forEach((key) => { + if (isSubMode(form.fields[key])) { + // 清空子表格或者子表单的初始值,可以确保后面的 reset 会清空子表格或者子表单的值 + (form.fields[key] as Field).initialValue = null; + } + }); + }); + await form.reset(); +} diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts index 4dfeb1b04..fb9b93f30 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaInitializer.test.ts @@ -1,5 +1,6 @@ import { createBlockInPage, expect, oneEmptyForm, test } from '@nocobase/test/e2e'; -import { T3106 } from './templatesOfBug'; +import { T3106, T3469 } from './templatesOfBug'; +import { uid } from '@formily/shared'; test.describe('where creation form block can be added', () => { test('page', async ({ page, mockPage }) => { @@ -101,6 +102,38 @@ test.describe('configure actions', () => { ).toHaveValue('test name'); }); + // https://nocobase.height.app/T-3469 + test('default values for fields should not be cleared after submission', async ({ page, mockPage }) => { + await mockPage(T3469).goto(); + + // username 的值不能重复,所以为了避免报错这里随机生成一个值 + const username = uid(); + + // Username 字段没有设置默认值,手动输入一个值 + await page + .getByLabel('block-item-CollectionField-users-form-users.username-Username') + .getByRole('textbox') + .fill(username); + + // Nickname 字段设置了默认值,应该显示出默认值 + await expect( + page.getByLabel('block-item-CollectionField-users-form-users.nickname-Nickname').getByRole('textbox'), + ).toHaveValue('Should be cleared after submission'); + await expect( + page.getByLabel('block-item-CollectionField-users-form-users.username-Username').getByRole('textbox'), + ).toHaveValue(username); + + // 点击提交按钮之后,Nickname 字段的默认值应该依然显示,Username 字段的值应该被清空 + await page.getByLabel('action-Action-Submit-submit-').click(); + + await expect( + page.getByLabel('block-item-CollectionField-users-form-users.nickname-Nickname').getByRole('textbox'), + ).toHaveValue('Should be cleared after submission'); + await expect( + page.getByLabel('block-item-CollectionField-users-form-users.username-Username').getByRole('textbox'), + ).toHaveValue(''); + }); + test('customize: save record', async ({ page, mockPage }) => { await mockPage(oneEmptyForm).goto(); diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts index 746d5cd65..7f8a5acd7 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/schemaSettings.test.ts @@ -1165,7 +1165,7 @@ test.describe('creation form block schema settings', () => { await page.getByRole('menuitem', { name: 'form Form' }).first().hover(); await page.getByRole('menuitem', { name: 'Users' }).hover(); await page.getByRole('menuitem', { name: 'Duplicate template' }).hover(); - await page.getByRole('menuitem', { name: 'Users_Form (Fields only)' }).click(); + await page.getByRole('menuitem', { name: 'Users_Form (Fields only)' }).first().click(); await page.mouse.move(300, 0); await expect(page.getByLabel('block-item-CardItem-users-form')).toBeVisible(); @@ -1174,7 +1174,7 @@ test.describe('creation form block schema settings', () => { await page.getByLabel('schema-initializer-Grid-CreateFormBlockInitializers-users').hover(); await page.getByRole('menuitem', { name: 'form Form' }).first().hover(); await page.getByRole('menuitem', { name: 'Reference template' }).hover(); - await page.getByRole('menuitem', { name: 'Users_Form (Fields only)' }).click(); + await page.getByRole('menuitem', { name: 'Users_Form (Fields only)' }).first().click(); await page.mouse.move(300, 0); await page.getByLabel('schema-initializer-Grid-CreateFormBlockInitializers-users').hover(); await expect(page.locator('.ant-drawer').getByLabel('block-item-CardItem-users-form')).toBeVisible(); @@ -1185,7 +1185,7 @@ test.describe('creation form block schema settings', () => { await page.getByLabel('schema-initializer-Grid-RecordBlockInitializers-users').click(); await page.getByRole('menuitem', { name: 'form Form' }).first().hover(); await page.getByRole('menuitem', { name: 'Reference template' }).hover(); - await page.getByRole('menuitem', { name: 'Users_Form (Fields only)' }).click(); + await page.getByRole('menuitem', { name: 'Users_Form (Fields only)' }).first().click(); await page.mouse.move(300, 0); //修改引用模板 diff --git a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts index 9f77d8448..c19e35cf7 100644 --- a/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts +++ b/packages/core/client/src/modules/blocks/data-blocks/form/__e2e__/form-create/templatesOfBug.ts @@ -5491,3 +5491,213 @@ export const T3251: PageConfig = { 'x-index': 1, }, }; + +export const T3469: PageConfig = { + pageSchema: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Page', + 'x-index': 1, + properties: { + '9u463vapg0k': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'BlockInitializers', + 'x-index': 1, + properties: { + isaxpp1w32b: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-index': 1, + properties: { + '5dld0j6dehi': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-index': 1, + properties: { + '96xwhprgfdg': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-acl-action-props': { + skipScopeCheck: true, + }, + 'x-acl-action': 'users:create', + 'x-decorator': 'FormBlockProvider', + 'x-decorator-props': { + dataSource: 'main', + resource: 'users', + collection: 'users', + }, + 'x-toolbar': 'BlockSchemaToolbar', + 'x-settings': 'blockSettings:createForm', + 'x-component': 'CardItem', + 'x-component-props': {}, + 'x-index': 1, + properties: { + '0sj1sfgjoxa': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'FormV2', + 'x-component-props': { + useProps: '{{ useFormBlockProps }}', + }, + 'x-index': 1, + properties: { + grid: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid', + 'x-initializer': 'FormItemInitializers', + 'x-index': 1, + properties: { + e4ke3wqxm2o: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + 'x-index': 1, + properties: { + l8uf6qeppmb: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + 'x-index': 1, + properties: { + nickname: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.nickname', + 'x-component-props': {}, + default: 'Should be cleared after submission', + 'x-index': 1, + 'x-uid': 'lpgebrm0gig', + 'x-async': false, + }, + }, + 'x-uid': 'novdv1tcz37', + 'x-async': false, + }, + }, + 'x-uid': 'kzf3dxvr0mb', + 'x-async': false, + }, + s17cswscq2o: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Row', + properties: { + oh79jnxzotw: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-component': 'Grid.Col', + properties: { + username: { + _isJSONSchemaObject: true, + version: '2.0', + type: 'string', + 'x-toolbar': 'FormItemSchemaToolbar', + 'x-settings': 'fieldSettings:FormItem', + 'x-component': 'CollectionField', + 'x-decorator': 'FormItem', + 'x-collection-field': 'users.username', + 'x-component-props': {}, + 'x-uid': 'z9dn5rnc3oi', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': '62vb90prmzk', + 'x-async': false, + 'x-index': 1, + }, + }, + 'x-uid': 'ti5u46epv08', + 'x-async': false, + 'x-index': 2, + }, + }, + 'x-uid': 'e365kp0cwm9', + 'x-async': false, + }, + '7660qws6e4c': { + _isJSONSchemaObject: true, + version: '2.0', + type: 'void', + 'x-initializer': 'FormActionInitializers', + 'x-component': 'ActionBar', + 'x-component-props': { + layout: 'one-column', + style: { + marginTop: 24, + }, + }, + 'x-index': 2, + properties: { + '79tmpu1usft': { + _isJSONSchemaObject: true, + version: '2.0', + title: '{{ t("Submit") }}', + 'x-action': 'submit', + 'x-component': 'Action', + 'x-toolbar': 'ActionSchemaToolbar', + 'x-settings': 'actionSettings:createSubmit', + 'x-component-props': { + type: 'primary', + htmlType: 'submit', + useProps: '{{ useCreateActionProps }}', + }, + 'x-action-settings': { + triggerWorkflows: [], + }, + type: 'void', + 'x-index': 1, + 'x-uid': '96sageysorb', + 'x-async': false, + }, + }, + 'x-uid': 'nnn377hn4o4', + 'x-async': false, + }, + }, + 'x-uid': 'j5qpwo8d6su', + 'x-async': false, + }, + }, + 'x-uid': 'rlcmlnna7k8', + 'x-async': false, + }, + }, + 'x-uid': 'b6n82zkmrsh', + 'x-async': false, + }, + }, + 'x-uid': 'agdpemeoz1v', + 'x-async': false, + }, + }, + 'x-uid': 'dn5nok8na2g', + 'x-async': false, + }, + }, + 'x-uid': 'lb8hlm9oyv8', + 'x-async': true, + }, +}; diff --git a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx index 4e31ebc64..ff815c84b 100644 --- a/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx +++ b/packages/core/client/src/schema-component/antd/association-field/SubTable.tsx @@ -175,7 +175,6 @@ export const SubTable: any = observer( onClick={() => { field.value = field.value || []; field.value.push(markRecordAsNew({})); - field.onInput(field.value); }} > {t('Add new')} diff --git a/packages/core/client/src/schema-component/antd/association-field/util.ts b/packages/core/client/src/schema-component/antd/association-field/util.ts index 9ed66183f..a5dc2ec1a 100644 --- a/packages/core/client/src/schema-component/antd/association-field/util.ts +++ b/packages/core/client/src/schema-component/antd/association-field/util.ts @@ -5,6 +5,7 @@ import { Tag } from 'antd'; import dayjs from 'dayjs'; import React from 'react'; import { CollectionFieldOptions_deprecated, useCollectionManager_deprecated } from '../../../collection-manager'; +import { Field } from '@formily/core'; export const useLabelUiSchemaV2 = () => { const { getCollectionJoinField } = useCollectionManager_deprecated(); @@ -102,6 +103,7 @@ export function isShowFilePicker(labelUiSchema) { /** * 当前字段的模式是否是 `子表格` 或者 `子表单` */ -export function isSubMode(fieldSchema: Schema) { - return ['Nester', 'SubTable', 'PopoverNester'].includes(fieldSchema['x-component-props']?.mode); +export function isSubMode(field: any) { + const mode = field['x-component-props']?.mode || (field as Field).componentProps?.mode; + return ['Nester', 'SubTable', 'PopoverNester'].includes(mode); }