vue elementui 实现搜索栏公共组件封装的实例代码
1、背景
vue后台管理系统,会有很多表格页面,表格上方会有一些搜索选项,表格直接使用el-table即可,而搜索栏区域每次写起来都很繁琐,而且多人开发情况下每个人写的样式都不相同,布局样式无法统一。
所以要考虑对搜索栏做一个封装,统一配置引用,提升开发维护效率和界面统一。
完成后的效果大概就是长这样:
2、分析
项目使用的是elementui框架,搜索栏这种表单提交,首先要使用el-form组件来封装,而复杂点就是表单项可能有很多种,例如input输入框、select选择框、日期时间选择框、日期时间范围选择框、cascader级联选择框等,每一项的字段名prop、名称label、绑定的属性方法都不尽相同。所以不能通过普通的绑定个别属性的方式来处理,而slot插槽的方式也无法简化,最终决定通过传递一个配置项数组的形式来解析生成相应的结构。
3、实现
目前实现的方式由两部分组成,一部分是form表单组件,接受父组件传递的配置项数组,一部分是封装一些常用的表单项组件,通过v-if来控制,form表单组件里引入该表单项组件,循环遍历,根据传递的表单项类型来匹配显示具体的表单项。
form表单组件(searchform.vue)示例代码:
<el-form :model="formdata" ref="formref" :inline="true" > <el-form-item v-for="(item, index) in formoptions" :key="newkeys[index]" :prop="item.prop" :label="item.label ? (item.label + ':') : ''" :rules="item.rules" > <formitem v-model="formdata[item.prop]" :itemoptions="item" /> </el-form-item> </el-form>
formitem表单项组件(formitem.vue)示例代码:
<el-input v-if="isinput" v-model="currentval" v-bind="bindprops" v-on="bindevents" size="mini" ></el-input> <el-select v-if="isselect" v-model="currentval" v-bind="bindprops" v-on="bindevents" size="mini" clearable > <el-option v-for="item in itemoptions.options" :key="item.value" :label="item.label" :value="item.value" ></el-option> </el-select>
4、关键点
由于elementui表单组件本身有很多配置属性,不可能把所有的属性和方法都写死封装,要想无缝支持,需要用到vue的v-bind和v-on特性,vue的v-bind和v-on支持赋值为对象类型,vue会自动遍历对象里的属性依次绑定,v2.4.0+支持。
5、参数配置项解释
(1)示例:
[{ label: '用户名', // label文字 prop: 'username', // 字段名 element: 'el-input', // 指定elementui组件 initvalue: '阿黄', // 字段初始值 placeholder: '请输入用户名', // elementui组件属性 rules: [{ required: true, message: '必填项', trigger: 'blur' }], // elementui组件属性 events: { // elementui组件方法 input (val) { console.log(val) }, } }]
label 用于绑定给el-form-item上的label,表单项标题
prop 用于绑定给el-form-item上的prop,字段名,必填
element 指定elementui表单项的组件名,必填
initvalue 表单项的初始值,可选
events 对象,对象里加方法,js原生方法或者elementui表单项组件支持的方法都可以加进去,通过v-on遍历绑定
… 其他elementui表单项组件支持的属性或者html原生属性都可以添加,常用的例如rules表单校验、placeholder提示,通过v-bind遍历绑定
(2)参数传递解析的流程:
首先,searchform.vue组件里通过props接收参数:
formoptions: { type: array, required: true, default () { return [] } },
created组件里处理初始值:
// 添加初始值 addinitvalue () { const obj = {} this.formoptions.foreach(v => { if (v.initvalue !== undefined) { obj[v.prop] = v.initvalue } }) this.formdata = obj }
一部分配置项绑定在el-form-item上,一部分传递给formitem表单项组件再绑定:
<el-form-item v-for="(item, index) in formoptions" :key="newkeys[index]" :prop="item.prop" :label="item.label ? (item.label + ':') : ''" :rules="item.rules" > <formitem v-model="formdata[item.prop]" :itemoptions="item" /> </el-form-item>
formitem.vue表单项组件里props接受传参:
itemoptions: { type: object, default () { return {} } }
computed里处理接收的参数itemoptions,生成要绑定的所有属性对象bindprops:
// 绑定属性 bindprops () { let obj = { ...this.itemoptions } // 移除已使用的或不相关的冗余属性 delete obj.label delete obj.prop delete obj.element delete obj.initvalue delete obj.rules delete obj.events if (obj.element === 'el-select') { delete obj.options } return obj },
computed里生成要绑定的所有方法对象bindevents:
// 绑定方法 bindevents () { return this.itemoptions.events || {} },
最后dom里使用这些数据绑定:
<el-input v-if="isinput" v-model="currentval" v-bind="bindprops" v-on="bindevents" ></el-input>
(3)特殊情况的处理
由于elementui的el-select里是通过el-option遍历实现的,而遍历数组options按elementui官方不是绑定在el-select上的,所以针对el-select的配置项再加一个options里属性,即select选择项的数据数组。
<el-select v-if="isselect" v-model="currentval" v-bind="bindprops" v-on="bindevents" size="mini" clearable > <el-option v-for="item in itemoptions.options" :key="item.value" :label="item.label" :value="item.value" ></el-option> </el-select>
elementui的日期时间选择器分了很多种,根据业务需要分别处理一下,我这里是根据type划分成了三种分开处理,最常用的是datetimerange日期时间范围选择器,作为默认项,还有一种monthrange,其余的都划为一种。(具体处理见文章末尾的完整代码)
6、按钮组
按钮其实就那么几个,没必要做太多的封装,根据业务有哪些按钮就封装进去,目前我这里就封装了三个按钮。
通过props接受一个字符串标识按钮组:
// 提交按钮项,多个用逗号分隔(query搜索, export导出, reset重置) btnitems: { type: string, default () { return 'search' } }
7、使用方式示例
dom:
<!-- 搜索 --> <searchform :formoptions="formoptions" @onsearch="onsearch"/>
vue data里:
formoptions: [ { label: '意见内容', prop: 'content', element: 'el-input' }, { label: '类型', prop: 'type', element: 'el-select', options: [ { label: '给点意见', value: '1' }, { label: '售后问题', value: '2' } ] }, { label: '状态', prop: 'status', element: 'el-select', options: getfeedbackstatus() }, { label: '提交时间', prop: 'timerange', element: 'el-date-picker' } ],
vue methods里:
// 获取搜索表单提交的数据 onsearch (val) { console.log(val) }
8、完整代码
(1)searchform.vue
/** * created by hanxueqiang on 200107 * * 搜索栏公共组件 */ <template> <div class="search-form-box"> <el-form :model="formdata" ref="formref" :inline="true" > <el-form-item v-for="(item, index) in formoptions" :key="newkeys[index]" :prop="item.prop" :label="item.label ? (item.label + ':') : ''" :rules="item.rules" > <formitem v-model="formdata[item.prop]" :itemoptions="item" /> </el-form-item> </el-form> <!-- 提交按钮 --> <div class="btn-box"> <el-button v-if="btnitems.includes('search')" size="mini" type="primary" class="btn-search" @click="onsearch" >搜索</el-button> <el-button v-if="btnitems.includes('export')" size="mini" type="primary" class="btn-export" @click="onexport" >导出</el-button> <el-button v-if="btnitems.includes('reset')" size="mini" type="default" class="btn-reset" @click="onreset" >重置</el-button> </div> </div> </template> <script> import formitem from './formitem' import tools from '@/utils/tools' export default { props: { /** * 表单配置 * 示例: * [{ * label: '用户名', // label文字 * prop: 'username', // 字段名 * element: 'el-input', // 指定elementui组件 * initvalue: '阿黄', // 字段初始值 * placeholder: '请输入用户名', // elementui组件属性 * rules: [{ required: true, message: '必填项', trigger: 'blur' }], // elementui组件属性 * events: { // elementui组件方法 * input (val) { * console.log(val) * }, * ...... // 可添加任意elementui组件支持的方法 * } * ...... // 可添加任意elementui组件支持的属性 * }] */ formoptions: { type: array, required: true, default () { return [] } }, // 提交按钮项,多个用逗号分隔(query, export, reset) btnitems: { type: string, default () { return 'search' } } }, data () { return { formdata: {} } }, computed: { newkeys () { return this.formoptions.map(v => { return tools.createuniquestring() }) } }, created () { this.addinitvalue() }, methods: { // 校验 onvalidate (callback) { this.$refs.formref.validate(valid => { if (valid) { console.log('提交成功') console.log(this.formdata) callback() } }) }, // 搜索 onsearch () { this.onvalidate(() => { this.$emit('onsearch', this.formdata) }) }, // 导出 onexport () { this.onvalidate(() => { this.$emit('onexport', this.formdata) }) }, onreset () { this.$refs.formref.resetfields() }, // 添加初始值 addinitvalue () { const obj = {} this.formoptions.foreach(v => { if (v.initvalue !== undefined) { obj[v.prop] = v.initvalue } }) this.formdata = obj } }, components: { formitem } } </script> <style lang='less' scoped> .search-form-box { display: flex; margin-bottom: 15px; .btn-box { padding-top: 5px; display: flex; button { height: 28px; } } .el-form { /deep/ .el-form-item__label { padding-right: 0; } .el-form-item { margin-bottom: 0; &.is-error { margin-bottom: 22px; } } // el-input宽度 /deep/ .form-item { > .el-input:not(.el-date-editor) { width: 120px; } } /deep/ .el-select { width: 120px; } /deep/ .el-cascader { width: 200px; } } } </style>
(2)formitem.vue
/** * created by hanxueqiang on 200107 * * 表单匹配项 */ <template> <div class='form-item'> <el-input v-if="isinput" v-model="currentval" v-bind="bindprops" v-on="bindevents" size="mini" ></el-input> <el-input-number v-if="isinputnumber" v-model="currentval" v-bind="bindprops" v-on="bindevents" :controls-position="itemoptions['controls-position'] || 'right'" size="mini" ></el-input-number> <el-select v-if="isselect" v-model="currentval" v-bind="bindprops" v-on="bindevents" size="mini" clearable > <el-option v-for="item in itemoptions.options" :key="item.value" :label="item.label" :value="item.value" ></el-option> </el-select> <!-- datetimerange/daterange --> <el-date-picker v-if="isdatepickerdaterange" v-model="currentval" v-bind="bindprops" v-on="bindevents" :type="itemoptions.type || 'datetimerange'" size="mini" clearable :picker-options="pickeroptionsrange" start-placeholder="开始日期" range-separator="至" end-placeholder="结束日期" :default-time="['00:00:00', '23:59:59']" value-format="yyyy-mm-dd hh:mm:ss" ></el-date-picker> <!-- monthrange --> <el-date-picker v-if="isdatepickermonthrange" v-model="currentval" v-bind="bindprops" v-on="bindevents" type="monthrange" size="mini" clearable :picker-options="pickeroptionsrangemonth" start-placeholder="开始日期" range-separator="至" end-placeholder="结束日期" value-format="yyyy-mm" ></el-date-picker> <!-- others --> <el-date-picker v-if="isdatepickerothers" v-model="currentval" v-bind="bindprops" v-on="bindevents" :type="itemoptions.type" size="mini" clearable placeholder="请选择日期" ></el-date-picker> <el-cascader v-if="iscascader" v-model="currentval" v-bind="bindprops" v-on="bindevents" size="mini" clearable ></el-cascader> </div> </template> <script> import tools from '@/utils/tools' export default { inheritattrs: false, props: { value: {}, itemoptions: { type: object, default () { return {} } } }, data () { return { pickeroptionsrange: tools.pickeroptionsrange, pickeroptionsrangemonth: tools.pickeroptionsrangemonth } }, computed: { // 双向绑定数据值 currentval: { get () { return this.value }, set (val) { this.$emit('input', val) } }, // 绑定属性 bindprops () { let obj = { ...this.itemoptions } // 移除冗余属性 delete obj.label delete obj.prop delete obj.element delete obj.initvalue delete obj.rules delete obj.events if (obj.element === 'el-select') { delete obj.options } return obj }, // 绑定方法 bindevents () { return this.itemoptions.events || {} }, // el-input isinput () { return this.itemoptions.element === 'el-input' }, // el-input-number isinputnumber () { return this.itemoptions.element === 'el-input-number' }, // el-select isselect () { return this.itemoptions.element === 'el-select' }, // el-date-picker (type: datetimerange/daterange) isdatepickerdaterange () { const isdatepicker = this.itemoptions.element === 'el-date-picker' const isdaterange = !this.itemoptions.type || this.itemoptions.type === 'datetimerange' || this.itemoptions.type === 'daterange' return isdatepicker && isdaterange }, // el-date-picker (type: monthrange) isdatepickermonthrange () { const isdatepicker = this.itemoptions.element === 'el-date-picker' const ismonthrange = this.itemoptions.type === 'monthrange' return isdatepicker && ismonthrange }, // el-date-picker (type: other) isdatepickerothers () { const isdatepicker = this.itemoptions.element === 'el-date-picker' return isdatepicker && !this.isdatepickerdaterange && !this.isdatepickermonthrange }, // el-cascader iscascader () { return this.itemoptions.element === 'el-cascader' } }, created () {}, methods: {}, components: {} } </script> <style lang='less' scoped> </style>
(3)依赖引入的一些函数方法 tools.js
/** * 创建唯一的字符串 * @return {string} ojgdvbvaua40 */ function createuniquestring () { const timestamp = +new date() + '' const randomnum = parseint((1 + math.random()) * 65536) + '' return (+(randomnum + timestamp)).tostring(32) } // elementui日期时间范围 快捷选项 const pickeroptionsrange = { shortcuts: [ { text: '今天', onclick (picker) { const end = new date() const start = new date(new date().todatestring()) start.settime(start.gettime()) picker.$emit('pick', [start, end]) } }, { text: '最近一周', onclick (picker) { const end = new date() const start = new date() start.settime(end.gettime() - 3600 * 1000 * 24 * 7) picker.$emit('pick', [start, end]) } }, { text: '最近一个月', onclick (picker) { const end = new date() const start = new date() start.settime(start.gettime() - 3600 * 1000 * 24 * 30) picker.$emit('pick', [start, end]) } }, { text: '最近三个月', onclick (picker) { const end = new date() const start = new date() start.settime(start.gettime() - 3600 * 1000 * 24 * 90) picker.$emit('pick', [start, end]) } } ] } // elementui月份范围 快捷选项 const pickeroptionsrangemonth = { shortcuts: [ { text: '今年至今', onclick (picker) { const end = new date() const start = new date(new date().getfullyear(), 0) picker.$emit('pick', [start, end]) } }, { text: '最近半年', onclick (picker) { const end = new date() const start = new date() start.setmonth(start.getmonth() - 6) picker.$emit('pick', [start, end]) } }, { text: '最近一年', onclick (picker) { const end = new date() const start = new date() start.setmonth(start.getmonth() - 12) picker.$emit('pick', [start, end]) } } ] }
(4)一些elmentui全局样式的修改
// el-input-number (controls-position="right") .el-input-number.is-controls-right { .el-input-number__decrease { display: none; } .el-input-number__increase { display: none; top: 2px; // fix style bug } &:hover { .el-input-number__decrease { display: inline-block; } .el-input-number__increase { display: inline-block; } } .el-input__inner { text-align: left; padding-left: 5px; padding-right: 40px; } } // el-date-picker datetimerange .el-date-editor.el-date-editor--datetimerange { .el-range-separator { width: 24px; color: #999; padding: 0; } .el-range__icon { margin-left: 0; } &.el-input__inner { vertical-align: middle; padding: 3px 5px; } &.el-range-editor--medium { width: 380px; .el-range-separator { line-height: 30px; } } &.el-range-editor--mini { width: 330px; .el-range-separator { line-height: 22px; } } } // el-date-picker not datetimerange .el-date-editor { .el-input__prefix { left: 0; top: 1px; } .el-input__suffix { right: 0; top: 1px; } .el-input__inner { padding: 0 25px; } &.el-input--mini { width: 175px; } &.el-input--medium { width: 195px; } } // input padding .el-input__inner { padding: 0 5px; }
总结
以上所述是小编给大家介绍的vue elementui 实现搜索栏公共组件封装,希望对大家有所帮助
上一篇: JS实现横向轮播图(中级版)
下一篇: JS基础之逻辑结构与循环操作示例