尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

el-table 操作列宽度动态自适应设置方案

el-table 操作列宽度动态自适应设置方案 后台管理系统做得多了你会发现真正让人反复返工的往往不是复杂业务逻辑而是表格里那一列操作。el-table 操作列动态自适应设置这个需求我在四个后台项目里都遇到过从最早的 Element UI 2.x 一路换到 Element Plus代码基本重写了一轮。核心痛点其实很朴素操作列宽度写死了权限少的角色看到一大片空白权限多的角色按钮被挤到换行甚至被裁掉业务一加功能就得回头改像素值。这篇文章就把根据操作项个数动态设置操作列宽度这件事从头拆一遍包括宽度到底该按什么算、缓存与测量怎么取舍、滚动条宽度对固定列的干扰、合并单元格场景下的取值逻辑以及我在真实项目里踩过的坑。前端新手能照着抄出可运行的方案有几年经验的同学可以重点关注第 4 节和第 5 节那几段是文档里基本不会写的部分。1. 操作列宽度为什么总是差一点1.1 写死宽度的三种典型翻车现场先说三种我亲眼见过、也亲手写出来过的翻车方式理解了失败的形态后面的方案才好选。第一种是按最小需求写死。项目初期操作列只有编辑和删除两个按钮随手写了width120看着挺紧凑。过了两个月业务说已提交的单据要加一个撤回按钮于是变成三个按钮120px 塞不下浏览器不会帮你自动加宽它的行为是让el-button换行或者溢出被overflow: hidden裁掉。换行之后行高变了整个表格的纵向节奏全乱裁掉更糟用户根本点不到那个按钮。这种问题的排查成本还特别高因为它只对特定角色、特定状态的某几行数据出现测试同学不一定覆盖到。第二种是按最大需求写死。吃过上面的亏之后人容易矫枉过正直接把宽定到 280px。这回按钮不会挤了但普通角色只能看到查看一个链接右边空出两百多像素的白。表格整体视觉重心偏左一眼看上去就像布局没做完。更麻烦的是响应式场景1300px 的笔记本上横向总宽本来就很紧张操作列还霸占 280px其它业务列被压到文字折行反而更难看。第三种是统一给所有表格用同一个宽度。这个坑最隐蔽。同一个系统里商品列表的操作列是编辑/上下架/删除三个按钮订单列表是详情/发货/关闭/打印四个用户列表只有重置密码一个。如果你在全局封装里写了个默认值 180那么订单列还不够用用户列浪费 150px。这种看起来只是一个数字的问题散落在十几个页面里改起来非常烦。1.2 真正的自变量不是个数而是内容 样式标题里说的是根据操作项个数动态设置宽度这个说法对但不够精确。个数只是最容易拿到的那个因子真正的自变量其实是下面这几个每个操作项的文案长度。查看和查看并审批宽度能差一倍。每个操作项的渲染形态。用link/text类型的按钮左右没有内边距宽度基本等于文字宽用默认default形态的实心按钮左右各 15px 内边距加 1px 边框同样是两个字能宽出 30px 以上。按钮之间的水平间距。Element Plus 里相邻按钮默认margin-left: 12pxElement UI 是 10px这个值会随版本变。单元格自身的内边距。.el-table .cell默认左右各 12pxElement UI 是 10px这部分是你的按钮之外必须预留的。表头label的宽度。这一条最容易被忽略。操作列表头文字一般是操作两个中文字很窄不会成为约束。但有的项目把表头写成操作管理甚至更多操作这时候列宽的约束条件就从按钮组变成了表头按按钮算出来的宽度会导致表头文字被省略号截断。所以我在函数里的输入参数不是简单的count而是一组结构化的操作项描述。个数只是这个数组的length是中间产物不是输入。这里给一个经验判断如果你发现自己的计算公式里只用了count * 固定值那这个方案迟早会在某个页面上翻车因为文案长度和按钮形态的差异会被完全抹平。1.3 什么场景下值得做动态什么场景下别折腾不是所有表格都值得上动态宽度。我的判断标准是操作项在不同用户/不同数据状态下会变化且变化幅度超过一个按钮宽度时才值得做。如果全站所有角色看到的操作按钮完全一致那直接写死一个经过实测的数字最省事性能零开销也没有任何时序问题。反过来只要涉及权限过滤、状态机不同单据状态给不同操作、或者不同业务线共用一套组件那动态计算就是刚需因为你在编译期根本不知道运行时会有几个按钮。还有一个中间场景按钮数量固定但文案会因为国际化切换而变长。比如中文编辑变成英文Edit长度差异不大但删除变成Delete就长了不少日文、德文某些词更长。这种情况我也建议上动态因为按最长语言写死宽度会让中文用户看着很空。2. 把宽度变成一个可计算的函数2.1 一个按钮到底占多宽量化一下要算总宽先得算单元。我把一个操作项的宽度拆成四块组成项Element Plus 默认值Element UI 2.x说明文字实际宽度由字体/字号决定同左中文字宽约等于字号英文约 0.5~0.6 倍字号左右内边距padding: 8px 15pxpadding: 9px 15pxlink/text形态为 0左右边框1px × 21px × 2link/text形态为 0按钮间距margin-left: 12pxmargin-left: 10px第一个按钮无间距再加两块组成列的总宽单元格左右内边距Element Plus 的.el-table .cell是 12px × 2 24pxElement UI 是 10px × 2 20px。安全余量我习惯留 6~10px用来吸收字体渲染差异和小数点取整误差。这个余量非常必要因为不同操作系统、不同浏览器对同一个字的渲染宽度能差 1px 左右。拿一个真实例子算Element Pluslink形态sizesmall字号 12px操作项是编辑、删除、更多三个。文字宽度12px 字号下每个中文字约 12px编辑24px删除24px更多24px。 按钮总宽24 × 3 72pxlink没有 padding 和 border。 间距12 × 2 24px。 单元格内边距24px。 安全余量8px。合计 72 24 24 8 128px。向上取整到 10 的倍数就是 130px。这个数字比很多人凭感觉写的 180 小了不少说明凭感觉往往写宽了。如果换成默认形态的sizesmall按钮每个按钮要加 15 × 2 30px padding 和 2px 边框三个按钮多出 96px总宽直接到 224px得取 230px。同一组文案两种形态差了整整 100px这就是为什么必须把形态纳入计算。2.2 三种实现路线对比与选型建议算宽度这件事落地方式有三种各有取舍。方案原理优点缺点适用场景A. 字典查表法预置每个字符宽度表累加零 DOM 操作同步返回无时序问题需要维护字宽表字体变化会失准字体固定、追求极致性能B. 真实 DOM 测量创建隐藏 span用字体渲染后量getBoundingClientRect精度最高任何字体文案都准有 DOM 开销需要缓存首次渲染前拿不到文案动态、国际化、字体不确定C. CSS 自适应不设宽靠min-width和表格自动分配零 JS操作列会被其它列挤压按钮还是会换行列数很少的简单表格我最终的方案是B 为主A 兜底用隐藏 span 测出单个文案的真实宽度并缓存到Map里下次同字体同文案直接命中缓存如果运行环境拿不到document比如 SSR 服务端渲染阶段退回到一个内置的近似宽表。选 B 不选 A 的原因很实际中文字体在不同系统上差异比想象中大。Windows 的微软雅黑、macOS 的苹方、Linux 上的思源黑体同一个编字在 12px 下的渲染宽度能差 0.5px 左右。单看不多但三个按钮加起来、再乘上小数点取整就有可能出现 1~2px 的偏差视觉上就是按钮贴边或者多余空隙。真实测量没有这个顾虑。选 B 不选 C 的原因更简单操作列是表格里最不希望被压缩的一列。如果交给 CSS 自动分配当其它业务列内容很宽时操作列会被压到按钮换行而当表格总宽不足容器宽时操作列又会被拉得很大。这两种结果都不是我们想要的我们需要的是刚好放得下。2.3 width 和 min-width 到底能不能不带 px这个点被问得特别多直接看结论能但只对纯数字或纯数字加 px 的字符串有效其它单位会被静默截断成错误的值。Element 内部处理列宽用的是一个类似parseInt的逻辑先尝试把传入值转成整数转不动就当没传。所以下面这些写法效果完全一致!-- 三种写法等价最终都是 160 -- el-table-column label操作 width160 / el-table-column label操作 width160px / el-table-column label操作 :width160 /但下面这些就有问题了!-- 危险会被解析成 12而不是 12rem 对应的像素值 -- el-table-column label操作 width12rem / !-- 危险会被解析成 16小数点被丢弃 -- el-table-column label操作 width16.5em / !-- 危险会被解析成 30语义完全变了 -- el-table-column label操作 width30% / !-- 危险解析失败等同于没设置宽度 -- el-table-column label操作 widthauto /min-width走的是同一套解析逻辑行为完全一致。所以我的建议是需要用 JS 动态计算时一律传纯数字也就是:width160别拼单位字符串。传数字还有一个好处你可以在计算函数里直接做Math.ceil和取整到 10 的倍数不用担心字符串拼接。还有一个容易混淆的点width和min-width同时设置时width优先级更高min-width基本被忽略。真正需要弹性的是那些内容宽度不固定的业务列给它们设min-width而不设width让表格把剩余空间按比例分配给它们。操作列这种必须精确的一律用width。补充一条实测结论当所有列都设了width且总和小于表格容器宽度时Element 会把多出来的空间平均分配给各列看起来像是所有列都变宽了一点。这时候操作列也会被撑大如果你不希望这样给任意一列设min-width就能把剩余空间吸走。3. 一套可以直接抄的通用实现3.1 先写宽度计算的核心工具函数整个方案的地基就是这个函数它不依赖任何框架纯 JS可以单独放进utils目录。// utils/actionWidth.js // 文案测量缓存key 是 字体|文案 const textWidthCache new Map(); // 兜底字宽表按 12px 字号估算单位 px const FALLBACK_CHAR_WIDTH { cjk: 12, // 中日韩文字 upper: 7.5, // 大写字母 lower: 6.5, // 小写字母 digit: 6.5, // 数字 other: 6 // 其它符号 }; function fallbackMeasure(text, fontSize) { let total 0; for (const ch of text) { if (/[\u4e00-\u9fa5\u3040-\u30ff]/.test(ch)) total FALLBACK_CHAR_WIDTH.cjk; else if (/[A-Z]/.test(ch)) total FALLBACK_CHAR_WIDTH.upper; else if (/[a-z]/.test(ch)) total FALLBACK_CHAR_WIDTH.lower; else if (/\d/.test(ch)) total FALLBACK_CHAR_WIDTH.digit; else total FALLBACK_CHAR_WIDTH.other; } // 兜底值是按 12px 算的按实际字号等比缩放 return total * (fontSize / 12); } export function measureText(text, fontSize 12, fontFamily ) { if (!text) return 0; // SSR 或者极早期环境没有 document走兜底 if (typeof document undefined) return fallbackMeasure(text, fontSize); const family fontFamily || -apple-system, BlinkMacSystemFont, PingFang SC, Microsoft YaHei, sans-serif; const font ${fontSize}px ${family}; const cacheKey ${font}|${text}; if (textWidthCache.has(cacheKey)) return textWidthCache.get(cacheKey); const span document.createElement(span); span.style.cssText [ position:absolute, left:-9999px, top:-9999px, white-space:nowrap, visibility:hidden, padding:0, border:0, font:${font} ].join(;); span.textContent text; document.body.appendChild(span); const width span.getBoundingClientRect().width; document.body.removeChild(span); textWidthCache.set(cacheKey, width); return width; }这里有几个设计取舍值得说清楚。缓存 key 把完整 font 串拼进去是因为同一个文案在不同字号下的宽度确实不同如果只用文案做 key切换size或者响应式改字号时会命中错误的缓存。隐藏 span 用position: absolute加负坐标而不是display: none因为display: none的元素没有布局盒量出来永远是 0。用visibility: hidden保留布局这是能测出宽度的关键。最后span 用完立刻从 DOM 上移除避免节点堆积。3.2 计算整列宽度的主函数在测量函数之上再封装一层业务语义的宽度计算。// utils/actionWidth.js续 /** * param {Array} actions 操作项数组每项形如 { label, buttonType } * param {Object} options * - size: small | default | large * - variant: link | button 链接形态还是按钮形态 * - label: 表头文案 * - gap: 按钮间距 * - cellPadding: 单元格左右内边距之和 * - safety: 安全余量 * - min: 最小宽度 * - max: 最大宽度 */ export function calcActionColumnWidth(actions [], options {}) { const { size small, variant link, label 操作, gap 12, cellPadding 24, safety 8, min 80, max 420 } options; const fontSizeMap { small: 12, default: 14, large: 14 }; const fontSize fontSizeMap[size] || 12; // 按钮形态才有的内边距和边框 const padX variant button ? 15 : 0; const borderX variant button ? 2 : 0; let buttonsWidth 0; actions.forEach((action, index) { const text typeof action string ? action : (action.label || ); const textWidth measureText(text, fontSize); buttonsWidth textWidth padX * 2 borderX; if (index 0) buttonsWidth gap; }); // 表头文字也可能成为约束条件 const labelWidth measureText(label, 14); const contentWidth Math.max(buttonsWidth, labelWidth); let total contentWidth cellPadding safety; total Math.max(total, min); total Math.min(total, max); // 向上取整到 10 的倍数视觉上更整齐 return Math.ceil(total / 10) * 10; }这里有两个细节我特意加进去了。第一个是Math.max(buttonsWidth, labelWidth)因为列宽的约束来自内容里最宽的那个表头文字和按钮组谁宽听谁的。第二个是向上取整到 10 的倍数纯粹是视觉习惯127和130在表格里看起来不是一个感觉后者更像是设计过的值。max这个上限也很重要。如果某个操作项文案特别长比如从后端拿到的自定义操作名不设上限会算出 600px 这种离谱数值直接把表格撑爆。设个 420 的上限超出部分让它自然溢出配合show-overflow-tooltip处理更好。3.3 在 Vue 组件里接上表格计算函数准备好了接下来是在表格里用起来。关键的思路是永远按全表最大操作项数算宽度而不是按单行算。因为列宽是整列共有的属性不能每行不一样。template el-table :datarows border el-table-column propname label名称 min-width180 / el-table-column propstatus label状态 width100 / el-table-column label操作 :widthactionColumnWidth fixedright aligncenter template #default{ row } el-button v-foritem in resolveActions(row) :keyitem.key link sizesmall :typeitem.type || primary clickitem.handler(row) {{ item.label }} /el-button /template /el-table-column /el-table /template script setup import { computed, ref } from vue; import { calcActionColumnWidth } from /utils/actionWidth; const rows ref([]); // 所有可能的操作项定义带权限标识 const ALL_ACTIONS [ { key: view, label: 查看, perm: detail, type: primary, handler: r openDetail(r) }, { key: edit, label: 编辑, perm: update, type: primary, handler: r openEdit(r) }, { key: revoke, label: 撤回, perm: revoke, type: warning, handler: r doRevoke(r) }, { key: del, label: 删除, perm: delete, type: danger, handler: r doDelete(r) } ]; function resolveActions(row) { return ALL_ACTIONS.filter(item { if (!hasPermission(item.perm)) return false; return stateGuard(item.key, row); }); } // 取全表最长的那一行来算宽度 const actionColumnWidth computed(() { let maxLen 0; let longestLabels []; rows.value.forEach(row { const list resolveActions(row); if (list.length maxLen) { maxLen list.length; longestLabels list.map(i i.label); } }); if (!maxLen) return 100; return calcActionColumnWidth(longestLabels, { size: small, variant: link, label: 操作 }); }); /script这段代码里最容易写错的地方是computed里对rows的遍历。有人会写成遍历ALL_ACTIONS然后取长度那是不对的——因为有权限的人才会看到按钮没权限的角色被过滤掉了按全量数组算出来的宽度一定偏大。必须用真实的resolveActions去过滤才有意义。另一个要注意的是性能。这个computed里面对每一行都调了一次resolveActions如果表格有 200 行、resolveActions里还有复杂的stateGuard判断就会有 200 次调用。实际优化时我会改成只取前若干行做采样或者干脆预先在数据加载时算好一个maxActionCount存在ref里然后computed里只依赖这个数字。下面给出采样版本// 采样计算最多看前 50 行 const SAMPLING_LIMIT 50; function pickLongestActionLabels(list, limit SAMPLING_LIMIT) { let longest []; const end Math.min(list.length, limit); for (let i 0; i end; i) { const actions resolveActions(list[i]); if (actions.length longest.length) { longest actions.map(a a.label); } // 已经达到全部操作项数量不可能更长提前结束 if (longest.length ALL_ACTIONS.length) break; } return longest; }那个提前break很有用。因为操作项总数是有限的一旦采样到的行已经拿到了全部按钮后面的行不可能再多了直接跳出循环。在权限差异不大的数据里通常前几行就能确定最终宽度。3.4 权限变化时宽度要跟着重算computed的依赖收集是自动的所以只要hasPermission读的是一个响应式状态权限一变宽度就会重算。但如果权限信息是挂在某个普通对象上、或者从缓存里同步读出来的就要手动触发。我比较推荐的做法是把当前用户的权限集合做成一个refSet所有判断都从它读。这样不仅在宽度计算上受益整个页面的按钮显隐都能保持一致。切换角色比如管理员用切换视图功能模拟普通用户时只需要替换这个Set表格宽度会自动更新。有个隐藏的坑如果你用了v-if控制整张表格的显隐表格在v-if为 false 时是不存在的computed照样会算但没关系因为它不依赖 DOM。真正依赖 DOM 的是隐藏 span 测量那个在组件卸载后调用会出问题吗不会因为 span 是挂在document.body上的跟组件无关。这也是我把测量函数写成框架无关的原因之一。4. 滚动条和合并单元格带来的连锁反应4.1 表格滚动条宽度是怎么影响操作列的这是我认为整个话题里最容易被忽略、也最容易导致算得对但看着不对的部分。先说现象。当表格内容总宽超过容器宽度时会出现横向滚动条当行数超过容器高度时会出现纵向滚动条。这两个滚动条会吃掉容器的一部分宽度和高度。Element 的表格布局在计算列宽时会把纵向滚动条的宽度内部叫 gutter加给固定列因为固定列覆盖在滚动条上方它需要额外撑开这部分空间才能视觉对齐。结果就是你给操作列设了 130实际渲染出来可能是 136 或 147取决于纵向滚动条的宽度。反过来当数据行数变化导致纵向滚动条出现或消失时操作列的渲染宽度会突变视觉上就是抖了一下。Element 用的是自定义滚动条组件宽度一般比浏览器原生滚动条窄很多通常在 6px 左右原生滚动条约 17px。所以你在调试时如果用一个自建 div 去测offsetWidth - clientWidth量出来的 17 和表格内部的 6 对不上这是正常的不是 bug。要精确拿到表格当前使用的滚动条宽度直接测表格自身的 DOMexport function getTableGutter(tableRef) { const root tableRef?.value?.$el || tableRef?.value; if (!root) return 0; // Element Plus 的滚动容器 const wrap root.querySelector(.el-scrollbar__wrap); if (!wrap) return 0; const rect wrap.getBoundingClientRect(); const style window.getComputedStyle(wrap); const borderLeft parseFloat(style.borderLeftWidth) || 0; const borderRight parseFloat(style.borderRightWidth) || 0; return Math.round((rect.width - borderLeft - borderRight - wrap.clientWidth) * 100) / 100; }getBoundingClientRect().width返回的是带小数的精确值而clientWidth是取整的。两者相减能拿到接近真实的滚动条宽度比单纯用offsetWidth - clientWidth精确一些。拿到这个值之后可以在计算操作列宽度时决定是否加上它const finalWidth computed(() { const base calcActionColumnWidth(longestLabels, opts); // 固定列且存在纵向滚动条时补上 gutter if (isFixedRight hasVerticalScrollbar.value) { return base gutterWidth.value; } return base; });但说实话大多数项目里我不建议去补这个值。原因有三一是补了之后滚动条出现/消失时列宽会跳变反而不如不补稳定二是纵向滚动条宽度很小6px 的差异在视觉上几乎不可感知三是判断当前是否有纵向滚动条本身需要监听scrollHeight和clientHeight还要在窗口resize和data变化时重新判断引入的复杂度不划算。我更推荐的做法是不补但在安全余量里多留一点。把safety从 8 提到 14就足以覆盖滚动条的干扰代码简单得多。4.2 合并单元格时操作列的宽度该怎么取span-method是另一个高频场景。表格里常见的是把同一类别的多行合并成一行显示比如订单分组、按部门汇总。合并之后有个直接影响被合并掉的那些行里的操作按钮不会渲染出来。因为单元格的行合并意味着那几个格子根本不存在template里的内容只在合并后的第一个格子里渲染一次。所以如果你在resolveActions里依赖rowIndex合并后的行为会和你预期不一致。我的处理原则是三条第一操作列永远不参与合并。在span-method里显式判断列索引命中操作列直接返回[1, 1]function spanMethod({ row, column, rowIndex, columnIndex }) { // 最后一列是操作列不参与任何合并 if (columnIndex columnCount - 1) return [1, 1]; if (columnIndex 0) { return mergeFirstColumn(rowIndex); } return [1, 1]; }如果操作列也被合并了那一格里会渲染出多个按钮而且是被合并的某一行对应的按钮集合语义上完全错乱。第二按合并后可见的行来采样宽度。合并之后实际渲染的行变少了采样范围应该跟着缩小。如果合并规则是同一个 orderNo 合并那采样的单位应该是一个订单而不是一行原始数据。这一点上我会把采样的入口改成先按合并键分组取每组的第一行做代表。function pickRepresentativeRows(list, mergeKey orderNo) { const seen new Set(); const result []; for (const row of list) { const key row[mergeKey]; if (seen.has(key)) continue; seen.add(key); result.push(row); } return result; }第三合并会改变行高进而改变纵向滚动条的状态。合并之后行数变少原本超出一屏的表格可能就不超了纵向滚动条消失固定列的宽度随之变化。这个变化本身无所谓但如果你同时开了show-overflow-tooltip可能会出现 tooltip 定位偏移。我的经验是给表格加一个固定的height而不是max-height让滚动条状态稳定下来避免这种抖动。提示max-height会让表格高度随内容变化滚动条频繁出现/消失height固定高度滚动条状态稳定得多。这个取舍在选择时要想清楚。5. 排查速查表和我踩过的那些坑5.1 常见问题速查表把我在项目里遇到过的现象整理成一张表遇到问题可以按现象反查原因。现象可能原因排查方式解决方式按钮换行显示列宽小于按钮总宽控制台选中td看offsetWidth调大safety或检查gap是否与实际不符按钮被裁掉一半列宽够但overflow裁切常见于fixed列检查是否有固定列叠加给操作列设width而非min-width表头操作显示省略号列宽被表头文字撑破量一下 label 实际宽度计算时把labelWidth纳入Math.max列宽算出来比预期大很多文案里混入了空格或不可见字符console.log(JSON.stringify(label))渲染前trim去掉首尾空白宽度在数据加载后跳变rows从空数组变成有数据采样结果变了打日志看computed触发次数首屏给一个稳定的默认宽度固定列右侧有阴影残留列宽变化后固定列重新计算动画未完成拖动窗口宽度复现宽度变化时给表格加key强制重建或用doLayout()频繁切换标签页宽度异常隐藏状态下测量得到 0在隐藏容器里测量测量函数要检查容器可见性不可见时走兜底移动端字体变小后宽度偏大缓存 key 没包含字体命中了旧值查看textWidthCache内容缓存 key 拼上完整 font 串最后一行那个缓存 key 的问题我在一个 H5 项目里真踩过。因为同一个页面在横竖屏切换时会调整根字号测量结果缓存后没失效导致竖屏下按钮之间永远多出一小段空白。排查了快一个小时才想到是缓存的问题。5.2 几个文档里不会写的实操心得心得一宽度取整到 10 的倍数不只是为了好看。设计稿上的间距体系通常是 4 或 8 的倍数列宽如果不是整数和相邻列拼起来在 2K 屏、4K 屏的高 DPI 缩放下容易出现 1px 的错位亮线。取整到 10 之后zoom级别的缩放都不会产生错位。心得二隐藏 span 测量要放在字体加载之后。如果你的项目用了自定义 Web Font字体加载完成前测量得到的是 fallback 字体的宽度加载完成后实际渲染会变宽。解决办法有两个要么在document.fonts.ready之后再触发一次重算要么干脆用系统字体做测量基准。我一般选后者因为后台系统很少用花哨的自定义字体用系统字体测量准得多。心得三给操作列一个稳定的默认宽度避免首屏闪动。数据是异步加载的rows一开始是空数组此时computed会走到if (!maxLen) return 100这个分支渲染 100px。等数据回来后重新算成 130px用户能看到明显的宽度跳变。我的做法是在computed外面再包一层只要还没加载过数据就一直用上次的值或者一个固定的默认值加载完成后才切到真实计算值。const widthSnapshot ref(130); watch(actionColumnWidth, (val) { if (rows.value.length 0) widthSnapshot.value val; });心得四fixedright和aligncenter搭配使用。操作列居中对齐时即使宽度稍微算多了一点左右两边的留白是均匀的观感上比左对齐好很多。左对齐的话多出来的空白全堆在右侧特别明显。这是个视觉技巧成本为零。心得五别给操作列加show-overflow-tooltip。这个属性会把单元格内容包一层ellipsis样式按钮上的文字会被截断并触发 tooltip用户点按钮的时候弹出个提示体验很怪。操作列的溢出应该通过宽度计算解决而不是靠 tooltip 兜底。6. 抽成组合式函数与版本差异处理6.1 把逻辑收进一个 useActionColumn上面这些散落在组件里的代码重复三个页面之后就该抽了。我抽出来的组合式函数大概是这个形态// composables/useActionColumn.js import { computed, ref, watch } from vue; import { calcActionColumnWidth } from /utils/actionWidth; export function useActionColumn(options) { const { actions [], // 全部候选操作项 rows, // 表格数据的 ref size small, variant link, label 操作, min 80, max 420, safety 12, samplingLimit 50 } options; const actionMap new Map(actions.map(a [a.key, a])); function resolveActions(row) { return actions.filter(item { if (item.visible !item.visible(row)) return false; return true; }); } function pickLongest(list) { let longest []; const end Math.min(list.length, samplingLimit); for (let i 0; i end; i) { const current resolveActions(list[i]); if (current.length longest.length) { longest current.map(i i.label); } if (longest.length actions.length) break; } return longest; } const width computed(() { const labels pickLongest(rows.value || []); if (!labels.length) return 100; return calcActionColumnWidth(labels, { size, variant, label, min, max, safety }); }); return { width, resolveActions }; }在使用方代码就只剩几行script setup const rows ref([]); const { width: actionWidth, resolveActions } useActionColumn({ actions: ALL_ACTIONS, rows, size: small, variant: link }); /script这套写法带来一个额外的好处操作项的显隐逻辑被统一收进了visible(row)这个函数里而不是散落在模板的v-if中。这样宽度计算和渲染用的是同一份逻辑永远不会出现算的时候显示三个渲染的时候只有一个的不一致。6.2 Element UI 和 Element Plus 的差异点如果你的项目还在 Element UI 2.x有几个地方要改。按钮间距Element UI 是margin-left: 10pxElement Plus 是12pxgap默认值要跟着调。单元格内边距Element UI 的.el-table .cell是左右各 10pxElement Plus 是 12pxcellPadding从 24 改成 20。按钮形态的命名也不同Element UI 2.x 用typetextElement Plus 里text已被标记废弃推荐用link两者的内边距都是 0宽度计算方式一致只是属性名要换。!-- Element UI 2.x -- el-button typetext sizesmall编辑/el-button !-- Element Plus -- el-button link sizesmall编辑/el-button还有一点是默认字号。Element Plus 的small按钮字号是 12pxdefault是 14pxElement UI 2.x 的small也是 12px但mini已废弃是 12pxdefault是 14px。字号的映射表在calcActionColumnWidth里用一个对象维护切换版本时改这一处就够了。我的建议是把这些差异做成一个preset对象而不是散在代码里export const PRESETS { elementPlus: { gap: 12, cellPadding: 24, sizeFontMap: { small: 12, default: 14, large: 14 } }, elementUI: { gap: 10, cellPadding: 20, sizeFontMap: { small: 12, default: 14, large: 14 } } };calcActionColumnWidth接收一个preset参数内部用展开运算符合并到默认配置里。这样以后换库或者库升级改了样式只动这一个对象。最后分享一个我在实际项目里的体会这套动态宽度方案上线后最大的收益其实不是宽度刚好而是业务加操作按钮时不用再找前端改样式了。以前每次加功能都要提一个操作列宽度调整的小需求现在后端配置里加一项、前端注册一个key宽度自动就对了。这种把重复劳动消灭掉的感觉比省下那几十像素有意义得多。
返回列表