element实现需同时满足多行合并和展开的表格

element实现需同时满足多行合并和展开的表格

需求描述:

以下面这张图为例,此表格的“一级表格”这一行可能存在多行数据,这种情况下需要将“一级指标”,“一级指标扣分xxx”,“一级指标关联xxx”这三列数据的行展示根据后面数据(“二级指标”,…等)进行合并。同时,对于含有多条“二级指标”的单行数据,需要支持可展开可合并。

image-20250224154117587

image-20250224154222445

技术选择:

当前项目的技术栈是vue3,虽然公司有自己组件库,但是私有库有不完美的地方,而且暴露出来的api不够详细,所以选择了element-plus。

element有案例提供“合并行或列”和“展开行的表格”,但是没有提供两者混合使用的表格,本文章以此为基础进行开发。

image-20250224155604294

image-20250224155642052

技术实现:

1. span-method

这是el-table中提供的合并行或列的计算方法以本段代码中提供的objectSpanMethod为例:

  1. 判断是否为子行

    • const isChildRow = row.first === "";:通过检查 row.first 是否为空字符串来判断当前行是否为子行。
  2. 处理列索引小于等于2的情况

    • 如果当前行有子行 (

      row.children
      

      ),进一步检查当前行是否展开 (

      row.expanded
      

      ):

      • 如果展开,返回 rowspan 为子行数加1,colspan 为1。
      • 如果未展开,返回 rowspancolspan 都为1。
    • 如果当前行是子行,返回 rowspancolspan 都为0,表示不显示该单元格。

    • 如果当前行既不是子行也没有子行,返回 rowspancolspan 都为1。

  3. 处理列索引大于2的情况

    • 默认返回 rowspancolspan 都为1。
    const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
      // 判断当前行是否为子行
      const isChildRow = row.first === "";
      
      // 如果列索引小于等于2 这里因为是前三列数据需要合并
      if (columnIndex <= 2) {
        // 如果当前行有子行
        if (row.children) {
          // 如果当前行是展开状态
          if (row.expanded) {
            return {
              rowspan: row.children.length + 1, // 合并行数为子行数加1
              colspan: 1, // 合并列数为1
            };
          } else {
            return {
              rowspan: 1, // 合并行数为1
              colspan: 1, // 合并列数为1
            };
          }
        } else if (isChildRow) {
          // 如果当前行是子行
          return {
            rowspan: 0, // 不显示该单元格
            colspan: 0, // 不显示该单元格
          };
        } else {
          // 如果当前行既不是子行也没有子行
          return {
            rowspan: 1, // 合并行数为1
            colspan: 1, // 合并列数为1
          };
        }
      }
      
      // 对于列索引大于2的情况,默认返回合并行数和列数都为1
      return {
        rowspan: 1,
        colspan: 1,
      };
    };
    
2. expand-change

当用户对某一行展开或者关闭的时候会触发该事件(展开行时,回调的第二个参数为 expandedRows;树形表格时第二参数为 expanded)

  • 保证数据状态同步:在行展开或折叠时,更新 row.expanded 属性可以让合并单元格的逻辑(如 objectSpanMethod)根据当前状态返回正确的 rowspancolspan 值。
  • 刷新布局保证显示正确:由于 Vue 更新 DOM 是异步的,使用 nextTick 确保在数据更新后再调用表格组件的 doLayout 方法,避免因布局未刷新而出现错位问题。
  • 应对树形结构合并单元格:对于存在 children 的行,其展开/折叠状态直接影响整个表格的显示效果,因此立即刷新布局可以确保所有合并单元格重新计算后显示正常。
const handleExpandChange = (row, expanded) => {
  if (row.children) {
    row.expanded = expanded;
// 使用 nextTick 包裹一个回调函数,确保在 Vue 完成 DOM 更新之后再刷新表格布局。
    nextTick(() => {
      // 如果 el-table 提供 doLayout 方法则调用刷新布局
      tableRef.value?.doLayout && tableRef.value.doLayout();
    });
  }
};

全部代码:

<template>
  <el-table
    ref="tableRef"
    :data="originalData"
    style="width: 100%; margin-bottom: 20px"
    row-key="id"
    border
    :span-method="objectSpanMethod"
    :tree-props="{ children: 'children' }"
    @expand-change="handleExpandChange"
  >
    <el-table-column prop="first" label="一级指标" />
    <el-table-column prop="directDeductPoint" label="一级指标扣分范围" />
    <el-table-column prop="relatedProcessVOS" label="一级指标关联流程" />
    <el-table-column prop="second" label="二级指标" />
    <el-table-column prop="third" label="三级指标" />
    <el-table-column prop="evalDesc" label="考核说明" />
    <el-table-column prop="deductType" label="考核扣分范围" />
    <el-table-column prop="enableYn" label="是否启用" />
  </el-table>
</template>

<script lang="ts" setup>
import { ref, nextTick } from "vue";

// 获取表格组件 ref
const tableRef = ref(null);

const originalData = ref([
  {
    id: 1,
    first: "一级指标1111",
    directDeductPoint: "一级指标扣分范围1111",
    relatedProcessVOS: "一级指标关联流程1111",
    second: " 二级指标1111",
    third: "三级指标1111",
    evalDesc: "考核说明1111",
    deductType: "考核扣分范围1111",
    enableYn: "是",
  },
  {
    id: 2,
    first: "一级指标2222",
    directDeductPoint: "一级指标扣分范围2222",
    relatedProcessVOS: "一级指标关联流程2222",
    second: "",
    third: " ",
    evalDesc: "",
    deductType: "",
    enableYn: "",
    children: [
      {
        id: 31,
        first: "",
        directDeductPoint: "",
        relatedProcessVOS: "",
        second: "二级指标212121",
        third: "三级指标212121",
        evalDesc: "考核说明212121",
        deductType: "考核扣分范围212121",
        enableYn: "是",
      },
      {
        id: 32,
        first: "",
        directDeductPoint: "",
        relatedProcessVOS: "",
        second: "二级指标222222",
        third: " 三级指标222222",
        evalDesc: "考核说明222222",
        deductType: "考核扣分范围222222",
        enableYn: "是",
      },
    ],
  },
  {
    id: 3,
    first: "一级指标3333",
    directDeductPoint: "一级指标扣分范围3333",
    relatedProcessVOS: "一级指标关联流程3333",
    second: "二级指标3333",
    third: "三级指标3333",
    evalDesc: "考核说明3333",
    deductType: "考核扣分范围3333",
    enableYn: "是",
  },
  {
    id: 4,
    first: "一级指标4444",
    directDeductPoint: "一级指标扣分范围4444",
    relatedProcessVOS: "一级指标关联流程4444",
    second: "二级指标4444",
    third: "三级指标4444",
    evalDesc: "考核说明4444",
    deductType: "考核扣分范围4444",
    enableYn: "是",
  },
  {
    id: 5,
    first: "一级指标5555",
    directDeductPoint: "一级指标扣分范围5555",
    relatedProcessVOS: "一级指标关联流程5555",
    second: "二级指标5555",
    third: "三级指标5555",
    evalDesc: "考核说明5555",
    deductType: "考核扣分范围5555",
    enableYn: "是",
  },
]);

const objectSpanMethod = ({ row, column, rowIndex, columnIndex }) => {
  const isChildRow = row.first === "";
  if (columnIndex <= 2) {
    if (row.children) {
      if (row.expanded) {
        return {
          rowspan: row.children.length + 1,
          colspan: 1,
        };
      } else {
        return {
          rowspan: 1,
          colspan: 1,
        };
      }
    } else if (isChildRow) {
      return {
        rowspan: 0,
        colspan: 0,
      };
    } else {
      return {
        rowspan: 1,
        colspan: 1,
      };
    }
  }
  return {
    rowspan: 1,
    colspan: 1,
  };
};

const handleExpandChange = (row, expanded) => {
  console.log("handleExpandChange", row, expanded);
  if (row.children) {
    row.expanded = expanded;
    nextTick(() => {
      // 如果 el-table 提供 doLayout 方法则调用刷新布局
      tableRef.value?.doLayout && tableRef.value.doLayout();
    });
  }
};
</script>

<style lang="scss" scoped>
.cus-table {
  :deep(.cell) {
    color: #002f59;
  }
  .table-column-btn {
    :deep(.kui-link--primary) {
      color: #005bac;
    }
  }
}
</style>

### Element Plus `el-table` 组件实现多行合并Element Plus框架中的`el-table`组件支持通过自定义函数来控制哪些单元格应该被合并。对于基于特定属性值相同的合并的情况,可以利用`span-method`属性提供的回调函数。 当表格数据加载完毕后,在遍历每一的数据时判断相邻之间指定字段的内容是否相等;如果相等,则设置当前该列的高度跨越数(rowSpan),并将下一对应位置设为0表示隐藏[^1]。 下面是一个简单的例子用于说明如何根据某个属性(例如name)来进的自动合并: ```html <template> <div style="margin: 20px;"> <el-table :data="tableData" border :span-method="objectSpanMethod"> <el-table-column prop="date" label="日期"></el-table-column> <el-table-column prop="name" label="姓名"></el-table-column> <el-table-column prop="amount1" label="数值 1"></el-table-column> <el-table-column prop="amount2" label="数值 2"></el-table-column> <el-table-column prop="address" label="地址"></el-table-column> </el-table> </div> </template> <script setup lang="ts"> import { ref } from &#39;vue&#39; const tableData = [ { date: "2016-05-03", name: "Tom", amount1: "1", amount2: "2", address: "No. 189, Grove St, Los Angeles" }, { date: "2016-05-02", name: "Tom", // 上一条记录的名字一样 amount1: "2", amount2: "3", address: "No. 189, Grove St, Los Angeles" } ] // 定义一个对象用来存储每组连续相同项的数量 let countMap = new Map() function objectSpanMethod({ row, column, rowIndex, columnIndex }) { if (columnIndex === 1) { // 只处理第二列即&#39;name&#39;这一列 const currentValue = row[column.property]; let prevValue; if(rowIndex !== 0){ prevValue = tableData[rowIndex - 1][column.property]; } if(currentValue===prevValue){ // 如果当前值等于前一值则返回{ rowspan: 0, colspan: 0 },意味着这不会显示出来 return { rowspan: 0, colspan: 0 }; }else{ // 否则计算有多少个连续相同的元素并更新countMap映射表 let nextIndex=rowIndex+1; while(nextIndex<tableData.length && tableData[nextIndex][column.property]==currentValue){ nextIndex++; } countMap.set(`${rowIndex}-${columnIndex}`,nextIndex-rowIndex); return { rowspan: nextIndex-rowIndex , colspan: 1}; } } } </script> ``` 此代码片段展示了怎样创建一个具有合并功能的表格,并且指定了具体的逻辑去决定何时以及如何合并某些下的单元格。注意这里只针对"name"这个字段做了特殊处理,实际应用中可以根据求调整条件。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值