element-ui表格的二次封装

118 阅读9分钟

封装是为了快速开发页面,提高开发效率,封装的表格只是针对于单级表头的情况,多级表格还是使用element-ui表格好一点。话不多说上代码。

<template>
  <div>
    <el-table ref="list"
              :border="tableBorder"
              :data="table.list"
              :class="tableCss"
              :max-height="maxHeight ? maxHeight : ''"
              v-loading="loading"
              :header-row-style="tableHeadStyle"
              :row-key="rowKey ? rowKey : getRowKey"
              default-expand-all
              @sort-change="table.sort ? table.sort.actionChange($event) : ''"
              :tree-props="table.expend"
              @select-all="table.selection ? selectAll : ''"
              @selection-change="table.selection ? table.selection.actionChange($event) : ''"
              :default-sort = "table.defaultSort"
              v-horizontal-scroll="scrollOptions"
              style="width: 100%">
      <el-table-column v-if="table.selection" type="selection" align="center" :selectable="selectRes" width="55" reserve-selection></el-table-column>
      <el-table-column v-for="(item, index) in table.head"
                       :key="index" :label="item.label"
                       :prop="item.props" :align="item.align ? item.align : 'center'"
                       :width="table.head.length===1 ? '' : item.width"
                       :min-width="item.minWidth ? item.minWidth : ''"
                       :sortable="item.sortable ? item.sortable : false"
                       :fixed="item.fixed ? item.fixed : false">
        <template slot="header" slot-scope="scope">
          <!--表格是否展示提示信息-->
          <template v-if="item.headerTips">
            <span>{{item.label}}</span>
            <el-tooltip effect="dark"  placement="top">
              <template slot="content">
                <div class="c-max-width600" v-html="item.headerTips"></div>
              </template>
              <svg-icon icon-class="icon_tips" class="c-fs-12 c-fc-sgray"></svg-icon>
            </el-tooltip>
          </template>
          <template v-else>
            {{item.label}}
          </template>
        </template>
        <template slot-scope="scope">
          <!--字典类型展示-->
          <div v-else-if="item.columnType === 'dictionary'" :style="{color:item.dictionaryColor?item.dictionaryColor[scope.row[item.props]]:''} " >
            <template v-if="(scope.row[item.props] || utilsIsNumber(scope.row[item.props])) && item.dictionary[scope.row[item.props]]">{{item.dictionary[scope.row[item.props]]}}</template>
            <template v-else>--</template>
          </div>
          <!--可以点击列-->
          <div v-else-if="item.columnType === 'underlineDictionary'" @click="item.action(scope.row, item.props)" class="c-fc-blue c-textD-u pointer">
            {{ scope.row[item.props] ? (item.dictionary[scope.row[item.props]] ? item.dictionary[scope.row[item.props]] : '--') : (scope.row[item.props] === 0 ? (item.dictionary[scope.row[item.props]] ? item.dictionary[scope.row[item.props]] : '--') :'--') }}
          </div>
          <!--点击可以修改的列-->
          <div v-else-if="item.columnType === 'numberFormat'" class="c-flex-row c-justify-center c-aligni-center">
            <number-format :value="scope.row[item.props]"></number-format><span v-if="item.unit">{{item.unit}}</span>
          </div>
          <!--可以点击列-->
          <div v-else-if="item.columnType === 'underline'" @click="item.action(scope.row, item.props)" class="c-fc-blue c-textD-u pointer">
            <template v-if="scope.row[item.props] || utilsIsNumber(scope.row[item.props])">{{scope.row[item.props]}}</template>
            <template v-else>--</template>
            <template v-if="item.unit">{{item.unit}}</template>
          </div>
          <!--可以点击列-->
          <div v-else-if="item.columnType === 'percentage'">
              <span v-if="scope.row[item.props] ||  utilsIsNumber(scope.row[item.props])">
                {{scope.row[item.props]}}%
              </span>
            <span v-else>--</span>
          </div>
          <!--点击可以修改的列-->
          <div v-else-if="item.columnType === 'underlineNumberFormat'" @click="item.action(scope.row, item.props)" class="c-fc-blue c-textD-u pointer c-flex-row c-justify-center c-aligni-center">
            <number-format :value="scope.row[item.props]"></number-format><span v-if="item.unit">{{item.unit}}</span>
          </div>
          <!--slot类型展示-->
          <template v-else-if="item.columnType === 'slot'">
            <slot :name="item.props" :data="scope.row"></slot>
          </template>
          <span v-else>
            {{ scope.row[item.props] ? scope.row[item.props] : (scope.row[item.props] === 0 ? scope.row[item.props] :'--') }}<span v-if="item.unit">{{item.unit}}</span>
          </span>
        </template>
      </el-table-column>
    </el-table>
    <el-pagination
      class="c-textAlign-r c-pv20"
      v-show="showPage"
      background
      @current-change="currentChange"
      @size-change="sizeChange"
      :page-sizes="[10, 20, 30, 40]"
      :current-page.sync="listBase.page"
      :page-size.sync="listBase.size"
      :layout="layout"
      :total="listBase.total"
    ></el-pagination>
  </div>
</template>

<script>
import numberFormat from '@/views/templatePage/numberFormat'
import { directiveVue1, directiveVue2 } from '@/components/horizontalScroll.js'
export default {
  name: 'index',
  components: { numberFormat },
  props: {
    // 加载状态
    loading: {
      type: Boolean,
      default: false
    },
    // 表格参数
    table: {
      type: Object,
      default: () => { return [] }
    },
    // 分页参数
    listBase: {
      type: Object,
      required: true,
      default: {
        page: 1,
        total: 1,
        size: 10
      }
    },
    // 是否提供tableKey
    rowKey: {
      type: String,
      default: ''
    },
    // 表格是否限制高度
    maxHeight: {
      type: Number,
      default: null
    },
    // 是否自动获取高度
    autoTableHeight: {
      type: Boolean,
      default: false
    },
    // 分页功能
    layout: {
      type: String,
      default: 'total, prev, pager, next, jumper'
    },
    // 表格边框
    tableBorder: {
      type: Boolean,
      default: false
    },
    /**
     * 滚动条自定义参数
     * closeScroll:关闭悬浮滚动条,不传默认开启悬浮滚动条
     * hasBottomBox:是否有底部操作栏(默认60px高),设为true时,需传bottom: 100px
     * bottom:悬浮滚动条距离页面底部距离,不传默认40px
     */
    scrollOptions: {
      type: Object,
      default: function() {
        return {}
      }
    }
  },
  watch: {
  },
  created() {
  },
  directives: {
    // 自定义指令,实现悬浮横向滚动条
    'horizontal-scroll': {
      ...directiveVue1,
      ...directiveVue2
    }
  },
  computed: {
    // 判断是否展示分页
    showPage() {
      if (this.listBase) {
        return (this.listBase.total / this.listBase.size) > 0
      } else {
        return false
      }
    }
  },
  data() {
    return {
      // 表格样式
      tableHeadStyle: { 'font-family': 'PingFangSC-Semibold', 'backgroundColor': '#f5f7fa', 'color': '#333333' },
      tableCss: 'c-width100 c-fc-333 family-PingFangSC-Regular c-fs-12'
    }
  },
  methods: {
    // 判断是否为数字
    utilsIsNumber(row) {
      return Object.prototype.toString.call(row) === '[object Number]'
    },
    // 改变分页大小
    sizeChange(row) {
      this.$emit('sizeChange', row)
    },

    // 表格唯一指定key
    getRowKey(row) {
      const random = this.randomString()
      return 'column' + random
    },
    // 获取随机字符串
    randomString(len) {
      len = len || 32
      const chars = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
      const maxPos = chars.length
      let pwd = ''
      for (let i = 0; i < len; i++) {
        pwd += chars.charAt(Math.floor(Math.random() * maxPos))
      }
      return pwd
    },
    // 选择全部
    selectAll(val) {
      val.map((item, index) => {
        if (item.selectable === false) {
          this.$refs.list.toggleRowSelection(item, false)
        }
      })
    },
    // 禁用选中, boolean 值
    selectRes(row) {
      return row.selectable === false ? row.selectable : true
    },
    // 提交分页
    currentChange(val) {
      this.$emit('currentChange', val)
    },
    // 清除选择
    clearSelect() {
      this.$refs.list.clearSelection()
    }
  }
}
</script>
<style lang="scss" scoped>
</style>

表格中设计的组件numberFormat数字格式化组件 当然你也可以不加

<template>
  <div class="c-flex-row c-flexw-wrap c-justify-center c-aligni-end" :class="valueClass">
    <template v-if="value || utilsIsNumber(value)">
      <template v-if="Number(value)!==0">
        <template v-if="Number(value) < 0">-</template>
        <template v-if="!!calculate">
          {{calculate}}
          <div class="c-fs-12 c-ph2" :class="unitClass">亿</div>
        </template>
        <template v-if="!!myriad">
          {{myriad}}
          <div class="c-fs-12 c-ph2" :class="unitClass">万</div>
        </template>
        <template v-if="!!thousand">{{thousand}}</template>
        <template :class="unitClass">{{unit}}</template>
      </template>
      <template v-else>
        0
      </template>
    </template>
    <template v-else>--</template>
  </div>
</template>

<script>
import { utilsIsNumber } from '@/utils'
export default {
  name: 'numberFormat',
  props: {
    value: {
      type: [String, Number],
      default: ''
    },
    unitClass: {
      type: String,
      default: ''
    },
    valueClass: {
      type: String,
      default: ''
    },
    unit: {
      type: String,
      default: ''
    }
  },
  data() {
    return {
      calculate: 0,
      myriad: 0,
      thousand: 0
    }
  },
  created() {
    this.calculateFormatHandle()
  },
  watch: {
    value() {
      this.calculateFormatHandle()
    }
  },
  methods: {
    // 判断是否为数字
    utilsIsNumber(row) {
      return Object.prototype.toString.call(row) === '[object Number]'
    },
    // 亿格式化数字方法
    calculateFormatHandle() {
      const number = Math.abs(Number(this.value))
      if (number >= 100000000) {
        this.calculate = parseInt((number / 100000000) + '')
      } else {
        this.calculate = 0
      }
      this.myriadFormatHandle()
    },
    // 万格式化数字方法
    myriadFormatHandle() {
      const number = Math.abs(Number(this.value)) - (this.calculate * 100000000)
      if (number >= 10000) {
        this.myriad = parseInt((number / 10000) + '')
      } else {
        this.myriad = 0
      }
      this.thousandFormatHandle()
    },
    // 千格式化数字方法
    thousandFormatHandle() {
      const number = Math.abs(Number(this.value)) - ((this.calculate * 100000000) + (this.myriad * 10000))
      if (number >= 0) {
        this.thousand = Number(Number(number).toFixed(2))
      } else {
        this.thousand = 0
      }
    }
  }
}
</script>

<style scoped>
</style>

表格中涉及的指令horizontalScroll.js用于优化表格滚动条 当然你也可以不加去除指令

/**
 * el-table表格,横向滚动条
 */
import { throttle } from 'throttle-debounce'

const THROTTLE_TIME = 1000 / 60

class Scroller {
  /**
   * 给tableBody创建一个scroller
   * @param {Element} targetTableWrapperEl
   * @param {Element} bottomIsVisibleObserverEl
   * @param {string} options 指令对象参数
   */
  constructor(targetTableWrapperEl, bottomIsVisibleObserverEl, options = {}) {
    if (!targetTableWrapperEl) {
      throw new Error('need have table element')
    }
    this.targetTableWrapperEl = targetTableWrapperEl
    this.fullwidth = false // 表格是否满宽,无需滚动
    this.mode = options.hasOwnProperty('mode') ? options.mode : 'always' // 滚动条的显示方式
    this.isVisible = false // 滚动条是否显示
    this.isBlock = false // 表格数据是否为空
    this.bottomIsVisibleObserverEl = bottomIsVisibleObserverEl
    this.hasBottomBox = options.hasOwnProperty('hasBottomBox') ? options.hasBottomBox : false // 页面是否有底部操作栏,有则进行延迟隐藏

    /**
     * 创建相关dom
     * scroller:横向滚动条容器
     * bar:滑动槽
     * thumb:滑块
     */
    const scroller = document.createElement('div')
    scroller.classList.add('el-scrollbar', 'el-table-horizontal-scrollbar')
    scroller.style.height = '30px'
    scroller.style.borderRadius = '100px'
    scroller.style.background = 'rgba(255, 255, 255, 0.7)'
    scroller.style.boxShadow = 'rgba(0, 0, 0, 0.15) 0px 2px 16px 0px'
    scroller.style.position = 'fixed'
    scroller.style.bottom = options.hasOwnProperty('bottom') ? options.bottom : '40px'
    scroller.style.zIndex = 10
    scroller.style.transition = 'all 0.3s ease'

    this.dom = scroller
    this.resetScroller()

    const bar = document.createElement('div')
    bar.classList.add('el-scrollbar__bar', 'is-horizontal')
    bar.style.width = '95%'
    bar.style.height = '10px'
    bar.style.borderRadius = '10px'
    bar.style.background = 'rgb(241, 241, 241)'
    bar.style.left = '0px'
    bar.style.margin = '8px auto'
    this.bar = bar
    scroller.appendChild(bar)

    const thumb = document.createElement('div')
    thumb.classList.add('el-scrollbar__thumb')
    bar.appendChild(thumb)
    this.thumb = thumb

    this.bottomIsVisible = false

    /**
     * 初始化配置
     */
    const instance = this

    this.checkIsScrollBottom = throttle(THROTTLE_TIME, function() {
      // 此处3个判断具有先后顺序,不能放一起判断
      // 移动端进入后台,因屏幕自带滚动,不需要该滚动条(最高优先级)
      if (window.innerWidth <= 768) {
        instance.hideScroller()
        return
      }
      // 如果表格数据为空,优先不显示滚动条
      if (instance.isBlock) {
        instance.hideScroller()
        return
      }
      // 滚动条是否显示
      if (!instance.isVisible) {
        instance.hideScroller()
        return
      }

      // 当前表格的底部是否出现在可视区域
      if (instance.bottomIsVisible) {
        instance.hideScroller()
        instance.hideBar()
      } else {
        // 需要重新设置一次当前宽度
        instance.resetBar(false)

        // 显示当前的bar
        instance.showScroller()

        if (instance.mode === 'always') {
          instance.showBar()
        }
        if (instance.mode === 'hover') {
          instance.hideBar()
        }
      }
    })

    // 自动同步,table => scroller
    targetTableWrapperEl.addEventListener('scroll', throttle(THROTTLE_TIME, function() {
      instance.resetThumbPosition()
    }))

    // 自动同步 scroller => table
    this.syncDestoryHandler = this.initScrollSyncHandler()

    this.tableElObserver = null

    if (MutationObserver) {
      // 监听table的dom变化,自动重新设置
      this.tableElObserver = new MutationObserver(() => {
        // 如果表格没有数据,则优先设置为不显示滚动条(主表格下存在空样式元素,防止嵌套表格的空样式元素影响了主表格)
        // :scope 可以用于代替当前元素的选择器,因主表格没有特殊记号表示,会导致能取到嵌套内表格的空元素,加上该选择器后就不会选到孙代元素
        this.isBlock = Boolean(targetTableWrapperEl.querySelector(':scope > .el-table__empty-block'))
        this.forceUpdate()
      })
      this.tableElObserver.observe(
        targetTableWrapperEl.querySelector('.el-table__body'),
        {
          childList: true,
          subtree: true,
          attributes: true,
          attributeFilter: ['style']
        }
      )
    }

    this.tableResizeObserver = null

    // 兼容一下不支持ResizeObserver的旧版浏览器
    if (window.ResizeObserver) {
      this.tableResizeObserver = new ResizeObserver(() => this.forceUpdate())
      this.tableResizeObserver.observe(targetTableWrapperEl)
    } else {
      window.addEventListener('resize', throttle(THROTTLE_TIME, function() {
        instance.forceUpdate()
      }))
    }

    this.tableIntersectionObserver = null
    this.bottomIsVisibleObserver = null
    if (IntersectionObserver) {
      // 本体是否可见
      this.tableIntersectionObserver = new IntersectionObserver(([entry]) => {
        this.isVisible = entry.intersectionRatio > 0
        this.forceUpdate()
      })
      this.tableIntersectionObserver.observe(targetTableWrapperEl)

      // 底部是否可见
      if (this.bottomIsVisibleObserverEl) {
        // 判断是否有底部操作栏,有则延迟60px再进行隐藏滚动条,防止因操作栏遮挡导致提前隐藏了滚动条
        const config = this.hasBottomBox ? { rootMargin: '0px 0px -60px 0px' } : null
        this.bottomIsVisibleObserver = new IntersectionObserver(([entry]) => {
          // 获取监听的底部元素的相对视口数据
          const elRect = this.bottomIsVisibleObserverEl.getBoundingClientRect() || { top: 0 }
          // 当页面没有滚动条,表格直接在视口内时,intersectionRatio会为0,悬浮滚动条会和表格本体一起出现
          // 因此这里额外判断一下,当底部元素的距视口高度,小于视口的高度时,将bottomIsVisible置为true,表示当前底部元素已在视口中
          // 如果有底部操作栏,视口高度要减去延迟的60px
          const inViewport = this.hasBottomBox ? elRect.top <= window.innerHeight - 60 : elRect.top <= window.innerHeight
          this.bottomIsVisible = inViewport ? true : entry.intersectionRatio > 0
          this.checkIsScrollBottom()
        }, config)
        this.bottomIsVisibleObserver.observe(this.bottomIsVisibleObserverEl)
      }
    }

    // bar宽度自动重制
    this.forceUpdate()
  }

  /**
   * 自动设置Bar
   * @param {boolean} changeScrollerVisible 是否开启自动设置滚动条显示与否
   */
  resetBar(changeScrollerVisible = true) {
    const { targetTableWrapperEl } = this
    const widthPercentage = (targetTableWrapperEl.clientWidth * 100 / targetTableWrapperEl.scrollWidth)
    const thumbWidth = Math.min(widthPercentage, 100)
    this.thumb.style.width = `${thumbWidth}%`

    this.fullwidth = thumbWidth >= 100

    if (changeScrollerVisible) {
      if (this.fullwidth) {
        this.hideScroller()
      } else {
        this.checkIsScrollBottom()
      }
    }
  }

  // 重置滑块定位
  resetThumbPosition() {
    this.thumb.style.transform = `translateX(${this.moveX}%)`
  }

  // 重置滚动条位置
  resetScroller() {
    const { targetTableWrapperEl, dom } = this
    const boundingClientRect = targetTableWrapperEl.getBoundingClientRect()
    dom.style.width = '500px'
    // 因fiexd定位是根据整个页面来的,外加侧边栏改动,部分表格不是占满页面,故更改公式,已主表格中心为准
    // 表格宽度的一半,减去滚动条宽度的一半,加上表格距离页面左侧的宽度,以实现滚动条始终在表格中心
    dom.style.left = `calc(${boundingClientRect.width / 2}px - 250px + ${boundingClientRect.left}px)`
  }

  forceUpdate() {
    setTimeout(() => {
      this.resetBar()
      this.resetScroller()
      this.resetThumbPosition()
      this.checkIsScrollBottom()
    }, THROTTLE_TIME)
  }

  // 获取横向偏移距离
  get moveX() {
    const { targetTableWrapperEl } = this
    return ((targetTableWrapperEl.scrollLeft * 100) / targetTableWrapperEl.clientWidth)
  }

  /**
   * 让scroller的拖动行为和table的同步
   * 处理类似element-ui的拖拽处理
   */
  initScrollSyncHandler() {
    let cursorDown = false
    let tempClientX = 0
    let rate = 1
    let tableRate = 1

    const { thumb, targetTableWrapperEl, bar } = this

    function getRate() {
      // 计算一下变换比例,拖拽走的是具体数字,但是这个实际上应该是按照比例变的
      return bar.offsetWidth / thumb.offsetWidth
    }

    function getTableRate() {
      // 获取表格滚动条和悬浮滚动条之间的宽度比例
      const scrollEl = targetTableWrapperEl.getBoundingClientRect()
      return scrollEl.width / bar.offsetWidth
    }

    const mouseMoveDocumentHandler = throttle(
      THROTTLE_TIME,
      function(e) {
        if (cursorDown === false) {
          return
        }
        const { clientX } = e
        const offset = clientX - tempClientX
        const originTempClientX = tempClientX
        tempClientX = clientX

        const tempScrollleft = targetTableWrapperEl.scrollLeft
        targetTableWrapperEl.scrollLeft += offset * rate * tableRate
        if (tempScrollleft === targetTableWrapperEl.scrollLeft) {
          tempClientX = originTempClientX
        }
      })
    function mouseUpDocumentHandler() {
      cursorDown = false
      document.removeEventListener('mousemove', mouseMoveDocumentHandler)
      document.removeEventListener('mouseup', mouseUpDocumentHandler)
      document.onselectstart = null
    }

    /**
     * 拖拽处理
     * @param {MouseEvent} e
     */
    function startDrag(e) {
      e.stopImmediatePropagation()
      cursorDown = true
      document.addEventListener('mousemove', mouseMoveDocumentHandler)
      document.addEventListener('mouseup', mouseUpDocumentHandler)
      document.onselectstart = () => false
    }

    thumb.onmousedown = function(e) {
      if (e.ctrlKey || e.button === 2) {
        return
      }

      const { clientX } = e
      tempClientX = clientX
      rate = getRate()
      tableRate = getTableRate()
      startDrag(e)
    }

    /**
     * 点击槽快速移动
     * @param {PointerEvent} e
     */
    bar.onclick = function(e) {
      const { target } = e
      if (target !== bar) {
        return
      }
      rate = getRate()
      tableRate = getTableRate()
      const { clientX } = e

      // 计算规则更改:鼠标点击的x轴偏移量,减去滑动槽左侧偏移量,点击位置在滑块右侧时,需额外减去滑块宽度
      let offset = 0
      const thumbPosition = thumb.getBoundingClientRect()
      const barPosition = bar.getBoundingClientRect()
      if (thumbPosition.left >= clientX) {
        offset = clientX - barPosition.left
      } else {
        offset = clientX - barPosition.left - thumbPosition.width
      }

      /**
        * 因 targetTableWrapperEl.scrollLeft 实际上是走的表格滚动条的 scrollLeft
        * 通过x轴偏移量计算出来的结果(offset),需要乘上槽与块的宽度比(rate),再乘上表格滚动条与悬浮滚动条的宽度比(tableRate)
        * 最后得出的结果就是实际上表格滚动条的 scrollLeft
        * 因悬浮滚动条的槽两端,很难点到头和尾,故再乘以1.01,使之点击槽两端时能尽量滚到头和尾
        */
      const targetScrollLeft = offset * rate * tableRate * 1.01

      // 兼容一下没有scrollTo方法的浏览器
      if (targetTableWrapperEl.scrollTo) {
        targetTableWrapperEl.scrollTo({
          left: targetScrollLeft,
          behavior: 'smooth' // 平滑滚动
        })
      } else {
        targetTableWrapperEl.scrollLeft = targetScrollLeft
      }
    }

    return function() {
      document.removeEventListener('mouseup', mouseUpDocumentHandler)
    }
  }

  /**
   * 显示整体
   */
  showScroller() {
    if (!this.fullwidth) {
      this.dom.style.display = 'initial'
    }
  }

  /**
   * 隐藏整体
   */
  hideScroller() {
    this.dom.style.display = 'none'
  }

  /**
   * 显示滚动条
   */
  showBar() {
    if (!this.isVisible) {
      return
    }
    if (this.mode === 'hover') {
      this.dom.style.opacity = 1
    }
    this.bar.style.opacity = 1
  }

  /**
   * 隐藏滚动条
   */
  hideBar() {
    if (!this.isVisible) {
      return
    }
    if (this.mode === 'hover') {
      this.dom.style.opacity = 0
    }
    this.bar.style.opacity = 0
  }

  destory() {
    // 取消所有监听事件,销毁滚动条元素
    this.tableElObserver && this.tableElObserver.disconnect()
    this.tableResizeObserver && this.tableResizeObserver.disconnect()
    const instance = this
    if (window.ResizeObserver && this.tableResizeObserver) {
      this.tableResizeObserver.disconnect()
    } else {
      window.removeEventListener('resize', throttle(THROTTLE_TIME, function() {
        instance.forceUpdate()
      }))
    }
    this.tableIntersectionObserver && this.tableIntersectionObserver.disconnect()
    this.bottomIsVisibleObserver && this.bottomIsVisibleObserver.disconnect()
    if (this.bottomIsVisibleObserverEl && this.bottomIsVisibleObserverEl.parentElement) {
      this.bottomIsVisibleObserverEl.parentElement.removeChild(this.bottomIsVisibleObserverEl)
    }
    this.syncDestoryHandler()
  }
}

export const directiveVue1 = {
  inserted(el, binding) {
    const options = binding.value || {}
    // 关闭滚动条
    if (options.hasOwnProperty('closeScroll') && options.closeScroll) {
      return
    }
    const tableBodyWrapper = el.querySelector('.el-table__body-wrapper')
    const bottomIsVisibleObserverEl = document.createElement('div')
    bottomIsVisibleObserverEl.style.transform = 'translateY(-12px)'
    const scroller = new Scroller(tableBodyWrapper, bottomIsVisibleObserverEl, options)

    el.appendChild(bottomIsVisibleObserverEl)
    el.appendChild(scroller.dom)
    el.horizontalScroll = scroller

    if (options.hasOwnProperty('mode') && options.mode === 'hover') {
      scroller.hideBar()
      el.addEventListener('mouseover', scroller.showBar.bind(scroller))
      el.addEventListener('mouseleave', scroller.hideBar.bind(scroller))
    } else {
      scroller.showBar()
    }
  },
  unbind(el, binding) {
    const options = binding.value || {}
    // 已关闭的滚动条不进行销毁
    if (options.hasOwnProperty('closeScroll') && options.closeScroll) {
      return
    }
    el.horizontalScroll.destory()
  }
}

export const directiveVue2 = {
  mounted(el, binding) {
    const options = binding.value || {}
    // 关闭滚动条
    if (options.hasOwnProperty('closeScroll') && options.closeScroll) {
      return
    }
    const tableBodyWrapper = el.querySelector('.el-table__body-wrapper .el-scrollbar__wrap')
    if (!tableBodyWrapper) {
      return
    }
    const bottomIsVisibleObserverEl = document.createElement('div')
    bottomIsVisibleObserverEl.style.transform = 'translateY(-12px)'
    el.appendChild(bottomIsVisibleObserverEl)

    const scroller = new Scroller(tableBodyWrapper, bottomIsVisibleObserverEl, options)
    el.appendChild(scroller.dom)
    el.horizontalScroll = scroller

    if (options.hasOwnProperty('mode') && options.mode === 'hover') {
      scroller.hideBar()
      el.addEventListener('mouseover', scroller.showBar.bind(scroller))
      el.addEventListener('mouseleave', scroller.hideBar.bind(scroller))
    } else {
      scroller.showBar()
    }
  },
  unmounted(el, binding) {
    const options = binding.value || {}
    // 已关闭的滚动条不进行销毁
    if (options.hasOwnProperty('closeScroll') && options.closeScroll) {
      return
    }
    el.horizontalScroll && el.horizontalScroll.destory()
  }
}

表格的封装已经结算了下面我们来看下要怎么去使用

<template>
  <div>
    <!--  表格组件  -->
    <table-list v-loading="pageLoading"  ref="table" row-key="id" :table="tableArr" :list-base="listInfo" @currentChange="changePage">
    </table-list>
  </div>
</template>

<script>
// 导入表格
import tableList from '@/views/templatePage/tableList'
// 接口api请换成你们自己的
import { getCustomerDayAct } from '@/api/tiktok'
export default {
  name: 'table',
  components: { tableList },
  props: {},
  data() {
    return {
      // 表格信息
      tableArr: {
        // 是否自定排序(需要排序是添加)
        defaultSort: { prop: 'totalMonthTalentNum', order: 'descending' },
        // 选择排序(需要排序是添加)
        sort: { actionChange: this.relateSort },
        // 是否多选(多选时添加)
        selection: { actionChange: this.selectChange },
        // 表头
        head: [],
        // 列表
        list: []
      },
      // 分页信息
      listInfo: { page: 1, size: 10, total: 0 },
      // 页面加载
      pageLoading: false,
      // 排序字段
      searchSort: {
        totalMonthTalentNumSort: 'DESC'
      },
      // 打款方式
      payMethodMap: {
        1: '微信'
      },
      // 多选
      multipleSelection: []
    }
  },
  created() {
    this.getTableHead()
    this.getCustomerDayActPort()
  },
  methods: {
    // 分页数据
    changePage(val) {
      this.listInfo.page = val
      this.getCustomerDayActPort()
    },
    // 列表选中项
    selectChange(val) {
      this.multipleSelection = val
    },
    // 获取表格头部
    getTableHead() {
      const tableHead = [
        { label: '运营经理', props: 'operationsManagerName', minWidth: 150, show: true },
        { label: '打款方式', props: 'payMethod', columnType: 'dictionary', dictionary: this.payMethodMap, show: true, minWidth: 100 },
        { label: '累计月交易达人数', props: 'totalMonthTalentNum', columnType: 'underline', action: this.dailyActivityDetailsHandle, sortable: 'custom', minWidth: 150, show: true }
      ]
      this.tableArr.head = tableHead.filter(item => item.show)
    },
    // 排序
    relateSort(row) {
      this.searchSort = {}
      if (row.order) {
        this.searchSort[row.prop + 'Sort'] = row.order === 'descending' ? 'DESC' : 'ASC'
      }
      this.searchHandle()
    },
    // 打开详情弹出框
    dailyActivityDetailsHandle() {},
    // 获取搜索参数
    getSearchParam() {
      const searchParam = { pageNo: this.listInfo.page, pageSize: this.listInfo.size, ...this.searchSort }
      const param = {}
      // 排除不需要的参数
      const excludeList = ['timeRange']
      Object.keys(searchParam).forEach(item => {
        if (!excludeList.includes(item) && (searchParam[item] || this.utilsIsNumber(searchParam[item]))) {
          param[item] = searchParam[item]
        }
      })
      return param
    },
    // 判断是否为数字(建议封装到utils包中)
    utilsIsNumber(row) {
      return Object.prototype.toString.call(row) === '[object Number]'
    },
    // 获取客户日活数据
    async getCustomerDayActPort() {
      if (this.pageLoading) return
      this.pageLoading = true
      const param = this.getSearchParam()
      const { data: { data: result }} = await getCustomerDayAct(param).catch(() => { this.pageLoading = false })
      this.pageLoading = false
      if (!result) return
      // 多选是是否禁止选择 selectable false-禁止选择 true-允许选择(多选是添加)
      this.tableArr.list = result.records.map(item => ({ ...item, selectable: false }))
      this.listInfo.total = result.total
    }
  }
}
</script>

<style scoped>
</style>

展示一下其他内容应用表格组件完成的效果 image.png