当前位置: > > > Vue.js - 自定义el-select教程(单行显示所有选中项、尾部显示数字标签)

Vue.js - 自定义el-select教程(单行显示所有选中项、尾部显示数字标签)

    Element UIel-select 下拉选择器支持多选,默认情况下如果选中项过多时,输入框高度会被撑开,选中项标签换行显示。

    如果我们不希望换行,则可用通过添加 collapse-tags="true",这样就会只显示一个选中标签,后面显示未显示的数量。

    但这样也有个问题,就是如果控件宽度比较大的话,右侧就会出现一大片空白区域,不够美观。下面介绍两种方式演示如何保证在单行状态下尽可能多地显示选中标签。

一、通过修改默认 CSS 样式

1,效果图

单行显示所选内容,超出部分直接截断不显示。

2,样例代码

<template>
  <div>    
    <div class="demo">
      <el-select v-model="values" multiple style="width: 255px;" class="my-el-select">
        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
        </el-option>
    </el-select>
    </div>
  </div>
</template>
<script>
  export default {
    data: () => ({
      options: [
        {value: '选项1',label: '黄金糕'}, 
        {value: '选项2', label: '双皮奶'}, 
        {value: '选项3', label: '蚵仔'},
        {value: '选项4', label: '龙须面'}, 
        {value: '选项5', label: '北京烤鸭'}
      ],
      values: [],
    })
  }
</script>
<style scoped>
  /deep/ .my-el-select .el-select__tags {
    flex-wrap: inherit;
    overflow: hidden;
  }
</style>

二、自定义多选下拉框组件

1,效果图

单行显示尽量多的选中标签,如果无法完全显示,尾部显示一个数字标签 +n,表示多余的选中项数量。

2,样例代码

(1)首先我们自定义一个多选下拉选择器组件 MultipleSelect.vue,该自定义组件的功能是对 Element UIel-select 组件进行扩展,其内部逻辑如下:
  • 使用 MutationObserver 监听 el-select 标签区域的子节点变动(childList)。
  • 每次变动时,计算所有标签的总宽度,并与容器宽度进行对比。
  • 根据计算结果动态调整每个标签的显示或隐藏状态。
  • 如果累计宽度超过容器宽度,隐藏超出的标签,并在最后一个可见标签后显示数字标签。
  • 确保数字标签也在宽度限制内,如超出,再隐藏一个标签。
<template>
    <el-select v-model="values" multiple style="width:100%" placeholder="请选择" 
        @change="handleChange" class="my-el-select">
        <el-option v-for="item in options" :key="item.value" 
            :label="item.label" :value="item.value">
        </el-option>
    </el-select>
</template>
<script>
    var observer = null;
    export default {
        props: {
            options: {
                type: Array,
                default: () => []
            },
            value: {
                type: Array,
                default: () => []
            }
        },
        computed: {
            values: {
                get() {
                    return this.value;
                },
                set(val) {
                    this.$emit("input", val);
                }
            }
        },
        mounted() {
            let tagLIstDom = document.querySelector(".el-select__tags");
            //   需要加上组件自定义的类名,防止监听失效
            let tagSpanDom = document.querySelector(".my-el-select .el-select__tags > span");
            let hideDom = document.createElement("span");
            hideDom.classList = ["count-node"]; //设置样式
            tagSpanDom.append(hideDom); //插入到span中
            var config = { childList: true };
            // 当观察到突变时执行的回调函数
            var callback = function (mutationsList) {
                mutationsList.forEach(function (item, index) {
                    if (item.type == "childList") {
                        let tagList = item.target.childNodes;
                        let tagWidth = 0; //标签总宽度
                        let tagNum = 0; //标签多余个数
                        let avaliableTagWidth = 0 //显示标签的总宽度
                        let lastShowTagIndex = 0 //最后一个显示标签的索引
                        for (let i = 0; i < tagList.length; i++) {
                            const e = tagList[i];
                            if (tagWidth > tagLIstDom.offsetWidth) {
                                e.style.display = "none"; //隐藏多余标签
                            } else {
                                e.style.display = "inline-block"; //显示标签
                                lastShowTagIndex = i-1
                            }
                            // 数字标签先不计算宽度
                            if (e.className != 'count-node') {
                                tagWidth += e.offsetWidth + 5;
                            }
                            if (tagWidth > tagLIstDom.offsetWidth) {
                                e.style.display = "none"; //隐藏多余标签
                            } else {
                                e.style.display = "inline-block"; //显示标签
                                lastShowTagIndex = i-1
                            }
                            if (e.style.display != "none") {
                                tagNum++;
                                hideDom.style.display = "none"; //隐藏多余标签个数
                                const margin = tagNum === 1 ? 0 : 6
                                avaliableTagWidth += e.offsetWidth + margin
                            } else {
                                hideDom.style.display = "inline-block"; //显示多余标签个数
                                hideDom.style.left = `${avaliableTagWidth}px` //数字标签的位置设置
                                hideDom.innerHTML = `+${tagList.length - tagNum}`;//显示多余标签个数
                            }
                        }
                        // 全部处理完毕后,还需要再验证下
                        // 如果加上数字标签后,标签总宽度大于可显示宽度,还要隐藏最后一个标签
                        if (hideDom.style.display == "inline-block" && 
                                avaliableTagWidth + hideDom.offsetWidth > tagLIstDom.offsetWidth) {
                            //数字标签的位置设置
                            avaliableTagWidth -= (tagList[lastShowTagIndex].offsetWidth + 6) 
                            hideDom.style.left = `${avaliableTagWidth}px`
                            hideDom.innerHTML = `+${tagList.length - tagNum + 1}`;//显示多余标签个数
                            tagList[lastShowTagIndex].style.display = "none"; //隐藏最后一个标签
                        }
                    }
                });
            };

            // 创建一个链接到回调函数的观察者实例
            observer = new MutationObserver(callback);

            // 开始观察已配置突变的目标节点
            observer.observe(tagSpanDom, config);

            // 随后,您还可以停止观察
            // observer.disconnect();
        },
        methods: {
            handleChange() {
                this.$emit("change", this.value);
            }
        },
        computed: {
            values: {
                get() {
                    return this.value;
                },
                set(val) {
                    this.$emit("input", val);
                }
            }
        },
        //销毁时
        beforeDestroy() {
            // 停止观察
            observer.disconnect();
        }
    }
</script>
<style>
.count-node {
    position: absolute;
    top: 2px;
    display: none;
    height: 24px;
    padding: 0 8px;
    line-height: 22px;
    margin-left: 6px;
    background-color: #f4f4f5;
    border: 1px solid #e9e9eb;
    border-radius: 4px;
    color: #909399;
    font-size: 12px;
    box-sizing: border-box;
}
</style>

(2)然后我们直接使用这个自定义组件即可:
<template>
  <div>    
    <div class="demo">
      <multiple-select :options="options" v-model="values" style="width: 255px;"></multiple-select>
    </div>
  </div>
</template>
<script>
  import MultipleSelect from './MultipleSelect.vue';
  export default {
    components: {
      MultipleSelect
    },
    data: () => ({
      options: [
        {value: '选项1',label: '黄金糕'}, 
        {value: '选项2', label: '双皮奶'}, 
        {value: '选项3', label: '蚵仔'},
        {value: '选项4', label: '龙须面'}, 
        {value: '选项5', label: '北京烤鸭'}
      ],
      values: [],
    })
  }
</script>
评论0