diff --git a/docs/zh-CN/components/form/options.md b/docs/zh-CN/components/form/options.md index 13e86c266e8..7c224d5713e 100755 --- a/docs/zh-CN/components/form/options.md +++ b/docs/zh-CN/components/form/options.md @@ -1558,6 +1558,16 @@ order: 2 } ``` +**初始不填充** + +从 3.1.0 版本开始,表单初始化时,选项有值时也会执行「自动填充」逻辑,从版本 6.1.0 版本开始 可以通过 `initAutoFill` 配置成 `false` 来关闭。 + +`initAutoFill` 有三种值分别如下,默认为 `fillIfNotSet`。 + +- `fillIfNotSet` 如果目标值不存在则填充,如果目标值存在则不填充。 +- `true` 总是填充,如果目标值存在则覆盖。 +- `false` 总是不填充,如果目标值存在则不填充。 + ## 控制选项高度 > 1.10.0 及以上版本 diff --git a/packages/amis-core/src/renderers/Options.tsx b/packages/amis-core/src/renderers/Options.tsx index a216fe0a73d..5f34211a5fd 100644 --- a/packages/amis-core/src/renderers/Options.tsx +++ b/packages/amis-core/src/renderers/Options.tsx @@ -54,7 +54,7 @@ import isPlainObject from 'lodash/isPlainObject'; import {normalizeOptions} from '../utils/normalizeOptions'; import {optionValueCompare} from '../utils/optionValueCompare'; import type {Option} from '../types'; -import {resolveEventData} from '../utils'; +import {deleteVariable, resolveEventData} from '../utils'; export {Option}; @@ -203,6 +203,12 @@ export interface FormOptionsControl extends FormBaseControl { autoFill?: { [propName: string]: string; }; + + /** + * @default fillIfNotSet + * 初始化时是否把其他字段同步到表单内部。 + */ + initAutoFill?: boolean | 'fillIfNotSet'; } export interface OptionsBasicConfig extends FormItemBasicConfig { @@ -303,6 +309,7 @@ export function registerOptionsControl(config: OptionsConfig) { placeholder: 'Select.placeholder', resetValue: '', deleteConfirmText: 'deleteConfirm', + initAutoFill: 'fillIfNotSet', ...Control.defaultProps }; static propsList: any = (Control as any).propsList @@ -314,6 +321,7 @@ export function registerOptionsControl(config: OptionsConfig) { input: any; mounted = false; + initedFilled = false; constructor(props: OptionsProps) { super(props); @@ -356,16 +364,27 @@ export function registerOptionsControl(config: OptionsConfig) { JSON.stringify(formItem.getSelectedOptions(formItem.tmpValue)), () => this.mounted && + this.initedFilled && this.syncAutoFill(formItem.getSelectedOptions(formItem.tmpValue)) ) ); - if ( - options && - formItem.tmpValue && - formItem.getSelectedOptions(formItem.tmpValue).length - ) { - this.syncAutoFill(formItem.getSelectedOptions(formItem.tmpValue)); + if (formInited || !addHook) { + this.initedFilled = true; + this.props.initAutoFill !== false && + this.syncAutoFill( + formItem.getSelectedOptions(formItem.tmpValue), + this.props.initAutoFill === 'fillIfNotSet' + ); + } else if (addHook) { + addHook(() => { + this.initedFilled = true; + this.props.initAutoFill !== false && + this.syncAutoFill( + formItem.getSelectedOptions(formItem.tmpValue), + this.props.initAutoFill === 'fillIfNotSet' + ); + }, 'init'); } // 默认全选。这里会和默认值\回填值逻辑冲突,所以如果有配置source则不执行默认全选 @@ -530,7 +549,7 @@ export function registerOptionsControl(config: OptionsConfig) { } } - syncAutoFill(selectedOptions: Array) { + syncAutoFill(selectedOptions: Array, skipIfExits = false) { const {autoFill, multiple, onBulkChange, data} = this.props; const formItem = this.props.formItem as IFormItemStore; // 参照录入|自动填充 @@ -579,13 +598,21 @@ export function registerOptionsControl(config: OptionsConfig) { Object.keys(autoFill).forEach(key => { const keys = keyToPath(key); + let value = getVariable(toSync, key); + + if (skipIfExits) { + const originValue = getVariable(data, key); + if (typeof originValue !== 'undefined') { + value = originValue; + } + } + + setVariable(result, key, value); // 如果左边的 key 是一个路径 // 这里不希望直接把原始对象都给覆盖没了 // 而是保留原始的对象,只修改指定的属性 if (keys.length > 1 && isPlainObject(tmpData[keys[0]])) { - const value = getVariable(toSync, key); - // 存在情况:依次更新同一子路径的多个key,eg: a.b.c1 和 a.b.c2,所以需要同步更新data setVariable(tmpData, key, value); result[keys[0]] = tmpData[keys[0]]; diff --git a/packages/amis/__tests__/renderers/Form/autoFill.test.tsx b/packages/amis/__tests__/renderers/Form/autoFill.test.tsx index d57e744cb8c..c9358fb4894 100644 --- a/packages/amis/__tests__/renderers/Form/autoFill.test.tsx +++ b/packages/amis/__tests__/renderers/Form/autoFill.test.tsx @@ -399,3 +399,206 @@ test('Form:options:autoFill:validation', async () => { expect(screen.queryByText(validationMsg1)).not.toBeInTheDocument(); expect(screen.queryByText(validationMsg2)).not.toBeInTheDocument(); }); + +test('6. AutoFill initAutoFill fillIfNotSet', async () => { + const onSubmit = jest.fn(); + const {debug, container, getByText, findByText} = render( + amisRender( + { + type: 'page', + body: [ + { + type: 'form', + title: 'The form', + controls: [ + { + type: 'hidden', + name: 'aId', + value: 123 + }, + { + type: 'hidden', + name: 'bId' + }, + { + type: 'radios', + name: 'a', + autoFill: { + aValue: '${value}', + aLabel: '${label}', + aId: '${id}', + bId: '${id}' + }, + value: 'a', + options: [ + { + label: 'OptionA', + value: 'a', + id: 233 + }, + { + label: 'OptionB', + value: 'b' + } + ] + } + ], + submitText: 'Submit' + } + ] + }, + { + onSubmit: onSubmit + }, + makeEnv() + ) + ); + + await wait(200); + fireEvent.click(getByText(/Submit/)); + await wait(200); + + expect(onSubmit).toBeCalledTimes(1); + expect(onSubmit.mock.calls[0][0]).toMatchObject({ + aId: 123, + a: 'a', + aValue: 'a', + aLabel: 'OptionA', + bId: 233 + }); +}); + +test('7. AutoFill initAutoFill false', async () => { + const onSubmit = jest.fn(); + const {debug, container, getByText, findByText} = render( + amisRender( + { + type: 'page', + body: [ + { + type: 'form', + title: 'The form', + controls: [ + { + type: 'hidden', + name: 'aId', + value: 123 + }, + { + type: 'hidden', + name: 'bId' + }, + { + type: 'radios', + name: 'a', + autoFill: { + aValue: '${value}', + aLabel: '${label}', + aId: '${id}', + bId: '${id}' + }, + initAutoFill: false, + value: 'a', + options: [ + { + label: 'OptionA', + value: 'a', + id: 233 + }, + { + label: 'OptionB', + value: 'b' + } + ] + } + ], + submitText: 'Submit' + } + ] + }, + { + onSubmit: onSubmit + }, + makeEnv() + ) + ); + + await wait(200); + fireEvent.click(getByText(/Submit/)); + await wait(200); + + expect(onSubmit).toBeCalledTimes(1); + expect(onSubmit.mock.calls[0][0]).toMatchObject({ + aId: 123, + a: 'a' + }); +}); + +test('8. AutoFill initAutoFill true', async () => { + const onSubmit = jest.fn(); + const {debug, container, getByText, findByText} = render( + amisRender( + { + type: 'page', + body: [ + { + type: 'form', + title: 'The form', + controls: [ + { + type: 'hidden', + name: 'aId', + value: 123 + }, + { + type: 'hidden', + name: 'bId' + }, + { + type: 'radios', + name: 'a', + autoFill: { + aValue: '${value}', + aLabel: '${label}', + aId: '${id}', + bId: '${id}' + }, + initAutoFill: true, + value: 'a', + options: [ + { + label: 'OptionA', + value: 'a', + id: 233 + }, + { + label: 'OptionB', + value: 'b' + } + ] + } + ], + submitText: 'Submit' + } + ] + }, + { + onSubmit: onSubmit + }, + makeEnv() + ) + ); + + await wait(200); + fireEvent.click(getByText(/Submit/)); + await wait(200); + + expect(onSubmit).toBeCalledTimes(1); + expect(onSubmit.mock.calls[0][0]).toMatchObject({ + aId: 233, + a: 'a', + aValue: 'a', + aLabel: 'OptionA', + bId: 233 + }); +}); diff --git a/packages/amis/__tests__/renderers/Form/options.test.tsx b/packages/amis/__tests__/renderers/Form/options.test.tsx index d82e9c3485c..fdbef14ad0a 100644 --- a/packages/amis/__tests__/renderers/Form/options.test.tsx +++ b/packages/amis/__tests__/renderers/Form/options.test.tsx @@ -283,6 +283,7 @@ test('options:autoFill-merge', async () => { makeEnv({}) ) ); + await wait(200); fireEvent.click(getByText('选项1')); await wait(300);