组件封装的缘由
开发后台管理系统,在业务上接触的最多就是表单(输入)和表格(输出)了。对于使用 Vue 框架进行开发的同学来说,组件库 Element 是肯定会接触的,而其中的 el-table 和 el-form 更是管理系统中的常客。
然而,一旦项目的表格或表单多起来,每个不同的配置,以及多一个字段少一个字段,都要在 template 中重新写一大段组件代码,显得非常麻烦。或许你会考虑将这些代码封装起来,可是又会发现,封装的表格、表单大多数只在一处地方使用,还不如不封装呢。到底要如何封装,可以让每处使用 el-table 或 el-form, 都可以复用相同的组件,减少代码量的同时又具备高度的可定制性?
本文章将会按照从无到有的步骤,按照封装组件常用的思路来封装 el-table,并且实现封装完成的组件支持 el-table 的全配置
最近安排一个模块主要是数据查询展示的列表模块,为了减少开发代码以及提供工作效率(程序猿的偷懒),就想着把相关的业务封装成一个组件,主要功能有
- 数据查询条件的筛选以及筛选条件过多默认展示几个,可通过更过和收起来控制显示的筛选条件的操作
- 表格的多选,自定义列,操作列的按钮自定等操作
- 集成了数据分页的公共
话不多说,直接看图撸代码
效果图
组件代码
<template><div><div class="formWrap"><el-form ref="form":rules="formRules":model="form" label-width="100px"><el-row :gutter="10"><template v-for="(item, index) in headerData"><template v-if="item.show"><el-col :span="item.span || 6":key="index"><template v-if="item.type !== 'slot'"><!-- 当类型为普通文本输入框时 --><el-form-item
v-if="item.itemType == 'text'":key="index":label="item.labelName":prop="item.propName"><el-input
:placeholder="item.placeholder"
v-model.trim="form[item.propName]"
clearable
size="small"></el-input></el-form-item><!-- 当类型为数字类型输入框时 --><el-form-item
v-if="item.itemType == 'number'":key="index":label="item.labelName":prop="item.propName"><el-input
:placeholder="item.placeholder"
v-model.trim="form[item.propName]"
clearable
size="small"><span slot="suffix">{{ item.unit }}</span></el-input></el-form-item><!-- 当类型为文本域输入框时 --><el-form-item
v-if="item.itemType == 'textarea'":key="index":label="item.labelName":prop="item.propName"><el-input
type="textarea":placeholder="item.placeholder"
v-model.trim="form[item.propName]"
clearable
size="small"></el-input></el-form-item><!-- 当类型为下拉框一时,固定下拉选项 --><el-form-item
v-if="item.itemType == 'selectOne'":key="index":label="item.labelName":prop="item.propName"><el-select
v-model="form[item.propName]":placeholder="item.placeholder"
clearable
size="small"><el-option
v-for="(ite, ind) in item.optionsArr":key="ind":label="ite.label":value="ite.value"></el-option></el-select></el-form-item><!-- 当类型为下拉框二时,属于枚举值(单选)下拉框,需要根据枚举id发请求获取枚举值 --><el-form-item
v-if="item.itemType == 'selectTwo'":key="index":label="item.labelName":prop="item.propName"><el-select
v-model="form[item.propName]":placeholder="item.placeholder"
clearable
@visible-change="
(flag)=>{getOptionsArr(flag, item)}
"
:loading="loadingSelect"
size="small"><el-option
v-for="(ite, ind) in selectTwoOptionsObj[item.propName]":key="ind":label="ite.label":value="ite.value"></el-option></el-select></el-form-item><!-- 当类型为下拉框三时,属于枚举值(多选)下拉框,需要根据枚举id发请求获取枚举值 --><el-form-item
v-if="item.itemType == 'selectThree'":key="index":label="item.labelName":prop="item.propName"><el-select
v-model="form[item.propName]":placeholder="item.placeholder"
clearable
@visible-change="
(flag)=>{getOptionsArr(flag, item)}
"
:loading="loadingSelect"
multiple
collapse-tags
size="small"><el-option
v-for="(ite, ind) in selectTwoOptionsObj[item.propName]":key="ind":label="ite.label":value="ite.value"></el-option></el-select></el-form-item><!-- 当类型为日期范围 --><el-form-item
v-if="item.itemType == 'dateRange'":key="index":label="item.labelName":prop="item.propName"><el-date-picker
v-model="form[item.propName]"
format="yyyy-MM-dd"
value-format="yyyy-MM-dd"
clearable
type="daterange"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
size="small"></el-date-picker></el-form-item></template><template v-else><el-form-item label-width="30px"><slot></slot></el-form-item></template></el-col></template></template></el-row></el-form><!-- 提交表单和重置表单部分 --></div><el-table
:data="tableData"
border
style="width: 100%":element-loading-text="loadingText":emptyText="emptyText"
@selection-change="handleSelectionChange":header-cell-style="{background:'#f1f1f1',color:'#606266',fontWeight:'normal',}"
highlight-current-row
><el-table-column
v-if="isShowCheckbox"
type="selection"
width="50"
align="center"></el-table-column><el-table-column
v-for="(item, index) in tableProps":key="index":label="item.label":width="item.width":show-overflow-tooltip="item.tooltip":align="item.align || 'center'"><template slot-scope="scope"><span v-if="!item.slot">{{ scope.row[item.prop]}}</span><div
v-else:style="setCorol(item.color)"class="slot-box"
@click="clickSlot(item.prop, scope.row)">{{ scope.row[item.prop]}}</div></template></el-table-column><!-- 操作栏 --><el-table-column
v-if="operation.length":fixed="operationFixed"
align="center"
label="操作":width="operationWidth"><template slot-scope="scope"><div class="t_c"><template v-for="(item, index) in operation"><div
:key="item.name":class="{ dis_in_center: direction === 'level' }"><el-button
:key="index":style="{ width: item.width }":type="item.btnType || 'primary'":icon="item.icon":circle="item.circle":plain="item.plain":size="item.size || 'mini'"
@click.stop="
handleOperation(scope.row, item.type, index, scope.$index)
"
><span :style="getBtnStyle(item)">{{ item.name }}</span></el-button></div></template></div></template></el-table-column></el-table><div class="pagination"><el-pagination
v-if="total > 0"
@size-change="handleSizeChange"
@current-change="handleCurrentChange":current-page="currentPage":page-sizes="pageSizes":page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper":total="total"></el-pagination></div></div></template><script>exportdefault{name:'search-table',props:{// 表单项数据formData:{type: Array,default:()=>[],},// 表单校验数组formRules:{type: Object,default:()=>{},},// 下拉项的展示数据对象selectTwoOptionsObj:{type: Object,default:()=>{},},// 下拉状态loadingSelect:{type: Boolean,default:false,},// 表单 更多/收起按钮的状态isActive:{type: Boolean,default:false,},// 初始化表单数据activeData:{type: Array,default:()=>[],},// 加载文字loadingText:{type: String,default:'拼命加载中',},// 表格无数据展示的文字emptyText:{type: String,default:'暂无数据',},// 是否多选isShowCheckbox:{type: Boolean,default:false,},// 表头数据tableProps:{type: Array,default:()=>[],},// 操作栏的展示规则direction:{type: String,default:'level',},// 表格数据tableData:{type: Array,default:()=>[],},// 操作项数据operation:{type: Array,default:()=>[],},// 操作栏是否固定operationFixed:{type:[String, Boolean],default:false,},// 操作栏宽度operationWidth:{type: Number,default:200,},// 分页拉下选择展示条数pageSizes:{type: Array,default:()=>[10,20,50,100],},// 一页展示多少条pageSize:{type: Number,default:10,},// 当前页currentPage:{type: Number,default:1,},// 总条数total:{type: Number,default:0,},},data(){return{// 绑定的数据form:{},// 表单配置项数据headerData:[],}},watch:{isActive:{handler(val){this.setFormData(val)},immediate:true,},},mounted(){this.handleFormData()},methods:{/**
* 动态控制表单
*/setFormData(val){if(this.activeData &&!this.activeData.length >0)returnthis.headerData =[]if(!val){this.formData.forEach((item, index)=>{if(this.activeData.includes(item.propName)){this.headerData.push(this.formData[index])}})}else{this.headerData =this.formData
}},handleFormData(){if(this.activeData &&!this.activeData.length >0){this.headerData =this.formData
}},// 获取下拉框数据asyncgetOptionsArr(flag, item){// console.log(flag, item);// 为true时表示展开,这里模拟根据枚举值id发请求,获取下拉框的值的if(flag){this.$emit('update:loadingSelect',true)// 使用了加载中效果,最好加上一个try catch捕获异常// let result = await this.$api.getEnumList({id:item.enumerationId})this.$emit('getOptionsArrData', item)}else{// 解决多选下拉框失去焦点校验规则仍然存在问题if(item.itemType =='selectThree'){// console.log("关闭时校验多选值", this.form[item.propName]);if(this.form[item.propName].length >0){// 如果至少选择一个了,说明符合要求,就再校验一次,这样校验规则就去掉了this.$refs.form.validateField(item.propName)}}}},// 保存提交表单submitForm(){let flag =nullthis.$refs.form.validate((valid)=>{if(valid){
flag =this.form
}else{
flag =false}})return flag
},// 重置表单resetForm(){this.$refs.form.resetFields()this.form ={}// 这里重置完了以后,要重新初始化数据,否则会出现输入不上去的问题},// 点击插槽事件clickSlot(prop, row){this.$emit('clickCell',{ prop, row })},// 全选handleSelectionChange(val){this.$emit('handle-selection-change', val)},// 编辑editTable(val){this.$emit('edit-table', val)},// 操作栏事件handleOperation(data, operaType, rowIndex, index){this.$emit('btnClick',{ data, operaType, rowIndex, index })},// 每页多少条handleSizeChange(val){this.$emit('changSize', val)},// 分页handleCurrentChange(val){this.$emit('changCurrent', val)},setCorol(e){return`color:${e}`},getBtnStyle(e){let str =''for(let i in e.style){
str +=`${i}:${e.style[i]};`}return str
},},}</script><style scoped lang="less">.formWrap {width:100%;/deep/.el-form {.el-form-item {
margin-bottom: 12px !important;.el-form-item__content {// 给下拉框指定宽度百分比.el-select {width:100%!important;}// 时间选择器指定宽度百分比.el-date-editor {width:100%!important;.el-range-separator {width:10%!important;}}.el-form-item__error {
padding-top: 1px !important;}}}}.btns {width:100%;
text-align: center;
margin-top: 12px;}}.dis_in_center {display: inline-block;
vertical-align: middle;}.slot-box {cursor: pointer;}.t_c {
text-align: center;}.pageContainer {margin: 10px;display: flex;
justify-content: flex-end;}.pagination {
margin-top: 20px;display: flex;
justify-content: center;}::v-deep .el-table__body tr.current-row > td {
background-color: #ffe0cc !important;color: #ff6600 !important;}</style>
使用代码
<template><div id="app"><search-table
ref="myForm":formData="formData":formRules="formRules":isActive="isActive":activeData="activeData":selectTwoOptionsObj="selectTwoOptionsObj":loadingSelect.sync="loadingSelect"
@getOptionsArrData="getOptionsArrData"
@submitForm="submitForm"
@resetForm="resetForm":tableData="tableData":tableProps="tableProps":operation="operation":isShowCheckbox="true":isShowPagination="true"
@clickCell="clickCell"
@btnClick="handleOperation":total="500"><div class="btns"><el-button type="primary" @click="submitForm" size="small">保存</el-button
><el-button @click="resetForm" size="small">重置</el-button><el-button @click="isActive = !isActive" size="small">{{!isActive ?'更多':'收起'}}</el-button></div></search-table></div></template><script>import SearchTable from'./lib/search-table/SearchTable.vue'import formJson from'./lib/testForm.js'exportdefault{name:'App',components:{
SearchTable,},data(){return{// 表单 更多/收起按钮的状态isActive:false,// 初始化展示的表单项activeData:['name','age','salary','btnSlot'],// 表头数组数据formData: formJson.formData,// 表单校验数组formRules: formJson.rules,// 用于下拉框加载时的效果loadingSelect:false,// 此对象用于存储各个下拉框的数组数据值,其实也可以挂在vue的原型上,不过个人认为写在data中好些selectTwoOptionsObj:{},// 表格操作栏数组operation: formJson.operation,// 表格数据tableData: formJson.tableData,// 表格配置数据tableProps: formJson.tableProps,}},mounted(){// 数据回显的时候,要先发请求获取枚举值下拉框的值才能够正确的回显,所以// 就提前发请求获取对应下拉框的值了,这里要注意!注意!注意!this.formData.forEach((item)=>{if((item.itemType =='selectTwo')|(item.itemType =='selectThree')){this.getOptionsArrData(item)}})},methods:{// 自定义插槽的点击事件clickCell(e){
console.log(e)},// 操作栏的点击事件handleOperation(e){
console.log(e)},// 获取表单填写的数据submitForm(){
console.log('表单提交喽',this.$refs.myForm.submitForm())},// 重置表单数据resetForm(){this.$refs.myForm.resetForm()},// 表单部分下拉数据的设置 模拟异步请求getOptionsArrData(item){setTimeout(()=>{this.loadingSelect =falseif(item.propName =='job'){this.selectTwoOptionsObj[item.propName]= formJson.jobList
}if(item.propName =='wish'){this.selectTwoOptionsObj[item.propName]= formJson.wishList
}if(item.propName =='hobby'){this.selectTwoOptionsObj[item.propName]= formJson.hobbyList
}if(item.propName =='wantPhone'){this.selectTwoOptionsObj[item.propName]= formJson.wantPhoneList
}this.$forceUpdate()// 这里需要强制更新一下,否则渲染不出来下拉框选项},500)},},}</script><style lang="less"></style>
表单、表格相关的JSON格式的配置文件
src/lib/testForm.js
/**
* 输入框类型3种
* 1. 普通文本输入框 text
* 2. 数字类型输入框 number
* 3. 文本域输入框 textarea
*
* 下拉框select类型2中
* 1. 固定配置的el-option selectOne
* 2. 枚举值的el-option单选 selectTwo
* 2. 枚举值的el-option多选 selectThree
*
* 时间选择器类型1种
* 1. 两个时间选择器、选取一个范围
*
* 等等,还有其他类型,这里举三种类型,别的类型仿照着即可写出来
* 组件封装适可而止。如果是比较复杂(奇葩)的需要联动的表单,建议一个个写
* 毕竟过度的封装,会导致代码不好维护(个人愚见)
*
* */const formData =[{itemType:'text',// 输入框类型labelName:'姓名:',// 输入框名字propName:'name',// 输入框字段名show:true,placeholder:'请填写名字',// 输入框placeholder提示语加上,可用于告知用户规则},{itemType:'number',labelName:'年龄:',propName:'age',show:true,unit:'year',// 数字类型的要有单位placeholder:'请输入年龄(大于0的正整数)',},{itemType:'number',labelName:'工资:',propName:'salary',show:true,unit:'元/月',// 数字类型的要有单位placeholder:'请输入每月工资金额(大于0且保留两位小数)',},{itemType:'selectOne',// 下拉框类型一,固定的选项可以写死在配置里,比如性别只有男女labelName:'性别:',propName:'gender',show:true,placeholder:'请选择性别',optionsArr:[{label:'男',value:1,},{label:'女',value:2,},],},{itemType:'selectTwo',// 下拉框类型二,枚举值单选,在点击下拉选项时根据枚举id发请求,获取枚举值labelName:'可选职业:',propName:'job',show:true,placeholder:'请选择职业',},{itemType:'selectTwo',// 下拉框类型二,枚举值单选,在点击下拉选项时根据枚举id发请求,获取枚举值labelName:'愿望:',propName:'wish',show:true,placeholder:'请选择愿望',},{itemType:'selectThree',// 下拉框类型三,枚举值多选,在点击下拉选项时根据枚举id发请求,获取枚举值labelName:'爱好:',propName:'hobby',show:true,placeholder:'请选择爱好',},{itemType:'selectThree',// 下拉框类型三,枚举值多选,在点击下拉选项时根据枚举id发请求,获取枚举值labelName:'想买手机:',propName:'wantPhone',show:true,placeholder:'请选择手机',},{itemType:'dateRange',// 日期范围类型labelName:'日期:',show:true,propName:'date',},{itemType:'text',labelName:'备注:',propName:'remark',show:true,placeholder:'请填写备注',},{propName:'btnSlot',type:'slot',show:true,},]const rules ={name:[{required:true,message:'请输入名字',trigger:'blur'}],}const operation =[{type:'update',name:'编辑',btnType:'text',size:'mini',isShow:true,id:'splitAccountRuleSystemEdit',style:{padding:'5px',},},{type:'delete',name:'删除',btnType:'text',size:'mini',isShow:true,id:'splitAccountRuleSystemDelete',style:{color:'red',padding:'5px',},},]const tableData =[{no:'1',date:'2016-05-02',name:'王小虎',address:'上海市普陀区金沙江路 1518 弄',},{no:'2',date:'2016-05-04',name:'王小虎',address:'上海市普陀区金沙江路 1517 弄',},{no:'3',date:'2016-05-01',name:'王小虎',address:'上海市普陀区金沙江路 1519 弄',},{no:'4',date:'2016-05-03',name:'王小虎',address:'上海市普陀区金沙江路 1516 弄',},]const tableProps =[{prop:'no',label:'序号',width:'60',},{prop:'date',label:'日期',width:'200',slot:true,color:'red',},{prop:'name',label:'姓名',width:'200',slot:true,color:'red',},{prop:'address',label:'地址',tooltip:true,},]const jobList =[{label:'医生',value:1,},{label:'教师',value:2,},{label:'公务员',value:3,},]const wishList =[{label:'成为百万富翁',value:1,},{label:'长生不老',value:2,},{label:'家人健康幸福平安',value:3,},]const hobbyList =[{label:'乒乓球',value:1,},{label:'羽毛球',value:2,},{label:'篮球',value:3,},]const wantPhoneList =[{label:'华为',value:1,},{label:'小米',value:2,},{label:'苹果',value:3,},{label:'三星',value:4,},]exportdefault{
formData,
rules,
operation,
tableData,
tableProps,
jobList,
wishList,
hobbyList,
wantPhoneList,}
版权归原作者 生活、追梦者 所有, 如有侵权,请联系我们删除。