Vue 3 + Element Plus 实现「动态表单组件」详解教程

✅ Vue 3 + Element Plus 实现「动态表单组件」详解教程

📌 适用场景:表单字段根据配置动态生成,支持校验、提交、自定义组件、复杂布局等。

🧩 技术栈:Vue 3 + TypeScript + Element Plus
🔧 核心特性:动态渲染 + 校验规则 + 支持 Slot + 响应式数据 + 提交与重置封装


🧠 一、实现思路概览

🔹1. 表单配置项(FormSchema)

  • 类型:FormSchema[]
  • 内容:字段名、label、组件类型、校验规则、组件属性等

🔹2. 响应式数据绑定

  • 自动从配置生成 formModel
  • 用于表单的双向绑定

🔹3. 动态渲染组件

  • 根据 component 字段动态渲染 ElInputElSelect、自定义组件等

🔹4. 校验规则

  • 同样从配置中读取 rules,应用在 ElForm

🔹5. 插槽与扩展性

  • 支持通过 v-slot 插入插槽或自定义组件

🧩 二、FormSchema 类型定义

// types/form-schema.ts
export type ComponentType = 'Input' | 'Select' | 'DatePicker' | 'Custom'

export interface FormSchema {
  field: string // 字段名
  label: string // 标签
  component: ComponentType // 组件类型
  colProps?: Record<string, any> // 栅格列属性
  componentProps?: Record<string, any> // 组件绑定属性
  options?: { label: string; value: any }[] // 下拉选项(用于Select)
  required?: boolean // 是否必填
  rules?: any[] // 校验规则
  slot?: string // 自定义slot名称
}

💡 三、核心组件 DynamicForm.vue

<template>
  <el-form
    ref="elFormRef"
    :model="formModel"
    :rules="formRules"
    label-width="100px"
    :inline="false"
  >
    <el-row :gutter="20">
      <el-col
        v-for="item in props.schemas"
        :key="item.field"
        v-bind="item.colProps || { span: 24 }"
      >
        <el-form-item :label="item.label" :prop="item.field">
          <!-- slot 优先 -->
          <slot v-if="item.slot" :name="item.slot" :model="formModel" />
          <!-- 渲染内置组件 -->
          <component
            v-else
            :is="getComponent(item)"
            v-model="formModel[item.field]"
            v-bind="item.componentProps"
          >
            <!-- 处理Select选项 -->
            <el-option
              v-if="item.component === 'Select'"
              v-for="opt in item.options"
              :key="opt.value"
              :label="opt.label"
              :value="opt.value"
            />
          </component>
        </el-form-item>
      </el-col>
    </el-row>
  </el-form>
</template>

<script setup lang="ts">
import { reactive, watch, ref, defineExpose } from 'vue'
import type { FormSchema } from '@/types/form-schema'
import { ElForm } from 'element-plus'

const props = defineProps<{
  schemas: FormSchema[]
  modelValue?: Record<string, any>
}>()

const elFormRef = ref<InstanceType<typeof ElForm>>()
const formModel = reactive<Record<string, any>>({})
const formRules = reactive<Record<string, any>>({})

/** 初始化表单数据与校验 */
const initForm = () => {
  props.schemas.forEach((schema) => {
    formModel[schema.field] = props.modelValue?.[schema.field] ?? ''
    if (schema.required || schema.rules) {
      formRules[schema.field] = schema.rules || [{ required: true, message: `${schema.label}不能为空`, trigger: 'blur' }]
    }
  })
}

watch(() => props.schemas, initForm, { immediate: true })

/** 组件映射 */
const getComponent = (item: FormSchema) => {
  switch (item.component) {
    case 'Input':
      return 'el-input'
    case 'Select':
      return 'el-select'
    case 'DatePicker':
      return 'el-date-picker'
    case 'Custom':
      return item.slot || 'div'
    default:
      return 'el-input'
  }
}

/** 对外暴露方法 */
defineExpose({
  formModel,
  validate: () => elFormRef.value?.validate(),
  resetFields: () => elFormRef.value?.resetFields()
})
</script>

🧪 四、使用示例

<template>
  <dynamic-form ref="formRef" :schemas="schemas" v-model="formData">
    <!-- slot 示例:自定义图片上传组件 -->
    <template #avatarUpload="{ model }">
      <el-upload
        :action="'/upload'"
        :on-success="(res) => (model.avatar = res.url)"
        :file-list="[]"
      >
        <el-button type="primary">上传头像</el-button>
      </el-upload>
    </template>
  </dynamic-form>

  <el-button @click="handleSubmit">提交</el-button>
  <el-button @click="handleReset">重置</el-button>
</template>

<script setup lang="ts">
import DynamicForm from '@/components/DynamicForm.vue'
import type { FormSchema } from '@/types/form-schema'

const formRef = ref()
const formData = ref({})

const schemas: FormSchema[] = [
  { field: 'name', label: '姓名', component: 'Input', required: true },
  { field: 'age', label: '年龄', component: 'Input' },
  {
    field: 'gender',
    label: '性别',
    component: 'Select',
    options: [
      { label: '男', value: 'male' },
      { label: '女', value: 'female' }
    ]
  },
  {
    field: 'avatar',
    label: '头像',
    component: 'Custom',
    slot: 'avatarUpload'
  }
]

const handleSubmit = () => {
  formRef.value?.validate().then(() => {
    console.log('✅ 提交数据:', formData.value)
  }).catch(() => {
    console.log('❌ 表单校验失败')
  })
}

const handleReset = () => {
  formRef.value?.resetFields()
}
</script>

🧭 五、进阶扩展建议

方向建议
🔌 自定义组件注册使用 componentMap 实现动态映射自定义组件
🧪 表单校验提示优化validate-on-rule-change 配合 trigger: 'blur'
📦 动态表单 + 分步表单可集成 <el-steps> 做分布式配置
🌐 接口驱动配置表单 schema 从接口返回配置 JSON,支持后端驱动
🌈 表单联动逻辑加入字段 watch,响应联动修改(如 A 变动时隐藏 B)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值