✅ 适用技术栈:Vue 3 +
+ Element Plus + JavaScript
✅ 教学目标:封装一个可复用的动态表单组件VFormBuilder
,支持插槽、动态字段、表单校验、组件映射、v-model 双向绑定等功能。
功能 | 说明 |
---|---|
✅ 动态表单项渲染 | 支持通过配置项动态渲染 el-input , el-select 等组件 |
✅ v-model 双向绑定 | 外部可使用 v-model:modelValue 绑定整个表单对象 |
✅ 表单验证暴露 | 通过 ref 可调用组件内部的 validate() 进行校验 |
✅ 支持插槽替换 | 支持使用 slot 指定字段自定义渲染内容 |
✅ label 支持函数返回组件 | item.label 可为组件函数,用于动态标签渲染 |
formItems
:传入的动态表单项配置formConfig
:用于传递给
的其他配置rules
:校验规则span
:每一列的栅格宽度
{{ item.label }}
import { ref, computed, h, defineExpose } from 'vue'
const formRef = ref(null)
const defaultLabelWidth = '80px'
const formItemsComputed = computed(() =>
props.formItems.filter((item) => item.hidden !== true)
)
function getFormItemProps(item) {
const { formProps = {} } = item
const { labelCol = {}, ...rest } = formProps
const formLabelWidth = props.formConfig?.labelCol?.style?.width
const labelWidth =
labelCol?.style?.width === '0px' ? formLabelWidth : labelCol?.style?.width
return {
labelWidth: labelWidth || defaultLabelWidth,
...rest,
}
}
const baseFieldReg = /^(type|label|props|on|span|key|hidden|required|rules|col|formProps)$/
const selectType = new Set(['select', 'date', 'time', 'treeSelect'])
const ComponentItem = {
props: ['item'],
setup({ item }) {
const compProps = Object.keys(item).reduce(
(prev, key) => {
if (!baseFieldReg.test(key)) prev[key] = item[key]
return prev
},
{ ...item.props, formData: model.value }
)
if (!('placeholder' in compProps)) {
const text = selectType.has(item.type) ? '请选择' : '请输入'
compProps.placeholder = text + item.label
}
const tag = getFormItemComponent(item.type)
return () =>
h(
tag,
{
...compProps,
modelValue: model.value[item.field],
'onUpdate:modelValue': (val) => {
model.value[item.field] = val
},
},
item.slots
)
},
}
type
自动选择组件(如 input
→ el-input
)modelValue
function validate() {
return formRef.value?.validate()
}
defineExpose({
validate,
formRef,
model, // 当前表单数据
})
const formData = ref({
name: '',
gender: '',
})
const formItems = [
{
field: 'name',
label: '姓名',
type: 'input',
required: true,
props: { clearable: true },
},
{
field: 'gender',
label: '性别',
type: 'select',
props: {
options: [
{ label: '男', value: 'male' },
{ label: '女', value: 'female' },
],
},
},
]
const formBuilder = ref(null)
const submit = async () => {
try {
await formBuilder.value.validate()
console.log('表单验证通过', formData.value)
} catch (err) {
console.warn('表单验证失败', err)
}
}
getFormItemComponent
封装一个组件映射函数:
// getFormItemComponent.js
export function getFormItemComponent(type) {
const map = {
input: 'el-input',
select: 'el-select',
date: 'el-date-picker',
time: 'el-time-picker',
treeSelect: 'el-tree-select',
}
return map[type] || 'el-input'
}
你已经成功搭建了一个可配置、可复用、灵活插槽支持的动态表单组件。后续你可以继续扩展: