✅ 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-formref="elFormRef":model="formModel":rules="formRules"label-width="100px":inline="false"><el-row :gutter="20"><el-colv-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" /><!-- 渲染内置组件 --><componentv-else:is="getComponent(item)"v-model="formModel[item.field]"v-bind="item.componentProps"><!-- 处理Select选项 --><el-optionv-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) |