1、序言
**项目地址:git clone form-demo: 封装通用el-form**
一个后台管理系统最常见的是**表单**,表单最常见的是**输入框、下拉选择、日期选择、单选、复选框**等等, 系统添加若干模块,就复制粘贴若干个**el-form、el-form-item**,有一说一,完成需求快是快,但是代码冗余的部分太多了,能不能通过配置方式,自动生成**el-form、el-form-item**
** ** 不封装代码前:
封装代码后:
** 两种封装方式的变量、方法名基本一致!**
2、自定义组件方式封装el-form
2.1、封装
(1)新建commentForm文件夹,并创建index.vue文件
(2)index.vue中
<template>
<div>
<el-form ref="form" :model="form" :rules="rules" :inline="inline" :label-width="labelWidth">
<template v-for="(item, index) in formItemList">
<!-- 输入框类型 -->
<template v-if="item.type === 'input'">
<el-form-item :label="item.label" :prop="item.model">
<el-input v-model.trim="form[item.model]" :type="`${item.category || 'text'}`"
:style="`width: ${item.width || '250px'}`" :clearable="item.clearable === undefined || item.clearable"
filterable :placeholder="item.placeholder" />
</el-form-item>
</template>
<!-- 下拉选择框 -->
<template v-if="item.type === 'select'">
<el-form-item :label="item.label" :prop="item.model">
<el-select v-model.trim="form[item.model]" :style="`width:${item.width || '250px'}`" clearable
:placeholder="item.placeholder || ''">
<el-option v-for="(i, key) in item.options" :key="i[item.value] || key" :label="i[item.label] || i.label"
:value="i[item.value] || i.value">
</el-option>
</el-select>
</el-form-item>
</template>
<!-- 日期选择器 -->
<template v-if="item.type === 'date-picker'">
<el-form-item :prop="item.model" :label="item.label">
<el-date-picker v-model.trim="form[item.model]" type="daterange" range-separator="至" start-placeholder="开始日期"
end-placeholder="结束日期">
</el-date-picker>
</el-form-item>
</template>
<!-- 开关 -->
<template v-if="item.type === 'switch'">
<el-form-item :prop="item.model" :label="item.label">
<el-switch v-model="form[item.model]"></el-switch>
</el-form-item>
</template>
<!-- 复选框 -->
<template v-if="item.type === 'checkbox'">
<el-form-item :prop="item.model" :label="item.label">
<el-checkbox-group v-model="form[item.model]">
<el-checkbox :label="item.label" :key="index" v-for="(item, index) in item.options"></el-checkbox>
</el-checkbox-group>
</el-form-item>
</template>
<!-- 单选框 -->
<template v-if="item.type === 'radio'">
<el-form-item :prop="item.model" :label="item.label">
<el-radio-group v-model="form[item.model]">
<el-radio :label="item.label" :key="index" v-for="(item, index) in item.options"></el-radio>
</el-radio-group>
</el-form-item>
</template>
<!-- 自己拓展... -->
</template>
</el-form>
</div>
</template>
<script>
export default {
props: {
form: {
type: Object,
default: () => { }
},
// 生成el-form-item的数组
formItemList: {
type: Array,
default: () => [],
},
// 是否行内表单模式
inline: {
type: Boolean,
default: false,
},
// el-form-item的label宽度
labelWidth: {
type: String,
default: '80px',
},
// 表单校验规则
rules: {
type: Object,
default: () => { },
},
},
data() {
return {
}
},
methods: {
// 返回经过校验的表单
returnForm() {
// 表单校验
this.$refs['form'].validate((valid) => {
if (valid) {
this.$emit('getForm', this.form)
}
})
},
// 重置表单
resetForm() {
// 重置复选框
this.$refs.form.resetFields();
}
}
}
</script>
<style scoped></style>
(3)注意事项:
- el-checkbox中v-model绑定的值要事先存在,不然就会报出无法找到length属性的错误
所以提前在data中声明一个数组,v-model绑定这个数组,返回表单给父组件时,将其添加到表单对象中,重置表单时,使这个数组为空即可实现重置表单功能!
- 重置不成功的原因可能是:(1)未添加prop (2)prop绑定的值与model绑定的对象所对应的属性不一致
2.2、使用
重点关注部位:
(1)formItemList中的type决定生成什么类型的表单项,如输入框、下拉选项等等
(2)formItemList中的model表示表单项双向绑定的名称,也是子组件返回对象给父组件中对象的属性名
(3)formItemList中的options表示下拉选项、复选框、单选按钮的选项值
(4)**@getForm绑定的事件可以获取子组件返回给父组件经过校验**的表单,通常不在子组件进行网络请求,让父组件进行网络请求
(5)父组件通过**$refs**获取组件实例从而调用子组件重置表单、提交表单的方法
<template>
<div id="app">
<CommonForm ref="form" :form="form" :rules="rules" :formItemList="formItemList" @getForm="getForm">
</CommonForm>
<el-row>
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
</el-row>
</div>
</template>
<script>
import CommonForm from '@/components/commonForm'
export default {
name: 'App',
components: {
CommonForm,
},
data() {
return {
form: {},
editForm: {
name: '张三',
region: 'shanghai',
delivery: true,
resource: '线上品牌商赞助',
startTime: [
'2023-05-10',
'2023-05-11'
],
types: '线下主题活动',
},
formItemList: [
{ label: '活动名称', type: 'input', model: 'name', placeholder: '请输入活动名称' },
{ label: '活动区域', type: 'select', model: 'region', placeholder: '请选择状态', options: [{ label: '上海', value: 'shanghai' }, { label: '北京', value: 'beijing' }] },
{ label: '活动时间', type: 'date-picker', model: 'startTime', },
{ label: '即时配送', type: 'switch', model: 'delivery' },
{ label: '活动性质', type: 'checkbox', model: 'type', modelString: 'types', options: [{ label: '美食/餐厅线上活动' }, { label: '地推活动' }, { label: '线下主题活动' }, { label: '单纯品牌曝光' },] },
{ label: '特殊资源', type: 'radio', model: 'resource', options: [{ label: '线上品牌商赞助' }, { label: '线下场地免费' }] },
],
rules: {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
}
}
},
created() {
// 表单初始化
this.formInit()
},
methods: {
// 表单初始化
formInit() {
this.form = JSON.stringify(this.editForm) ? this.editForm : {}
// checkbox类型的组件需要初始化数组
this.formItemList.forEach(item => {
if (['checkbox'].includes(item.type)) {
if (!this.form[item.modelString]) {
this.$set(this.form, item.model, [])
} else {
this.$set(this.form, item.model, this.form[item.modelString]?.split(','))
}
}
})
},
// 接受子组件传回来的form表单内容
getForm(val) {
this.formItemList.forEach(item => {
//
if (['checkbox'].includes(item.type)) {
if (this.form[item.model] && this.form[item.model]?.length != 0) {
val[item.modelString] = val[item.model]?.join(',')
}
}
})
console.log('val:', val);
// 此处可以请求后台了
},
// 点击提交,触发表单校验
submitForm() {
this.$refs.form.returnForm();
},
// 重置表单
resetForm() {
this.$refs.form.resetForm();
}
}
}
</script>
<style>
#app {
display: flex;
flex-direction: column;
align-items: center;
color: #2c3e50;
}
</style>
2.3、回显数据
准备一个对象:**editForm** 模拟**form**已经有了数据,对**editForm**进行非空判断,如果有值则将其赋值给**form**,无值则赋值**{}**
this.form = JSON.stringify(this.editForm) ? this.editForm : {}
刚刚说过**elementUI**中**checkbox**使用**v-model**绑定的值需要为真实存在的数组,若在form组件额外实例化一个变量专门处理**checkbox**显得不那么智能化![](https://img-blog.csdnimg.cn/eba81f256f1d43fea7f35c114716e5af.png)
如何做到传一个{}对象给form组件,不报上述错误,并且还能回显数据,这就要在引用**form**组件的组件进行一下对象的初始化了
(1)像**elementui**中**checkbox复选框、Cascader 级联选择器、日期组件**等等**v-model**绑定的是数组,而有时候后端接口文档不是数组,需要你手动处理,打个比方:
前端将多个选中的值(这些值存在数组)使用字符串形式且里面的值用逗号隔开,比如:
[1, 2, 3, 4] => "1,2,3,4",这样传给后端,后端返回“1,2,3,4”,要求你能转化成数组**[1,2,3,4]**渲染到elementui组件。
split(','):将字符串中逗号分隔的元素转成数组,[1, 2, 3, 4] => "1,2,3,4"
join(','):将数组元素转成逗号分隔的字符串, "1,2,3,4" => [1, 2, 3, 4]
$set(数组/对象,property/索引,具体值):vue2中使用Object.defineProperty监听响应式变化,对数组和对象的监听不是那么友好,很多时候数据变化了但是视图没发生变化,**$set**的出现解决了这个问题
// 表单初始化
formInit() {
this.form = JSON.stringify(this.editForm) ? this.editForm : {}
// checkbox类型的组件需要初始化数组
this.formItemList.forEach(item => {
if (['checkbox'].includes(item.type)) {
if (!this.form[item.modelString]) {
this.$set(this.form, item.model, [])
} else {
this.$set(this.form, item.model, this.form[item.modelString]?.split(','))
}
}
})
}
// 接受子组件传回来的form表单内容
getForm(val) {
this.formItemList.forEach(item => {
//
if (['checkbox'].includes(item.type)) {
if (this.form[item.model] && this.form[item.model]?.length != 0) {
val[item.modelString] = val[item.model]?.join(',')
}
}
})
console.log('val:', val);
// 此处可以请求后台了
},
3、jsx方式封装el-form
jsx封装方式借鉴了这篇文章:element-ui 通用表单封装及VUE JSX应用 - 掘金
3.1、封装
(1)上篇博客有简单介绍jsx:两种方式对el-table二次封装_码上编程的博客-CSDN博客
(2) 新建jsxForm文件夹,并创建index.js文件
(3)封装代码:
代码逻辑图如下:
export default {
name: 'jsxForm',
props: {
// 生成el-form-item的数组
formItemList: {
type: Array,
default: () => [],
},
// 是否行内表单模式
inline: {
type: Boolean,
default: false,
},
// el-form-item的label宽度
labelWidth: {
type: String,
default: '100px',
},
// 表单校验规则
rules: {
type: Object,
default: () => { },
},
// 按钮列表
buttonList: {
type: Array,
default: () => [],
}
},
data() {
return {
form: {},
checkboxList: []
}
},
methods: {
// 生成选项
generateOption(itemObj) {
let options = []
for (let index = 0; index < itemObj.options.length; index++) {
const item = itemObj.options[index]
switch (itemObj.type) {
// 下拉菜单
case 'select':
options.push(<el-option label={item.label} value={item.value}></el-option>)
break
// 多选框
case 'checkbox':
options.push(<el-checkbox label={item.label}></el-checkbox>)
break
// 单选框
case 'radio':
options.push(<el-radio label={item.label}>{item.label}</el-radio>)
break
}
}
return options
},
// 生成下拉菜单
generateSelect(item) {
return <el-select v-model={this.form[item.model]}>{this.generateOption(item)}</el-select>
},
// 生成多选框
generateCheckbox(item) {
this.form[item.model] = this.checkboxList
return <el-checkbox-group v-model={this.checkboxList}>{this.generateOption(item)}</el-checkbox-group>
},
// 生成单选
generateRadio(item) {
return <div>
<el-radio-group v-model={this.form[item.model]}>{this.generateOption(item)}</el-radio-group>
</div>
},
// 生成输入框
generateInput(item) {
return (<div>
<el-input v-model={this.form[item.model]} style={{ width: `${this.formItemContentWidth}` }}></el-input>
</div>)
},
// 生成开关
generateSwitch(item) {
return <div>
<el-switch v-model={this.form[item.model]}></el-switch>
</div>
},
// 生成日期组件
generateDate(item) {
return <div>
<el-date-picker v-model={this.form[item.model]} type={"daterange"} range-separator={"至"}
start-placeholder={"开始日期"} end-placeholder={"结束日期"}>
</el-date-picker>
</div >
},
// 生成表单项
generateFormItems(list = []) {
let formItems = []
list.forEach(item => {
let formItemContent = ''
switch (item.type) {
// 下拉菜单
case 'select':
formItemContent = this.generateSelect(item)
break
// 单选框
case 'radio':
formItemContent = this.generateRadio(item)
break
// 输入框
case 'input':
formItemContent = this.generateInput(item)
break;
// 开关
case 'switch':
formItemContent = this.generateSwitch(item)
break;
// 日期
case 'date-picker':
formItemContent = this.generateDate(item)
break;
// 复选框
case 'checkbox':
formItemContent = this.generateCheckbox(item)
break;
default:
break
}
formItems.push(<el-form-item label={item.label} prop={item.model}>{formItemContent}</el-form-item>)
})
return formItems
},
// 按钮列表
generateBtnList() {
let buttons = []
this.buttonList?.forEach(item => {
buttons.push(<el-button type={item.type} onClick={() => item.event()}>{item.text}</el-button>)
})
return buttons
},
// 重置表单
resetForm() {
// 重置复选框
this.checkboxList = []
this.$refs.form.resetFields();
},
// 返回经过校验的表单
returnForm() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.$emit('submitForm', this.form)
}
})
}
},
render() {
return (
<div>
<el-form ref="form" props={{ model: this.form }} rules={this.rules} inline={this.inline} label-width={this.labelWidth || '150px'}>
{this.generateFormItems(this.formItemList)}
<el-form-item>
{this.generateBtnList()}
</el-form-item>
</el-form>
</div>
)
}
}
3.2、使用
<template>
<div id="app">
<jsxForm ref="form" :buttonList="buttonList" :rules="rules" :formItemList="formItemList" @submitForm="getForm">
</jsxForm>
</div>
</template>
<script>
import jsxForm from '@/components/jsxForm';
export default {
name: 'App',
components: {
JsxForm,
},
data() {
return {
formItemList: [
{ label: '活动名称', type: 'input', model: 'name', placeholder: '请输入活动名称' },
{ label: '活动区域', type: 'select', model: 'region', placeholder: '请选择状态', options: [{ label: '上海', value: 'shanghai' }, { label: '北京', value: 'beijing' }] },
{ label: '活动时间', type: 'date-picker', model: 'startTime', },
{ label: '即时配送', type: 'switch', model: 'delivery' },
{ label: '活动性质', type: 'checkbox', model: 'type', options: [{ label: '美食/餐厅线上活动' }, { label: '地推活动' }, { label: '线下主题活动' }, { label: '单纯品牌曝光' },] },
{ label: '特殊资源', type: 'radio', model: 'resource', options: [{ label: '线上品牌商赞助' }, { label: '线下场地免费' }] },
],
rules: {
name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
},
buttonList: [
{ text: '提交', type: 'primary', event: this.submitForm },
{ text: '重置', event: this.resetForm },
]
}
},
methods: {
// 接受子组件传回来的form表单内容
getForm(val) {
console.log('val:', val);
},
// 点击提交,触发表单校验
submitForm() {
this.$refs.form.returnForm();
},
// 重置表单
resetForm() {
this.$refs.form.resetForm();
}
}
}
</script>
版权归原作者 码上编程 所有, 如有侵权,请联系我们删除。