ElementUI - select 全选组件

基于vue2 +element-ui

kotlin 复制代码
<template>
  <el-select
    v-model="internalSelectedValues"
    v-bind="$attrs"
    multiple
    @remove-tag="handleRemoveTag"
    @change="handleChange"
  >
    <el-option
      v-if="showSelectAll"
      :value="selectAllValue"
      :label="selectAllLabel"
      class="select-all-option"
    >
      <el-checkbox
        :value="isFullySelected"
        :indeterminate="isIndeterminate"
        @change="handleSelectAllChange"
        @click.native.stop
      >
        {{ selectAllLabel }}
      </el-checkbox>
    </el-option>
    <el-option
      v-for="item in options"
      :key="item[getValue()]"
      :label="item[getLabel()]"
      :value="item[getValue()]"
    >
      <el-checkbox
        :value="isItemSelected(item[getValue()])"
        @change="(val) => toggleItemSelection(item[getValue()], val)"
        @click.native.stop
      >
        {{ item[getLabel()] }}
      </el-checkbox>
    </el-option>
  </el-select>
</template>

<script>
export default {
  name: 'TeCascadeSelect',

  props: {
    options: {
      type: Array,
      required: true,
      default: () => []
    },
    showSelectAll: {
      type: Boolean,
      default: true
    },
    selectAllLabel: {
      type: String,
      default: '全选'
    },
    selectAllValue: {
      type: String,
      default: 'SELECT_ALL'
    },
    value: {
      type: Array,
      default: () => []
    },
    showAllTag: {
      type: Boolean,
      default: false
    }
  },

  data() {
    return {
      internalSelectedValues: [...this.value],
      debounceTimer: null
    }
  },

  computed: {
    allOptions() {
      return this.options.map((item) => item[this.getValue()])
    },
    isFullySelected() {
      if (this.allOptions.length === 0) return false
      // 修复:只检查实际选项,忽略全选标记
      const selectedCount = this.internalSelectedValues.filter(
        (v) => v !== this.selectAllValue && this.allOptions.includes(v)
      ).length
      return selectedCount === this.allOptions.length
    },

    isIndeterminate() {
      if (this.allOptions.length === 0) return false
      // 修复:只检查实际选项,忽略全选标记
      const selectedCount = this.internalSelectedValues.filter(
        (v) => v !== this.selectAllValue && this.allOptions.includes(v)
      ).length
      return selectedCount > 0 && selectedCount < this.allOptions.length
    }
  },

  watch: {
    value: {
      immediate: true,
      handler(newVal) {
        // 修复:深度比较并同步值
        if (
          JSON.stringify(newVal) !==
          JSON.stringify(
            this.internalSelectedValues.filter((v) => v !== this.selectAllValue)
          )
        ) {
          this.internalSelectedValues = [...newVal]
          // 如果全选了,添加全选标记
          if (
            newVal.length === this.allOptions.length &&
            this.allOptions.length > 0
          ) {
            this.echoAllTag(this.internalSelectedValues)
          }
        }
      }
    }
  },

  methods: {
    getValue() {
      return this.$attrs.valueKey ? this.$attrs.valueKey : 'value'
    },

    getLabel() {
      return this.$attrs.labelKey ? this.$attrs.labelKey : 'label'
    },
    isItemSelected(value) {
      return this.internalSelectedValues.includes(value)
    },

    toggleItemSelection(value, selected) {
      this.$nextTick(() => {
        let newValues = [...this.internalSelectedValues]

        if (selected) {
          if (!newValues.includes(value)) {
            newValues.push(value)
          }
        } else {
          newValues = newValues.filter((v) => v !== value)
        }

        // 处理全选标记
        const allSelected = this.allOptions.every((opt) =>
          newValues.includes(opt)
        )
        if (allSelected && !newValues.includes(this.selectAllValue)) {
          this.echoAllTag(newValues)
        } else if (!allSelected && newValues.includes(this.selectAllValue)) {
          newValues = newValues.filter((v) => v !== this.selectAllValue)
        }

        this.internalSelectedValues = [...new Set(newValues)]
        this.emitChange()
      })
    },
    // 是否回显全选标记
    echoAllTag(list) {
      if (!this.showAllTag) return
      list.push(this.selectAllValue)
    },
    handleSelectAllChange(selected) {
      this.$nextTick(() => {
        let newValues = [...this.internalSelectedValues]

        if (selected) {
          // 添加所有选项
          this.allOptions.forEach((opt) => {
            if (!newValues.includes(opt)) {
              newValues.push(opt)
            }
          })
          // 添加全选标记
          if (!newValues.includes(this.selectAllValue)) {
            this.echoAllTag(newValues)
          }
        } else {
          // 移除所有选项
          newValues = newValues.filter((v) => !this.allOptions.includes(v))
          // 确保移除全选标记
          newValues = newValues.filter((v) => v !== this.selectAllValue)
        }

        this.internalSelectedValues = [...new Set(newValues)]
        this.emitChange()
      })
    },

    handleRemoveTag(tag) {
      if (tag === this.selectAllValue) {
        this.internalSelectedValues = []
      } else {
        this.internalSelectedValues = this.internalSelectedValues.filter(
          (v) => v !== tag
        )
        // 如果移除了全选标记对应的最后一个选项
        if (
          this.isFullySelected &&
          this.internalSelectedValues.includes(this.selectAllValue)
        ) {
          this.internalSelectedValues = this.internalSelectedValues.filter(
            (v) => v !== this.selectAllValue
          )
        }
      }
      this.emitChange()
    },

    handleChange() {
      this.emitChange()
    },

    emitChange() {
      const valuesToEmit = this.internalSelectedValues.filter(
        (v) => v !== this.selectAllValue
      )
      this.$emit('input', valuesToEmit)
      this.$emit('change', valuesToEmit)
    }
  }
}
</script>

<style lang="scss" scoped>
.select-all-option {
  border-bottom: 1px solid #ebeef5;
  padding-bottom: 5px;
  padding-right: 0 !important;
  margin-bottom: 5px;
  .el-checkbox {
    width: 100%;
  }
}

.el-checkbox {
  margin-right: 8px;
}
</style>
相关推荐
刘一说17 小时前
Vue 动态路由参数丢失问题详解:为什么 `:id` 拿不到值?
前端·javascript·vue.js
熊猫钓鱼>_>17 小时前
动态网站发布部署核心问题详解
前端·nginx·容器化·网页开发·云服务器·静态部署
方也_arkling17 小时前
elementPlus按需导入配置
前端·javascript·vue.js
我的xiaodoujiao18 小时前
使用 Python 语言 从 0 到 1 搭建完整 Web UI自动化测试学习系列 44--将自动化测试结果自动推送至钉钉工作群聊
前端·python·测试工具·ui·pytest
沛沛老爹18 小时前
Web开发者转型AI:多模态Agent视频分析技能开发实战
前端·人工智能·音视频
David凉宸18 小时前
vue2与vue3的差异在哪里?
前端·javascript·vue.js
笔画人生18 小时前
Cursor + 蓝耘API:用自然语言完成全栈项目开发
前端·后端
AC赳赳老秦18 小时前
外文文献精读:DeepSeek翻译并解析顶会论文核心技术要点
前端·flutter·zookeeper·自动化·rabbitmq·prometheus·deepseek
小宇的天下18 小时前
Calibre 3Dstack --每日一个命令day18【floating_trace】(3-18)
服务器·前端·数据库
毕设源码-钟学长18 小时前
【开题答辩全过程】以 基于web技术的酒店信息管理系统设计与实现-为例,包含答辩的问题和答案
前端