无尘阁日记

无尘阁日记

vue-easytable跨单元格问题一探
2025-08-27

问:

vue-easytable

跨单元格,是这样的,一个单元格的文字如果超出了就跨列显示,每个单元格前边的文字跨列不能超过后边的有文字的单元格显示

你给分析分析怎么做到?

答:

https://happy-coding-clans.github.io/vue-easytable/#/zh/doc

好的,我照着你给的 vue-easytable 官方文档(这个库)来设计,基于它支持的 单元格合并(Cell Span) 能力来模拟“像 Excel 一样向右跨列显示,遇到右侧第一个非空单元格就停止”的效果。
vue-easytable 是通过 cell-span-option 里的 bodyCellSpan 回调来控制某个单元格返回 {rowspan, colspan} 的,这在仓库与示例里都有明确说明与用法示例。(Gitee, CSDN博客, oss.issuehunt.io)

实现思路(与文档一致)

  • <ve-table> 传入 :cell-span-option="{ bodyCellSpan }"

  • bodyCellSpan({ row, column, rowIndex /*, columnIndex?*/ }) 里:

    • 若当前格有值 → 向右数连续“空值”列的个数,用作 colspan,直到碰到第一个非空单元格为止。

    • 若当前格为空 → 判断它是否属于左侧最近的非空格的合并范围;属于就返回 {rowspan:0, colspan:0} 隐藏,不属于就 {1,1} 正常显示。

  • “空”的判定:null/undefined/''/仅空白 都算空。

这就是“允许文字跨过空格,但不能超过后面的有文字单元格”的核心规则;完全通过 colspan 动态计算达成。

代码示例(可直接用)

<template>
  <ve-table
    :columns="columns"
    :table-data="rows"
    :cell-span-option="{ bodyCellSpan }"
    border-x
  />
</template>

<script>
export default {
  data() {
    return {
      columns: [
        { field: 'c1', title: '1', width: 160 },
        { field: 'c2', title: '2', width: 160 },
        { field: 'c3', title: '3', width: 160 },
        { field: 'c4', title: '4', width: 220 },
      ],
      rows: [
        // 例子:与截图类似
        { c1: '4567890昌', c2: '', c3: '4567890昌鱼123455', c4: '4567890昌鱼123455667788' },
        { c1: '清蒸武昌',   c2: '', c3: '',                 c4: '' },
        // ...
      ],
    };
  },
  methods: {
    isEmpty(v) {
      return v === null || v === undefined || String(v).trim() === '';
    },
    bodyCellSpan({ row, column /*, rowIndex */ }) {
      // vue-easytable 的回调里部分版本不带 columnIndex,
      // 用 field 反查当前列索引更稳妥
      const cols = this.columns;
      const colIdx = cols.findIndex(c => c.field === column.field);
      const val = row[column.field];

      // 1) 当前格非空:向右吞并连续空格,直到第一个非空处停止
      if (!this.isEmpty(val)) {
        let colspan = 1;
        for (let k = colIdx + 1; k < cols.length; k++) {
          const f = cols[k].field;
          if (this.isEmpty(row[f])) colspan++;
          else break;
        }
        return { rowspan: 1, colspan };
      }

      // 2) 当前格为空:看是否被左侧最近的非空格“覆盖”
      let left = colIdx - 1;
      while (left >= 0 && this.isEmpty(row[cols[left].field])) left--;
      if (left >= 0) {
        // 计算左侧非空格会合并多少列
        let span = 1;
        for (let k = left + 1; k < cols.length; k++) {
          if (this.isEmpty(row[cols[k].field])) span++;
          else break;
        }
        // 当前列位于合并范围 → 隐藏(返回 0,0)
        if (colIdx <= left + span - 1) {
          return { rowspan: 0, colspan: 0 };
        }
      }
      // 否则就是独立的空格
      return { rowspan: 1, colspan: 1 };
    },
  },
};
</script>

<style>
/* 让合并后的单元格里文字一行展示,像“跨列延伸”的感觉 */
.ve-table .ve-table-td {
  white-space: nowrap;
  text-overflow: clip;
  overflow: hidden;
}
</style>

可选:只有“确实溢出时”才跨列

如果你想更像 Excel —— 只有文字宽度超过本列宽才去吞并右侧空列,可在上面的 非空分支 里引入文本宽度测量(canvas.measureText),把“需要宽度”与“本列+右侧空列总宽度”做对比,直到够容纳或遇到非空停止。伪代码:

getTextWidth(text, font) {
  this._canvas || (this._canvas = document.createElement('canvas'));
  const ctx = this._canvas.getContext('2d');
  ctx.font = font || '14px/1.4 -apple-system,Segoe UI,Roboto,Arial';
  return ctx.measureText(String(text)).width;
},

// 在 bodyCellSpan 的非空分支里:
const need = this.getTextWidth(val);
let colspan = 1, total = +cols[colIdx].width || 120;
if (need > total) {
  for (let k = colIdx + 1; k < cols.length; k++) {
    const f = cols[k].field;
    if (this.isEmpty(row[f])) {
      colspan++; total += (+cols[k].width || 120);
      if (total >= need) break;
    } else break;
  }
}
return { rowspan: 1, colspan };

注意:这种写法要求列设置固定 width,并在列宽变化时(拖拽/窗口缩放)节流重算。


与文档的一致性

  • vue-easytable 支持通过 Cell Span 来做行/列合并;实际用法是给表格传 cell-span-option,其中 bodyCellSpan 返回 {rowspan, colspan} 控制合并范围。(Gitee)

  • 社区示例与问题帖里都用了同样的写法:
    <ve-table> 上传 :cell-span-option="cellSpanOption",并提供 bodyCellSpan({ row, column, rowIndex }),返回 {rowspan, colspan},被合并隐藏的单元格返回 {0,0}。(CSDN博客, oss.issuehunt.io)


如夜话,至此。