做 el-table 的列宽自适应、让表格列根据内容自动撑满、再顺手解决内容换行,这三件事凑在一起,基本就是一个前端在后台管理系统里绕不开的一道坎。我做过不下十个中后台项目,几乎每个项目都会有人在群里问:"这列怎么这么窄"、"为什么右边空了一块"、"能不能让文字换行别省略号"。表面上看是三个独立的小需求,实际上它们背后是同一件事——Element UI 的表格用的是table-layout: fixed,列的宽度由<colgroup>里的<col>决定,跟内容没关系。你写多少 CSS 去让.cell撑开,都撑不开列本身。
这篇文章我想把这套东西从头捋一遍:先讲清楚 el-table 的宽度分配规则到底是什么,再说三种把"内容宽度"变成"列宽"的可行方案,然后是换行相关的坑、合并单元格场景的例外、滚动条和容器缩放的翻车点,最后给一个可以直接抄走的封装。适合已经用过 el-table 但被列宽折磨过的同学,也适合刚接手一个中后台项目、正在被产品追着改表格的人。
1. el-table 的列宽从来不是内容说了算
很多人第一次调 el-table 列宽,直觉是去改 CSS:给.cell加个width: auto,或者给td加min-width。改完发现毫无变化,于是开始怀疑人生。要理解为什么,得先看一眼 el-table 到底渲染出了什么。
1.1 从 table-layout: fixed 说起
el-table 最终渲染出来的结构,简化后大概是这样:
<div class="el-table__header-wrapper"> <table style="table-layout: fixed"> <colgroup> <col width="120"> <col width="200"> <col width="80"> </colgroup> <thead>...</thead> </table> </div> <div class="el-table__body-wrapper"> <table style="table-layout: fixed"> <colgroup>同上</colgroup> <tbody>...</tbody> </table> </div>关键就是table-layout: fixed和<col>上的width。在 fixed 布局下,浏览器完全忽略单元格内容的宽度,只看第一行(表头行)和各列的<col>声明,剩下的等分。这就是为什么你在.cell上写什么宽度都白搭——.cell只是<td>里的一个 div,它的宽度被<td>限制死,而<td>的宽度又被<col>限制死。
所以所有"内容撑开列宽"的需求,本质上只有一个解法:把内容算出来的宽度,通过width属性喂回给el-table-column。没有第二种办法。
那 el-table 自己又怎么决定<col>上写多少?这就是下面的分配逻辑。
1.2 width、min-width、什么都不写,差别到底在哪
Element UI 内部的updateColumnsWidth方法,大致做了这么几件事:
先遍历所有列,累加一个bodyMinWidth:
- 如果这列写了
width,把width加进去; - 否则如果写了
min-width,把min-width加进去; - 否则按一个默认值 80 加进去。
同时把所有"没有写 width"的列收集到一个flexColumns数组里。然后比较bodyMinWidth和表格容器的实际宽度:
- 容器够宽:剩余宽度(容器宽 - bodyMinWidth - 竖向滚动条宽度)按各列
min-width的比例,分给flexColumns里的列。如果flexColumns只有一列,这一列直接吃掉全部剩余宽度。 - 容器不够宽:所有列收缩到各自的
min-width,超出的部分交给横向滚动条。
这个逻辑解释了几个常见现象。第一,只写min-width不写width的列,永远会"跟着容器长大",因为它是弹性列。第二,写了width的列,无论容器多宽多窄,它就是那么宽,既不长大也不缩小。第三,如果所有列都写了固定 width,flexColumns是空的,剩余空间没有任何列来承接,表格右边就会空出一块——很多人遇到的"表格右侧留白"就是这个原因。
这里还有两个容易忽略的细节。第一个是默认值 80:一列什么都不写,它参与计算的基础宽度是 80px,不是 0。你可以把它理解成隐式的min-width="80"。第二个是show-overflow-tooltip会给单元格的.cell加一个min-width: 50px,在列特别窄的时候会影响实际表现。
1.3 width 可以不带 px,但千万别写百分比
热词里有个el-table width min-width 可以不带px,这个是对的。Element UI 内部有个parseWidth:
const parseWidth = (width) => { if (width !== undefined) { width = parseInt(width, 10); if (isNaN(width)) width = null; } return width; };所以width="120"和width="120px"效果完全一样。:width="120"也一样。这个设计挺方便,写起来省事。
但这里有个大坑:parseInt会把"50%"解析成50。也就是说,你写了width="50%",el-table 不会理解成"占一半",而是当成 50px。表格会变得又窄又难看,而且不报错、不警告,特别难排查。所以列宽只能用像素值或者不写,百分比方案在 el-table 上是行不通的。
注意:如果你是从别的表格库(比如 Ant Design 的 Table)迁过来的,那边
width支持百分比语义,直接搬配置过来会踩这个坑。
2. 把内容宽度变成列宽的三种做法
前面说了结论:想按内容自适应,就得自己算宽度。算的方式有三种,精度和性能各不相同,我按推荐程度从低到高讲。
2.1 方案一:什么都不写,只在最后加一列弹性列
这是最省事的做法,虽然它不叫"内容自适应",但能解决 60% 的"右边留白"和"列太挤"的问题。
具体操作是把最重要的、内容最长的列(比如"备注"、"描述"、"名称")设成min-width,其余列写固定width。这样容器宽的时候,剩余空间全给这一列;容器窄的时候,这一列收缩到 min-width,其他列不变。
<el-table :data="rows" style="width: 100%"> <el-table-column prop="id" label="ID" width="80" /> <el-table-column prop="name" label="名称" width="160" /> <el-table-column prop="desc" label="描述" min-width="240" /> <el-table-column prop="time" label="时间" width="180" /> </el-table>这套配置的好处是零成本、无副作用、不会因为数据变化抖动。缺点是它只保证"撑满",不保证"按内容",如果那一列的内容普遍很短,你依然会觉得它太宽。所以这招适合内容长度分布比较均匀的场景,比如描述类文本。
2.2 方案二:Canvas measureText 预计算,快且够用
真要让列宽贴合内容,最实用的是 Canvas 的measureText。它的原理是:创建一个离屏 canvas,设置好和目标文本一致的字体,让浏览器按同样的字体度量算出这段文本的宽度。这个计算不涉及 DOM,速度极快。
const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const FONT_NORMAL = '14px -apple-system, "PingFang SC", "Microsoft YaHei", Arial, sans-serif'; const FONT_BOLD = 'bold 14px -apple-system, "PingFang SC", "Microsoft YaHei", Arial, sans-serif'; function measureText(text, font = FONT_NORMAL) { ctx.font = font; return ctx.measureText(text == null ? '' : String(text)).width; }拿到宽度之后,还要加上各种"额外占用",否则算出来的列永远比实际需要的窄一点点:
| 占用项 | 典型值 | 说明 |
|---|---|---|
| 单元格左右 padding | 20 ~ 24px | Element UI 默认.cell左右各 10px,部分版本是 12px,以 F12 实测为准 |
| 单元格右边框 | 1px | border-right |
| 排序图标 | 约 24px | 列开了sortable才需要 |
| 筛选图标 | 约 24px | 列开了filters才需要 |
| 省略号预留 | 约 12px | 列可能被截断时留一点余量 |
| 安全余量 | 5% ~ 10% | 应对浏览器缩放、字体回退导致的偏差 |
然后把表头文字(用 bold 字体)和整列所有单元格文字(用 normal 字体)分别量一遍取最大值:
function calcColumnWidth(col, rows, opts) { const { maxWidth = 400, extra = 24 } = opts; const headerW = measureText(col.label, FONT_BOLD); let bodyW = 0; for (const row of rows) { const w = measureText(row[col.prop]); if (w > bodyW) bodyW = w; } const raw = Math.max(headerW, bodyW) + extra; return Math.min(maxWidth, Math.ceil(raw * 1.05)); }这里有几个必须处理的边界,我一个个说。
第一是字体必须对齐。canvas 里写的字体串要和表格实际渲染的一致,包括字号、字重、字体族顺序。表头默认是粗体(th的浏览器默认样式,Element UI 没有重置),所以表头要用 bold 测。如果你的项目里全局改过字体(比如引入了思源黑体),记得同步。
第二是必须设上限。一列"备注"里如果有一条 800 字的长文本,按内容算出来的宽度能到 3000px,直接把整个表格撑爆。所以一定要有maxWidth,超出就交给换行或者 tooltip。我一般设 320 ~ 400px,具体看业务,列表页设小一点,详情页可以放宽。
第三是采样。如果表格有 5000 行、12 列,那就是 6 万次measureText。实测在主流机器上大概 30 ~ 80ms,能接受但不算无感。真要优化,就做两层:一是采样,每列只测前 100 行加随机 100 行;二是缓存,用一个Map把${prop}::${value}作为 key 缓存测量结果,同一份数据反复渲染时就不会重复算。
第四是measureText的精度受页面缩放影响。如果用户把浏览器缩放调到 125%,canvas 量出来的值和实际渲染会有几个像素的偏差。这就是为什么我建议乘一个 1.05 的系数。想更精确也不是不行,但那点精度不值得花这个代价。
2.3 方案三:DOM 测量,准但慢
DOM 测量的思路是把文本塞进一个隐藏的span,读它的offsetWidth:
let ghost = null; function measureByDom(text, font) { if (!ghost) { ghost = document.createElement('span'); ghost.style.cssText = 'position:absolute;left:-9999px;top:-9999px;visibility:hidden;white-space:nowrap;pointer-events:none;'; document.body.appendChild(ghost); } ghost.style.font = font; ghost.textContent = text == null ? '' : String(text); return ghost.offsetWidth; }它的优势是"所见即所得"——浏览器怎么渲染文本,量出来就是多宽,不用担心字体回退、字距调整、连字这些 canvas 度量对不上的细节。对于中文混排英文、数字特别多的表格,DOM 测量的结果往往比 canvas 更贴合。
代价也很明显:每次读offsetWidth都会强制浏览器进行一次同步布局(forced reflow)。如果你在一个循环里"写文本 → 读宽度 → 写文本 → 读宽度",那就是典型的 layout thrashing。我做过的实测是,同样 5000 行 × 10 列,DOM 方案比 canvas 方案慢 5 到 8 倍,在低端机上能明显感觉到卡顿。
所以 DOM 测量只适合两种场景:列数和数据量都不大(比如 20 行以内),或者你需要"点一下按钮手动触发一次精确自适应"。真要在列表页用,记得把读写分离开——先批量设置所有文本,再批量读一遍宽度。
2.4 方案四:读渲染后的真实宽度,但有前提
还有一种做法是等表格渲染完,直接去量已经渲染出来的.cell。这条路能走通,但有很多前提。
首先,table-layout: fixed下<td>的宽度是被<col>锁死的,所以.cell的offsetWidth等于列宽,没有意义。有意义的是.cell的scrollWidth——它反映的是内容在不受限情况下的自然宽度。只有当.cell的overflow是visible或者内容处于nowrap状态时,scrollWidth才准确。
await nextTick(); const cells = document.querySelectorAll('.my-table .el-table__body tr:first-child .cell'); const widths = Array.from(cells).map(el => el.scrollWidth);这个方案的适用场景很窄:表格已经渲染完、内容不再变化、你只需要一次性对齐。我一般把它用在"导出前预览"或者"用户手动点击自适应按钮"这种低频操作上。日常渲染用 canvas 就好。
综合下来我的选择是:默认用 canvas 测量 + 采样 + 上限 + 缓存,特殊场景保留一个手动触发 DOM 精确测量的入口。
3. show-overflow-tooltip 和内容换行只能二选一
换行这件事,我见过最多的错误做法是:既开了show-overflow-tooltip,又写了一堆 CSS 想让内容换行。结果就是文字确实换行了,但鼠标悬浮上去还会弹一个 tooltip,tooltip 里显示的是完整的单行文本,跟换行后的多行内容完全重复,用户看了很懵。
3.1 show-overflow-tooltip 到底做了什么
当你在列上写了show-overflow-tooltip,Element UI 会给这个单元格渲染出来的.cell加上el-tooltip这个 class,对应的样式是:
.el-table .cell.el-tooltip { white-space: nowrap; min-width: 50px; }再加上.cell本身的overflow: hidden; text-overflow: ellipsis;,就形成了"单行、超出省略、悬浮看全文"的效果。注意white-space: nowrap是加在 class 上的,不是内联样式,理论上你写一条权重更高的 CSS 就能覆盖。但覆盖之后 tooltip 的监听还在,悬浮照样弹。所以正确的结论是:要换行,就把show-overflow-tooltip关掉,别指望两者共存。
3.2 单元格换行和表头换行的分开处理
如果你的诉求是"内容太长就换行显示,把行高撑开",配置和样式是这样:
<el-table :data="rows" class="wrap-table"> <el-table-column prop="desc" label="描述" min-width="260" /> </el-table>.wrap-table .el-table__body-wrapper .cell { white-space: normal; word-break: break-word; overflow-wrap: anywhere; line-height: 20px; }word-break和overflow-wrap这两个属性的差别很多人搞不清楚,我列个表:
| 属性值 | 行为 | 适用场景 |
|---|---|---|
word-break: normal | 只在空格和连字符处断行 | 英文为主的普通文本 |
word-break: break-all | 任意字符处都能断 | 长串 ID、哈希值、无空格 URL |
overflow-wrap: break-word | 优先正常断行,实在放不下才断单词 | 中英混排的通用选择 |
overflow-wrap: anywhere | 类似 break-word,但断行后的最小内容宽度会更小 | 与min-width配合时更稳 |
实际项目中,如果列宽是我们自己算的、并且算的时候已经把最长内容考虑进去了,其实很少触发断词。真正需要anywhere的是那些"用户自己输入的备注"或者"拼接出来的长 ID"。
表头换行是另一回事。表头的.cell默认也是不换行的,而且表头文字普遍比较短,容易忽略。如果你有一列叫"最近一次登录时间(含时区)",它在窄列里会被截断。加一条:
.wrap-table .el-table__header-wrapper .cell { white-space: normal; word-break: break-word; }但表头换行有个副作用:表头行会变高,而且表头的<th>高度是独立计算的,如果和 body 的行高对不齐,在固定列场景下会错位。这块放到后面讲。
还有一种更可控的表头换行方式,是直接用render-header或者 header slot 手写断行:
renderHeader(h, { column }) { return h( 'div', { style: 'line-height: 18px' }, column.label.split('\n').flatMap((part, i) => (i === 0 ? [part] : [h('br'), part])) ); }用\n在 label 里标好断点,这样表头什么时候换行完全由你控制,不用依赖 CSS 的自动断行。我比较推荐这种方式,因为它的结果稳定,不会因为容器宽度变化而跳来跳去。
3.3 换行之后紧接着的三个连锁问题
第一个是固定列错位。Element UI 2.x 里的fixed列,是把整个表格又渲染了一份,用绝对定位盖在原表格左边或者右边的。<el-table__fixed>里面有独立的 header wrapper 和 body wrapper,行高需要和主表同步。内容换行之后行高变了,主表先重排,固定列如果没跟上,就会出现"左边固定列的行和右边内容行错开半行"的经典现象。解决办法是换行相关的样式改完之后,调一次this.$refs.table.doLayout()。
第二个是行高不一致导致的 hover 背景错乱。.el-table__row的高度本来是靠内容撑的,但如果你在row-style里返回了固定height,换行后的内容就会溢出被裁掉。所以用换行方案时,row-style里别写height和line-height。
第三个是垂直对齐。默认情况下<td>的vertical-align是middle还是top,取决于 Element UI 版本和你的全局样式。换行之后,一行高、一行矮,如果对齐方式是 top,短内容会贴着顶部,视觉上不居中。可以在.cell上加display: flex; align-items: center;,但要小心——这会破坏text-overflow: ellipsis的省略号,因为 flex 容器不产生文本溢出。所以只有在确定不需要省略号的表格上再用这招。
4. 合并单元格场景下,前面所有结论都要重新想一遍
span-method是 el-table 里最容易和列宽打架的功能。我见过一个需求:左侧一列"分类"要纵向合并,右侧一列"子项"要横向合并三列。做出来的效果是:合并后的内容全部挤在最左边那一列里,被截得只剩三个字。
4.1 span-method 为什么不改变列宽
因为span-method返回的[rowspan, colspan]只影响<td>上的属性,它不参与<colgroup>的计算,也不在updateColumnsWidth的考虑范围之内。一个colspan="3"的单元格,它的宽度就是这 3 列宽度之和。你没办法通过"让这一格内容变宽"来让组成它的某一列变宽。
更微妙的一点是:被合并掉的那些<td>不再渲染,但它们在<colgroup>里的列还在。所以视觉上会出现"那三列里只有第一列有内容,后面两列空着"的情况。这也是为什么合并表头看起来总是有点别扭。
4.2 合并列的宽度该怎么定
我的做法是:给合并的起始列一个足够大的min-width,让它能容纳合并区域内最长的那条内容。
比如横跨三列的那一格,最长的内容是"华东区域-上海-浦东新区",量出来大约 180px。如果这格要跨 3 列,那么这 3 列的min-width之和必须大于 180 + padding。分配的时候我会把大头放在第一列,比如min-width设成 120、40、40,而不是均分。
如果是分组表头(嵌套的el-table-column),还有个规则要记住:父列的width会被忽略,只有叶子列生效。也就是说:
<el-table-column label="地址" width="600"> <el-table-column prop="province" label="省" width="200" /> <el-table-column prop="city" label="市" width="200" /> <el-table-column prop="detail" label="详址" width="200" /> </el-table-column>这里父列的width="600"是没用的,实际宽度是 200+200+200。父列的min-width同样不生效。所以分组表头要控制总宽度,得去调每个子列。
还有一个和换行有关的坑:纵向合并(rowspan)之后,这一格的高度是所有被合并行的高度之和。如果其中某一行因为内容换行变得特别高,这一格就会出现大量空白,垂直居中的时候看起来像"内容飘在中间"。这种时候可以考虑给这一格单独设置vertical-align: top,或者干脆控制一下被合并行的内容长度。
5. 滚动条、容器缩放、tab 切换,这三个地方最容易翻车
前面讲的都是静态计算,实际项目里真正让人抓狂的是动态场景。
5.1 那 15px 的滚动条到底吃了谁的宽度
Element UI 在初始化时会通过getScrollBarWidth算出一个滚动条宽度,Chrome/Edge 在 Windows 上通常是 15px 或 17px,macOS 上如果系统开启了"自动隐藏滚动条"就是 0。这个值存在this.gutterWidth里。
当表格出现了竖向滚动条(也就是你设置了height或max-height,且内容超出了),el-table 会在表头那一层的<colgroup>末尾额外加一个gutter列,宽度就是gutterWidth。作用是让表头的总宽度比内容区多出一个滚动条的宽度,这样表头和内容才能左右对齐。
这套机制在正常情况下工作得很好。但只要你在外面多包一层overflow: auto的 div,或者自己改了.el-table__body-wrapper的overflow,就可能出问题:竖向滚动条出现在了外层容器上,而 el-table 认为自己没有滚动条,于是表头不留 gutter。结果就是表头比内容宽出 15px,最右边一列的文字和表头错开。
排查方法很简单,F12 选中.el-table__body-wrapper,看clientWidth和offsetWidth的差值。如果差值是 15 或者 17,说明滚动条在它身上;如果是 0,说明滚动条在别的地方。
const el = document.querySelector('.el-table__body-wrapper'); console.log('scroll width:', el.offsetWidth - el.clientWidth);解决方式就是别在外面套滚动容器,把高度交给 el-table 的height属性去管。
5.2 ResizeObserver 重算列宽,别 observe 错对象
容器宽度变了,列宽得重算。现代浏览器里首选ResizeObserver:
let ro = null; let timer = null; onMounted(() => { ro = new ResizeObserver(() => { clearTimeout(timer); timer = setTimeout(() => { tableRef.value && tableRef.value.doLayout(); }, 120); }); ro.observe(wrapperRef.value); }); onBeforeUnmount(() => { ro && ro.disconnect(); clearTimeout(timer); });三个要点。
第一,observe 外层容器,不要 observe el-table 本身。doLayout()会重新计算列宽、改内部 DOM 的宽度,如果你 observe 的是被改动的元素,就会触发"改宽度 → 触发回调 → 再改宽度"的死循环,控制台会刷出一堆警告。
第二,一定要防抖。侧边栏折叠动画通常是 300ms,动画过程中每一帧宽度都在变,如果每帧都重算列宽,性能直接爆掉。防抖 120ms 是个不错的折中。如果你的列宽是自己用 canvas 算的,那更要注意——重算意味着要重新遍历数据,成本比doLayout()高得多。
第三,动画场景优先用transitionend。如果侧边栏折叠是可预期的(点了按钮就折叠),监听transitionend再调doLayout()比防抖更精确,也不用猜动画多久结束:
sidebarEl.addEventListener('transitionend', () => { tableRef.value && tableRef.value.doLayout(); });5.3 隐藏容器里渲染的表格,宽度全是错的
这是新手最容易踩的坑,而且现象很迷惑:表格在 tab 的第二页里,第一次切过去,列宽全是默认的 80px,刷新一下或者拖动窗口又好了。
原因是:容器在display: none状态下,offsetWidth是 0。el-table 初始化的时候拿到容器宽度 0,bodyMinWidth又大于 0,于是走了"容器不够宽"的分支,所有列都收缩到min-width。等容器真正显示出来,el-table 并不知道要重算。
解决办法按容器类型分:
- el-tabs:监听
tab-click或者用v-model监听 activeName,在nextTick之后调doLayout()。 - el-dialog:监听
@opened事件,因为 dialog 有动画,@open的时候宽度还没到位。 - el-collapse:监听
@transitionend,等折叠动画结束。 - v-show:
v-if的话组件会重新挂载,宽度会重算;v-show只是切display,必须手动doLayout()。
我一般会写一个小工具函数,把这些时机统一起来:
function refreshTable(tableRef) { nextTick(() => { requestAnimationFrame(() => { tableRef.value && tableRef.value.doLayout(); }); }); }requestAnimationFrame这一层是为了确保浏览器已经把新的布局应用上去了。实测下来,少了这一层在部分场景下还是会拿到旧宽度。
6. 封装一个能直接抄的自适应表格
讲了这么多原理,最后给一个我实际项目里在用的封装思路。核心是把"测量"和"渲染"分开:测量逻辑放在一个 composable 里,表格组件只负责把算好的宽度绑上去。
// useAutoColumns.js import { ref, watch } from 'vue'; const canvas = document.createElement('canvas'); const ctx = canvas.getContext('2d'); const cache = new Map(); const FONT = { normal: '14px -apple-system, "PingFang SC", "Microsoft YaHei", Arial, sans-serif', bold: 'bold 14px -apple-system, "PingFang SC", "Microsoft YaHei", Arial, sans-serif' }; function measure(text, font) { const key = font + '::' + text; if (cache.has(key)) return cache.get(key); ctx.font = font; const w = ctx.measureText(text == null ? '' : String(text)).width; cache.set(key, w); return w; } export function useAutoColumns(columns, rowsRef, options = {}) { const { maxWidth = 400, extra = 24, sampleLimit = 200 } = options; const widths = ref({}); function calc() { const rows = rowsRef.value || []; const sample = rows.length > sampleLimit ? rows.slice(0, sampleLimit).concat(rows.slice(-20)) : rows; const next = {}; columns.forEach(col => { if (col.width) { next[col.prop] = col.width; return; } const headerW = measure(col.label || '', FONT.bold); let bodyW = 0; for (const row of sample) { const w = measure(row[col.prop], FONT.normal); if (w > bodyW) bodyW = w; } const raw = Math.max(headerW, bodyW) + extra + (col.sortable ? 24 : 0); next[col.prop] = Math.min(maxWidth, Math.ceil(raw * 1.05)); }); widths.value = next; } watch(rowsRef, calc, { immediate: true, deep: false }); return { widths, recalc: calc }; }然后在页面里这么用:
<el-table ref="tableRef" :data="rows" class="wrap-table"> <el-table-column v-for="col in columns" :key="col.prop" :prop="col.prop" :label="col.label" :width="col.width || widths[col.prop]" /> </el-table>注意这里我把算出来的宽度全部绑到了width上,而不是min-width。原因是:当所有列都固定宽度时,表格不会有多余空间可以分配,这样"列宽完全等于我算出来的值",结果最可控。代价是容器变宽时表格右侧会留白——如果你不能接受留白,就把最长的那个业务列改成min-width,让它去承接剩余空间,其余列用算出来的width。
再补一张参数对照表,方便你按项目调:
| 参数 | 建议值 | 说明 |
|---|---|---|
maxWidth | 列表页 300,详情页 480 | 单列宽度上限,防止长文本撑爆表格 |
extra | 24 | 单元格左右 padding + 边框的补偿 |
sampleLimit | 200 | 采样行数,行数多时按头尾取样 |
1.05 | 1.03 ~ 1.08 | 安全系数,应对字体回退和页面缩放 |
| 表头字体 | bold | 表头默认是粗体,不加 bold 会算窄 |
配合这段 CSS,换行和自适应的效果就都齐了:
.wrap-table .el-table__body-wrapper .cell { white-space: normal; word-break: break-word; overflow-wrap: anywhere; line-height: 20px; } .wrap-table .el-table__header-wrapper .cell { white-space: normal; word-break: break-word; }有一点我要再强调一遍:列表页默认不要开换行。换行会把行高变得不可预测,一页显示的行数就变少了,用户滚动成本变高,而且表格会显得很"松散"。我的一般策略是列表页用show-overflow-tooltip,让每行保持固定高度;只有详情页、审批页这种"要看全"的场景才开换行。这个取舍决定了你后面要不要处理固定列错位、行高同步那一堆麻烦事。
至于doLayout()这个 API,它值得单独记一下:它只做一件事——重新计算列宽并把结果应用到 DOM。它不会重新请求数据,不会重置排序,也不会清空选中项。所以你可以放心地在窗口 resize、tab 切换、侧边栏折叠这些时机里调它。我踩过的一个坑是:有一段时间我以为它会把表格状态重置,所以每次切换都去改:key强制重渲染,结果表格闪一下、滚动位置丢失、用户骂人。后来换成doLayout(),问题全没了。
最后分享一个排查列宽问题的习惯动作:打开 F12,选中任意一个<col>,看它的width属性值,再对照你配置里的width/min-width。如果<col>上的值和你的配置完全对不上,那说明问题出在"分配阶段",去看看是不是所有列都写了固定宽度、或者有列的宽度算成了负数;如果<col>上的值是对的,但视觉上不对,那问题在 CSS 层,去查是不是有别的样式覆盖了.cell的宽度或者box-sizing。这一招能在一分钟内把问题范围缩小一半,比盲目改样式高效得多。