✅ Vue 3 + Element Plus 实现「动态表单组件」详解教程
📌 适用场景:表单字段根据配置动态生成,支持校验、提交、自定义组件、复杂布局等。
🧩 技术栈:Vue 3 + TypeScript + Element Plus
🔧 核心特性:动态渲染 + 校验规则 + 支持 Slot + 响应式数据 + 提交与重置封装
🧠 一、实现思路概览
🔹1. 表单配置项(FormSchema)
- 类型:
FormSchema[]
- 内容:字段名、label、组件类型、校验规则、组件属性等
🔹2. 响应式数据绑定
- 自动从配置生成
formModel
- 用于表单的双向绑定
🔹3. 动态渲染组件
- 根据
component
字段动态渲染ElInput
、ElSelect
、自定义组件等
🔹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) |