Bootstrap

vue3+elementPlus实现利用 JSON 数据(`formItems`)描述表单结构,配置化生成表单

一、功能点

(一)组件功能点

  • 动态表单生成
    根据 formItems 配置动态生成表单项,支持多种类型(如 inputradioselect)。
  • 表单校验
    通过 rules 定义表单校验规则,调用 validate 方法触发校验。
  • 双向绑定
    表单项通过 v-model 绑定到 form 对象,实现数据同步。
  • 支持扩展性
    支持通过 formItems 扩展表单项类型,例如 checkboxswitch 等。

(二)核心改进点(2024/1/6日)

  1. 动态绑定属性

    • 使用 v-bind="item.props" 动态绑定组件的 props,避免每个组件手动声明属性。
  2. 动态插槽支持

    • 利用 item.slots 支持动态插槽内容,通过 v-forv-slot:[slotName] 渲染自定义插槽。
  3. 统一组件类型

    • 使用 <component :is="item.component"> 动态渲染不同类型的表单组件。

(二)优点

  1. 简化代码
    减少了重复性代码,表单逻辑更加集中化,易于维护。

  2. 更高的扩展性
    支持任意表单组件类型的扩展,只需在 formItems 中添加对应的 componentprops

  3. 动态插槽灵活
    插槽配置更加灵活,能够适应复杂的表单布局和自定义需求。


(二)父组件功能点

  • 表单调用与交互
    父组件通过 ref 引用子组件的方法(如 validate)。
  • 数据提交
    提供提交按钮,调用后端接口完成数据更新并进行状态提示(成功/失败)。
  • 提交按钮加载状态
    在提交过程中设置按钮 loading 状态,防止重复点击。

二、知识点

(一)Vue 3 特性

  1. definePropsdefineExpose

    • 通过 defineProps 接收父组件传递的属性,包括 formformItemsrules 等。
    • 使用 defineExpose 暴露子组件方法(如 formRef),供父组件调用。
  2. refreactive

    • 使用 ref 定义可响应的表单引用 formRef
    • 表单数据通过响应式对象绑定到 form
  3. 插槽与动态模板 component :isslot

    • 使用动态模板渲染不同类型的表单组件(如 inputradioselect)。
    • 通过 v-for 循环生成表单项。component :is语法配合 template slot动态插槽

(二)Element Plus 使用

  1. el-formel-form-item

    • 使用 el-form 作为表单容器,配置 sizerules 等属性。
    • el-form-item 绑定标签和校验规则,展示动态生成的表单项。
  2. 表单组件

    • el-input:文本输入框。
    • el-radio-groupel-radio:单选框组。
    • el-selectel-option:下拉选择框。
  3. 表单校验

    • 使用 validate 方法触发校验,提供 valid 回调参数判断校验状态。

(三)其他知识点

  1. 动态表单生成

    • 利用 JSON 数据(formItems)描述表单结构,方便配置化生成表单。
  2. 异步提交与状态提示

    • 通过 thencatch 处理异步请求的成功或失败状态,并显示相应的提示信息。
  3. 数字格式处理

    • 提交前通过 Number() 转换字段类型(如 RATIO 字段)为数值,确保数据正确性。

改进后的代码通过 v-bind 动态传递 form-item 中不同组件的 props,避免重复声明,从而使代码更加简洁和易于维护。以下是优化后的代码和功能点说明:


三、源码

(一)组件代码

<script setup>
import { ref } from 'vue';

defineOptions({
  name: 'MyForm',
});

// eslint-disable-next-line no-unused-vars, unused-imports/no-unused-vars
const props = defineProps({
  form: {
    type: Object,
    required: true,
    default: () => ({}),
  },
  formLabelWidth: {
    type: String,
    default: '50px',
  },
  // 表单配置项
  formItems: {
    type: Array,
    required: true,
  },
  rules: {
    type: Object,
    required: true,
  },
});

const formRef = ref(null);
// 暴露 formRef
defineExpose({
  formRef,
});
</script>

<template>
  <div>
    <el-form
      ref="formRef"
      :model="form"
      :rules="rules"
      size="small"
      style="margin-top: 0"
    >
      <el-form-item
        v-for="(item, index) in formItems"
        :key="index"
        :label="item.label"
        :label-width="formLabelWidth"
        :prop="item.prop"
      >
        <component
          :is="item.component"
          v-model="form[item.prop]"
          v-bind="item.props"
        >
          <!-- 动态渲染插槽内容 -->
          <template
            v-for="(slotItem, slotIndex) in item.slots?.default || []"
            :key="slotIndex"
          >
            <component :is="slotItem.component" v-bind="slotItem.props" />
          </template>
        </component>
      </el-form-item>
    </el-form>
  </div>
</template>

<style scoped></style>


(二)父组件调用

<script setup>
import { ref } from 'vue';

import MyForm from '../../components/self-components/element_ui_plus/form.vue';

const formItems = [
  {
    label: '用户名',
    prop: 'username',
    component: 'el-input',
    props: {
      placeholder: '请输入用户名',
    },
  },
  {
    label: '性别',
    prop: 'gender',
    component: 'el-radio-group',
    props: {},
    slots: {
      default: [
        {
          component: 'el-radio',
          props: { value: 'male', label: '男' },
        },
        {
          component: 'el-radio',
          props: { value: 'female', label: '女' },
        },
      ],
    },
  },
  {
    label: '国家',
    prop: 'country',
    component: 'el-select',
    props: {
      placeholder: '请选择国家',
    },
    slots: {
      default: [
        {
          component: 'el-option',
          props: { label: '中国', value: 'China' },
        },
        {
          component: 'el-option',
          props: { label: '美国', value: 'USA' },
        },
      ],
    },
  },
];
const editForm = ref({
  username: '',
  gender: '',
  country: '',
});
const rules = ref({});
const submitEdit = () => {};
</script>

<template>
  <MyForm
    ref="editFormRef"
    :form="editForm"
    :form-items="formItems"
    :rules="rules"
  />
  <el-button
    :loading="false"
    size="small"
    type="primary"
    @click="submitEdit(editFormRef)"
  >
    确 定
  </el-button>
</template>


四、注意点

(1) 使用自动按需导入是无法使用的,需要全局注册对应elementUiPlus组件使用

import {
  ElInput,
  ElOption,
  ElRadio,
  ElRadioGroup,
  ElSelect,
} from 'element-plus';
  // 在main.ts/js组件中注册
  app.use(ElInput).use(ElRadioGroup).use(ElSelect).use(ElOption).use(ElRadio);
;