vue elementui 实现搜索栏公共组件封装的实例代码


Posted in Javascript onJanuary 20, 2020

1、背景

vue后台管理系统,会有很多表格页面,表格上方会有一些搜索选项,表格直接使用el-table即可,而搜索栏区域每次写起来都很繁琐,而且多人开发情况下每个人写的样式都不相同,布局样式无法统一。

所以要考虑对搜索栏做一个封装,统一配置引用,提升开发维护效率和界面统一。

完成后的效果大概就是长这样:

vue elementui 实现搜索栏公共组件封装的实例代码

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 实现搜索栏公共组件封装,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
大家未必知道的Js技巧收藏
Apr 07 Javascript
this和执行上下文实现代码
Jul 01 Javascript
Javascript生成json的函数代码(可以用php的json_decode解码)
Jun 11 Javascript
JavaScript中几个重要的属性(this、constructor、prototype)介绍
May 19 Javascript
使用js显示当前时间示例
Mar 02 Javascript
实例详解angularjs和ajax的结合使用
Oct 22 Javascript
jQuery获取某天的农历日期并判断是否除夕或新年的方法
Mar 01 Javascript
Bootstrap基本组件学习笔记之缩略图(13)
Dec 08 Javascript
jquery自定义插件结合baiduTemplate.js实现异步刷新(附源码)
Dec 22 Javascript
解析ajaxFileUpload 异步上传文件简单使用
Dec 30 Javascript
Bootstrap Table使用整理(三)
Jun 09 Javascript
vue-router单页面路由
Jun 17 Javascript
D3.js 实现带伸缩时间轴拓扑图的示例代码
Jan 20 #Javascript
阿望教你用vue写扫雷小游戏
Jan 20 #Javascript
JavaScript Window窗口对象属性和使用方法
Jan 19 #Javascript
浅谈webpack和webpack-cli模块源码分析
Jan 19 #Javascript
js原生map实现的方法总结
Jan 19 #Javascript
Node.js操作MongoDB数据库实例分析
Jan 19 #Javascript
JS运算符简单用法示例
Jan 19 #Javascript
You might like
有关JSON以及JSON在PHP中的应用
2010/04/09 PHP
PHP的基本常识小结
2013/07/05 PHP
Laravel中Facade的加载过程与原理详解
2017/09/22 PHP
TNC vs BOOM BO3 第三场2.13
2021/03/10 DOTA
jquery学习笔记二 实现可编辑的表格
2010/04/09 Javascript
javascript中的float运算精度实例分析
2010/08/21 Javascript
js中使用DOM复制(克隆)指定节点名数据到新的XML文件中的代码
2011/07/27 Javascript
js面向对象设计用{}好还是function(){}好(构造函数)
2011/10/23 Javascript
jQuery.lazyload+masonry改良图片瀑布流代码
2014/06/20 Javascript
JavaScript原生节点操作小结
2017/01/17 Javascript
js+html5实现复制文字按钮
2017/07/15 Javascript
Vue0.1的过滤代码如何添加到Vue2.0直接使用
2017/08/23 Javascript
微信小程序中this.data与this.setData的区别详解
2018/09/17 Javascript
JavaScript怎样在删除前添加确认弹出框?
2019/05/27 Javascript
vue项目中将element-ui table表格写成组件的实现代码
2019/06/12 Javascript
Vue发布订阅模式实现过程图解
2020/04/30 Javascript
在Python中用get()方法获取字典键值的教程
2015/05/21 Python
python使用正则表达式提取网页URL的方法
2015/05/26 Python
numpy中的delete删除数组整行和整列的实例
2018/05/09 Python
python 基本数据类型占用内存空间大小的实例
2018/06/12 Python
Django Rest framework解析器和渲染器详解
2019/07/25 Python
python opencv将图片转为灰度图的方法示例
2019/07/31 Python
pyinstaller打包成无控制台程序时运行出错(与popen冲突的解决方法)
2020/04/15 Python
Python实现初始化不同的变量类型为空值
2020/06/02 Python
python中_del_还原数据的方法
2020/12/09 Python
css3制作动态进度条以及附加jQuery百分比数字显示
2012/12/13 HTML / CSS
详解HTML5通讯录获取指定多个人的信息
2016/12/20 HTML / CSS
详解px单位html5响应式方案
2018/03/08 HTML / CSS
美国体育用品在线:Modell’s Sporting Goods
2018/06/07 全球购物
Volcom英国官方商店:美国殿堂级滑板、冲浪、滑雪服装品牌
2019/03/13 全球购物
WebSphere面试题:在WebSphere里面如何部署一个应用
2015/08/02 面试题
建筑项目策划书
2014/01/13 职场文书
单位实习鉴定评语
2015/01/04 职场文书
公司人事管理制度
2015/08/05 职场文书
GitHub上77.9K的Axios项目有哪些值得借鉴的地方详析
2021/06/15 Javascript
springmvc直接不经过controller访问WEB-INF中的页面问题
2022/02/24 Java/Android