在 Vue2 中后台管理系统开发中,表单是贯穿始终的核心交互组件 —— 数据查询、新增编辑、参数配置等场景都离不开它。但重复编写表单结构、复制粘贴验证逻辑、维护零散的表单状态,不仅效率低下,还会导致代码冗余、风格不统一。本文基于 Vue2 + Element UI,封装一个配置化、高复用、高灵活的通用表单组件,让表单开发从 “重复编码” 变成 “配置化组装”,大幅提升开发效率。
一、封装目标与核心价值
1. 封装核心目标
- 支持 Element UI 常用表单控件(输入框、选择器、日期选择器、开关等)
- 配置化生成表单,无需重复编写 HTML 结构
- 统一表单验证、重置、清除验证等基础方法
- 兼容 Element UI 表单核心特性(行内布局、标签位置、尺寸、禁用状态等)
- 支持自定义插槽,满足特殊业务场景(如自定义控件、联动逻辑)
2. 相比原生开发的优势
| 对比维度 | 原生开发方式 | 通用表单封装方式 |
|---|---|---|
| 开发效率 | 重复编写 HTML,效率低下 | 配置化生成,一行配置一个表单项 |
| 代码维护 | 零散分布,修改需改多处 | 配置集中管理,修改更便捷 |
| 风格统一性 | 易出现样式 / 交互不一致 | 统一封装,风格完全一致 |
| 学习成本 | 需熟悉每个控件的使用细节 | 基于 Element UI,上手即会 |
| 扩展性 | 需手动扩展,兼容性难保证 | 支持插槽扩展,兼容特殊场景 |
二、技术选型
- 核心框架:Vue2(Options API)
- UI 组件库:Element UI(2.x 版本,表单控件核心依赖)
- 样式预处理:SCSS(结构化编写样式,支持组件样式隔离)
三、通用表单组件完整实现(BaseForm)
1. 组件核心思路
通过 props 接收表单数据、表单项配置、验证规则等核心参数,内部通过 v-for 遍历表单项配置,动态渲染对应 Element UI 控件,同时封装统一的表单方法(验证、重置等),对外暴露调用接口。
2. 完整组件代码(BaseForm.vue)
<template>
<div class="base-form">
<!-- Element UI 表单容器,绑定核心属性 -->
<el-form
ref="form"
:model="formData"
:rules="rules"
:label-width="labelWidth"
:label-position="labelPosition"
:inline="inline"
:size="size"
:disabled="disabled"
>
<!-- 遍历表单项配置,动态渲染控件 -->
<template v-for="(item, index) in formItems">
<el-form-item
:key="index"
:label="item.label"
:prop="item.prop"
:rules="item.rules" <!-- 支持单个表单项独立验证规则 -->
:hidden="item.hidden" <!-- 支持动态隐藏表单项 -->
>
<!-- 1. 输入框(input) -->
<el-input
v-if="item.type === 'input'"
v-model="formData[item.prop]"
v-bind="item.props || {}" <!-- 透传控件原生属性 -->
:placeholder="item.placeholder || `请输入${item.label}`"
/>
<!-- 2. 下拉选择器(select) -->
<el-select
v-else-if="item.type === 'select'"
v-model="formData[item.prop]"
v-bind="item.props || {}"
:placeholder="item.placeholder || `请选择${item.label}`"
>
<el-option
v-for="option in item.options"
:key="option.value"
:label="option.label"
:value="option.value"
/>
</el-select>
<!-- 3. 日期选择器(date) -->
<el-date-picker
v-else-if="item.type === 'date'"
v-model="formData[item.prop]"
v-bind="item.props || {}"
:placeholder="item.placeholder || `请选择${item.label}`"
/>
<!-- 4. 时间选择器(time) -->
<el-time-picker
v-else-if="item.type === 'time'"
v-model="formData[item.prop]"
v-bind="item.props || {}"
:placeholder="item.placeholder || `请选择${item.label}`"
/>
<!-- 5. 日期时间选择器(datetime) -->
<el-date-picker
v-else-if="item.type === 'datetime'"
v-model="formData[item.prop]"
v-bind="item.props || {}"
type="datetime"
:placeholder="item.placeholder || `请选择${item.label}`"
/>
<!-- 6. 开关(switch) -->
<el-switch
v-else-if="item.type === 'switch'"
v-model="formData[item.prop]"
v-bind="item.props || {}"
:active-text="item.activeText"
:inactive-text="item.inactiveText"
/>
<!-- 7. 单选框组(radio) -->
<el-radio-group
v-else-if="item.type === 'radio'"
v-model="formData[item.prop]"
v-bind="item.props || {}"
>
<el-radio
v-for="option in item.options"
:key="option.value"
:label="option.value"
:disabled="option.disabled"
>
{{ option.label }}
</el-radio>
</el-radio-group>
<!-- 8. 复选框组(checkbox) -->
<el-checkbox-group
v-else-if="item.type === 'checkbox'"
v-model="formData[item.prop]"
v-bind="item.props || {}"
>
<el-checkbox
v-for="option in item.options"
:key="option.value"
:label="option.value"
:disabled="option.disabled"
>
{{ option.label }}
</el-checkbox>
</el-checkbox-group>
<!-- 9. 文本域(textarea) -->
<el-input
v-else-if="item.type === 'textarea'"
v-model="formData[item.prop]"
type="textarea"
v-bind="item.props || {}"
:placeholder="item.placeholder || `请输入${item.label}`"
/>
<!-- 10. 自定义插槽(支持特殊业务场景) -->
<slot
v-else-if="item.type === 'slot'"
:name="item.slotName"
:formData="formData"
:item="item"
></slot>
</el-form-item>
</template>
</el-form>
</div>
</template>
<script>
export default {
name: 'BaseForm', // 组件名称,便于调试和复用
props: {
// 1. 表单核心数据(双向绑定的表单值)
formData: {
type: Object,
required: true,
validator: (val) => {
// 验证 formData 是纯对象(避免数组/null 等)
return Object.prototype.toString.call(val) === '[object Object]'
}
},
// 2. 表单项配置(数组,每个元素对应一个表单项)
formItems: {
type: Array,
default: () => [],
validator: (val) => {
// 验证每个表单项必须包含 type 和 prop
return val.every(item => item.type && item.prop)
}
},
// 3. 全局表单验证规则(Element UI 原生规则格式)
rules: {
type: Object,
default: () => {}
},
// 4. 标签宽度(支持 px/百分比,同 Element UI)
labelWidth: {
type: String,
default: '100px'
},
// 5. 标签位置(left/right/top,同 Element UI)
labelPosition: {
type: String,
default: 'right',
validator: (val) => ['left', 'right', 'top'].includes(val)
},
// 6. 是否行内表单(适用于查询表单)
inline: {
type: Boolean,
default: false
},
// 7. 表单尺寸(large/medium/small,同 Element UI)
size: {
type: String,
default: 'medium',
validator: (val) => ['large', 'medium', 'small'].includes(val)
},
// 8. 是否禁用整个表单
disabled: {
type: Boolean,
default: false
}
},
methods: {
/**
* 表单验证(对外暴露,返回 Promise 便于异步处理)
* @returns {Promise} 验证成功 resolve(formData),失败 reject(error)
*/
validate() {
return new Promise((resolve, reject) => {
this.$refs.form.validate((valid, error) => {
if (valid) {
resolve({ ...this.formData }) // 深拷贝返回,避免外部修改原数据
} else {
reject(error) // 返回验证失败信息
}
})
})
},
/**
* 重置表单(恢复初始值 + 清除验证状态)
*/
resetForm() {
this.$refs.form.resetFields()
},
/**
* 清除指定表单项的验证状态(支持单个或多个)
* @param {String/Array} props - 表单项 prop 名称(单个字符串或数组)
*/
clearValidate(props) {
this.$refs.form.clearValidate(props)
},
/**
* 手动设置表单项值(支持外部动态修改表单数据)
* @param {Object} data - { prop: 值 } 格式的对象
*/
setFormData(data) {
Object.keys(data).forEach(prop => {
if (this.formData.hasOwnProperty(prop)) {
this.$set(this.formData, prop, data[prop]) // 响应式设置
}
})
}
}
}
</script>
<style lang="scss" scoped>
.base-form {
width: 100%;
// 表单项间距优化(避免原生 Element UI 间距过窄)
.el-form-item {
margin-bottom: 20px;
// 行内表单间距调整
&.el-form-item--inline {
margin-right: 30px;
margin-bottom: 15px;
}
}
// 适配文本域自动高度
.el-textarea {
resize: vertical;
}
}
</style>
四、组件核心特性详解
1. 配置化表单项(formItems)
formItems 是组件的核心配置,每个元素对应一个表单项,支持以下关键属性:
| 属性名 | 类型 | 说明 | 必传 |
|---|---|---|---|
type | String | 表单控件类型(input/select/date 等) | 是 |
prop | String | 表单数据绑定的 key(与 formData 对应) | 是 |
label | String | 表单项标签文本 | 是 |
placeholder | String | 输入提示文本(默认 “请输入 / 选择 XX”) | 否 |
rules | Array | 单个表单项的验证规则(覆盖全局 rules) | 否 |
props | Object | 透传控件原生属性(如 maxlength、multiple) | 否 |
options | Array | 选择器 / 单选框 / 复选框的选项([{label, value}]) | 否 |
hidden | Boolean | 是否隐藏该表单项(动态控制显示) | 否 |
activeText | String | switch 组件激活状态文本 | 否 |
inactiveText | String | switch 组件未激活状态文本 | 否 |
slotName | String | 自定义插槽名称(type 为 slot 时必传) | 否 |
2. 统一表单方法
组件封装了 Element UI 表单常用方法,对外暴露统一接口,无需手动操作 $refs:
validate():表单验证(返回 Promise,适配异步提交场景)resetForm():重置表单(恢复初始值 + 清除验证)clearValidate(props):清除指定表单项验证状态setFormData(data):外部动态修改表单数据(保证响应式)
3. 灵活的扩展性
(1)支持控件原生属性透传
通过 props 字段可以透传 Element UI 控件的原生属性,例如:
{
type: 'input',
prop: 'username',
label: '用户名',
props: {
maxlength: 20, // 输入长度限制
showWordLimit: true, // 显示字数统计
disabled: false // 单独禁用该输入框
}
}
(2)支持自定义插槽(特殊业务场景)
当现有控件无法满足需求时(如自定义联动控件、第三方组件),可通过 slot 类型扩展:
<!-- 父组件中使用插槽 -->
<base-form :formData="formData" :formItems="formItems">
<!-- 自定义插槽:例如“密码强度显示”控件 -->
<template #custom-password="scope">
<el-input
v-model="scope.formData.password"
placeholder="请输入密码"
type="password"
/>
<password-strength :value="scope.formData.password" />
</template>
</base-form>
<!-- 表单项配置 -->
formItems: [
{
type: 'slot',
prop: 'password',
label: '密码',
slotName: 'custom-password' // 与插槽名称对应
}
]
(3)支持动态显示 / 隐藏表单项
通过 hidden 字段控制表单项显示状态,结合父组件数据实现联动:
// 父组件中
data() {
return {
formData: {
userType: 'ordinary',
companyName: ''
},
formItems: [
{
type: 'select',
prop: 'userType',
label: '用户类型',
options: [
{ label: '普通用户', value: 'ordinary' },
{ label: '企业用户', value: 'company' }
]
},
{
type: 'input',
prop: 'companyName',
label: '企业名称',
hidden: this.formData.userType !== 'company' // 企业用户才显示
}
]
}
}
五、组件使用示例
<template>
<el-dialog title="新增用户" :visible.sync="isShow" width="500px">
<base-form
ref="addForm"
:formData="formData"
:formItems="formItems"
:rules="formRules"
/>
<div slot="footer" class="dialog-footer">
<el-button @click="isShow = false">取消</el-button>
<el-button type="primary" @click="handleSubmit">提交</el-button>
</div>
</el-dialog>
</template>
<script>
import BaseForm from '@/components/BaseForm'
export default {
components: { BaseForm },
data() {
return {
isShow: false,
formData: {
username: '',
password: '',
gender: 1,
status: 1,
email: ''
},
// 全局验证规则
formRules: {
username: [
{ required: true, message: '请输入用户名', trigger: 'blur' },
{ min: 3, max: 20, message: '用户名长度在 3-20 字符之间', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入密码', trigger: 'blur' },
{ min: 6, message: '密码长度不少于 6 字符', trigger: 'blur' }
],
email: [
{ required: true, message: '请输入邮箱', trigger: 'blur' },
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
]
},
// 表单项配置
formItems: [
{
type: 'input',
prop: 'username',
label: '用户名',
props: { showWordLimit: true, maxlength: 20 }
},
{
type: 'input',
prop: 'password',
label: '密码',
props: { type: 'password', maxlength: 20 }
},
{
type: 'radio',
prop: 'gender',
label: '性别',
options: [
{ label: '男', value: 1 },
{ label: '女', value: 2 }
]
},
{
type: 'select',
prop: 'status',
label: '状态',
options: [
{ label: '启用', value: 1 },
{ label: '禁用', value: 0 }
]
},
{
type: 'input',
prop: 'email',
label: '邮箱'
}
]
}
},
methods: {
// 打开弹窗(编辑时传入初始值)
openDialog(initData = {}) {
this.isShow = true
// 重置表单
this.$refs.addForm.resetForm()
// 填充初始值(编辑场景)
if (Object.keys(initData).length > 0) {
this.$refs.addForm.setFormData(initData)
}
},
// 提交表单
async handleSubmit() {
try {
const formData = await this.$refs.addForm.validate()
console.log('提交数据:', formData)
// 调用新增/编辑接口...
this.isShow = false
} catch (error) {
console.log('表单验证失败:', error)
}
}
}
}
</script>