vue实现分类列表查询-搜索列表-组件封装

 实现动态“展开”,取值自定义。



 

封装组件

 

<template>
  <div class='w100'>
    <div class="w100 br-2 search-bar-comp-box show-flex-box-c" :class="{'is-expanded': isExpand}">
      <dt class="collapse simple-search-bar show-flex-box-r w100 p-20 pb-0 normal-bg">
        <ul class="show-flex-box-c w100">
          <li
            class="show-flex-box-r"
            :class="`${isExpand ? 'mb-8' : 'mb-4'} ${item.type == 'timePicker' ? 'mb-24' : ''}`"
            v-for="(item, index) in searchList"
            :key="index"
          >
            <span class="title mr-16 white-space" :class="{'time-picker-title': item.type == 'timePicker'}">{{item.title}}</span>

            <ol class="search-option-list show-flex-box-r" v-if="item.type != 'timePicker'">
              <li
                :class="{'selected': checkIsSelectedAll(item.options)}"
                @click="!checkIsSelectedAll(item.options) && chooseAllType(item, index)"
              >
                <strong>全部</strong>
              </li>
              <li
                :class="{'selected': subItem.selected}"
                v-for="(subItem, subIndex) in item.options"
                :key="subIndex"
                @click="chooseThisItem(index, subIndex, item, subItem)"
              >
                <strong>{{subItem.name}}</strong>
              </li>
            </ol>

            <div v-if="item.type == 'timePicker'" class="fs-0 time-picker-bar">
              <el-date-picker
                style="width: 200px;"
                type="date"
                placeholder="请选择预警日期"
                v-model="choosedTime"
                :editable="false"
                size="small"
                clearable
                format="yyyy-MM-dd"
                value-format="timestamp"
                @change="changeChoosedTime"
              ></el-date-picker>
            </div>

            <div class="handle-bar show-flex-box-r" v-if="item.type != 'timePicker'">
              <div
                v-if="item.showBtn"
                class="expand-btn-bar"
                :class="{ 'mr-20': index == 0 && !isExpand }"
              >
                <span v-if="item.isExpand" @click="changeClasslyItemExpandStatus(false, index)">收起</span>
                <span v-else @click="changeClasslyItemExpandStatus(true, index)">展开</span>
              </div>

              <div class="search-btn-bar" v-if="index == 0 && !isExpand">
                <div class="search" @click="clickBtnBar('search')">查 询</div>
                <div class="reset" @click="clickBtnBar('reset')">重 置</div>
              </div>
            </div>
          </li>
        </ul>

        <div class="search-btn-bar mb-20" v-show="isExpand">
          <div class="search" @click="clickBtnBar('search')">查 询</div>
          <div class="reset" @click="clickBtnBar('reset')">重 置</div>
        </div>

        <div class="expand-btn" @click="changeExpandStatus()">
          <img :src="expandIconUrl" >
        </div>
      </dt>

      <dd
        v-if="selectedList.length > 0"
        class="border-box w100 selected-item-list show-flex-box-r mt-24 br-2 normal-bg pt-20 pl-20"
      >
        <strong class="fs-0 white-space">已选择项</strong>
        <ul class="show-flex-box-r">
          <li
            class="show-flex-box-r mr-20 mb-20"
            v-for="(item, index) in selectedList"
            :key="index"
          >
            <strong class="mr-6 white-space">{{item.name}}</strong>
            <em class="fs-0 cursor-pointer" @click="removeChoosedTag(item, index)"></em>
          </li>
        </ul>
      </dd>
    </div>
  </div>
</template>

<script>
import $ from "jquery";
import '@/../static/js/jquery.actual.js';
const expandIconUrl = require('@/assets/images/cockpit-risk/expand-icon.png');
import CommonUtils from '@/utils/commonUtils';

export default {
  props: {
    showList: {
      type: Array,
      default: [],
    },
  },
  data() {
    return {
      expandIconUrl,

      isExpand: false,
      searchList: [],
      selectedList: [],
      choosedTime: '',

      classlyItemHeightList: [],

    };
  },
  computed: {
    sidebar() {
      return this.$store.state.app.sidebar;
    },
  },
  watch: {
    showList(){
      this.setShowSearchList();
    },
    sidebar: {
      handler(newName, oldName) {
        this.windowResizeToResetCheck();
      },
      deep: true
    }
  },
  mounted() {
    window.onresize = () => {
      this.windowResizeToResetCheck();
    };

    this.setShowSearchList();
  },
  methods: {
    windowResizeToResetCheck(){
      setTimeout(() => {
        this.resetCheck();
      }, 300);
    },
    resetCheck(){
      $(`.simple-search-bar > ul > li`).each(function(i){
        let olDom = `.simple-search-bar > ul > li:eq(${i}) > .search-option-list`;
        $(olDom).css('max-height', 'unset');
        $(olDom).css('overflow-y', 'unset');
      });

      this.setCheckItemHeightIsOver();
    },
    setShowSearchList(notCheck){
      let circulationList = [];

      if (notCheck) {
        circulationList = this.searchList;
      } else {
        circulationList = this.showList;
      }

      let list = circulationList.map(item => {
        let options = item.options || [];

        let newOptions = options.map(subItem => {
          return Object.assign(subItem, { selected: false });
        });

        const obj = {
          ...item,
          showBtn: item.showBtn || false,
          isExpand: item.isExpand || false,
        };

        obj.options = newOptions;

        return obj;
      });

      this.searchList = list;

      if (!notCheck) {
        this.setCheckItemHeightIsOver();
      }
    },
    // 检查是否要显示'展开'或'收起'按钮栏
    setCheckItemHeightIsOver(){
      let list = JSON.parse(JSON.stringify(this.searchList));
      const _this = this;
      _this.classlyItemHeightList = [];
      
      this.$nextTick(()=>{
        $(`.simple-search-bar > ul > li`).each(function(i){
          let classlyItemHeight = $(this).actual('height');

          _this.classlyItemHeightList.push(classlyItemHeight);

          if (classlyItemHeight > 40) {
            list[i].showBtn = true;
          } else {
            list[i].showBtn = false;
          }

          _this.changeClasslyItemExpandStatus(list[i].isExpand, i);

          // let olDom = `.simple-search-bar > ul > li:eq(${i}) > .search-option-list`;

          // if (list[i].isExpand) {
          //   $(olDom).css('max-height', '200px'); // unset
          //   $(olDom).css('overflow-y', 'unset');
          // } else {
          //   $(olDom).css('max-height', '40px');
          //   $(olDom).css('overflow-y', 'hidden');
          // }
        });

        this.searchList = list;
      });
    },
    // 展开和收起所有筛选栏
    changeExpandStatus(){
      $(`.simple-search-bar > ul > li`).each(function(i){
        let olDom = `.simple-search-bar > ul > li:eq(${i}) > .search-option-list`;
        $(olDom).css('max-height', 'unset');
        $(olDom).css('overflow-y', 'unset');
      });
      
      this.isExpand = !this.isExpand;
      this.setCheckItemHeightIsOver(true);
    },
    // 点击展开或者收起的按钮
    changeClasslyItemExpandStatus(isExpand, index){
      if (index == 0) {
        if (isExpand) {
          $('.simple-search-bar').css('max-height', this.classlyItemHeightList[0] + 24 + 'px');
        } else {
          $('.simple-search-bar').css('max-height', '64px');
        }
      }

      let curClickLiDom = `.simple-search-bar > ul > li:eq(${index}) > .search-option-list`;

      if (isExpand) {
        $(curClickLiDom).css('max-height', '800px'); // unset 200px
        $(curClickLiDom).css('overflow-y', 'unset');
      } else {
        $(curClickLiDom).css('max-height', '40px');
        $(curClickLiDom).css('overflow-y', 'hidden');
      }
      
      this.searchList[index].isExpand = isExpand;
    },
    // 是否是选中全部
    checkIsSelectedAll(options = [], returnList = false){
      let list = options || [];
      
      let result = list.filter(item => {
        return item.selected;
      });

      return returnList ? result : result.length == 0;
    },
    // 删除已选择项
    removeChoosedTag(tag, index){
      this.selectedList.splice(index, 1);
      
      let list = this.searchList;

      let firstIndex, secondIndex, optionItem;

      for (let i = 0; i < list.length; i++) {
        const item = list[i];
        if (tag.type == item.type) {
          firstIndex = i;
          const options = item.options || [];
          for (let j = 0; j < options.length; j++) {
            const subItem = options[j];
            if (subItem.id == tag.id) {
              secondIndex = j;
              optionItem = subItem;
              break;
            }
          }
          break;
        }
      }

      this.changeThisItemSelectedStatus(firstIndex, secondIndex, optionItem);
    },
    // 日期选择器
    changeChoosedTime(){},
    // 选择全部的这一分类选项
    chooseAllType(item, index){
      let list = item.options || [];
      let newOptions = list.map(item=>{
        return Object.assign(item, { selected: false });
      });

      this.searchList[index].options = newOptions;
      this.deleteThisTypeOptions(item.type);
    },
    // 选中某一个选项
    chooseThisItem(index, subIndex, item, subItem){
      const flag = !subItem.selected;
      this.changeThisItemSelectedStatus(index, subIndex, subItem);
      this.setSelectedItem(flag, item.type, subItem.id, subItem.name);
    },
    changeThisItemSelectedStatus(index, subIndex, subItem){
      const flag = !subItem.selected;
      this.searchList[index].options[subIndex].selected = flag;

      this.$forceUpdate();
    },
    setSelectedItem(isSelected, type, id, name){
      if (isSelected) {
        this.selectedList.push({
          type: type,
          id: id,
          name: name,
        });
      } else {
        let list = this.selectedList;

        let index = list.findIndex(item => {
          return item.type == type && item.id == id;
        });

        this.selectedList.splice(index, 1);
      }
    },
    // 删除该分类的选项
    deleteThisTypeOptions(type){
      this.selectedList = this.selectedList.filter(item=>{
        return item.type != type;
      })
    },
    // 查询 重置
    clickBtnBar(type){
      if (type == 'search') {
        this.toSearch();
      } else {
        this.toReset();
      }
    },
    // 查询
    toSearch(){
      let params = {};

      let list = this.searchList;
      
      list.forEach(item => {
        let options = item.options || [];
        let selectedOptions = this.checkIsSelectedAll(options, true); // 是否有选中改分类的项
        if (selectedOptions.length > 0) {
          let ids = CommonUtils.getIdsFromList(selectedOptions, item.field || 'id');
          params[item.type] = ids;
        }
      });

      if (this.choosedTime) {
        params.warningDate = this.choosedTime;
      }

      this.sendInfoOutside(params);
    },
    // 重置
    toReset(){
      this.setShowSearchList(true);
      this.resetCheck();
      this.choosedTime = '';
      this.selectedList = [];
      this.sendInfoOutside({});
    },
    sendInfoOutside(info = {}){
      this.$emit('handle-search', info);
    },


    
  },
}
</script>

<style lang='scss' scoped>
  .search-bar-comp-box{
    .simple-search-bar{
      position: relative;
      max-height: 64px;
      transition: all 0.3s linear;
      > ul{
        > li{
          // display: none;
          visibility: hidden;
          opacity: 0;
          transition: all 0.3s linear;
          > .time-picker-title{
            line-height: 32px;
            height: 32px;
          }
        }
        > li:first-child{
          // display: flex;
          visibility: visible;
          opacity: 1;
        }
      }
      .search-option-list{
        // width: 0;
        flex-grow: 1;
        flex-wrap: wrap;
        transition: all 0.3s linear;
        > li{
          display: flex;
          margin-bottom: 16px;
          margin-right: 24px;
          > strong{
            cursor: pointer;
            box-sizing: border-box;
            white-space: nowrap;
            border: 1px solid #C9CDD4;
            border-radius: 2px;
            padding: 3px 12px;
            font-family: 'PingFang SC';
            font-style: normal;
            font-weight: 500;
            font-size: 12px;
            line-height: 16px;
            color: #4E5969;
          }
        }
        > .selected{
          > strong{
            border: 1px solid transparent;
            background: #00A3E0;
            color: #FFFFFF;
          }
        }
      }
      .expand-btn{
        display: flex;
        align-items: center;
        justify-content: center;

        cursor: pointer;
        position: absolute;
        bottom: -18px;
        left: 50%;
        width: 100px;
        height: 18px;
        background: url('../../../assets/images/cockpit-risk/expand-bg-icon.png') center center no-repeat;
        background-size: 100% 100%;
        > img{
          width: 10px;
          height: 12px;
          transition: all ease-in-out 0.3s;
          transform: rotate(0deg);
          user-select:none;
        }
      }
    }
    .selected-item-list{
      overflow: hidden;
      > strong{
        width: 76px;
        font-family: 'PingFang SC';
        font-style: normal;
        font-weight: 400;
        font-size: 14px;
        line-height: 24px;
        color: #3E3E3E;
      }
      > ul{
        flex-wrap: wrap;
        margin-right: -20px;
        > li{
          padding: 4px 8px;
          align-items: center;
          background: rgba(0, 163, 224, 0.08);
          border-radius: 2px;
          > strong{
            font-family: 'PingFang SC';
            font-style: normal;
            font-weight: 600;
            font-size: 12px;
            line-height: 16px;
            color: #00A3E0;
          }
          > em{
            width: 12px;
            height: 12px;
            background: url('../../../assets/images/cockpit-risk/close-icon.png') center center no-repeat;
            background-size: 100% 100%;
          }
        }
      }
    }
    
  }

  .is-expanded{
    .simple-search-bar{
      flex-direction: column;
      max-height: 3000px !important;
      > ul{
        > li{
          // display: flex;
          visibility: visible;
          opacity: 1;
        }
      }
    }
    > .simple-search-bar{
      > .expand-btn{
        > img{
          transform: rotate(180deg);
        }
      }
    }
  }

  .title{
    // width: 115px;
    width: 130px;
    flex-shrink: 0;
    font-family: 'PingFang SC';
    font-style: normal;
    font-weight: 400;
    font-size: 14px;
    line-height: 24px;
    height: 24px;
    color: #3E3E3E;
  }

  .search-btn-bar{
    display: flex;
    > div{
      cursor: pointer;
      display: flex;
      flex-shrink: 0;
      justify-content: center;
      align-items: center;
      width: 78px;
      height: 24px;
      border-radius: 2px;

      font-family: 'PingFang SC';
      font-style: normal;
      font-weight: 400;
      font-size: 14px;
      line-height: 20px;
    }
    > .search{
      margin-right: 12px;
      background: #012169;
      color: #FFFFFF;
    }
    > .reset{
      background: #E5E6EB;
      color: #012169;
    }
  }

  .expand-btn-bar{
    cursor: pointer;
    white-space: nowrap;
    font-family: 'PingFang SC';
    font-style: normal;
    font-weight: 500;
    font-size: 12px;
    line-height: 24px;
    color: #00A3E0;
  }


  
  .time-picker-bar ::v-deep .el-input__prefix{
    left: unset;
    top: 9px;
    right: 9px;
    width: 14px;
    height: 14px;
    background: url('../../../assets/images/cockpit-risk/date-picker-icon.png') center center no-repeat;
    background-size: 100% 100%;
  }
  .time-picker-bar ::v-deep .el-input__suffix{
    right: 24px;
  }
  .time-picker-bar ::v-deep .el-icon-date:before{
    content: '';
  }
  .time-picker-bar ::v-deep .el-input__inner{
    border: 1px solid #E5E6EB;
    background-color: #F8F9FB;
    border-radius: 0px;
    padding-left: 12px;
    padding-right: 0px;
    font-family: 'PingFang SC';
    font-style: normal;
    font-weight: 400;
    font-size: 14px;
    line-height: 20px;
    letter-spacing: -0.01em;
    color: #86909C;
  }
</style>
 getIdsFromList(list, field = 'id') {
    let result = [];

    list.forEach(item => {
      result.push(item[field]);
    });

    return result;
  },

使用组件

<template>
  <div class='demo'>
    <search-bar :showList="searchList" @handle-search="handleSearch" />
  </div>
</template>

<script>
import searchBar from './components/search-bar-comp';
export default {
  components: {
    searchBar
  },
  data() {
    return {
      searchFieldList: [
        { field: 'industry', dataFixed: false, name: '行业分类', searchField: 'industry', valueField: 'code', },
        { field: 'area', dataFixed: false, name: '所属区域', searchField: 'area', valueField: 'code', },
        { field: 'conceptualBlock', dataFixed: false, name: '概念板块', searchField: 'concept', valueField: 'name', },
        { field: 'shortTermRiskLevel', dataFixed: false, name: '短期风险等级', searchField: 'shortTermRiskLevel', valueField: 'code', },
        { field: 'longTermRiskLevel', dataFixed: false, name: '长期风险等级', searchField: 'longTermRiskLevel', valueField: 'code', },

        { field: 'isTwoFinancialTargets', dataFixed: true, name: '是否交易所两融标的', searchField: 'isTwoFinancialTargets', options: [{ id: 1, name: '是', }, { id: 0, name: '否', },], valueField: 'id', },
        { field: 'marginSecurities', dataFixed: true, name: '保证金证券', searchField: 'marginSecurities', options: [{ id: 1, name: '是', }, { id: 0, name: '否', },], valueField: 'id', },

        { field: 'tradingMarket', dataFixed: false, name: '交易市场', searchField: 'tradingMarket', valueField: 'name', },

        { field: 'timePicker', dataFixed: true, name: '预警日期', searchField: 'timePicker', valueField: 'id', },
      ],
      searchList: [],
      data: { //后端给的数据
        "code": 0,
        "message": "成功",
        "data": {
          "industry": [
            {
              "id": 1,
              "type": 1,
              "name": "医药生物",
              "code": "2",
              "createdAt": 1676454312000,
              "updatedAt": 1676454312000
            },
          ],
          "area": [],
          "conceptualBlock": [],
          "shortTermRiskLevel": [],
          "longTermRiskLevel": [],
          "tradingMarket": []
        }
      }
    };
  },
  mounted() {
    this.configData();
  },
  methods: {
    configData() {
      this.reqFilterList();
    },
    // 列表页筛选项
    reqFilterList() {
      let list = [];
      for (let i = 0; i < this.searchFieldList.length; i++) {
        const item = this.searchFieldList[i];

        let obj = {
          title: item.name,
          type: item.searchField,
          field: item.valueField,
          asdasd: '123',
          options: [],
        };

        if (item.dataFixed) {
          obj.options = item.options || [];
        } else {
          obj.options = this.data[item.field] || [];
        }

        list.push(obj);
      }

      this.searchList = list;
    },
    handleSearch(params){
      this.filterForm = params;
      // 调接口

    },
  },
}
</script>

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值