vue开发个性化select,带分页以及搜索功能的组件

前提:必须先安装了element-ui

自定义开发带分页以及搜索框的select

 

 

用法

select.vue文件

<template>
  <div v-clickoutside="()=>visible=false" class="com-select" :class="{'is-disabeled' : disabled}">
    <div ref="reference" class="com-select-input" @click="clickSelectInput">
      <div class="one"></div>
      <!-- tags展示 -->
      <div v-if="value.length>0" class="two">
        <!-- <template v-if="isCheckAll">
          <el-tag
            size="small"
            closable
            hit
            type="info"
            disable-transitions
            @close="cancelCheckAll()">
            <span class="el-select__tags-text">全选</span>
          </el-tag>
        </template>
        <template v-else> -->
        <el-tag
          v-for="item in selectedArr"
          :key="item[listKey]"
          size="small"
          closable
          hit
          type="info"
          disable-transitions
          @close="deleteTag(item)">
          <span class="el-select__tags-text">{{ item[listVal] }}</span>
        </el-tag>
        <!-- </template> -->
      </div>
      <div v-else class="two" style="color: #c4c9db;">请选择</div>
      <div
        class="three"
        @mouseenter.stop="inputHovering = true"
        @mouseleave.stop="inputHovering = false"
      >
        <i v-if="showClose" class="el-select__caret el-input__icon el-icon-circle-close" @click.stop="cancelCheckAll"></i>
        <i v-else :class="`el-icon-arrow-${visible ? 'up' : 'down'} el-select__caret el-input__icon`"></i>
      </div>
    </div>
    <transition name="el-zoom-in-top">
      <!-- 下拉框 -->
      <the-select-menu
        v-show="visible"
        ref="popper"
        append-to-body>
        <!-- 搜索框 -->
        <el-input v-model="queryVal" placeholder="请输入" size="small" clearable @input="handleSearch" @keyup.enter.native="handleSearch">
          <el-button slot="append" icon="el-icon-search" @click="handleSearch"></el-button>
        </el-input>
        <!-- select选项 -->
        <div class="com-select-body">
          <div
            v-for="(item, i) in list"
            v-show="isShowFilterArr ? item._isShow : item.isShow"
            :key="item[listKey]"
            :title="item[listVal]"
            :style="formatColumn()"
            :class="['com-option', {'selected': item.selected,}]"
            @click="selectOptionClick(item, i)"
          >
            {{ item[listVal] }}
          </div>
          <div v-if="list.length === 0" class="com-select-nodata">
            <img :src="require('@/assets/image/nodatatable.png')" />
            <div>暂无数据</div>
          </div>
          <template v-if="nextPageStaus">
            <i v-for="v in 3" :key="'AA' + v" :style="formatColumn()"></i>
          </template>
        </div>
        <!-- 分页 -->
        <div class="com-footer-page">
          <el-button size="small" :disabled="prePageStaus" @click="prePage">上一页</el-button>
          <el-button type="primary" size="small" @click="checkAll">全选</el-button>
          <el-button size="small" :disabled="nextPageStaus" @click="nextPage">下一页</el-button>
        </div>
      </the-select-menu>
    </transition>
  </div>
</template>

<script>
import TheSelectMenu from './select-dropdown.vue';
import Clickoutside from 'element-ui/src/utils/clickoutside';
import { valueEquals } from 'element-ui/src/utils/util';
export default {
  name: 'SelectPro',
  components: { TheSelectMenu },
  directives: { Clickoutside },
  props: {
    value: { // 默认选中的值
      type: Array,
      default: () => [],
      required: true
    },
    options: { // 传进来的选项数组
      type: Array,
      default: () => []
    },
    props: {
      type: Object,
      default: () => ({ label: 'label', value: 'value' })
    },
    column: { // 展示多少列 暂时支持1-4列
      type: Number,
      default: 3
    },
    disabled: { // 置灰
      type: Boolean,
      default: false
    },
    selectAll: { // 初始化是否全选
      type: Boolean,
      default: false
    }
  },
  data() {
    return {
      optionArr: this.initOptions(),
      inputHovering: false,
      selectedArr: [],
      filterArr: [],
      queryVal: '',
      visible: false,
      pageSize: this.column * 4,
      currentPage: 1,
      filterCurrentPage: 1
    };
  },
  computed: {
    list() {
      return this.isShowFilterArr ? this.filterArr : this.optionArr;
    },
    listKey() {
      return this.props['value'] || 'value';
    },
    listVal() {
      return this.props['label'] || 'label';
    },
    isCheckAll() {
      return this.selectedArr.length === this.optionArr.length;
    },
    isShowFilterArr() {
      return this.filterArr.length > 0 || this.queryVal !== '';
    },
    showClose() {
      return this.selectedArr.length > 0 && this.inputHovering && !this.disabled;
    },
    prePageStaus() {
      if (this.isShowFilterArr ? this.filterCurrentPage === 1 : this.currentPage === 1) {
        return true;
      }
      return false;
    },
    nextPageStaus() {
      const len = Math.ceil(this.list.length / this.pageSize);
      if (len === 0) return true;
      if (this.isShowFilterArr ? this.filterCurrentPage === len : this.currentPage === len) {
        return true;
      }
      return false;
    }
  },
  watch: {
    selectedArr(val) {
      const arr = val.map(item => {
        return item[this.listKey];
      });
      this.$emit('input', arr);
    },
    value(val, oldVal) {
      if (!valueEquals(val, oldVal)) {
        this.selectedArr = [];
        this.optionArr.forEach(item => {
          if (this.selectAll || val.includes(item[this.listKey])) {
            item.selected = true;
            this.selectedArr.push(item);
          } else {
            item.selected = false;
          }
        });
      }
    },
    options() {
      this.optionArr = this.initOptions();
      // 重置默认选中
      this.selectedArr = [];
      this.optionArr.forEach(item => {
        if (this.selectAll || this.value.includes(item[this.listKey])) {
          this.selectedArr.push(item);
        }
      });
      // 重置分页
      this.currentPage = 1;
      this.filterCurrentPage = 1;
    }
  },
  mounted() {
    // 默认选中
    this.optionArr.forEach(item => {
      if (this.selectAll || this.value.includes(item[this.listKey])) {
        this.selectedArr.push(item);
      }
    });
  },
  methods: {
    initOptions() { // 初始化选项数组
      return this.options.map((item, i) => {
        return {
          isShow: i < this.column * 4,
          _isShow: false,
          selected: this.selectAll || this.value.includes(item[this.props['value'] || 'value']),
          ...item
        };
      });
    },
    deleteTag(item) { // 删除当前tag
      if (this.disabled) return;
      this.selectedArr.some((v, i) => {
        if (v[this.listKey] === item[this.listKey]) {
          item.selected = false;
          this.selectedArr.splice(i, 1);
          return true;
        }
      });
    },
    handleSearch() { // 搜索过滤
      if (this.queryVal === '') { // 输入框清空时
        this.filterArr = [];
        return;
      }
      console.log(this.queryVal);
      // 搜索前清除之前搜索的
      this.filterArr = [];
      // 重置搜索数组分页
      this.filterCurrentPage = 1;
      let num = 0;
      this.optionArr.forEach(item => {
        if (item[this.listVal].indexOf(this.queryVal) !== -1) { // 如果like 输入值
          item._isShow = num < this.pageSize; // 只展示一页的结果
          this.filterArr.push(item);
          num++;
        }
      });
    },
    clickSelectInput() { // 点击输入框
      if (this.disabled) return;
      this.visible = !this.visible;
    },
    selectOptionClick(item, index) { // 单击check
      item.selected = !item.selected;
      // 如果已经选过
      if (this.selectedArr.some(v => v[this.listKey] === item[this.listKey])) {
        this.deleteTag(item);
      } else {
        this.selectedArr.push(item);
      }
    },
    checkAll() { // 全选
      this.selectedArr = [];
      this.optionArr.forEach(item => {
        item.selected = true;
        this.selectedArr.push(item);
      });
    },
    cancelCheckAll() { // 取消全选
      if (this.disabled) return;
      this.optionArr.forEach(item => {
        item.selected = false;
      });
      this.selectedArr = [];
    },
    prePage() { // 上一页
      if (this.isShowFilterArr) { // 搜索展示时
        this.changeFilterPageShow(false);
        this.filterCurrentPage--;
        this.changeFilterPageShow(true);
      } else {
        this.changePageShow(false);
        this.currentPage--;
        this.changePageShow(true);
      }
    },
    nextPage() { // 下一页
      if (this.isShowFilterArr) { // 搜索展示时
        this.changeFilterPageShow(false);
        this.filterCurrentPage++;
        this.changeFilterPageShow(true);
      } else {
        this.changePageShow(false);
        this.currentPage++;
        this.changePageShow(true);
      }
    },
    changePageShow(isOrNo) { // 改变当前页状态
      const num = (this.currentPage - 1) * this.pageSize;
      const num2 = num + this.pageSize;
      for (let index = num; index < num2; index++) {
        if (this.optionArr[index]) this.optionArr[index].isShow = isOrNo;
      }
    },
    changeFilterPageShow(isOrNo) { // 改变搜索当前页状态
      const num = (this.filterCurrentPage - 1) * this.pageSize;
      const num2 = num + this.pageSize;
      for (let index = num; index < num2; index++) {
        if (this.filterArr[index]) this.filterArr[index]._isShow = isOrNo;
      }
    },
    formatColumn() {
      // 展示多少列
      if (this.column === 1) {
        return { width: '100%' };
      } else if (this.column === 2) {
        return { width: '49%' };
      } else if (this.column === 3) {
        return { width: '32%' };
      } else {
        return { width: '24%' };
      }
    }
  }
};
</script>
<style lang="less" scoped>
.com-select {
  border-radius: 4px;
  border: 1px solid #DCDFE6;
  box-sizing: border-box;
  cursor: pointer;
  background: #ffffff;
  min-width: 230px;
  min-height: 32px;
  .com-select-input {
    display: flex;
    align-items: center;
    .one {
      width: 10px;
    }
    .two {
      flex: 1;
      line-height: 29px;
      padding: 0 0 1px 0;
      /deep/ .el-tag {
        background-color: #f4f4f5;
        border-color: #e9e9eb;
        color: #909399;
        margin-right: 6px;
      }
      /deep/ .el-tag__close.el-icon-close {
        background-color: #C0C4CC;
      }
    }
    .three {
      width: 30px;
    }
  }
}
.is-disabeled {
  cursor: not-allowed;
  background-color: #F5F7FA;
  border-color: #E4E7ED;
}
.el-select-dropdown {
  height: 280px;
  padding: 10px;
  border: 1px solid #E4E7ED;
  border-radius: 4px;
  box-shadow: 1px 2px 6px 0px #7d7d7d;
  box-sizing: border-box;
  cursor: default;
  .com-select-body {
    margin-top: 6px;
    height: 178px;
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-content: flex-start;
    .com-option {
      // width: 32%;
      white-space: nowrap;
      overflow: hidden;
      text-overflow: ellipsis;
      color: #797D82;
      // background-color: rgb(240, 248, 250);
      border: 1px solid #dcdfe6;
      height: 32px;
      line-height: 32px;
      box-sizing: border-box;
      position: relative;
      text-align: center;
      margin-bottom: 10px;
      cursor: pointer;
      &:hover {
        border-color: #409EFF;
        color: #409EFF;
      }
    }
    // 选中样式
    .selected {
      border-color: #409EFF;
      &::after{
        content: '';
        width: 15px;
        height: 14px;
        background: url("~@/assets/image/checked.png") no-repeat 0 0;
        position: absolute;
        right: 0;
        bottom: 0;
      }
    }
    .com-select-nodata {
      color: #C0C4CC;
      width: 100%;
      text-align: center;
    }
  }
  // 分页样式
  .com-footer-page {
    display: flex;
    justify-content: space-between;
  }
}
</style>

select-down.vue文件

<template>
  <div
    class="el-select-dropdown el-popper"
    :style="{ width: width }">
    <slot></slot>
  </div>
</template>

<script>
export default {
  name: 'MySelectDropdown',
  data() {
    return {
      width: ''
    };
  },
  mounted() {
    this.width = this.$parent.$el.clientWidth + 'px';
    window.addEventListener('resize', this.resizeWidth);
  },
  beforeDestroy() {
    window.removeEventListener('resize', this.resizeWidth);
  },
  methods: {
    resizeWidth() {
      this.width = this.$parent.$el.clientWidth + 'px';
    }
  }
};
</script>
posted @ 2021-07-11 13:51  七度丢失的梦  阅读(401)  评论(0编辑  收藏  举报