ElementUI 的 table 记录

进击的学霸...大约 4 分钟ElementUItable

这两次开发的需求都是和表格相关的,做下记录,有些细节怕忘了

单元格内容不换行的实现

做报表的需求遇到,一些表体内容或者表头内容比较长引起折行,折行之后表格整体所需高度增加,好多内容可能就折行了一两个字,不是很美观。做普通表格的话,预留一下数据大体需要的宽度就可以了,但是自定义报表的话你不知道这里将会是什么样的数据,预留太多可能宽了,预留太少可能窄了。要解决这个问题很自然的我们想到,只要知道这一列最宽的那位,将它设置为列宽不就完了🤔,好的现在问题变成了获取本列最长的选手。参赛的选手主要有:

表头宽度 + padding [ + tip icon 宽度] [ + 排序 icon 宽度]
行1内容宽度 + padding
 .
 .
 .
行n内容宽度 + padding

第一种 计算字符数,以最大的字符数来设置宽度

简单点来实现就是算下折合字符数, 字符数 * 字号 + padding + 容错量 ,基本能保证 98% 的满足需求,剩下 2% 的话,因为列宽可拖动,可以自己手动调节一下,但是这样计算问题蛮多的,一是字符数计算出来的不准,字符的种类太多,不同的字符的显示大小是有区别的,数目计算不准的话误差特别大;二是css样式对宽度有影响;三是直接加容错量可能会导致过宽。

/**
 * @description: 获取数组中最长的字符长度,计算表格列宽的主要函数之一。实际的宽度要在使用的地方根据字号、有无icon组合计算
 * @param {Array} arr 字符串或数字的数组(列名与列数据组成的数组)
 * @return {Number} 这之中最大的字符长度
 */
export function getStrMaxLength(arr: any[]) {
  let maxLen = arr.reduce((acc, item) => {
    if (item) {
      const str = item.toString()
      const char = str.match(/[\u2E80-\u9FFF]/g)
      const charLen = char ? char.length : 0
      const num = str.match(/\d|\./g)
      const numLen = num ? num.length : 0
      const otherLen = str.length - charLen - numLen
      // 中文字符按 1 个长度,数字按 .6 个长度,其他字符按 .6 个长度
      let calcLen = charLen * 1 + numLen * 0.6 + otherLen * 0.6
      if (acc < calcLen) {
        acc = calcLen
      }
    }
    return acc
  }, 0)
  return Math.ceil(maxLen)
}

第二种 使用 DOM 测最大宽

通过 DOM 配合内容所处环境相同的样式可以测到准确的宽度,最后得到的最大宽就不会有过多冗余了

/**
 * @description: 获取最长的文本宽度
 * @param {function} textArr 文本数据列
 * @param {BasicObject} style 文本样式
 * @return {Number} 最大宽度
 */
export function getMaxContentWidth(textArr: (string | number)[], style: BasicObject | undefined = undefined) {
  let tempEl = document.createElement('span')
  tempEl.style.position = 'fixed'
  tempEl.style.left = '0'
  tempEl.style.bottom = '-999px'
  tempEl.style.fontSize = '12px'
  tempEl.style.fontFamily = '"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif'
  if (style) {
    for (const key in style) {
      // 定位禁止配置
      if (['position', 'top', 'right', 'bottom', 'left'].some(val => val === key)) { continue }
      if (Object.prototype.hasOwnProperty.call(style, key)) {
        (tempEl.style as any)[key] = style[key]
      }
    }
  }
  document.body.append(tempEl)
  let maxWidth = 0
  // 使用 getBoundingClientRect 计算最准确,向上取整,抹去小数
  const range = document.createRange()
  // 把每个文本都放进临时元素计算它们之中的最宽的
  for (let i = 0; i < textArr.length; i++) {
    tempEl.innerText = textArr[i] + ''
    range.setStart(tempEl, 0)
    range.setEnd(tempEl, tempEl.childNodes.length)
    const tempElWidth = range.getBoundingClientRect().width
    if (tempElWidth > maxWidth) {
      maxWidth = tempElWidth
    }
  }
  document.body.removeChild(tempEl)
  return Math.ceil(maxWidth)
}

/**
 * @description: 获取最大列宽
 * @param {String} columnHeaderText 列的表头文本
 * @param {Array} contentArr 列的内容文本数组
 * @param {Object} config 列的配置
 * @return {Number} 最大列宽
 */
interface ColumnConfig {
  sortable?: boolean, // 是否有排序
  hasTip?: boolean, // 是否有 tip
  hasFilter?: boolean, // 是否有筛选 icon
  cellPadding?: number // 单元格的 padding
}
export function handleGetTableColumnMaxWidth(columnHeaderText: string, contentArr: (string | number)[], config: ColumnConfig = {}) {
  const {
    cellPadding = 20,
    sortable = false,
    hasTip = false
  } = config
  let contentMaxWidth = getMaxContentWidth(contentArr, {
    fontWeight: 500
  })
  let columnHeaderWidth = getMaxContentWidth([columnHeaderText], {
    fontWeight: 500
  })
  contentMaxWidth += cellPadding
  columnHeaderWidth += cellPadding
  if (sortable) {
    columnHeaderWidth += 24
  }
  if (hasTip) {
    columnHeaderWidth += 18
  }
  let returnWidth = columnHeaderWidth > contentMaxWidth ? columnHeaderWidth : contentMaxWidth
  // 加1像素容错
  returnWidth += 1
  return returnWidth
}

分成两个函数,计算最大宽度的函数是通用公共函数,因为有两个页面的表格都需要计算,所以再封装一个 handleGetTableColumnMaxWidth 便于业务逻辑处直接调用,DOM计算受 css 影响比较多,具体使用还是根据具体业务改进参数配置

列表重渲染的时候排序样式会丢失

因为有做一个单元格内容不换行的需求,所以排序的时候可能会引起表头的重渲染,重渲染会导致排序样式的丢失,使用自己写排序的方式来修

列表没有重渲染,导致表头未更新响应式数据

table 的 column 是循环出来的,列表没有重渲染的时候发现表头的数据已变化但页面上并没有变化。通过给 column 加 key 值,确保数据变化的时候一定会重渲染

评论
  • 按正序
  • 按倒序
  • 按热度