具体效果如下图所示,初始参考了这篇文章[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,有的用名称,所以使用了索引值。

最后还需要对原数据进行遍历,匹配到与选中的索引值相同的数据。

Logo

前往低代码交流专区

更多推荐