el-table二次封装支持全选所有(带禁选,反选功能,全选后不用锁定去勾选)

120 阅读1分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

el-table二次封装支持全选所有(带禁选,反选功能,全选后不用锁定去勾选)

网上面很多方案都是全选后锁定了,不能筛选了,不够灵活,产品希望,全选之后,还能去掉一些选项,于是自己开始造轮子了。

<template>
  <div class="ws-base-table">
    <el-table
      ref="WsBaseTable"
      v-bind="$attrs"
      v-on="$listeners"
      v-loading="loading"
      :element-loading-text="elementLoadingText"
      style="width: 100%"
      :style="styleText"
      :header-cell-style="headerCellStyle"
      :row-style="rowStyle"
      @select="select"
      @select-all="selectAll"
      @selection-change="selectionChange"
    >
      <el-table-column
        v-if="selectInfo.total >= 0"
        width="40"
        type="selection"
        reserve-selection
        :selectable="selectInfo.selectable"
      ></el-table-column>

      <el-table-column v-for="col of tableColumns" v-bind="col" :key="col.prop" :align="col.align || align">
        <template v-slot:header="scope">
          <slot v-if="col.header" :name="col.header" :scope="{ header: col.label, ...scope }"></slot>
          <span v-else>{{ col.label }}</span>
        </template>
        <template v-if="!['selection', 'index', 'expand'].includes(col.type)" v-slot:default="scope">
          <slot :name="col.prop" :scope="{ ...scope, ...col }">
            <span>{{ scope.row[col.prop] }}</span>
          </slot>
        </template>
      </el-table-column>

      <!-- 空数据时显示的文本内容 -->
      <template slot="empty">
        <slot name="empty">
          <p>{{ loading ? '' : '暂无数据' }}</p>
        </slot>
      </template>

      <!-- 插入至表格最后一行之后的内容,如果需要对表格的内容进行无限滚动操作,可能需要用到这个 slot。若表格有合计行,该 slot 会位于合计行之上。 -->
      <template slot="append">
        <slot name="append"></slot>
      </template>
    </el-table>

    <!-- 分页组件插槽 -->
    <slot v-if="paginationTotal > paginationPageSize" name="pagination"></slot>

    <!-- 自定义全选 -->
    <div v-if="selectedNumber > 1 && selectInfo.total >= 0" class="select-all-box">
      <span v-show="!isSelectAll" class="selected"
        >{{ `已选择${selectSuffixText} ${selectedNumber} 个${selectInfo.selectText || '数据'}` }},</span
      >
      <span v-show="isSelectAll" class="all-selected"
        >{{
          `已选择${
            selectInfo.total === selectedNumber + (selectInfo.disableSelectTotal || 0) ? '全部' : ''
          } ${selectedNumber} 个${selectInfo.selectText || '数据'}`
        }},</span
      >
      <span
        v-if="selectedNumber < selectInfo.total - (selectInfo.disableSelectTotal || 0)"
        class="select-all"
        @click="customSelectAll"
        >{{
          `选择全部 ${selectInfo.total - (selectInfo.disableSelectTotal || 0)} 个${selectInfo.selectText || '数据'}`
        }}</span
      >
      <span v-else class="cancel-select-all" @click="cancelSelectAll">取消全选</span>
    </div>
  </div>
</template>

<script>
export default {
  name: 'WsBaseTable',
  inheritAttrs: false,
  props: {
    // el-table表格列属性:不够可以加
    tableColumns: {
      type: Array,
      default: () => [],
      required: true
    },
    // 对齐方式
    align: {
      type: String,
      default: 'center'
    },
    // 加载状态
    loading: {
      type: Boolean,
      default: false
    },
    elementLoadingText: {
      type: String,
      default: '加载中...'
    },
    headerCellStyle: {
      type: [Object, Function],
      default: () => ({
        background: '#F6F6F9',
        height: '40px',
        color: '#909399',
        'font-size': '12px'
      })
    },
    rowStyle: {
      type: [Object, Function],
      default: () => {
        return {
          height: '56px',
          'white-space': 'nowrap',
          'font-size': '14px'
        };
      }
    },
    // 可勾选的信息: total:表格总条数; disableSelectTotal: 禁勾选总数; canSelectField: 可勾选字段名, 对应值为boolean类型,true代表可勾选; selectable(和el-table的selectable保持一致)
    selectInfo: {
      type: Object,
      default: () => ({
        total: -1, // 全选必须
        selectText: '数据',
        disableSelectTotal: 0, // 可选 禁选必须
        canSelectField: '', // 可选 禁选必须
        selectable: () => true // 可选 禁选必须
      })
    }
  },
  data() {
    return {
      selectSuffixText: '',
      // 是否全选
      isSelectAll: false,
      // 已勾选的条数
      selectedNumber: 0,
      // 非全选时,已勾选数组
      selectedRows: [],
      // 全选时,未勾选数组
      unSelectedRows: []
    };
  },
  computed: {
    // 计算总条数,只有一页时隐藏分页
    paginationTotal() {
      let paginationTotal = 0;
      if (this.$slots.pagination && this.$slots.pagination[0].componentOptions.propsData.total) {
        paginationTotal = this.$slots.pagination[0].componentOptions.propsData.total;
      }
      return paginationTotal;
    },
    // 计算分页条数
    paginationPageSize() {
      let paginationPageSize = 10;
      if (this.$slots.pagination && this.$slots.pagination[0].componentOptions.propsData.pageSize) {
        paginationPageSize = this.$slots.pagination[0].componentOptions.propsData.pageSize;
      }
      return paginationPageSize;
    },
    // 全选条展示的高度
    styleText() {
      return {
        '--all-select-height': this.selectedNumber > 1 ? '40px' : 0
      };
    }
  },
  created() {
    this.rowKey = this.$attrs['row-key'] || 'id';
    // 判断是否绑定了行点击事件,自动给鼠标添加小手样式
    if (this.$listeners['row-click'] && typeof this.rowStyle === 'object') {
      this.rowStyle.cursor = 'pointer';
    }
  },
  watch: {
    selectedNumber: {
      handler(n) {
        if (n === this.selectInfo.total - (this.selectInfo.disableSelectTotal || 0)) {
          this.isSelectAll = true;
        }
      }
    },
    '$attrs.data': {
      handler(newData) {
        this.tableData = newData;
        this.isSelectAll && this.autoCheck();
      },
      immediate: true
    }
  },
  methods: {
    // 向外暴露已选数据的方法
    getSelectedInfo() {
      let { isSelectAll, selectedNumber } = this;
      return {
        isSelectAll,
        selectedNumber,
        list: this.isSelectAll ? this.unSelectedRows : this.selectedRows
      };
    },
    // 封装自动勾选
    autoCheck() {
      const currentSelectedRows = this.getTwoArrayDiff(this.tableData, this.unSelectedRows, this.rowKey);
      let addCurrentSelectedRows = currentSelectedRows.filter(
        row => !this.selection.map(item => item[this.rowKey]).includes(row[this.rowKey])
      );
      if (this.selectInfo.canSelectField) {
        addCurrentSelectedRows = addCurrentSelectedRows.filter(item => item[this.selectInfo.canSelectField]);
      }
      addCurrentSelectedRows.forEach(row => {
        this.toggleRowSelection(row, true);
        this.selection.push(row);
      });
    },
    // 自定义全选
    customSelectAll() {
      this.isSelectAll = true;
      this.selectedNumber = this.selectInfo.total - (this.selectInfo.disableSelectTotal || 0);
      this.unSelectedRows = [];
      this.autoCheck();
    },
    // 取消全选
    cancelSelectAll() {
      this.clearSelection();
      this.clearCustomSelection();
    },
    // 重置勾选(外部查询条件变更,切记不忘了调用)
    resetSelect() {
      this.cancelSelectAll();
    },
    // 当用户手动勾选数据行的 Checkbox 时触发的事件
    select(selection, row) {
      // 未设置全选所有时
      if (this.selectInfo.total < 0) return this.$emit('select', selection, row);

      if (this.isSelectAll) {
        if (this.itemIsInArray(row, this.unSelectedRows, this.rowKey)) {
          this.unSelectedRows = this.getTwoArrayDiff(this.unSelectedRows, [row], this.rowKey);
          this.selectedNumber += 1;
        } else {
          this.unSelectedRows.push(row);
          this.selectedNumber -= 1;
        }
      }
      this.selectSuffixText = '';
      this.selection = [...selection];
    },
    // 当用户手动勾选全选 Checkbox 时触发的事件
    selectAll(selection) {
      // 未设置全选所有时
      if (this.selectInfo.total < 0) return this.$emit('select-all', selection);

      if (this.isSelectAll) {
        if (selection.length >= this.selection.length) {
          // 取增量
          const newAddRows = this.getTwoArrayIntersection(selection, this.unSelectedRows, this.rowKey);
          this.unSelectedRows = this.getTwoArrayDiff(this.unSelectedRows, newAddRows, this.rowKey);
          this.selectedNumber += newAddRows.length;
        } else {
          // 取减量
          let newReducedRows = this.tableData;
          if (this.selectInfo.canSelectField) {
            newReducedRows = newReducedRows.filter(row => row[this.selectInfo.canSelectField]);
          }
          this.unSelectedRows.push(...newReducedRows);
          this.selectedNumber -= newReducedRows.length;
        }
      } else {
        this.selectSuffixText = selection.length === this.paginationPageSize ? '本页' : '';
      }
      this.selection = [...selection];
    },
    // 当选择项发生变化时会触发该事件
    selectionChange(selection) {
      // 未设置全选所有时
      if (this.selectInfo.total < 0) return this.$emit('selection-change', selection);

      if (!this.isSelectAll) {
        this.selectedNumber = selection.length;
        this.selectedRows = selection;
      }
    },
    // 清空自定义全选
    clearCustomSelection() {
      this.isSelectAll = false;
      this.selectedNumber = 0;
      this.selectedRows = [];
      this.unSelectedRows = [];
      this.selection = [];
    },
    // 两个数组对象取差集
    getTwoArrayDiff(arr1, arr2, key) {
      let diffArray = arr1.filter(i => !arr2.map(j => j[key]).includes(i[key]));
      return diffArray;
    },
    // 两个数组对象取交集
    getTwoArrayIntersection(arr1, arr2, key) {
      let intersectionArray = arr1.filter(i => arr2.map(j => j[key]).includes(i[key]));
      return intersectionArray;
    },
    // 判断元素是否在数组对象中
    itemIsInArray(item, arr, key) {
      return arr.map(i => i[key]).includes(item[key]);
    },
    // el-table自带方法,不够可以加,暂时没有想到如何如何避免重复的写法(不想嵌套$refs,只能一个一个添加)
    // 用于多选表格,清空用户的选择
    clearSelection() {
      this.$refs.WsBaseTable.clearSelection();
    },
    // 用于多选表格,切换某一行的选中状态,如果使用了第二个参数,则是设置这一行选中与否(selected 为 true 则选中)
    toggleRowSelection(row, selected) {
      this.$refs.WsBaseTable.toggleRowSelection(row, selected);
    },
    // 对 Table 进行重新布局。当 Table 或其祖先元素由隐藏切换为显示时,可能需要调用此方法
    doLayout() {
      this.$refs.WsBaseTable.doLayout();
    }
  }
};
</script>

<style lang="scss" scoped>
.ws-base-table {
  position: relative;
  .select-all-box {
    position: absolute;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 100%;
    height: 32px;
    margin: 4px 0;
    left: 0;
    top: 40px;
    background: #fffae2;
    border-radius: 4px;
    span {
      height: 17px;
      font-size: 12px;
      line-height: 17px;
    }
    .selected {
      color: #333333;
    }
    .select-all,
    .cancel-select-all {
      cursor: pointer;
      margin-left: 12px;
      color: #294ba3;
    }
  }
  /deep/ .el-table__header {
    padding-bottom: var(--all-select-height);
  }
  /deep/ .el-checkbox__inner {
    width: 18px;
    height: 18px;
  }
  /deep/ .el-checkbox__inner::after {
    height: 10px;
    left: 6px;
  }
  /deep/ .el-checkbox__input.is-indeterminate .el-checkbox__inner::before {
    top: 7px;
  }
  /deep/ .el-table-column--selection .cell {
    text-overflow: initial;
  }
}
</style>