在实际项目中很多列表页面的筛选器组件重复性很高,本文进行尝试性的封装,下方是一些伪代码,主要是记录一下自己做这个功能时的想法,可供大家参考,不足处欢迎补充。
注:都是手撸代码,部分标点符号不准确
主要思路:
1.每个列表使用一个数组对象来维护当前列表要使用的筛选器
2.以当前列表页面的route.path为唯一值
3.筛选器的数据存储在vuex里面,vuex中维护一个对象,键名为当前列表页的route.path
###列表页面中的关键代码:
<ListSearch :searchCom="searchCom" :routePath="$route.path"> // html直接用动态组件
import { getPlantList } '@/api'
// data中声明的变量
searchCom:[
// 下拉框
{ key: 'createdBy', com: 'SearchSelect', options: { optionsKey: 'createdByOptions', title:'创建人', plactholder: '请选择创建人', multiple: ture } },
// 下拉框远程实时搜索
{ key: 'plant', com: 'SearchSelect', options: { optionsKey: 'plantOptions', title:'工厂', plactholder: '请选择工厂', multiple: ture, remoteMethod: getPlantList } }, // 直接把当前页面引入的api传进去
// input输入框
{ key: 'infomation', com: 'SearchInput', options: { title: '信息', plactholder: '请输入信息' } },
]
###ListSearch组件中的关键代码:
<template>
<div class="search-box">
<component v-for="item in searchCom"
v-bind="$attrs"
v-on="$listeners"
:key="item.key"
:valueKey="item.valueKey"
:options="item.options"
:routePath="routePath"
:is="item.com"/>
</div>
</template>
// 这里引入要用到的组件,以这两个为案例
import SearchSelect from './components/SearchSelect'
import SearchInput from './components/SearchInput'
props:{
searchCom:{
type: Array,
defualt: () => []
},
routePath:{
type: String,
defualt: () => this.$route.path
}
},
data(){
return {
comMap:{
SearchSelect,
SearchSelect
},
getOptions: [] // 当前筛选器要进行提前调用下拉框数据的接口
}
},
created() {
this.$store.commit('listSearch/setDefultRoutePath') // 首先把当前路由告诉vuex
this.getOptions = []
// 循环传过来的searchCom参数,渲染组件
for(let i = this.searchCom.length - 1; i >= 0; i--) {
this.renderCom(this.searchCom[i].com)
if(this.searchCom[i].options?.optionsKey) {
// 下拉框数据
this.getOptions.push(this.searchCom[i].options?.optionsKey)
}
}
},
mounted() {
this.getOptionsList()
}
methods:{
// 动态渲染组件
renderCom(com) {
// 手动给当前实例挂载组件
this.constructor.component(com, () => {
return {
loading: comLoading, // 加载组件时loading效果,自己随便配置
component: new Promise((resolve) => {
resovle(this.comMap[com])
})
}
})
},
getOptionsList() {
// 子组件加载完后,在mounted方法中调用此方法,获取筛选钱中所有的下拉框数据
this.$store.dispatch('listSearch/getSearchOptions', this.getOptions)
}
}
<style lang="less" scoped>
// 样式自适应
.search-box{
display: grid;
grid-template-columns: repeat(4, 25%);
gap:12px 0;
}
<style/>
###SearchSelect组件的关键代码:
<template>
<div class="search-item">
<div class="title">{{options.title}}</div>
<el-select v-module="currentValue"
@change="onChange"
:filterable="options.filterable || true"
:collapse-tags="options.collapseTags || true"
:multiple="options.multiple || false"
:remote=method="val => remoteMethod(val, options)" // 通过父组件直接把api传过来
:remote="options.remoteMethod?ture:false"
:placeholder="item.placeholder || '请选择'"
:loading="loading">
<el-options v-for="item in optionsList"
:key="item.label + item.value"
:label="item.label" // 默认就是value和label,可以不用写这两行,只要保证options都有这两个属性就可以了,如果没有,下方还有进行转换的配置属性
:value="item.value"
/>
</el-selec>
</div>
</template>
import {mapState} from 'vuex'
props:{
// 当前组件绑定的key值,也是后续进行接口搜索时传给后端的值
valueKey:{
type: String,
defualt: ''
},
options:{
type: Object,
defualt: () => ({})
},
routePath:{
type: String,
defualt: this.$route.path
},
},
data(){
return {
currentValue: '', // 当前组件维护的module
returnValue: {}, // return出去的值,暂时没什么用,可以直接从vuex通过路由path来取
optionsList: [], // 当前组件下拉框数据
loading: false // 远程搜索时的loading
}
},
compunted: {
...mapState({
allOptions: state => state.listSearch.allOptions, // 更新下拉框数据时
optionsUpdate: state => state.listSearch.optionsUpdate, // 更新下拉框数据时
listSearchMap: state => state.listSearch.listSearchMap // 当前页面绑定的vuex对象
})
},
watch: {
currentValue: {
handler(val){
if(this.valueKey){
this.$set(this.returnValue, `${this.valueKey}`, val)
}
}
},
listSearchMap: {
handler(val){
// vuex中的对象更新了,并且对象键名===当前路由path,更新的key值和当前页面绑定的valueKey一样,就刷新当前组件的currentValue
if(val[this.routePath][this.valueKey]){
this.currentValue = val[this.routePath][this.valueKey]
}
},
deep: true
},
optionsUpdate: {
handler(val){
if(this.options.remoteMethod) reutrn // 如果是远程搜素,不需要通过此方法来更新下拉框数据
this.optionsList = this.allOptions[this.options.optionsKey]
// 如果需要转换下拉框数据
if(this.options.transferOPtions){
const {label,value} = this.options.transferOPtions
const options = this.options.map(e=>({...e,{label:e[label],value:e[value]}}))
}
}
}
}
methods: {
remoteMethod(val,options){
if(!val) return
if(options.remoteMethods){
this.loading = true
// 让后端吧所有的下拉框接口统一,在接口成功返回后进行switch
options.remoteMethods({search:val,pageNum:1,pageSize:50}).then(res=>{
if(res.code === '200'){
switch(this.options.optionsKey === 'plant'){
// ...进行一些操作
}
}
})
}
},
onChange(val){
// 根据当前的valueKey进行一些操作,某些操作可以放在vuex里,比如说实时更新vuex里对应routepath的值,比如a筛选项依赖b筛选项, 只有选了c筛选项才能选d筛选项之类的逻辑,这个看不同的业务场景,可以持续的迭代
switch(this.valueKey){
case 'plant':
this.$store.commit('listSearch/plantChange')
this.$store.commit('listSearch/setListSearchRoutePathKey', {plant:val,other1:[],other2:[]})// 选择plant的时候清空other1和other2逻辑,
break
// ...
}
this.$emit('onChange', val)
}
}
###SearchInput的关键代码:
// 跟SearchSelect差不多,逻辑更简单,参考着写吧
###vuex的关键代码:
const currentState= {
listSearchMap: {},// 所有列表筛选器的储存对象,动态添加routePath属性
currentPath: '',// 当前路由信息,每次新页面加载筛选器的时候先更新这个变量
originAllOptions: {},// 保留原始的下拉框对象,看每个人不同的业务场景
allOptions: {},// 组件要用到的下拉框数据
optionsUpdate: 0 // 更新下拉框数据的时候改变此数值,让筛选器里面的组件通过watch监听此变量,完成实时更新
}
const mutations ={
setListSearchMapPathKey(state,payload){
if(!state.listSearchMap[state.currentPath]){
// 以页面路由为key设置listSearchMap ,用object.assign方法,可以让listSearchMap 中的新的属性可以双向绑定,类似于组件中的$set()的作用
state.listSearchMap = Object.assign({},state.listSearchMap ,{[state.currentPath]:[]})
}
state.listSearchMap[state.currentPath] = Object.assign({},state.listSearchMap[state.currentPath] ,{...payload})
},
// 设置一些默认值,如当前筛选器的路由,筛选器的默认选项之类的
setdefaultSearch(state, payload){
const {routePath} = payload
state.currentPath = routePath
},
plantChange(state, payload){
// SearchSelect组件里面的change事件,这里根据不同业务场景不同写法
// 如果涉及到了筛选器下拉框数据变更,就加下方这段代码
// state.optionsUpdate++
}
}
const actions= {
getSearchOptions({state},payload){
for(let i = 0;i<payload.length;i++){
switch(payload[i]){
case 'plant'{
//比如plant需要获取下拉框数据,就在这里执行代码
}
}
}
// 等所有的下拉框数据都获取到后进行更新
state.optionsUpdate++
}
}
export default{
namespaced:true,
state:currentState,
mutations,
actions
}