安装

已封装到npm, 可直接安装使用:

$ npm install ex-table-column --save
$ yarn add ex-table-column

github地址:
https://github.com/mnm1001/ex-table-column

需求

项目中使用element-uiel-tableel-table-column,
需求上需要实现列宽根据内容自适应.

实现方案

element-ui 的table组件本身有着强大的功能, 所以我们期望保留其现有功能, 然后进行扩展. 基于这样的前提, 继承el-table-column组件进行扩展是一个比较合适的方案.

Vue组件继承

vue 提供 extends 以扩展已有组件, 所以我们很容易实现一个扩展于 el-table-column 的组件 ex-table-column:

import { TableColumn } from 'element-ui';

export default {
  name: 'ExTableColumn',
  extends: TableColumn, // 指定继承组件
};

介入el-table-columnwidth计算

element-ui 的 table-column.js 源码中, 我们发现以下一些内容和width计算有关.

computed: {
    realWidth() {
      return parseWidth(this.width);
    },

    realMinWidth() {
      return parseMinWidth(this.minWidth);
    },
  },
  methods: {
    setColumnWidth(column) {
      if (this.realWidth) {
        column.width = this.realWidth;
      }
      if (this.realMinWidth) {
        column.minWidth = this.realMinWidth;
      }
      if (!column.minWidth) {
        column.minWidth = 80;
      }
      column.realWidth = column.width === undefined ? column.minWidth : column.width;
      return column;
    },
  },
  created() {
    const chains = compose(this.setColumnRenders, this.setColumnWidth, this.setColumnForcedProps);
    column = chains(column);

    this.columnConfig = column;
  },

我们的实现思路是Table先渲染, 计算当列内容的宽度中的最大值, 再将最大值赋予列宽然后刷新. 所以:

  • 无法在created中介入, 因为此时Table还没有mouted,
  • 若在methods中介入的话, 我们还需要手动刷新一次组件以触发新的width, 也不合适,
  • 所以修改computed是相对合适的, 因为computed所依赖的值发生变化以后会自动触发组件的更新.

计算内容宽度并更新列宽

Table组件mounted后遍历当列内容, 获取最大值. 然后将此值存入dataautoWidth中, 并使autoWidth介入到realMinWidthcomputed计算中, 这样就简单的实现了自动列宽的功能.

data() {
    return {
      autoWidth: 0,
    };
  },
  computed: {
    realMinWidth() {
      return this.autoWidth;
    },
  },
  mounted() {
    let cells = window.document.querySelectorAll(`td.${this.columnId} .cell`);
    const autoMinWidth = max(map(cells, item => item.getBoundingClientRect().width));
    this.autoWidth = autoMinWidth;
  },

完善

至此我们已基本实现自动列宽功能, 然而还有一些问题需要完善:

兼容

我们其实期望不去改变el-table-column的原有特性, 而这里直接覆盖realMinWidth的值稍显粗暴, 所以我们可以通过传入一个名为autoFixprop来决定是否启用自动内容列宽功能, 若autoFixfalse, 则保留el-table-column原有的realMinWidth计算逻辑:

props: {
    autoFit: {
      type: Boolean,
      default: false,
    },
  },
  data() {
    return {
      autoWidth: 0,
    };
  },
  computed: {
    realMinWidth() {
      if (this.autoFit) {
        return parseMinWidth(max([this.minWidth, this.autoWidth]));
      }
      return parseMinWidth(this.minWidth);
    },
  },
灵活性

el-table-column有复杂的solt内容, 我们可能期望指定某一个element来计算列宽, 所以可以传入一个fitByClass的属性来指定计算列宽所依赖的element:

props: {
    fitByClass: {
      type: String,
      default: 'cell',
    },
  },
  mounted() {
    let cells = window.document.querySelectorAll(`td.${this.columnId} .${this.fitByClass}`);
    if (isEmpty(cells)) {
      cells = window.document.querySelectorAll(`td.${this.columnId} .cell`);
    }
    const autoMinWidth = max(map(cells, item => item.getBoundingClientRect().width));
    this.autoWidth = autoMinWidth;
  },
分页更新

当Table带有分页时, 切换分页不会重新触发mounted, 但Table的列内容的最大宽度可能需要重新计算, 所以需要在updated中重新计算autoWidth:

methods: {
    updateAutoWidth() {
      let cells = window.document.querySelectorAll(`td.${this.columnId} .${this.fitByClass}`);
      if (isEmpty(cells)) {
        cells = window.document.querySelectorAll(`td.${this.columnId} .cell`);
      }

      const autoMinWidth = max(map(cells, item => item.getBoundingClientRect().width));
      if (this.autoWidth !== autoMinWidth) {
        this.autoWidth = autoMinWidth;
      }
    },
  },
  updated() {
    this.updateAutoWidth();
  },
  mounted() {
    this.updateAutoWidth
  },

最后

中间代码有所精简, 完整源码可以参考 ExTableColumn.js .
已封装到npm, 可通过npm install ex-table-column --save安装使用.