likes
comments
collection
share

基于 Element Plus 封装的 JSON 配置表单组件

作者站长头像
站长
· 阅读数 3

基于 Element Plus 封装的 JSON 配置表单组件

不想多写一行代码了!!!懒,真的是进化的原动力啊。为了在各种XX表单的复制粘贴中解放出来,我写下了这篇文章

在现代前端开发中,表单是一个常见的功能,它们通常需要灵活性和可配置性。基于 Vue 3 和 Element Plus 库,我们可以创建一个动态表单组件,该组件能够通过 JSON 对象来配置表单的结构和行为。这种方式可以大大简化表单的创建过程,提高开发效率。

开始之前

在开始之前,确保你的项目已经安装了 Vue 3 和 Element Plus。如果还没有安装,可以通过以下命令进行安装:

npm install vue@next
npm install element-plus

好的,让我们根据需求来一步步构建和应用这个组件。

需求分析

在构建动态表单组件之前,我们需要明确我们的需求:

  1. 动态性:能够根据传入的 JSON 配置动态生成表单项。
  2. 灵活性:支持不同类型的表单控件,如输入框、选择器、开关等。
  3. 校验:支持表单验证规则,确保用户输入的数据有效性。
  4. 可维护性:通过 JSON 配置管理表单项,易于维护和扩展。

为什么要封装表单组件

在大型应用中,表单可能在不同的地方以不同的结构和规则出现。如果我们手动创建每个表单,会导致大量重复的代码,且每次变更都需要手动更新每个表单。通过封装一个动态表单组件,我们可以:

  • 减少重复代码,提高开发效率。
  • 通过配置来管理表单,使得更新变得简单快速。
  • 提高代码的可读性和可维护性。

实现动态表单组件

第一步:定义组件接口

首先,我们定义 TypeScript 接口来描述表单项和验证规则:

// formTypes.ts
export interface FormField {
  type: string;
  label: string;
  model: string;
  options?: Array<{ label: string; value: any }>;
  placeholder?: string;
  rules?: Array<FormRule>;
}

export interface FormRule {
  required?: boolean;
  message: string;
  trigger: string | string[];
  validator?: (rule: FormRule, value: string, callback: Function) => void;
}

export interface FormConfig {
  fields: Array<FormField>;
}

第二步:构建动态表单组件

使用 <script setup lang="ts"> 语法糖来构建我们的 DynamicForm.vue 组件:

<template>
  <el-form :model="formModel" :rules="formRules" ref="dynamicForm">
    <!-- 动态生成表单项 -->
    <el-row :gutter="20">
      <el-col :span="8" v-for="field in formConfig.fields" :key="field.model">
        <el-form-item :label="field.label" :prop="field.model">
          <component
            :is="field.type"
            v-model="formModel[field.model]"
            :placeholder="field.placeholder"
            v-bind="field.options ? { options: field.options } : {}"
          ></component>
        </el-form-item>
      </el-col>
    </el-row>
    <!-- 表单操作按钮 -->
    <el-form-item>
      <el-button type="primary" @click="submitForm">提交</el-button>
      <el-button @click="resetForm">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import { ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElButton, ElRow, ElCol } from 'element-plus';
import type { FormConfig } from './formTypes';

const props = defineProps({
  config: {
    type: Object as () => FormConfig,
    required: true
  }
});

const formModel = ref({});
const formRules = ref({});
const dynamicForm = ref(null);

// 初始化表单模型和规则
for (const field of props.config.fields) {
  formModel.value[field.model] = '';
  formRules.value[field.model] = field.rules || [];
}

// 提交表单
const submitForm = () => {
  dynamicForm.value.validate((valid) => {
    if (valid) {
      // 表单验证成功,处理数据
    } else {
      // 表单验证失败,提示用户
    }
  });
};

// 重置表单
const resetForm = () => {
  dynamicForm.value.resetFields();
};
</script>

第三步:在父组件中使用动态表单组件

现在我们可以在父组件中定义表单的配置,并传递给 DynamicForm 组件:

<template>
  <dynamic-form :config="formConfig" />
</template>

<script setup lang="ts">
import DynamicForm from './DynamicForm.vue';
import type { FormConfig } from './formTypes';

const formConfig: FormConfig = {
  fields: [
    {
      type: 'el-input',
      label: '姓名',
      model: 'name',
      placeholder: '请输入姓名',
      rules: [
        { required: true, message: '姓名不能为空', trigger: 'blur' }
      ]
    },
    // 其他表单项配置...
  ]
};
</script>

添加成功和失败回调

为了让动态表单组件更加灵活,我们可以向 DynamicForm.vue 组件添加一个回调函数的入参,以便在表单提交并通过验证后执行。同时,我们可以提供一个默认的处理函数,以便在表单验证失败时执行。

我们需要修改 DynamicForm.vue 组件,以接收一个额外的回调函数作为参数,并在表单验证成功后调用它。

<template>
  <!-- ...省略其它模板代码... -->
  <el-form-item>
    <el-button type="primary" @click="submitForm">提交</el-button>
    <el-button @click="resetForm">重置</el-button>
  </el-form-item>
</template>

<script setup lang="ts">
// ...省略其它导入和定义...

const props = defineProps({
  config: {
    type: Object as () => FormConfig,
    required: true
  },
  onSubmitSuccess: Function as PropType<(formData: any) => void>,
  onSubmitFail: Function as PropType<(errors: any) => void>
});

const formModel = ref({});
const formRules = ref({});
const dynamicForm = ref(null);

// ...省略初始化代码...

// 提交表单
const submitForm = () => {
  dynamicForm.value.validate((valid: boolean, invalidFields: any) => {
    if (valid) {
      props.onSubmitSuccess?.(formModel.value);
    } else {
      props.onSubmitFail
        ? props.onSubmitFail(invalidFields)
        : defaultSubmitFail(invalidFields);
    }
  });
};

// 默认的表单验证失败处理函数
const defaultSubmitFail = (errors: any) => {
  console.error('表单验证失败:', errors);
  // 这里可以添加一些默认的错误处理,比如弹出通知或消息提示用户
};

// 重置表单
const resetForm = () => {
  dynamicForm.value.resetFields();
};
</script>

在父组件中传递回调函数

在父组件中,我们可以定义提交成功和失败时的处理函数,并将它们传递给 DynamicForm 组件。

<template>
  <dynamic-form
    :config="formConfig"
    :on-submit-success="handleSuccess"
    :on-submit-fail="handleFail"
  />
</template>

<script setup lang="ts">
import DynamicForm from './DynamicForm.vue';
import type { FormConfig } from './formTypes';

const formConfig: FormConfig = {
  // ...表单配置...
};

// 表单提交成功的回调函数
const handleSuccess = (formData: any) => {
  console.log('表单提交成功, 表单数据:', formData);
  // 这里可以执行提交到服务器的操作
};

// 表单提交失败的回调函数
const handleFail = (errors: any) => {
  console.error('表单提交失败, 错误信息:', errors);
  // 这里可以执行一些错误处理,比如显示错误信息
};
</script>

这样,父组件就能够控制表单提交成功和失败后的行为了。如果父组件没有提供 onSubmitSuccessonSubmitFail 函数,动态表单组件将使用默认的处理函数。这提供了灵活性,同时确保了即使没有自定义处理函数,组件也能够正常工作。

异步校验支持

异步校验是现代表单不可或缺的一部分,尤其在需要与后端服务交互来验证输入的有效性时。为了支持异步校验,我们可以在表单项配置中添加一个异步校验函数。

{
  // ...其他配置项
  asyncValidator: (rule, value) => {
    return new Promise((resolve, reject) => {
      // 这里可以调用API进行异步校验
      someApiCheck(value).then(response => {
        if (response.isValid) {
          resolve();
        } else {
          reject('该值不可用');
        }
      });
    });
  },
}

表单状态管理

表单可能有多种状态,如加载、禁用、提交中。我们可以在组件中添加一个formState对象来管理这些状态,并允许父组件通过属性来控制这些状态。

<template>
  <el-form :disabled="formState.disabled">
    <!-- 表单项 -->
  </el-form>
</template>

<script setup>
// ...省略其他代码

const props = defineProps({
  // ...其他属性
  formState: {
    type: Object,
    default: () => ({ disabled: false, loading: false })
  }
});
</script>

表单项依赖关系

在一些场景下,某些表单项的显示可能依赖于其他项的值。我们可以在每个表单项的配置中添加一个showIf函数,该函数根据当前表单的值决定该项是否显示。

{
  // ...其他配置项
  showIf: (formData) => {
    return formData.someOtherField === 'certainValue';
  },
}

表单布局自定义

为了支持更灵活的布局配置,我们可以使用插槽(slot)来允许父组件控制表单项的布局。

<template>
  <el-form>
    <slot name="custom-layout">
      <!-- 默认布局 -->
    </slot>
  </el-form>
</template>

表单数据初始化和变更

表单数据的初始化和动态变更可以通过v-modelprops来实现。我们可以监听这些属性的变化来更新表单的状态。

<script setup>
// ...省略其他代码

const props = defineProps({
  // ...其他属性
  modelValue: {
    type: Object,
    default: () => ({})
  }
});

watch(() => props.modelValue, (newValue) => {
  formModel.value = newValue;
}, { deep: true });
</script>

表单提交的进一步抽象

我们可以将表单提交逻辑进一步抽象出来,定义一个submitHandler方法,以支持不同的提交行为。

const submitHandler = async () => {
  try {
    await dynamicForm.value.validate();
    await props.onSubmit(formModel.value);
  } catch (errors) {
    props.onSubmitFail ? props.onSubmitFail(errors) : defaultSubmitFail(errors);
  }
};

完整代码

<template>
  <el-form
    :model="formModel"
    :rules="formRules"
    ref="dynamicForm"
    :disabled="formState.disabled"
    @submit.prevent="handleSubmit"
  >
    <el-row :gutter="20">
      <el-col :span="12" v-for="field in formConfig.fields" :key="field.model" v-if="showField(field)">
        <el-form-item :label="field.label" :prop="field.model" :rules="field.rules">
          <component
            :is="getFieldComponent(field)"
            v-model="formModel[field.model]"
            v-bind="field.props"
            v-on="field.listeners"
          />
        </el-form-item>
      </el-col>
    </el-row>
    <el-form-item>
      <el-button type="primary" @click="submitForm">提交</el-button>
      <el-button @click="resetForm">重置</el-button>
    </el-form-item>
  </el-form>
</template>

<script setup lang="ts">
import { ref, watch, defineProps, PropType, reactive, toRefs } from 'vue';
import { ElForm, ElFormItem, ElInput, ElSelect, ElOption, ElButton, ElRow, ElCol } from 'element-plus';
import type { FormField, FormRule, FormConfig } from './formTypes';

// 注册 Element Plus 组件
const components = {
  ElInput,
  ElSelect,
  ElOption,
  // ...其他你需要支持的 Element Plus 组件
};

// 定义组件的 props
const props = defineProps({
  formConfig: {
    type: Object as PropType<FormConfig>,
    required: true
  },
  formState: {
    type: Object as PropType<{
      disabled: boolean;
      loading: boolean;
    }>,
    default: () => ({ disabled: false, loading: false })
  }
});

// 表单模型和规则
const formModel = reactive({});
const formRules = reactive({});

// 初始化表单模型和规则
props.formConfig.fields.forEach((field) => {
  formModel[field.model] = field.defaultValue || '';
  if (field.rules) {
    formRules[field.model] = field.rules;
  }
});

// 表单引用
const dynamicForm = ref<typeof ElForm | null>(null);

// 显示逻辑
const showField = (field: FormField) => {
  return !field.showIf || field.showIf(formModel);
};

// 获取对应的表单项组件
const getFieldComponent = (field: FormField) => {
  return components[field.type] || field.type;
};

// 提交表单
const submitForm = () => {
  dynamicForm.value?.validate((valid: boolean) => {
    if (valid) {
      props.formConfig.onSubmit?.(formModel);
    } else {
      props.formConfig.onSubmitFail?.('表单验证失败');
    }
  });
};

// 重置表单
const resetForm = () => {
  dynamicForm.value?.resetFields();
};

// 表单提交处理
const handleSubmit = () => {
  submitForm();
};

// 监听表单状态变化
watch(() => props.formState, (newState) => {
  if (dynamicForm.value) {
    dynamicForm.value.loading = newState.loading;
    dynamicForm.value.disabled = newState.disabled;
  }
}, { deep: true });
</script>

总结

通过上述步骤,我们成功地实现了一个动态表单组件,它可以根据 JSON 配置来生成表单,并包括了数据绑定和验证。这种方法简化了表单的创建流程,提高了开发效率,并增强了代码的可维护性。