Browse Source

feat: add updateSchema to form api (#4453)

* feat: add updateSchema to form api

* chore: typo

* chore: typo
Vben 1 year ago
parent
commit
60cffb0dec

+ 62 - 1
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');
+  });
+});

+ 32 - 1
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<FormSchema>[]) {
+    const updated: Partial<FormSchema>[] = [...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<string, any> = {};
+
+    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<ValidationOptions>) {
     const form = await this.getForm();
     return await form.validate(opts);

+ 44 - 4
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(
 <template>
   <Page description="表单组件api操作示例。" title="表单组件">
     <Space class="mb-5 flex-wrap">
+      <Button @click="handleClick('updateSchema')">updateSchema</Button>
       <Button @click="handleClick('labelWidth')">更改labelWidth</Button>
       <Button @click="handleClick('resetLabelWidth')">还原labelWidth</Button>
       <Button @click="handleClick('disabled')">禁用表单</Button>