二次封装el-select组件

1,148 阅读1分钟

首先放上el-select的官网地址,对这块知识不是很清楚的可以先看下这里select选择器

之前项目有个需求是这样的:多选的情况下显示两个tag,其余的tag以数字形式展示,并且当鼠标悬浮在数字上时可以看到所有选中的值。

而选择器多选的效果只能显示成这样

image.png

那就只能封装一个新的组件来满足业务需求,话不多说,直接上代码:

<template>
  <div class="v-multiple-select">
    <el-select v-model="value" multiple placeholder="请选择" value-key="userId" :size="size" @change="handleChange">
      <el-option label="全选" value="all" @click.native="selectAll" v-if="isShowSelectAll"></el-option>
      <el-option v-for="item in options" :key="item[listValue]" :label="item[listLabel]" :value="item"></el-option>
      <template slot="prefix">
        <div class="max-tag">
          <el-tag v-for="tag in tags" :key="tag[listValue]" closable type="info" @close="removeTag(tag[listValue])" size="small">{{ tag[listLabel] }}</el-tag>
          <!-- <div class="popover" v-show="isPopover"> -->
          <div class="popover" v-if="isPopover">
            <el-popover placement="top-start" title="已选中" width="200" trigger="hover" :content="value.map(i => i[listLabel]).join(',') || '暂未选择'">
              <span class="tag-length" slot="reference">{{ '+' + tagsLength }}</span>
            </el-popover>
          </div>
        </div>
      </template>
    </el-select>
  </div>
</template>

<script>
import { isObjectValueEqual } from '@/utils/common'
export default {
  name: 'MutipleSelect',
  data() {
    return {
      // 真正的选中人员数组
      value: this.list ? this.list : [],
      // 限制展示数量的人员数组
      tags: [],
      tagsLength: null,
      // 判断是否显示弹出框
      isPopover: false
    }
  },
  props: {
    options: {
      // select的下拉数据
      type: Array,
      default: () => []
    },
    list: {
      // 选中数据
      type: Array,
      default: () => []
    },
    listValue: {
      // 数据值的key字段,默认value
      type: String,
      default: 'value'
    },
    listLabel: {
      // 数据显示文本的key字段,默认label
      type: String,
      default: 'label'
    },
    maxTag: {
      // 文本框显示最大tag数
      type: String,
      default: '3'
    },
    size: {
      // select的size
      type: String,
      default: 'small'
    },
    isShowSelectAll: {
      // 是否有全选选项
      type: Boolean,
      default: false
    }
    // width: {
    //   // select的宽度
    //   type: String,
    //   default: '2rem'
    // }
  },
  watch: {
    list: {
      handler(newVal, oldVal) {
        if (!isObjectValueEqual(newVal, oldVal) && newVal) {
          this.value = newVal
          this.tags = newVal.length > parseInt(this.maxTag) ? newVal.slice(0, parseInt(this.maxTag)) : newVal
          this.tagsLength = newVal.length > parseInt(this.maxTag) ? newVal.length - parseInt(this.maxTag) : null
        }
        if (!newVal) {
          this.value = newVal
          this.tags = newVal
          this.tagsLength = null
        }
      },
      deep: true
      // immediate: true
    },
    tagsLength() {
      if (!this.tagsLength) this.isPopover = false
      else this.isPopover = true
    }
  },
  mounted() {
    if (this.list) {
      this.tags = this.list.length > parseInt(this.maxTag) ? this.list.slice(0, parseInt(this.maxTag)) : this.list
    } else this.tags = null
    this.tagsLength = this.value.length > parseInt(this.maxTag) ? this.value.length - parseInt(this.maxTag) : null
  },
  methods: {
    removeTag(v) {
      // this.value.splice(this.value.indexOf(v), 1)
      const t = []
      this.value.forEach(item => {
        if (item.userId !== v) t.push(item)
      })
      this.value = t
      this.handleChange(this.value)
    },
    handleChange(arr) {
      // if (arr.length < parseInt(this.maxTag) || arr.length < this.tags.length) this.tags = arr
      if (arr.length <= parseInt(this.maxTag) || arr.length <= this.tags.length) this.tags = arr
      if (arr.length < this.tags.length + parseInt(this.tagsLength)) {
        this.tags = arr.slice(0, arr.length - parseInt(this.tagsLength) + 1)
      }
      this.tagsLength = this.value.length > parseInt(this.maxTag) ? this.value.length - parseInt(this.maxTag) : null
      this.$emit('update:list', arr)
    },
    selectAll() {
      if (this.value.length === this.options.length) {
        this.value = []
        this.tags = []
        this.tagsLength = null
      } else {
        const arr = this.options.slice(1).filter(item => {
          return this.value.every(item1 => {
            return item.userId !== item1.userId
          })
        })
        this.value = this.value.concat(arr)
        this.tags = this.value.slice(0, parseInt(this.maxTag))
        this.tagsLength = this.value.length - parseInt(this.maxTag)
      }
      this.$emit('update:list', this.value)
    }
  }
}
</script>
<style lang="scss" scoped>
.v-multiple-select {
  position: relative;
  display: flex;
  width: 100%;
}
/* .v-multiple-select .el-select {
  overflow: hidden;
} */
/* .v-multiple-select .el-select__tags {
  display: none;
} */
.popover {
  // width: 10%;
  display: contents;
  align-items: center;
  justify-content: center;
}
.v-multiple-select .max-tag {
  width: 100%;
  height: 100%;
  /* overflow: hidden; */
}
/* .v-multiple-select .max-tag .el-tag.el-tag--info .el-tag__close {
  background-color: #c0c4cc;
} */
.v-multiple-select .el-input--prefix .el-input__inner {
  padding-left: 0.15rem;
}
.tag-length {
  display: inline-block;
  color: #ec7259;
  margin-left: 0.02rem;
}
/deep/ .el-select__tags {
  display: none;
}
/deep/ .el-select .el-tag {
  display: initial;
  font-size: 0.13rem;
  overflow: hidden;
  text-overflow: ellipsis;
}
/deep/ .el-select .el-tag .el-icon-close {
  top: -0.02rem;
}
</style>

props里的一些变量都是需要父组件传入的,在使用这个组件的时候需要斟酌一下应该怎么传,css我这样写也是因为项目配置了一些默认的样式,我需要覆盖掉原本的样式,所以样式这块可能也要按需调整一下。注释也有解释设置变量的目的,所以应该还是比较好懂的吧,就不再多解释了233

如果有疑惑或者觉着这个组件有可以优化的地方,欢迎留言讨论~祝大家秃头愉快