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 相关文章推荐
javascript函数库-集合框架
Apr 27 Javascript
javascript脚本编程解决考试分数统计问题
Oct 18 Javascript
Jquery 快速构建可拖曳的购物车DragDrop
Nov 30 Javascript
基于jquery的模态div层弹出效果
Aug 21 Javascript
JavaScript中的this实例分析
Apr 28 Javascript
javascript中万恶的function实例分析
May 25 Javascript
JavaScript调用ajax获取文本文件内容实现代码
Mar 28 Javascript
选择复选框按钮置灰否则按钮可用
May 22 Javascript
用js将long型数据转换成date型或datetime型的实例
Jul 03 Javascript
JS实现select选中option触发事件操作示例
Jul 13 Javascript
Vue中用props给data赋初始值遇到的问题解决
Nov 27 Javascript
Canvas跟随鼠标炫彩小球的实现
Apr 11 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
PHP 将数组打乱 shuffle函数的用法及简单实例
2016/06/17 PHP
js自带函数备忘 数组
2006/12/29 Javascript
解析JSON对象与字符串之间的相互转换
2013/12/18 Javascript
Javscript删除数组中指定元素并返回新数组
2014/03/06 Javascript
扩展jQuery对象时如何扩展成员变量具体怎么实现
2014/04/25 Javascript
js实现iPhone界面风格的单选框和复选框按钮实例
2015/08/18 Javascript
JavaScript判断表单为空及获取焦点的方法
2016/02/12 Javascript
javascript实现的左右无缝滚动效果
2016/09/19 Javascript
JavaScript实现水平进度条拖拽效果
2017/01/18 Javascript
boostrapTable的refresh和refreshOptions区别浅析
2017/01/22 Javascript
基于Bootstrap分页的实例讲解(必看篇)
2017/07/04 Javascript
JS实现按钮控制计时开始和停止功能
2017/07/27 Javascript
利用ES6实现单例模式及其应用详解
2017/12/09 Javascript
微信小程序实现给嵌套template模板传递数据的方式总结
2017/12/18 Javascript
Vue匿名插槽与作用域插槽的合并和覆盖行为
2019/04/22 Javascript
Python使用filetype精确判断文件类型
2017/07/02 Python
python3+PyQt5实现自定义流体混合窗口部件
2018/04/24 Python
详解Django+Uwsgi+Nginx的生产环境部署
2018/06/25 Python
Python中的groupby分组功能的实例代码
2018/07/11 Python
对Python3.x版本print函数左右对齐详解
2018/12/22 Python
python实现简易淘宝购物
2019/11/22 Python
用于ETL的Python数据转换工具详解
2020/07/21 Python
Pyqt助手安装PyQt5帮助文档过程图解
2020/11/20 Python
python Xpath语法的使用
2020/11/26 Python
CSS3样式linear-gradient的使用实例
2017/01/16 HTML / CSS
银河香水:Galaxy Perfume
2019/03/25 全球购物
大学生护理专业自荐信
2013/10/03 职场文书
巾帼文明岗申报材料
2014/05/01 职场文书
幸福中国演讲稿
2014/09/12 职场文书
司法局2014法制宣传日活动总结
2014/11/01 职场文书
工会文体活动总结
2015/05/07 职场文书
总结Python变量的相关知识
2021/06/28 Python
Vue实现导入Excel功能步骤详解
2021/07/03 Vue.js
Centos7中MySQL数据库使用mysqldump进行每日自动备份的编写
2021/08/02 MySQL
Nginx配置之实现多台服务器负载均衡
2021/08/02 Servers
Redis监控工具RedisInsight安装与使用
2022/03/21 Redis