Browse Source

fix: improve the scroll bar flashing when the modal box is opened (#4438)

Vben 6 months ago
parent
commit
161820dbc1

+ 16 - 0
packages/@core/base/shared/src/utils/dom.ts

@@ -69,3 +69,19 @@ export function getScrollbarWidth() {
   scrollDiv.remove();
   return scrollbarWidth;
 }
+
+export function needsScrollbar() {
+  const doc = document.documentElement;
+  const body = document.body;
+
+  // 检查 body 的 overflow-y 样式
+  const overflowY = window.getComputedStyle(body).overflowY;
+
+  // 如果明确设置了需要滚动条的样式
+  if (overflowY === 'scroll' || overflowY === 'auto') {
+    return doc.scrollHeight > window.innerHeight;
+  }
+
+  // 在其他情况下,根据 scrollHeight 和 innerHeight 比较判断
+  return doc.scrollHeight > window.innerHeight;
+}

+ 7 - 1
packages/@core/composables/src/use-scroll-lock.ts

@@ -1,4 +1,4 @@
-import { getScrollbarWidth } from '@vben-core/shared/utils';
+import { getScrollbarWidth, needsScrollbar } from '@vben-core/shared/utils';
 
 import {
   useScrollLock as _useScrollLock,
@@ -13,6 +13,9 @@ export function useScrollLock() {
   const scrollbarWidth = getScrollbarWidth();
 
   tryOnBeforeMount(() => {
+    if (!needsScrollbar()) {
+      return;
+    }
     document.body.style.paddingRight = `${scrollbarWidth}px`;
 
     const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
@@ -30,6 +33,9 @@ export function useScrollLock() {
   });
 
   tryOnBeforeUnmount(() => {
+    if (!needsScrollbar()) {
+      return;
+    }
     isLocked.value = false;
     const layoutFixedNodes = document.querySelectorAll<HTMLElement>(
       `.${SCROLL_FIXED_CLASS}`,

+ 114 - 0
packages/@core/preferences/__tests__/__snapshots__/config.test.ts.snap

@@ -0,0 +1,114 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`defaultPreferences immutability test > should not modify the config object 1`] = `
+{
+  "app": {
+    "accessMode": "frontend",
+    "authPageLayout": "panel-right",
+    "checkUpdatesInterval": 1,
+    "colorGrayMode": false,
+    "colorWeakMode": false,
+    "compact": false,
+    "contentCompact": "wide",
+    "defaultAvatar": "https://unpkg.com/@vbenjs/static-source@0.1.6/source/avatar-v1.webp",
+    "dynamicTitle": true,
+    "enableCheckUpdates": true,
+    "enablePreferences": true,
+    "enableRefreshToken": false,
+    "isMobile": false,
+    "layout": "sidebar-nav",
+    "locale": "zh-CN",
+    "loginExpiredMode": "page",
+    "name": "Vben Admin",
+    "preferencesButtonPosition": "auto",
+    "watermark": false,
+  },
+  "breadcrumb": {
+    "enable": true,
+    "hideOnlyOne": false,
+    "showHome": false,
+    "showIcon": true,
+    "styleType": "normal",
+  },
+  "copyright": {
+    "companyName": "Vben",
+    "companySiteLink": "https://www.vben.pro",
+    "date": "2024",
+    "enable": true,
+    "icp": "",
+    "icpLink": "",
+  },
+  "footer": {
+    "enable": true,
+    "fixed": false,
+  },
+  "header": {
+    "enable": true,
+    "hidden": false,
+    "mode": "fixed",
+  },
+  "logo": {
+    "enable": true,
+    "source": "https://unpkg.com/@vbenjs/static-source@0.1.6/source/logo-v1.webp",
+  },
+  "navigation": {
+    "accordion": true,
+    "split": true,
+    "styleType": "rounded",
+  },
+  "shortcutKeys": {
+    "enable": true,
+    "globalLockScreen": true,
+    "globalLogout": true,
+    "globalPreferences": true,
+    "globalSearch": true,
+  },
+  "sidebar": {
+    "collapsed": false,
+    "collapsedShowTitle": false,
+    "enable": true,
+    "expandOnHover": true,
+    "extraCollapse": true,
+    "hidden": false,
+    "width": 224,
+  },
+  "tabbar": {
+    "dragable": true,
+    "enable": true,
+    "height": 38,
+    "keepAlive": true,
+    "persist": true,
+    "showIcon": true,
+    "showMaximize": true,
+    "showMore": true,
+    "showRefresh": true,
+    "styleType": "chrome",
+  },
+  "theme": {
+    "builtinType": "default",
+    "colorDestructive": "hsl(348 100% 61%)",
+    "colorPrimary": "hsl(212 100% 45%)",
+    "colorSuccess": "hsl(144 57% 58%)",
+    "colorWarning": "hsl(42 84% 61%)",
+    "mode": "dark",
+    "radius": "0.5",
+    "semiDarkHeader": false,
+    "semiDarkSidebar": true,
+  },
+  "transition": {
+    "enable": true,
+    "loading": true,
+    "name": "fade-slide",
+    "progress": true,
+  },
+  "widget": {
+    "fullscreen": true,
+    "globalSearch": true,
+    "languageToggle": true,
+    "lockScreen": true,
+    "notification": true,
+    "sidebarToggle": true,
+    "themeToggle": true,
+  },
+}
+`;

+ 10 - 0
packages/@core/preferences/__tests__/config.test.ts

@@ -0,0 +1,10 @@
+import { describe, expect, it } from 'vitest';
+
+import { defaultPreferences } from '../src/config';
+
+describe('defaultPreferences immutability test', () => {
+  // 创建快照,确保默认配置对象不被修改
+  it('should not modify the config object', () => {
+    expect(defaultPreferences).toMatchSnapshot();
+  });
+});

+ 3 - 3
packages/@core/preferences/src/preferences.test.ts → packages/@core/preferences/__tests__/preferences.test.ts

@@ -1,8 +1,8 @@
 import { beforeEach, describe, expect, it, vi } from 'vitest';
 
-import { defaultPreferences } from './config';
-import { PreferenceManager } from './preferences';
-import { isDarkTheme } from './update-css-variables';
+import { defaultPreferences } from '../src/config';
+import { PreferenceManager } from '../src/preferences';
+import { isDarkTheme } from '../src/update-css-variables';
 
 describe('preferences', () => {
   let preferenceManager: PreferenceManager;

+ 1 - 1
packages/@core/preferences/tsconfig.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/tsconfig",
   "extends": "@vben/tsconfig/web.json",
-  "include": ["src"],
+  "include": ["src", "__tests__"],
   "exclude": ["node_modules"]
 }

+ 146 - 0
packages/@core/ui-kit/form-ui/__tests__/form-api.test.ts

@@ -0,0 +1,146 @@
+// 假设这个文件为 FormApi.ts
+import { beforeEach, describe, expect, it, vi } from 'vitest';
+
+import { FormApi } from '../src/form-api';
+
+vi.mock('@vben-core/shared/utils', () => ({
+  bindMethods: vi.fn(),
+  createMerge: vi.fn((mergeFn) => {
+    return (stateOrFn, prev) => {
+      mergeFn(prev, 'key', stateOrFn);
+      return { ...prev, ...stateOrFn };
+    };
+  }),
+  isFunction: (fn: any) => typeof fn === 'function',
+  StateHandler: vi.fn().mockImplementation(() => ({
+    reset: vi.fn(),
+    setConditionTrue: vi.fn(),
+    waitForCondition: vi.fn().mockResolvedValue(true),
+  })),
+}));
+
+describe('formApi', () => {
+  let formApi: FormApi;
+
+  beforeEach(() => {
+    formApi = new FormApi();
+  });
+
+  it('should initialize with default state', () => {
+    expect(formApi.state).toEqual(
+      expect.objectContaining({
+        actionWrapperClass: '',
+        collapsed: false,
+        collapsedRows: 1,
+        commonConfig: {},
+        handleReset: undefined,
+        handleSubmit: undefined,
+        layout: 'horizontal',
+        resetButtonOptions: {},
+        schema: [],
+        showCollapseButton: false,
+        showDefaultActions: true,
+        submitButtonOptions: {},
+        wrapperClass: 'grid-cols-1',
+      }),
+    );
+    expect(formApi.isMounted).toBe(false);
+  });
+
+  it('should mount form actions', async () => {
+    const formActions: any = {
+      meta: {},
+      resetForm: vi.fn(),
+      setFieldValue: vi.fn(),
+      setValues: vi.fn(),
+      submitForm: vi.fn(),
+      validate: vi.fn(),
+      values: { name: 'test' },
+    };
+
+    await formApi.mount(formActions);
+    expect(formApi.isMounted).toBe(true);
+    expect(formApi.form).toEqual(formActions);
+  });
+
+  it('should get values from form', async () => {
+    const formActions: any = {
+      meta: {},
+      values: { name: 'test' },
+    };
+
+    await formApi.mount(formActions);
+    const values = await formApi.getValues();
+    expect(values).toEqual({ name: 'test' });
+  });
+
+  it('should set field value', async () => {
+    const setFieldValueMock = vi.fn();
+    const formActions: any = {
+      meta: {},
+      setFieldValue: setFieldValueMock,
+      values: { name: 'test' },
+    };
+
+    await formApi.mount(formActions);
+    await formApi.setFieldValue('name', 'new value');
+    expect(setFieldValueMock).toHaveBeenCalledWith(
+      'name',
+      'new value',
+      undefined,
+    );
+  });
+
+  it('should reset form', async () => {
+    const resetFormMock = vi.fn();
+    const formActions: any = {
+      meta: {},
+      resetForm: resetFormMock,
+      values: { name: 'test' },
+    };
+
+    await formApi.mount(formActions);
+    await formApi.resetForm();
+    expect(resetFormMock).toHaveBeenCalled();
+  });
+
+  it('should call handleSubmit on submit', async () => {
+    const handleSubmitMock = vi.fn();
+    const formActions: any = {
+      meta: {},
+      submitForm: vi.fn().mockResolvedValue(true),
+      values: { name: 'test' },
+    };
+
+    const state = {
+      handleSubmit: handleSubmitMock,
+    };
+
+    formApi.setState(state);
+    await formApi.mount(formActions);
+
+    const result = await formApi.submitForm();
+    expect(formActions.submitForm).toHaveBeenCalled();
+    expect(handleSubmitMock).toHaveBeenCalledWith({ name: 'test' });
+    expect(result).toEqual({ name: 'test' });
+  });
+
+  it('should unmount form and reset state', () => {
+    formApi.unmounted();
+    expect(formApi.isMounted).toBe(false);
+    expect(formApi.stateHandler.reset).toHaveBeenCalled();
+  });
+
+  it('should validate form', async () => {
+    const validateMock = vi.fn().mockResolvedValue(true);
+    const formActions: any = {
+      meta: {},
+      validate: validateMock,
+    };
+
+    await formApi.mount(formActions);
+    const isValid = await formApi.validate();
+    expect(validateMock).toHaveBeenCalled();
+    expect(isValid).toBe(true);
+  });
+});

+ 7 - 3
packages/@core/ui-kit/form-ui/src/form-api.ts

@@ -43,13 +43,13 @@ function getDefaultState(): VbenFormProps {
 }
 
 export class FormApi {
-  // private prevState!: ModalState;
-  private state: null | VbenFormProps = null;
   // private api: Pick<VbenFormProps, 'handleReset' | 'handleSubmit'>;
   public form = {} as FormActions;
-
   isMounted = false;
 
+  // private prevState!: ModalState;
+  public state: null | VbenFormProps = null;
+
   stateHandler: StateHandler;
 
   public store: Store<VbenFormProps>;
@@ -92,6 +92,10 @@ export class FormApi {
     this.store.batch(cb);
   }
 
+  getState() {
+    return this.state;
+  }
+
   async getValues() {
     const form = await this.getForm();
     return form.values;

+ 1 - 1
packages/@core/ui-kit/form-ui/tsconfig.json

@@ -1,6 +1,6 @@
 {
   "$schema": "https://json.schemastore.org/tsconfig",
   "extends": "@vben/tsconfig/web.json",
-  "include": ["src"],
+  "include": ["src", "__tests__"],
   "exclude": ["node_modules"]
 }

+ 2 - 1
playground/src/locales/langs/en-US.json

@@ -78,7 +78,8 @@
         "query": "Query Form",
         "rules": "Form Rules",
         "dynamic": "Dynamic Form",
-        "custom": "Custom Component"
+        "custom": "Custom Component",
+        "api": "Api"
       },
       "captcha": {
         "title": "Captcha",

+ 2 - 1
playground/src/locales/langs/zh-CN.json

@@ -78,7 +78,8 @@
         "query": "查询表单",
         "rules": "表单校验",
         "dynamic": "动态表单",
-        "custom": "自定义组件"
+        "custom": "自定义组件",
+        "api": "Api"
       },
       "captcha": {
         "title": "验证码",

+ 8 - 0
playground/src/router/routes/modules/examples.ts

@@ -99,6 +99,14 @@ const routes: RouteRecordRaw[] = [
               title: $t('page.examples.form.custom'),
             },
           },
+          {
+            name: 'FormApiExample',
+            path: '/examples/form/api',
+            component: () => import('#/views/examples/form/api.vue'),
+            meta: {
+              title: $t('page.examples.form.api'),
+            },
+          },
         ],
       },
     ],

+ 208 - 0
playground/src/views/examples/form/api.vue

@@ -0,0 +1,208 @@
+<script lang="ts" setup>
+import { Page } from '@vben/common-ui';
+
+import { Button, Card, message, Space } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+
+const [BaseForm, formApi] = useVbenForm({
+  // 所有表单项共用,可单独在表单内覆盖
+  commonConfig: {
+    // 所有表单项
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  // 使用 tailwindcss grid布局
+  // 提交函数
+  handleSubmit: onSubmit,
+  // 垂直布局,label和input在不同行,值为vertical
+  layout: 'horizontal',
+  // 水平布局,label和input在同一行
+  schema: [
+    {
+      // 组件需要在 #/adapter.ts内注册,并加上类型
+      component: 'Input',
+      // 对应组件的参数
+      componentProps: {
+        placeholder: '请输入用户名',
+      },
+      // 字段名
+      fieldName: 'field1',
+      // 界面显示的label
+      label: 'field1',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'field2',
+      label: 'field2',
+    },
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'field3',
+      label: 'field3',
+    },
+  ],
+  // 大屏一行显示3个,中屏一行显示2个,小屏一行显示1个
+  wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',
+});
+
+function onSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form values: ${JSON.stringify(values)}`,
+  });
+}
+
+function handleClick(
+  action:
+    | 'batchAddSchema'
+    | 'batchDeleteSchema'
+    | 'disabled'
+    | 'hiddenAction'
+    | 'hiddenResetButton'
+    | 'hiddenSubmitButton'
+    | 'labelWidth'
+    | 'resetDisabled'
+    | 'resetLabelWidth'
+    | 'showAction'
+    | 'showResetButton'
+    | 'showSubmitButton'
+    | 'updateActionAlign'
+    | 'updateResetButton'
+    | 'updateSubmitButton',
+) {
+  switch (action) {
+    case 'labelWidth': {
+      formApi.setState({
+        commonConfig: {
+          labelWidth: 150,
+        },
+      });
+      break;
+    }
+    case 'resetLabelWidth': {
+      formApi.setState({
+        commonConfig: {
+          labelWidth: 100,
+        },
+      });
+      break;
+    }
+    case 'disabled': {
+      formApi.setState({ commonConfig: { disabled: true } });
+      break;
+    }
+    case 'resetDisabled': {
+      formApi.setState({ commonConfig: { disabled: false } });
+      break;
+    }
+    case 'hiddenAction': {
+      formApi.setState({ showDefaultActions: false });
+      break;
+    }
+    case 'showAction': {
+      formApi.setState({ showDefaultActions: true });
+      break;
+    }
+    case 'hiddenResetButton': {
+      formApi.setState({ resetButtonOptions: { show: false } });
+      break;
+    }
+    case 'showResetButton': {
+      formApi.setState({ resetButtonOptions: { show: true } });
+      break;
+    }
+    case 'hiddenSubmitButton': {
+      formApi.setState({ submitButtonOptions: { show: false } });
+      break;
+    }
+    case 'showSubmitButton': {
+      formApi.setState({ submitButtonOptions: { show: true } });
+      break;
+    }
+    case 'updateResetButton': {
+      formApi.setState({
+        resetButtonOptions: { disabled: true },
+      });
+      break;
+    }
+    case 'updateSubmitButton': {
+      formApi.setState({
+        submitButtonOptions: { loading: true },
+      });
+      break;
+    }
+    case 'updateActionAlign': {
+      formApi.setState({
+        // 可以自行调整class
+        actionWrapperClass: 'text-center',
+      });
+      break;
+    }
+    case 'batchAddSchema': {
+      formApi.setState((prev) => {
+        const currentSchema = prev?.schema ?? [];
+        const newSchema = [];
+        for (let i = 0; i < 3; i++) {
+          newSchema.push({
+            component: 'Input',
+            componentProps: {
+              placeholder: '请输入',
+            },
+            fieldName: `field${i}${Date.now()}`,
+            label: `field+`,
+          });
+        }
+        return {
+          schema: [...currentSchema, ...newSchema],
+        };
+      });
+      break;
+    }
+    case 'batchDeleteSchema': {
+      formApi.setState((prev) => {
+        const currentSchema = prev?.schema ?? [];
+        return {
+          schema: currentSchema.slice(0, -3),
+        };
+      });
+      break;
+    }
+  }
+}
+</script>
+
+<template>
+  <Page description="表单组件api操作示例。" title="表单组件">
+    <Space class="mb-5 flex-wrap">
+      <Button @click="handleClick('labelWidth')">更改labelWidth</Button>
+      <Button @click="handleClick('resetLabelWidth')">还原labelWidth</Button>
+      <Button @click="handleClick('disabled')">禁用表单</Button>
+      <Button @click="handleClick('resetDisabled')">解除禁用</Button>
+      <Button @click="handleClick('hiddenAction')">隐藏操作按钮</Button>
+      <Button @click="handleClick('showAction')">显示操作按钮</Button>
+      <Button @click="handleClick('hiddenResetButton')">隐藏重置按钮</Button>
+      <Button @click="handleClick('showResetButton')">显示重置按钮</Button>
+      <Button @click="handleClick('hiddenSubmitButton')">隐藏提交按钮</Button>
+      <Button @click="handleClick('showSubmitButton')">显示提交按钮</Button>
+      <Button @click="handleClick('updateResetButton')">修改重置按钮</Button>
+      <Button @click="handleClick('updateSubmitButton')">修改提交按钮</Button>
+      <Button @click="handleClick('updateActionAlign')">
+        调整操作按钮位置
+      </Button>
+      <Button @click="handleClick('batchAddSchema')"> 批量添加表单项 </Button>
+      <Button @click="handleClick('batchDeleteSchema')">
+        批量删除表单项
+      </Button>
+    </Space>
+    <Card title="操作示例">
+      <BaseForm />
+    </Card>
+  </Page>
+</template>

+ 1 - 2
playground/src/views/examples/form/basic.vue

@@ -14,12 +14,11 @@ const [BaseForm, baseFormApi] = useVbenForm({
       class: 'w-full',
     },
   },
-  // 使用 tailwindcss grid布局
   // 提交函数
   handleSubmit: onSubmit,
   // 垂直布局,label和input在不同行,值为vertical
-  layout: 'horizontal',
   // 水平布局,label和input在同一行
+  layout: 'horizontal',
   schema: [
     {
       // 组件需要在 #/adapter.ts内注册,并加上类型

+ 1 - 5
playground/src/views/examples/form/custom.vue

@@ -16,12 +16,11 @@ const [BaseForm] = useVbenForm({
     },
     labelClass: 'w-2/6',
   },
-  // 使用 tailwindcss grid布局
   // 提交函数
   handleSubmit: onSubmit,
   // 垂直布局,label和input在不同行,值为vertical
-  layout: 'horizontal',
   // 水平布局,label和input在同一行
+  layout: 'horizontal',
   schema: [
     {
       // 组件需要在 #/adapter.ts内注册,并加上类型
@@ -31,7 +30,6 @@ const [BaseForm] = useVbenForm({
       suffix: () => h('span', { class: 'text-red-600' }, '元'),
     },
     {
-      // 组件需要在 #/adapter.ts内注册,并加上类型
       component: 'Input',
       fieldName: 'field1',
       label: '自定义组件slot',
@@ -41,14 +39,12 @@ const [BaseForm] = useVbenForm({
       }),
     },
     {
-      // 组件需要在 #/adapter.ts内注册,并加上类型
       component: h(Input, { placeholder: '请输入' }),
       fieldName: 'field2',
       label: '自定义组件',
       rules: 'required',
     },
     {
-      // 组件需要在 #/adapter.ts内注册,并加上类型
       component: 'Input',
       fieldName: 'field3',
       label: '自定义组件(slot)',

+ 0 - 17
playground/src/views/examples/form/dynamic.vue

@@ -6,10 +6,8 @@ import { Button, Card, message } from 'ant-design-vue';
 import { useVbenForm } from '#/adapter';
 
 const [Form, formApi] = useVbenForm({
-  // 使用 tailwindcss grid布局
   // 提交函数
   handleSubmit: onSubmit,
-  // 水平布局,label和input在同一行
   schema: [
     {
       component: 'Switch',
@@ -55,12 +53,9 @@ const [Form, formApi] = useVbenForm({
         show(values) {
           return !!values.field2Switch;
         },
-        // 只有指定的字段改变时,才会触发
         triggerFields: ['field2Switch'],
       },
-      // 字段名
       fieldName: 'field2',
-      // 界面显示的label
       label: '字段2',
     },
     {
@@ -69,12 +64,9 @@ const [Form, formApi] = useVbenForm({
         disabled(values) {
           return !!values.field3Switch;
         },
-        // 只有指定的字段改变时,才会触发
         triggerFields: ['field3Switch'],
       },
-      // 字段名
       fieldName: 'field3',
-      // 界面显示的label
       label: '字段3',
     },
     {
@@ -83,12 +75,9 @@ const [Form, formApi] = useVbenForm({
         required(values) {
           return !!values.field4Switch;
         },
-        // 只有指定的字段改变时,才会触发
         triggerFields: ['field4Switch'],
       },
-      // 字段名
       fieldName: 'field4',
-      // 界面显示的label
       label: '字段4',
     },
     {
@@ -100,13 +89,10 @@ const [Form, formApi] = useVbenForm({
           }
           return null;
         },
-        // 只有指定的字段改变时,才会触发
         triggerFields: ['field1'],
       },
-      // 字段名
       fieldName: 'field5',
       help: '当字段1的值为`123`时,必填',
-      // 界面显示的label
       label: '动态rules',
     },
     {
@@ -150,13 +136,10 @@ const [Form, formApi] = useVbenForm({
           }
           return {};
         },
-        // 只有指定的字段改变时,才会触发
         triggerFields: ['field2'],
       },
-      // 字段名
       fieldName: 'field6',
       help: '当字段2的值为`123`时,更改下拉选项',
-      // 界面显示的label
       label: '动态配置',
     },
     {

+ 2 - 4
playground/src/views/examples/form/query.vue

@@ -18,9 +18,8 @@ const [QueryForm] = useVbenForm({
   // 提交函数
   handleSubmit: onSubmit,
   // 垂直布局,label和input在不同行,值为vertical
-  layout: 'horizontal',
-  // 使用 tailwindcss grid布局
   // 水平布局,label和input在同一行
+  layout: 'horizontal',
   schema: [
     {
       // 组件需要在 #/adapter.ts内注册,并加上类型
@@ -101,9 +100,8 @@ const [QueryForm1] = useVbenForm({
   // 提交函数
   handleSubmit: onSubmit,
   // 垂直布局,label和input在不同行,值为vertical
-  layout: 'horizontal',
-  // 使用 tailwindcss grid布局
   // 水平布局,label和input在同一行
+  layout: 'horizontal',
   schema: (() => {
     const schema = [];
     for (let index = 0; index < 14; index++) {

+ 1 - 3
playground/src/views/examples/form/rules.vue

@@ -13,12 +13,11 @@ const [Form, formApi] = useVbenForm({
       class: 'w-full',
     },
   },
-  // 使用 tailwindcss grid布局
   // 提交函数
   handleSubmit: onSubmit,
   // 垂直布局,label和input在不同行,值为vertical
-  layout: 'horizontal',
   // 水平布局,label和input在同一行
+  layout: 'horizontal',
   schema: [
     {
       // 组件需要在 #/adapter.ts内注册,并加上类型
@@ -80,7 +79,6 @@ const [Form, formApi] = useVbenForm({
       },
       fieldName: 'number',
       label: '数字',
-      // 预处理函数,将空字符串或null转换为undefined
       rules: 'required',
     },
     {