vue2多条件筛选,配合el-tooltip实现二级分类
vue2完成多条件筛选功能,配合elementui中的el-tooltip,实现一级或二级分类筛选功能,同时增加头部分类显示,可删除或清空。超过一行高度的分类项进行隐藏,通过按钮实现展开和收起功能,效果如图所示。
具体效果如下图所示,初始参考了这篇文章[vue] 实现多条件筛选_lyt-top的博客-CSDN博客_vue多条件筛选组件
但后续需求更改,需要增加头部分类显示,可删除或清空,同时增加二级分类筛选。同时展开和收起在上述文章中是通过每个分类超过多少项进行显示“更多”按钮的,但每一项文字长度不同,这种方式不适合,所以在下文对“更多”按钮的显示也进行了修改。
对代码进行了简单的整理,因为前前后后改的太多了,不一定是最方便的方法。
注意:本组件暂不支持只有一级分类和存在二级分类的类别混合使用,比如下图这样的,就需要修改所选项的存储方式了,这是因为一开始的时候没有考虑到这个问题,所以后期可能会重新写一个更完整的筛选组件。
1、参数传递
向组件传递参数,分类以数组的形式进行传递,传递前将数组处理成以下格式,只要包含dictLabel即可,因为在组件中识别的是这个字段,组件内修改了字段,传递参数时也要修改。
modelArray:[
{id: 1, dictLabel: '劳模创新工作室1'},
{id: 2, dictLabel: '劳模创新工作室2'},
{id: 3, dictLabel: '劳模创新工作室3'},
{id: 4, dictLabel: '劳模创新工作室4'},
{id: 5, dictLabel: '劳模创新工作室5'},
{id: 6, dictLabel: '劳模创新工作室6'},
{id: 7, dictLabel: '劳模创新工作室7'},
{id: 8, dictLabel: '劳模创新工作室8'},
]
二级分类参数传递,使用的是children字段
classifyArray:[
{
dictLabel:'安全类',
children:[
{dictLabel: '1子类1'},
{dictLabel: '1子类2'},
{dictLabel: '1子类3'}]
},{
dictLabel:'生产类',
children:[
{dictLabel: '2子类1'},
{dictLabel: '2子类2'},
{dictLabel: '2子类3'},
{dictLabel: '2子类4'}]
},{
dictLabel:'营销类',
children:[
{dictLabel: '3子类1'},
{dictLabel: '3子类2'}]
},{
dictLabel:'土建类',
children:[
{dictLabel: '4子类1'},
{dictLabel: '4子类2'},
{dictLabel: '4子类3'}]
},{
dictLabel:'物资类',
children:[
{dictLabel: '5子类1'},
{dictLabel: '5子类2'}]
},{
dictLabel:'信息类',
children:[
{dictLabel: '6子类1'}]
}
]
组件中参数接收
props: {
getList: {
type: Array,
default: () => []
}
},
2、代码
HTML部分
配合elementui中的el-tooltip实现二级分类的选择,将只有一级分类和存在二级分类的情况分开讨论。
<template>
<div class="demo">
<!-- 头部显示所选分类,可清空或删除 -->
<div style="padding-bottom: 8px;">
<div class="header">
<div class="item type"><i class="el-icon-delete delete" @click="tabClickAll(null)"></i>所有分类 ></div>
<div class="item select" :key="item.dictLabel" v-for="(item) in selectTab">
<div>{{ item.title + item.dictLabel }}<i class="el-icon-close close" @click="tabClick(null,item.typeIndex,item.valIndex,item.valIndex2)"></i></div>
</div>
</div>
</div>
<div class="demo-warp">
<div class="demo-flex" v-for="(item,typeIndex) in filterList" :key="typeIndex">
<span class="demo-title">{{ item.title }}</span>
<div class="demo-content">
<!-- <div class="demo-tab"> -->
<!--数据多时只显示一行-->
<div class="demo-tab" ref="content">
<span
:class="{'demo-active': isAll(typeIndex)}"
class="tab-item"
@click="tabClickAll(typeIndex)">全部</span>
<template v-for="(val, valIndex) in item.children">
<!-- 存在子类 -->
<template v-if="val.children">
<el-popover
placement="bottom"
width="400"
trigger="hover"
:key="valIndex">
<!--一级分类-->
<template slot="reference">
<span
:class="{'demo-active': isActive(typeIndex,valIndex)}"
class="tab-item spanClass">
{{ val.dictLabel }}
<span class="el-icon-arrow-down"></span>
</span>
</template>
<!--二级分类-->
<span
v-for="(val2, valIndex2) in val.children" :key="valIndex2"
:class="{'demo-active': isActive(typeIndex,valIndex,valIndex2)}" class="tab-item"
style="display:inline-block;margin: 0 5px 15px 5px;cursor: pointer;padding: 5px 10px;color: #999;"
@click="tabClick(val2,typeIndex,valIndex,valIndex2)"
>{{val2.dictLabel}}</span>
</el-popover>
</template>
<!-- 不存在子类 -->
<template v-else>
<span
:class="{'demo-active': isActive(typeIndex,valIndex)}"
class="tab-item spanClass"
@click="tabClick(val,typeIndex,valIndex)"
:key="valIndex"
>{{ val.dictLabel }}</span>
</template>
</template>
</div>
</div>
<div ref="more" class="demo-more" @click="changeShow(typeIndex)">更多</div>
</div>
</div>
</div>
</template>
JS部分
export default {
name:'index',
props: {
getList: {
type: Array,
default: () => []
}
},
data() {
return {
isShow: false,
selected: {},
displayTab:false
}
},
computed: {
//这里对传入的数据做了处理,初始化了超过一行的都进行隐藏
filterList(){
let list = [...this.getList]
list.map(item=>{
this.$set(item,'isShow',false)
})
return list
},
//头部已选项
selectTab() {
//格式化需要在头部显示和操作的数据
const allInfo = []
//遍历已选数组
for (const typeIndex in this.selected) {
//(父)所有分类
const childs = this.getList[typeIndex].children;
//分类标题
const title = this.getList[typeIndex].title;
const val = this.selected[typeIndex]
//遍历选中的父类,valChildren:存储的子类/无子类的分类索引值 valIndex:父类索引值
val.forEach((valChildren, valIndex)=>{
//存在子类
if(valChildren instanceof Array){
//遍历选中的子类(存储的是子类的索引值)
valChildren.forEach(valIndex2 => {
//找到子类在原数组中对应的对象 如:{dictLabel: '子类1'},可以直接扩展
const val2 = childs[valIndex].children[valIndex2]
allInfo.push({
title, //分类标题
typeIndex, //当前分类索引值
valIndex, //父类索引值
...val2, //子类名
valIndex2 //子类索引值
});
})
}else{
//不存在子类
const val2 = childs[valChildren]
allInfo.push({
title, //分类标题
typeIndex,//当前分类索引值
valIndex:valChildren, //无子类的分类索引值
...val2,//父类名
})
}
})
}
return allInfo
},
},
mounted(){
this.$refs.content.map((item,index) =>{
//这里一行高度是46
if(item.clientHeight > 46){
//超过高度显示“更多”,隐藏超出部分
this.$refs.more[index].style.visibility = 'visible'
this.$refs.content[index].classList.add('demo-hide')
}
})
},
methods: {
//清空全部或当前分类全部选择,即显示全部或显示当前分类全部
tabClickAll(typeIndex) {
if (typeIndex === null){
//清空全部选择,即显示全部
this.selected = {}
}else{
//清空当前分类,即显示当前分类全部
this.selected[typeIndex] = []
}
this.selected = Object.assign({}, this.selected)
this.$emit('get-sel-data', this.selected)
this.$forceUpdate()
},
//data 选中的数据 typeIndex 第几条筛选条件 valIndex 父类 valIndex2子类
tabClick(data, typeIndex, valIndex, valIndex2) {
//初始化该类选择为空数组
if (!this.selected[typeIndex]) {
this.selected[typeIndex] = []
}
//不存在子类
if(valIndex2 === undefined){
//是否已选中该项
const idx = this.selected[typeIndex].indexOf(valIndex)
if (idx === -1){
//未选中,将选中值的索引值存入
this.selected[typeIndex].push(valIndex)
}else{
//已选中,再点击,将选中值的索引值删除
this.selected[typeIndex].splice(idx, 1)
}
}else{
//存在子类(不需要存储父类,所以不需要进行上述操作)
//初始化该子类选择为空数组
if (!this.selected[typeIndex][valIndex]){
this.selected[typeIndex][valIndex] = []
}
//是否已选中该项
const idx2 = this.selected[typeIndex][valIndex].indexOf(valIndex2)
if (idx2 === -1){
//未选中,将选中值的索引值存入
this.selected[typeIndex][valIndex].push(valIndex2)
} else {
//已选中,再点击,将选中值的索引值删除
this.selected[typeIndex][valIndex].splice(idx2, 1)
}
}
this.selected = Object.assign({}, this.selected)
this.$emit('get-sel-data', this.selected)
this.$forceUpdate()
},
//高亮显示
isActive(typeIndex, valIndex, valIndex2) {
//不存在子类、存在子类的父类
//两种情况使用或者判断,不存在子类:当前分类存在索引值;存在子类的父类:父类的数组长度不为0
if (valIndex2 === undefined){
//选中再清空 存在数组 长度为0不能高亮
return this.selected[typeIndex] &&
(
(this.selected[typeIndex][valIndex] && this.selected[typeIndex][valIndex].length) ||
this.selected[typeIndex].includes(valIndex)
)
}
//父类的子类:包含子类索引值
return this.selected[typeIndex] &&
this.selected[typeIndex][valIndex] &&
this.selected[typeIndex][valIndex].some(it => it == valIndex2);
},
isAll(typeIndex) {
//未选中过为空,选中再清除后会有记录,所以判断数组长度为0
//含有子项的要判断遍历父类是否为空,以及长度为0
return (this.selected[typeIndex] || []).length === 0 &&
(this.selected[typeIndex] || []).every(it=>(it || []).length === 0);
},
//显示和隐藏
changeShow(index){
this.filterList[index].isShow = !this.filterList[index].isShow
if(this.filterList[index].isShow){
this.$refs.content[index].classList.remove('demo-hide')
}else{
this.$refs.content[index].classList.add('demo-hide')
}
},
}
}
CSS部分
.demo {
height: auto !important;
.header {
display: flex;
.type {
padding: 0 5px 0 0;
.delete {
padding: 0 5px;
cursor: pointer;
}
&:hover {
.delete {
color:rgb(129, 169, 255);
font-weight: bold;
}
}
}
.select {
padding: 0 5px;
border: 1px solid silver;
font-size: 13px;
color: #999;
margin-right: 5px;
cursor: pointer;
user-select: none;
.close {
padding: 0 0 0 5px;
}
&:hover {
border-color: rgb(129, 169, 255);
.close {
color: rgb(129, 169, 255);
font-weight: bold;
}
}
}
}
.demo-warp {
border: #ccc 1px solid;
margin-bottom: 15px;
display: flex;
margin: auto;
height: 100%;
flex-direction: column;
padding: 15px 15px 5px 15px;
position: relative;
.demo-flex {
display: flex;
margin-bottom: 15px;
.demo-title {
flex-basis: 100px;
margin-top: 5px;
}
.demo-content {
display: flex;
flex: 1;
.demo-tab {
flex: 1;
margin-right: 15px;
min-height: 35px;
.tab-item {
display: inline-block;
margin: 0 5px 15px 5px;
cursor: pointer;
padding: 5px 10px;
color: #999;
}
}
}
.demo-more {
visibility: hidden;
margin-top: 5px;
cursor: pointer;
}
}
.demo-flex:last-of-type {
margin-bottom: 0;
}
}
}
.demo-active {
background-color: rgb(129, 169, 255);
color: white !important;
border-radius: 3px;
}
.demo-tab .tab-item:hover {
background-color:rgb(129, 169, 255);
color: white !important;
border-radius: 3px;
}
.demo-hide {
height: 35px;
overflow: hidden;
}
3、获取选中数据
通过emit方法传递数据给父组件,下方是在父组件中使用该筛选组件。
<FilterDemo
:getList="filterList"
@get-sel-data="getFilterSelData"
/>
在方法中测试一下获取的数据
getFilterSelData(data){
console.log("data",data)
}
选中这些数据后
产生的结果是
获取的数据依然是索引值,所以还要在传递参数给后端前还要对数据进行修改。
这里用此方法的原因是因为,条件的不同,传递到后端的参数形式不同,有的用id,有的用名称,所以使用了索引值。
最后还需要对原数据进行遍历,匹配到与选中的索引值相同的数据。
更多推荐
所有评论(0)