Просмотр исходного кода

feat(form): add merge form functionality (#4495)

* feat: captcha example

* fix: fix lint errors

* chore: event handling and methods

* chore: add accessibility features ARIA labels and roles

* refactor: refactor code structure and improve captcha demo page

* feat: add captcha internationalization

* chore: 适配时间戳国际化展示

* fix: 1. 添加点击位置边界校验,防止点击外部导致x,y误差。2. 演示页面宽度过长添加滚动条。3. 添加hooks

* feat: sync test

* feat: 添加合并表单功能

* fix: 修复上一步不展示问题

---------

Co-authored-by: vince <vince292007@gmail.com>
Squall2017 7 месяцев назад
Родитель
Сommit
fdc5b02c30

+ 41 - 0
packages/@core/ui-kit/form-ui/src/form-api.ts

@@ -123,6 +123,47 @@ export class FormApi {
     return form.values;
   }
 
+  merge(formApi: FormApi) {
+    const chain = [this, formApi];
+    const proxy = new Proxy(formApi, {
+      get(target: any, prop: any) {
+        if (prop === 'merge') {
+          return (nextFormApi: FormApi) => {
+            chain.push(nextFormApi);
+            return proxy;
+          };
+        }
+        if (prop === 'submitAllForm') {
+          return async (needMerge: boolean = true) => {
+            try {
+              const results = await Promise.all(
+                chain.map(async (api) => {
+                  const form = await api.getForm();
+                  const validateResult = await api.validate();
+                  if (!validateResult.valid) {
+                    return;
+                  }
+                  const rawValues = toRaw(form.values || {});
+                  return rawValues;
+                }),
+              );
+              if (needMerge) {
+                const mergedResults = Object.assign({}, ...results);
+                return mergedResults;
+              }
+              return results;
+            } catch (error) {
+              console.error('Validation error:', error);
+            }
+          };
+        }
+        return target[prop];
+      },
+    });
+
+    return proxy;
+  }
+
   mount(formActions: FormActions) {
     if (!this.isMounted) {
       Object.assign(this.form, formActions);

+ 1 - 0
packages/effects/common-ui/src/components/captcha/hooks/useCaptchaPoints.ts

@@ -7,6 +7,7 @@ export function useCaptchaPoints() {
   function addPoint(point: CaptchaPoint) {
     points.push(point);
   }
+
   function clearPoints() {
     points.splice(0, points.length);
   }

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

@@ -79,7 +79,8 @@
         "rules": "Form Rules",
         "dynamic": "Dynamic Form",
         "custom": "Custom Component",
-        "api": "Api"
+        "api": "Api",
+        "merge": "Merge Form"
       },
       "captcha": {
         "title": "Captcha",

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

@@ -79,7 +79,8 @@
         "rules": "表单校验",
         "dynamic": "动态表单",
         "custom": "自定义组件",
-        "api": "Api"
+        "api": "Api",
+        "merge": "合并表单"
       },
       "captcha": {
         "title": "验证码",

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

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

+ 116 - 0
playground/src/views/examples/form/merge.vue

@@ -0,0 +1,116 @@
+<script lang="ts" setup>
+import { ref } from 'vue';
+
+import { Page } from '@vben/common-ui';
+
+import { Button, Card, message, Step, Steps, Switch } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter';
+
+const currentTab = ref(0);
+function onFirstSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form1 values: ${JSON.stringify(values)}`,
+  });
+  currentTab.value = 1;
+}
+function onSecondReset() {
+  currentTab.value = 0;
+}
+function onSecondSubmit(values: Record<string, any>) {
+  message.success({
+    content: `form2 values: ${JSON.stringify(values)}`,
+  });
+}
+
+const [FirstForm, firstFormApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  handleSubmit: onFirstSubmit,
+  layout: 'horizontal',
+  resetButtonOptions: {
+    show: false,
+  },
+  schema: [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'formFirst',
+      label: '表单1字段',
+      rules: 'required',
+    },
+  ],
+  submitButtonOptions: {
+    text: '下一步',
+  },
+  wrapperClass: 'grid-cols-1 md:grid-cols-1 lg:grid-cols-1',
+});
+const [SecondForm, secondFormApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+  },
+  handleReset: onSecondReset,
+  handleSubmit: onSecondSubmit,
+  layout: 'horizontal',
+  resetButtonOptions: {
+    text: '上一步',
+  },
+  schema: [
+    {
+      component: 'Input',
+      componentProps: {
+        placeholder: '请输入',
+      },
+      fieldName: 'formSecond',
+      label: '表单2字段',
+      rules: 'required',
+    },
+  ],
+  wrapperClass: 'grid-cols-1 md:grid-cols-1 lg:grid-cols-1',
+});
+const needMerge = ref(true);
+async function handleMergeSubmit() {
+  const values = await firstFormApi
+    .merge(secondFormApi)
+    .submitAllForm(needMerge.value);
+  message.success({
+    content: `merged form values: ${JSON.stringify(values)}`,
+  });
+}
+</script>
+
+<template>
+  <Page
+    description="表单组件合并示例:在某些场景下,例如分步表单,需要合并多个表单并统一提交。默认情况下,使用 Object.assign 规则合并表单。如果需要特殊处理数据,可以传入 false。"
+    title="表单组件"
+  >
+    <Card title="基础示例">
+      <template #extra>
+        <Switch
+          v-model:checked="needMerge"
+          checked-children="开启字段合并"
+          class="mr-4"
+          un-checked-children="关闭字段合并"
+        />
+        <Button type="primary" @click="handleMergeSubmit">合并提交</Button>
+      </template>
+      <div class="mx-auto max-w-lg">
+        <Steps :current="currentTab" class="steps">
+          <Step title="表单1" />
+          <Step title="表单2" />
+        </Steps>
+        <div class="p-20">
+          <FirstForm v-show="currentTab === 0" />
+          <SecondForm v-show="currentTab === 1" />
+        </div>
+      </div>
+    </Card>
+  </Page>
+</template>