项目背景
日前在一些中后台项目中遇到到了类似如下图这样的需求,产品的列表页(还有其他的很多列表页面),首先就是一个大大的Table,然后页顶一个搜索栏,同时右边一个操作按钮(也可能是多个)每次都要去写各种方法去处理整个过滤栏的数据,很麻烦,写多了会吐的...

需求分析
搜索栏无非就是一些表单组件,常用的应该是Select(本地写死,或者远程拉取option),Input,单选多选等等,dom都可以用对象表示,我们为何不用一个json结构去描述呢?我们在点击搜索的时候我们希望得到的是一个以拼装好的json键值对结构,直接请求后端岂不快哉?赶紧写一波!
定义数据结构
每一个对象包括一个type(描述对象是什么组件),name(输出的表单key值),placeholder,async等属性,如下所示。
let filterBar = [
{
key: 1,
type: 'select',
async: true,
searchType: 'vendor',
placeholder: '请选择品牌',
name: 'vendor_id'
},
{
key: 2,
type: 'select',
async: false,
placeholder: '请选择状态',
name: 'status',
options: [
{ key: 1, label: '等待接入', value: 0 },
{ key: 2, label: '开发中', value: 1 },
]
},
{
key: 3,
type: 'input',
async: false,
placeholder: '请输入产品名称',
name: 'keyword'
}
]
开撸
我们期望的组件是这样子的:
<Option :option="filterBar" @onSearch="onSearch" />
中首先我们先给它遍历一波,根据不容的type去渲染不同的组件:
<template>
<div class="optionBar">
<div class="leftBar">
<template v-for="(item,index) in optionData">
<el-input
class="inputstyle"
v-if="item.type=='input'"
:key="item.key"
:placeholder="item.placeholder"
v-model="optionData[index].value"
@input="onChange"
></el-input>
<AsyncSelector
v-if="item.type=='select' && item.async"
:allOption="{optionData,connectName:optionData[index].connect || ''}"
:key="item.key"
:placeholder="item.placeholder"
:searchType="item.searchType"
:showCheckAll="item.showCheckAll"
:defaultFetchKey="optionData[index].defaultfetch"
v-model="optionData[index].value"
:emitEvent="emitEvent"
@onSelect="(value)=>onSelect({connectName:optionData[index].connect || '',index,value})"
@changeDefaultfetch="changeDefaultfetch"
/>
<el-select
class="inputstyle option_margin"
v-if="item.type=='select' && item.async != true"
v-model="optionData[index].value"
:placeholder="item.placeholder"
:key="item.key"
@change="onChange"
>
<el-option
v-for="item in item.options"
:key="item.key"
:label="item.label"
:value="item.value"
></el-option>
</el-select>
</template>
<el-button type="primary" size="mini" icon="el-icon-search" round @click="onSearch">搜索</el-button>
</div>
<div class="rightBar">
<Render :render="extra" />
</div>
</div>
</template>
然后我们的所有状态都在当前filterBar中维护,我们在组件created时候把传入的json描述对象给它添加到当前filterBar的数据中供template使用v-model等等
export default {
props: {
option: {
type: Array,
required: true
},
extra: {
type: Function,
required: false
}
},
components: { AsyncSelector, Render },
data: function() {
return {
optionData: []
}
},
// 动态生成model
created: function() {
let length = this.option.length
for (let i = 0; i < length; i++) {
this.optionData.push({
...this.option[i],
value:
this.option[i].defaultValue === 0
? 0
: this.option[i].defaultValue || '',
defaultfetch: ''
})
}
},
mounted() {},
methods: {
onSelect: function({ connectName, index, value }) {
// 当前下拉框选择后,查询与之联动查询的selectorName
if (connectName !== '') {
// 找出对应联合查询的seletorIndex
let optionSelectorIndex = this.optionData.findIndex((v, k) => {
return v.name === connectName
})
// 修改对应selector的默认选项,在AsyncSelector组建中观测defaultfetch改变,作为参数请求另外的selector的options
this.optionData[optionSelectorIndex].defaultfetch = value
}
this.emitEvent()
},
onChange: function(e) {
this.emitEvent()
},
onSearch: function() {
this.$emit('onSearch', this.formatData())
},
// 格式化输出数据,转化为{key,value}形式
formatData: function() {
let searchData = {}
if (this.optionData.length !== 0) {
this.optionData.forEach(v => {
searchData[v.name] = v.value
})
}
return searchData
},
emitEvent: function() {
let data = { ...this.formatData() }
this.$emit('onChange', data)
},
changeDefaultfetch: function({ optionSelectorIndex, value }) {
// 反馈下一个seletor的搜索参数
this.optionData[optionSelectorIndex].defaultfetch = value
}
}
}
实现过程还是蛮艰辛的,毕竟随着项目的需求变动,一直在修修改改的,不过好在是完成了,想要做搜索过滤的地方,几行json就可以生成,太特么的爽了,对了那个异步select组件也是基于可远程获取数据的select组件只是用了一个searchType去区分到底是请求哪些数据,同时呢也支持多级联动查询,原理也就是通过vue的双向数据绑定以及组件内部的watch检测数据变动的原理去动态的改变相关联项select的搜索参数,从而达到联动的效果。