diff --git a/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts b/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts index 79734d7c1fd..be9bcc7dbaa 100644 --- a/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts +++ b/packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts @@ -6,7 +6,7 @@ import { FormApi } from '../src/form-api'; vi.mock('@vben-core/shared/utils', () => ({ bindMethods: vi.fn(), createMerge: vi.fn((mergeFn) => { - return (stateOrFn, prev) => { + return (stateOrFn: any, prev: any) => { mergeFn(prev, 'key', stateOrFn); return { ...prev, ...stateOrFn }; }; @@ -144,3 +144,64 @@ describe('formApi', () => { expect(isValid).toBe(true); }); }); + +describe('updateSchema', () => { + let instance: FormApi; + + beforeEach(() => { + instance = new FormApi(); + instance.state = { + schema: [ + { component: 'text', fieldName: 'name' }, + { component: 'number', fieldName: 'age', label: 'Age' }, + ], + }; + }); + + it('should update the schema correctly when fieldName matches', () => { + const newSchema = [ + { component: 'text', fieldName: 'name' }, + { component: 'number', fieldName: 'age', label: 'Age' }, + ]; + + instance.updateSchema(newSchema); + + expect(instance.state?.schema?.[0]?.component).toBe('text'); + expect(instance.state?.schema?.[1]?.label).toBe('Age'); + }); + + it('should log an error if fieldName is missing in some items', () => { + const newSchema: any[] = [ + { component: 'textarea', fieldName: 'name' }, + { component: 'number' }, + ]; + + const consoleErrorSpy = vi + .spyOn(console, 'error') + .mockImplementation(() => {}); + + instance.updateSchema(newSchema); + + expect(consoleErrorSpy).toHaveBeenCalledWith( + 'All children of the form Schema array that need to be updated must contain the `field` field', + ); + }); + + it('should not update schema if fieldName does not match', () => { + const newSchema = [{ component: 'textarea', fieldName: 'unknown' }]; + + instance.updateSchema(newSchema); + + expect(instance.state?.schema?.[0]?.component).toBe('text'); + expect(instance.state?.schema?.[1]?.component).toBe('number'); + }); + + it('should not update schema if updatedMap is empty', () => { + const newSchema: any[] = [{ component: 'textarea' }]; + + instance.updateSchema(newSchema); + + expect(instance.state?.schema?.[0]?.component).toBe('text'); + expect(instance.state?.schema?.[1]?.component).toBe('number'); + }); +}); diff --git a/packages/@core/ui-kit/form-ui/src/form-api.ts b/packages/@core/ui-kit/form-ui/src/form-api.ts index 191f4233fd2..1476778f2dd 100644 --- a/packages/@core/ui-kit/form-ui/src/form-api.ts +++ b/packages/@core/ui-kit/form-ui/src/form-api.ts @@ -5,7 +5,7 @@ import type { ValidationOptions, } from 'vee-validate'; -import type { FormActions, VbenFormProps } from './types'; +import type { FormActions, FormSchema, VbenFormProps } from './types'; import { toRaw } from 'vue'; @@ -186,6 +186,37 @@ export class FormApi { this.stateHandler.reset(); } + updateSchema(schema: Partial[]) { + const updated: Partial[] = [...schema]; + const hasField = updated.every( + (item) => Reflect.has(item, 'fieldName') && item.fieldName, + ); + + if (!hasField) { + console.error( + 'All items in the schema array must have a valid `fieldName` property to be updated', + ); + return; + } + const currentSchema = [...(this.state?.schema ?? [])]; + + const updatedMap: Record = {}; + + updated.forEach((item) => { + if (item.fieldName) { + updatedMap[item.fieldName] = item; + } + }); + + currentSchema.forEach((schema, index) => { + const updatedData = updatedMap[schema.fieldName]; + if (updatedData) { + currentSchema[index] = merge(updatedData, schema) as FormSchema; + } + }); + this.setState({ schema: currentSchema }); + } + async validate(opts?: Partial) { const form = await this.getForm(); return await form.validate(opts); diff --git a/playground/src/views/examples/form/api.vue b/playground/src/views/examples/form/api.vue index 32f1222548b..8efcaaa90b6 100644 --- a/playground/src/views/examples/form/api.vue +++ b/playground/src/views/examples/form/api.vue @@ -41,12 +41,25 @@ const [BaseForm, formApi] = useVbenForm({ label: 'field2', }, { - component: 'Input', + component: 'Select', componentProps: { - placeholder: '请输入', + allowClear: true, + filterOption: true, + options: [ + { + label: '选项1', + value: '1', + }, + { + label: '选项2', + value: '2', + }, + ], + placeholder: '请选择', + showSearch: true, }, - fieldName: 'field3', - label: 'field3', + fieldName: 'fieldOptions', + label: '下拉选', }, ], // 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个 @@ -75,9 +88,35 @@ function handleClick( | 'showSubmitButton' | 'updateActionAlign' | 'updateResetButton' + | 'updateSchema' | 'updateSubmitButton', ) { switch (action) { + case 'updateSchema': { + formApi.updateSchema([ + { + componentProps: { + options: [ + { + label: '选项1', + value: '1', + }, + { + label: '选项2', + value: '2', + }, + { + label: '选项3', + value: '3', + }, + ], + }, + fieldName: 'fieldOptions', + }, + ]); + break; + } + case 'labelWidth': { formApi.setState({ commonConfig: { @@ -181,6 +220,7 @@ function handleClick(