ElementUI 的 table 记录
2021年6月23日...大约 4 分钟
这两次开发的需求都是和表格相关的,做下记录,有些细节怕忘了
单元格内容不换行的实现
做报表的需求遇到,一些表体内容或者表头内容比较长引起折行,折行之后表格整体所需高度增加,好多内容可能就折行了一两个字,不是很美观。做普通表格的话,预留一下数据大体需要的宽度就可以了,但是自定义报表的话你不知道这里将会是什么样的数据,预留太多可能宽了,预留太少可能窄了。要解决这个问题很自然的我们想到,只要知道这一列最宽的那位,将它设置为列宽不就完了🤔,好的现在问题变成了获取本列最长的选手。参赛的选手主要有:
表头宽度 + 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 值,确保数据变化的时候一定会重渲染