实现el-table自适应高度(自定义指令踩坑版)

161 阅读4分钟

问题

使用 ElementUI 的 el-table 组件,默认情况下,当每页的数据过多时,浏览器就会撑开产生纵向的滚动条,这导致用户上下滚动查找表格数据时很不方便(滚到顶部看不到分页栏、滚到底部看不到搜索栏)

因此想把表格改成整个页面不产生纵向滚动条,即可视区域内能看到搜索栏+表格数据+分页栏,数据过多时,让表格自适应高度产生滚动条

思路

想实现上述需求的关键就是让表格自适应高度

首先我自己使用过的方法是通过 window.onresize 动态计算高度,然后赋值给 table 的 height 属性,但是这种方法先且不说能完全实现(如果搜索栏是可以展开收起的,这种情况没法监听window.onresize),光是使用就比较繁琐,先要封装计算方法,然后组件里还得写三步,不方便开发者使用。

export default function() {
  return {
    /**
     * 基于Vue全局的封装 监听table高度变化
     * @param {String} myRef 表格的ref属性,不传默认是'table'。补充:就算默认table,也要确保有ref='table'属性,不然报错
     * @param {Number} customHeight 额外可调整的高度,如页码部分的高度
     */
    tableHeight: (myRef = 'table', customHeight = 0) => {
      this.$nextTick(() => {
        // window.innerHeight 可视区域的高度
        // this.$refs[myRef].$el.getBoundingClientRect().top 该元素到可视区域顶部的距离
        const offsetHeight = this.$refs[myRef].$el.getBoundingClientRect().top
        this.tableHeight = window.innerHeight - offsetHeight - customHeight
        window.onresize = () => {
          this.tableHeight = window.innerHeight - offsetHeight - customHeight
        }
      })
    }
  }
}
import heightUtils from '@/utils/tableHeight'
Vue.prototype.$heightUtils = heightUtils
data() {
  return {
    tableHeight: 0
  }
},
mounted() {
  this.$nextTick(() => {
    this.$heightUtils().tableHeight('table', 120)
  })
}

<template> 
  <table :height="tableHeight" />
</template>

以上方法固然不适用了,那么就换一种实现方式,其实网上有很多相关文章,但是我看百分之八十都是cv的,或者也有可能那些作者只考虑了最简单的表格场景,下面介绍我自己实践后的版本。

自定义指令

自定义指令

封装

directive/table/tableHeight.js

import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'

// 设置表格高度
const doResize = (el, binding, vnode) => {
  // 获取表格Dom对象
  const { componentInstance: $table } = vnode
  // 获取调用传递过来的数据
  const { value } = binding
  // 没有直接终止
  if (!$table) return
  // 获取距底部距离(用于展示页码等信息)
  const customHeight = (value && value.customHeight) || 30
  // 计算列表高度并设置
  const height = window.innerHeight - el.getBoundingClientRect().top - customHeight
  $table.layout.setHeight(height)
  $table.doLayout()
}

export default {
  // 初始化设置
  bind(el, binding, vnode) {
    // 设置resize监听方法
    el.resizeListener = () => {
      doResize(el, binding, vnode)
    }
    // 绑定监听方法到addResizeListener
    addResizeListener(window.document.body, el.resizeListener)
  },
  // 所在组件的 VNode 更新时设置
  // 如果搜索栏是可以展开收起的,用此方法更新
  update(el, binding, vnode) {
    doResize(el, binding, vnode)
  },
  // 销毁时设置
  unbind(el) {
    // 移除resize监听
    removeResizeListener(window.document.body, el.resizeListener)
  }
}

directive/index.js

import tableHeight from './table/tableHeight'

const install = function(Vue) {
  Vue.directive('tableHeight', tableHeight)
}

export default install

main.js

import directive from './directive'
Vue.use(directive)

使用

<template>
  <table v-tableHeight="{customHeight: 76}" />
</template>

踩坑

以上代码的实现思路是网上能搜索到的(vue2/vue3都有,因为大体思路是一致的),但是其实是有坑的!

  1. 没有添加 overflow: auto 导致表格不能滚动,可以在table中添加style样式

  2. 表头没有固定,需要额外写样式 ::v-deep .el-table__header-wrapper{position: sticky; top: 0; z-index: 1;}

  3. 对于设置了 fixed 属性的table列,会出现问题,暂无解决方法

出现以上问题我的猜想是: $table.layout.setHeight(height) 只是单单设置了表格的高度,但其实 table 组件中的的 height 属性传进去之后,源码做了其他处理,比如fixed的高度等。

那么是不是可以把这个自适应高度添加到一个div外壳上,让div的高度自适应,然后table添加 height: 100%

完善

directive/table/tableHeight.js

import { addResizeListener, removeResizeListener } from 'element-ui/src/utils/resize-event'

// 设置表格高度
const doResize = (el, binding) => {
  // 获取调用传递过来的数据
  const { value } = binding
  // 获取距底部距离(用于展示页码等信息)
  const customHeight = (value && value.customHeight) || 76
  // 计算列表高度
  const height = window.innerHeight - el.getBoundingClientRect().top - customHeight
  // 设置高度
  el.style.height = height + "px"
}

export default {
  // 初始化设置
  bind(el, binding) {
    // 设置resize监听方法
    el.resizeListener = () => {
      doResize(el, binding)
    }
    // 绑定监听方法到addResizeListener
    addResizeListener(window.document.body, el.resizeListener)
  },
  // 所在组件的 VNode 更新时设置
  // 页面上搜索表单是可以展开收起的,当展开更多表单搜索时,表格高度没变
  update(el, binding) {
    doResize(el, binding)
  },
  // 销毁时设置
  unbind(el) {
    // 移除resize监听
    removeResizeListener(window.document.body, el.resizeListener)
  }
}

directive/index.js

import tableHeight from './table/tableHeight'

const install = function(Vue) {
  Vue.directive('tableHeight', tableHeight)
}

export default install

main.js

import directive from './directive'
Vue.use(directive)

组件中

<template>
  <div v-tableHeight="{customHeight: 76}">
    <table height="100%" />
  </div>
</template>

总结

大功告成,在自己项目中测了很多种情况,暂时未发现什么问题,如果jym发现我这种方式还存在问题或者有其他更便捷的方法,欢迎评论区交流!