您好,登錄后才能下訂單哦!
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 實現搜索欄公共組件封裝,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對億速云網站的支持!
如果你覺得本文對你有幫助,歡迎轉載,煩請注明出處,謝謝!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。