表格合并单元格(vue3)

222 阅读5分钟

table

<!-- 表格标签 边框 宽 高 水平的对齐方式left|center|right 单元格与内容之间的距离 单元格与单元格之间的距离 -->
<table border="1" width="500" height="200" align="left" cellpadding="10" cellspacing="0">
  <!-- 表格标题标签 -->
  <caption>表格标题</caption>
  <!-- 表格行标签 -->
  <tr>
    <!-- 表格表头单元格标签 -->
    <th>姓名</th>
    <th>年龄</th>
  </tr>
  <tr>
    <!-- 表格普通单元格标签 -->
    <td>Tom</td>
    <td rowspan="2">18</td> <!-- 跨行合并rowspan -->
  </tr>
  <tr>
    <!-- 表格普通单元格标签 -->
    <td>Jerry</td>
    <!-- <td>16</td> --> <!-- 跨行合并,删除被合并行 -->
  </tr>
  <tr> 
    <!-- 表格普通单元格标签 --> 
    <td colspan="2">other</td>  <!-- 跨列合并colspan -->
    <!-- <td colspan="2">other</td> -->  <!-- 跨列合并,删除被合并列 -->
  </tr>
</table>

el-table

1. 固定合并span-method

<el-table
  :span-method="objectSpanMethod"
  ...
>
  ...
</el-table>

// 当前行 当前列 当前行号 当前列号
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  if (columnIndex <= 3) {
    if (rowIndex % 2 === 0) {
      return {
        rowspan: 2,
        colspan: 1,
      };
      // return [2, 1];
    } else {
      return {
        rowspan: 0,
        colspan: 0,
      };
      // return [0, 0];
    }
  }
}

2. 动态合并

2.1 无序号

<el-table
  :data="tableData"
  :span-method="objectSpanMethod"
>
  <el-table-column
    prop="grade"
    label="年级"
  />
  <el-table-column
    prop="class"
    label="班级"
  />
  <el-table-column
    prop="name"
    label="姓名"
  />
</el-table>

const tableData = ref([]);
// 需要合并的列表头数组
const colFields = ref(['grade', 'class']);
// 需要合并的列合并配置
const spanArr = ref({});

// 查找每一列的相同数据
const getSpanArr = (data, arr) => {
  arr.forEach((key) => {
    let row = 0; 
    spanArr.value[key] = [];
    data.forEach((item, index) => {
      if (index === 0) {
        // 第一行push1占位
        spanArr.value[key].push(1);
      } else {
        // 判断当前单元格数据与当前列上一行数据是否相同,且合并项grade是否相同(避免不同grade的class发生合并),相同push0,不同push1
        if (
          item[key] === data[index - 1][key] &&
          item.grade === data[index - 1].grade
        ) {
          // 相同时,合并列数+1,指针row不动,push0占位表示该列被合并
          spanArr.value[key][row] += 1;
          spanArr.value[key].push(0);
        } else {
          // 不同时,指针指向当前列,push1占位
          row = index;
          spanArr.value[key].push(1);
        }
      }
    });
  });
};
// 当前行 当前列 当前行号 当前列号
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  // 该列是否需要合并
  if (colFields.value.indexOf(column.property) !== -1) {
    // 合并配置信息里该列对应的数字,n代表需要合并n列,0代表被合并的列
    if (spanArr.value[column.property][rowIndex]) {
      return [spanArr.value[column.property][rowIndex], 1];
    } else {
      return [0, 0];
    }
  }
};

onMounted(() => getSpanArr(tableData.value, colFields.value));

2.2 有序号,前端添加index实现

<el-table
  :data="tableData"
  :span-method="objectSpanMethod"
>
  <el-table-column
    type="index"
    :index="indexMethod"
    label="序号"
  />
  <el-table-column
    prop="province"
    label="省份"
  />
  <el-table-column
    prop="area"
    label="地区"
  />
  <el-table-column
    prop="weather"
    label="天气"
  />
</el-table>

const tableData = ref([
  { province: 'zj', area: 'nb', weather: 'rainy' },
  { province: 'zj', area: 'hz', weather: 'sunny' },
  { province: 'js', area: 'nj', weather: 'windy' },
]);
// 需要合并的列表头数组
const colFields = ref(['province']);
// 需要合并的列合并配置
const spanArr = ref({});
// 序号
const indexNum = ref([]);

// 查找每一列的相同数据
const getSpanArr = (data, arr) => {
  arr.forEach((key) => {
    let row = 0; 
    spanArr.value[key] = [];
    data.forEach((item, index) => {
      // 默认赋值[1, 1]
      spanArr.value[key][index] = [1, 1];
      if (index === 0) {} else {
        // 判断当前单元格数据与当前列上一行数据是否相同
        if (item[key] === data[index - 1][key]) {
          // 相同时,指针row不动,row指向列合并列数+1,当前列修改值为[0, 1]表示该列被合并
          spanArr.value[key][row][0] += 1;
          spanArr.value[key][index][0] = 0;
        } else {
          // 不同时,指针指向当前列
          row = index;
        }
      }
    });
  });
  // spanArr详情 { province: [[2, 1], [0, 1], [1, 1]] }
  // 自定义序号,找第一个合并列的合并配置信息,非0时自增
  let num = 0; 
  spanArr.value[arr[0]].map((item, index) => {
    if (item[0] != 0) {
      indexNum.value[index] = num += 1;
    }
  });
};
// 当前行 当前列 当前行号 当前列号
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  if (colFields.value.indexOf(column.property) !== -1) {
    // 合并列,合并配置信息里该列对应的数字,n代表需要合并n列,0代表被合并的列
    if (spanArr.value[column.property].length) {
      return spanArr.value[column.property][rowIndex];
    }
  } else if (columnIndex === 0 && spanArr.value[colFields.value[0]]) {
    // 序号列,合并情况与第一个合并列一致
    return spanArr.value[colFields.value[0]][rowIndex];
  }
};
// 返回indexNum
const indexMethod = (index) => {
  return indexNum.value[index];
};

onMounted(() => getSpanArr(tableData.value, colFields.value));

2.3 有序号,后端返回字段number实现

<el-table
  :data="tableData"
  :span-method="objectSpanMethod"
>
  <el-table-column
    prop="number"
    label="序号"
  />
  <el-table-column
    prop="province"
    label="省份"
  />
  <el-table-column
    prop="area"
    label="地区"
  />
  <el-table-column
    prop="weather"
    label="天气"
  />
</el-table>

const tableData = ref([
  { number: 1, province: 'zj', area: 'nb', weather: 'rainy' },
  { number: 1, province: 'zj', area: 'hz', weather: 'sunny' },
  { number: 2, province: 'js', area: 'nj', weather: 'windy' },
]);
// 需要合并的列表头数组
const colFields = ref(['number', 'province']);
// 需要合并的列合并配置
const spanArr = ref({});

// 查找每一列的相同数据
const getSpanArr = (data, arr) => {
  arr.forEach((key) => {
    let row = 0; 
    spanArr.value[key] = [];
    data.forEach((item, index) => {
      if (index === 0) {
        // 第一行push1占位
        spanArr.value[key].push(1);
      } else {
        // 判断当前单元格数据与当前列上一行数据是否相同,相同push0,不同push1
        if (item[key] === data[index - 1][key]) {
          // 相同时,合并列数+1,指针row不动,push0占位表示该列被合并
          spanArr.value[key][row] += 1;
          spanArr.value[key].push(0);
        } else {
          // 不同时,指针指向当前列,push1占位
          row = index;
          spanArr.value[key].push(1);
        }
      }
    });
  });
  // spanArr详情 { number: [2, 0, 1], province: [2, 0, 1] }
};
// 当前行 当前列 当前行号 当前列号
const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  // 该列是否需要合并
  if (colFields.value.indexOf(column.property) !== -1) {
    // 合并配置信息里该列对应的数字,n代表需要合并n列,0代表被合并的列
    if (spanArr.value[column.property][rowIndex]) {
      return [spanArr.value[column.property][rowIndex], 1];
    } else {
      return [0, 0];
    }
  }
};

onMounted(() => getSpanArr(tableData.value, colFields.value));

2.3 排序问题,合并单元格是当前单元格数据与上一行比较,当表格无序时合并会有问题

const tableData = ref([
  { province: 'zj', area: 'nb' },
  { province: 'js', area: 'nj' },
  { province: 'zj', area: 'hz' },
  { province: 'ah', area: 'hf' },
]);

const getSortArr = (data, sortAttr) => {
  // 遍历数组,将数组中的元素按指定字段进行分组,处理结果累积到acc中
  const sortArr = data.reduce((acc, curr) => {
    // 在累加器中查找是否存在与当前数据项相同指定字段的第一条数据项
    // find不改变原数组,只返回符合条件的项,existingArr引用该项
    const existingArr = acc.find((i) => i[0][sortAttr] === curr[sortAttr]);
    if (existingArr) {
      // existingArr存在,将当前数据项添加到对应指定字段值的数组中,原数组acc随之变化
      existingArr.push(curr);
    } else {
      // existingArr不存在,在acc中创建对应指定字段值的新数组,新数组第一项为当前数据项
      acc.push([curr]);
    }
    return acc;
  }, []);
  // sortArr详情 
  // [
  //   [{ province: 'zj', area: 'nb' }, { province: 'zj', area: 'hz' }], 
  //   [{ province: 'js', area: 'nj' }], 
  //   [{ province: 'ah', area: 'hf' }]
  // ]
  // 扁平化处理,得到排序后的数组
  return sortArr.flat();
}

onMounted(() => tableData.value = getSortArr(tableData.value, 'province'));