7

前言

vue2+element-ui的项目,el-table本身支持超出宽度显示省略号,悬浮展示tooltip。最近产品非常严肃的提了要求,“你得让客户能复制啊,不然这隐藏还不如不隐藏”。

网上翻了翻,看见官网里有个issue [Bug Report] el-table component adds show-overflow-tooltip, and text in tooltip cannot be copied by mouse selection
image.png
唉,技术更新快,但是旧项目跟不上啊,搞吧!

实现

这个issue下面有个大佬的评论,提供了临时解决办法,尝试之后还有一点小问题,计时器的时差会导致在该出现的时候关闭了,体验不是很好。于是看源码,并且根据大佬利用计时器的思路重新写了一下。

el-tooltipenterable默认就是true,读el-table源码发现这里在使用的时候并没有传这个属性,之所以鼠标不能进入,是因为一个table整体使用一个el-tooltip,并且在table-body里去直接控制了tooltip的操作。

重写覆盖原有的handleCellMouseLeave方法,在离开时延迟100毫秒关闭,让鼠标可以移入,核心改动如下。

  handleCellMouseLeave(event) {
    ...
    tooltip.setExpectedState(false);
    clearTimeout(tooltip.timeoutLeave);
    tooltip.timeoutLeave = setTimeout(() => {
      if (!tooltip.expectedState) {
        tooltip.handleClosePopper();
      }
      tooltip.timeoutLeave = null;
    }, 100);
    ...
  }

此时移入是ok了,但是又有个小问题,如果鼠标快速进入另一个会展示tooltip的单元格,tooltip就会直接消失。是由于这行代码导致的。

tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none');

于是改动了一下handleCellMouseEnter方法,如果tooltip存在那就需要重新打开一下。而且考虑到鼠标移入tooltip的时候,存在路过另一个要展示tooltip单元格的情况,还需要给个路过的计时。

  handleCellMouseEnter(event, row) {
    ...
    if (tooltip.showPopper) {
      clearTimeout(tooltip.timeoutEnter);
      tooltip.timeoutEnter = setTimeout(() => {
        if (!tooltip.expectedState) {
          tooltip.handleClosePopper();
          showTooltip();
        }
        tooltip.timeoutEnter = null;
      }, 100);
      return;
    }
    ...
  }

因为想要全局引入,那加了个功能,还得加个配置(万一产品说有的地方不能让他复制呢)。这里我把配置加在el-table上了,如果想要加在el-table-column上,renderCell方法需要引入支持jsx,我的项目里并没有,如果某位大佬有兴趣可以改下子。(2023年了,感觉没必要。。)

const ElTable = {
  extends: Table,
  props: {
    tooltipEnterable: {
      type: Boolean,
      default: true,
    },
  },
};

朋友们反馈有闪烁的问题,又排查了一下,源代码中这一行就是用来避免闪烁的,也要将它加在showTooltip

tooltip.$refs.popper && (tooltip.$refs.popper.style.display = 'none');

最终完整代码如下

// main.js
import ElTable from '@/components/elTable';

...
Vue.use(ElTable);
// elTable.js
import { Table } from 'element-ui';
import { getCell, getColumnByCell } from 'element-ui/packages/table/src/util';
import { getStyle, hasClass } from 'element-ui/src/utils/dom';

Object.assign(Table.components.TableBody.methods, {
  handleCellMouseEnter(event, row) {
    const { table } = this;
    const cell = getCell(event);

    if (cell) {
      const column = getColumnByCell(table, cell);
      table.hoverState = { cell, column, row };
      const { hoverState } = table;
      table.$emit('cell-mouse-enter', hoverState.row, hoverState.column, hoverState.cell, event);
    }

    // 判断是否text-overflow, 如果是就显示tooltip
    const cellChild = event.target.querySelector('.cell');
    if (!(hasClass(cellChild, 'el-tooltip') && cellChild.childNodes.length)) {
      return;
    }
    // use range width instead of scrollWidth to determine whether the text is overflowing
    // to address a potential FireFox bug: https://bugzilla.mozilla.org/show_bug.cgi?id=1074543#c3
    const range = document.createRange();
    range.setStart(cellChild, 0);
    range.setEnd(cellChild, cellChild.childNodes.length);
    const rangeWidth = range.getBoundingClientRect().width;
    const padding = (parseInt(getStyle(cellChild, 'paddingLeft'), 10) || 0)
        + (parseInt(getStyle(cellChild, 'paddingRight'), 10) || 0);
    if (
      (rangeWidth + padding > cellChild.offsetWidth
          || cellChild.scrollWidth > cellChild.offsetWidth)
        && this.$refs.tooltip
    ) {
      const { tooltip } = this.$refs;
      const { tooltipEnterable } = this.table;
      const showTooltip = () => {
        this.tooltipContent = cell.innerText || cell.textContent;
        tooltip.referenceElm = cell;
        if (tooltip.$refs.popper) {
          tooltip.$refs.popper.style.display = 'none';
        }
        tooltip.doDestroy();
        tooltip.setExpectedState(true);
        this.activateTooltip(tooltip);
      };
      if (tooltipEnterable && tooltip.showPopper) {
        clearTimeout(tooltip.timeoutEnter);
        tooltip.timeoutEnter = setTimeout(() => {
          if (!tooltip.expectedState) {
            tooltip.handleClosePopper();
            showTooltip();
          }
          tooltip.timeoutEnter = null;
        }, 100);
        return;
      }

      showTooltip();
    }
  },
  handleCellMouseLeave(event) {
    const { tooltip } = this.$refs;
    if (tooltip) {
      tooltip.setExpectedState(false);
      const { tooltipEnterable } = this.table;
      if (tooltipEnterable) {
        clearTimeout(tooltip.timeoutLeave);
        tooltip.timeoutLeave = setTimeout(() => {
          if (!tooltip.expectedState) {
            tooltip.handleClosePopper();
          }
          tooltip.timeoutLeave = null;
        }, 100);
      } else {
        tooltip.handleClosePopper();
      }
    }
    const cell = getCell(event);
    if (!cell) return;

    const oldHoverState = this.table.hoverState || {};
    this.table.$emit(
      'cell-mouse-leave',
      oldHoverState.row,
      oldHoverState.column,
      oldHoverState.cell,
      event,
    );
  },
});

/**
 * @description 扩展el-table,实现当showOverflowTooltip时,鼠标可移入tooltip功能
 * @prop {Boolean} tooltipEnterable 仅在列属性showOverflowTooltip为true时生效,鼠标是否可进入到 tooltip 中,默认为true
*/
const ElTable = {
  extends: Table,
  props: {
    tooltipEnterable: {
      type: Boolean,
      default: true,
    },
  },
};
export default (Vue) => {
  Vue.component('ElTable', ElTable);
};

新人写文章,希望老师多多指点,多多交流。


安昊
600 声望15 粉丝

过去之时已过去,将来之日正将来。