当前位置: 首页 > news >正文

Ant Design Vue 表格复杂数据合并单元格

Ant Design Vue 表格复杂数据合并单元格

官方合并效果

在这里插入图片描述

官方示例

表头只支持列合并,使用 column 里的 colSpan 进行设置。
表格支持行/列合并,使用 render 里的单元格属性 colSpan 或者 rowSpan 设值为 0 时,设置的表格不会渲染。

<template>
  <a-table :columns="columns" :data-source="data" bordered>
    <template slot="name" slot-scope="text">
      <a>{{ text }}</a>
    </template>
  </a-table>
</template>
<script>
// In the fifth row, other columns are merged into first column
// by setting it's colSpan to be 0
const renderContent = (value, row, index) => {
  const obj = {
    children: value,
    attrs: {},
  };
  if (index === 4) {
    obj.attrs.colSpan = 0;
  }
  return obj;
};

const data = [
  {
    key: '1',
    name: 'John Brown',
    age: 32,
    tel: '0571-22098909',
    phone: 18889898989,
    address: 'New York No. 1 Lake Park',
  },
  {
    key: '2',
    name: 'Jim Green',
    tel: '0571-22098333',
    phone: 18889898888,
    age: 42,
    address: 'London No. 1 Lake Park',
  },
  {
    key: '3',
    name: 'Joe Black',
    age: 32,
    tel: '0575-22098909',
    phone: 18900010002,
    address: 'Sidney No. 1 Lake Park',
  },
  {
    key: '4',
    name: 'Jim Red',
    age: 18,
    tel: '0575-22098909',
    phone: 18900010002,
    address: 'London No. 2 Lake Park',
  },
  {
    key: '5',
    name: 'Jake White',
    age: 18,
    tel: '0575-22098909',
    phone: 18900010002,
    address: 'Dublin No. 2 Lake Park',
  },
];

export default {
  data() {
    const columns = [
      {
        title: 'Name',
        dataIndex: 'name',
        customRender: (text, row, index) => {
          if (index < 4) {
            return <a href="javascript:;">{text}</a>;
          }
          return {
            children: <a href="javascript:;">{text}</a>,
            attrs: {
              colSpan: 5,
            },
          };
        },
      },
      {
        title: 'Age',
        dataIndex: 'age',
        customRender: renderContent,
      },
      {
        title: 'Home phone',
        colSpan: 2,
        dataIndex: 'tel',
        customRender: (value, row, index) => {
          const obj = {
            children: value,
            attrs: {},
          };
          if (index === 2) {
            obj.attrs.rowSpan = 2;
          }
          // These two are merged into above cell
          if (index === 3) {
            obj.attrs.rowSpan = 0;
          }
          if (index === 4) {
            obj.attrs.colSpan = 0;
          }
          return obj;
        },
      },
      {
        title: 'Phone',
        colSpan: 0,
        dataIndex: 'phone',
        customRender: renderContent,
      },
      {
        title: 'Address',
        dataIndex: 'address',
        customRender: renderContent,
      },
    ];
    return {
      data,
      columns,
    };
  },
};
</script>

实际项目中实现效果

在这里插入图片描述

实现原理

分层说明

  1. 数据预处理

    • 使用prepareData方法按markId字段分组
    • 组内数据按mergeIs字段排序(值为"是"的排在前)
  2. 双层级合并机制

    • 主合并层:相同markId的"名称"列合并
    • 次级合并层:在相同markId组内,连续mergeIs === '是’的"数量"列合并
  3. 合并标识管理

    • 通过rowSpan属性控制行合并数
    • rowSpan=0表示该单元格被合并
    • originalIndex记录原始位置用于合并定位
  4. 动态计数器机制

    • primarySpan跟踪名称列合并跨度
    • secondarySpan跟踪数量列合并跨度
    • 遇到分组边界或状态变化时重置计数器
{
  markId: "分组标识",  // 用于主合并层级
  mergeIs: "是/否",   // 用于次级合并层级
  name: "显示内容",    // 名称列数据
  num: "数值"         // 数量列数据
}

数据流向示意图

分组排序
原始数据
预处理模块
结构优化数据
合并处理器
可合并数据集
表格渲染

表格组件配置

<template>
  <section class="console-section-box">
    <div class="con">
      <a-table
        :columns="columns"
        :data-source="tableData"
        :showHeader="true"
        :loading="tableLoading"
        :pagination="pagination"
        :bordered="true"
        :rowKey="
          (record, index) => {
            return index;
          }
        "
        :scroll="{ x: true }"
      >
      </a-table>
    </div>
    <a-back-top />
  </section>
</template>

合并逻辑

<script>
import { mockData } from '~/mock/index.js';
const productColumn = [
  {
    title: '名称',
    dataIndex: 'name',
    customRender: (value, row, index) => {
      const { rowSpan, originalIndex } = row.nameCellObj || { rowSpan: 1, originalIndex: index };
      const obj = {
        children: value,
        attrs: {}
      };
      if (index === originalIndex) {
        obj.attrs.rowSpan = rowSpan;
        obj.attrs.colSpan = 1;
      }
      return obj;
    },
    align: 'center',
    width: 90
  },
  {
    title: '类型',
    dataIndex: 'type',
    align: 'center',
    width: 100
  },
  {
    title: '数量',
    dataIndex: 'num',
    key: 'num',
    customRender: (value, row, index) => {
      const { rowSpan, originalIndex } = row.numCellObj || { rowSpan: 1, originalIndex: index };
      const obj = {
        children: value,
        attrs: {}
      };
      if (index === originalIndex) {
        obj.attrs.rowSpan = rowSpan;
        obj.attrs.colSpan = 1;
      }
      return obj;
    },
    align: 'center',
    width: 90
  }
];
export default {
  name: '',
  data() {
    return {
      tableLoading: false,
      tableData: [],
      pagination: {
        current: 1, // 当前页码
        pageSize: 10000, // 每页显示条数
        total: 0,
        showTotal: total => `共有 ${total} 条数据` //分页中显示总的数据
      },
      columns: productColumn,
    };
  },
  async mounted() {
    await this.fetchData();
  },
  methods: {
    async fetchData() {
      this.tableLoading = true;
      try {
        const res = await this.XXXX();
        if (res.code === 0) {
          this.tableData = mockData;
          this.pagination.total = res.data.length;
          this.handleCellMerge(this.tableData);
        }
      } catch (error) {
        console.error('Error fetching data:', error);
      }
      this.tableLoading = false;
    },
   // 根据数据合并单元格
    handleCellMerge(arr) {
      if (!arr?.length) return;

      const processor = {
        currentMarkId: null,
        currentMergeIs: null,
        primarySpan: 1,
        secondarySpan: 1,

        // 初始化单元格状态
        initialize(row, index) {
          row.nameCellObj = { rowSpan: 1, originalIndex: index };
          row.numCellObj = { rowSpan: 1, originalIndex: index };
        },

        // 主合并逻辑
        processPrimary(index, rows) {
          if (rows[index].markId === this.currentMarkId) {
            this.primarySpan++;
            rows[index - this.primarySpan + 1].nameCellObj.rowSpan = this.primarySpan;
            rows[index].nameCellObj.rowSpan = 0;
            return true;
          }
          this.currentMarkId = rows[index].markId;
          this.primarySpan = 1;
          return false;
        },

        // 次级合并逻辑
        processSecondary(index, rows) {
          if (rows[index].mergeIs === this.currentMergeIs && this.currentMergeIs === '是') {
            this.secondarySpan++;
            rows[index - this.secondarySpan + 1].numCellObj.rowSpan = this.secondarySpan;
            rows[index].numCellObj.rowSpan = 0;
            return true;
          }
          this.currentMergeIs = rows[index].mergeIs;
          this.secondarySpan = 1;
          return false;
        }
      };

      const sortedData = this.prepareData(arr);
      processor.currentMarkId = sortedData[0].markId;
      processor.currentMergeIs = sortedData[0].mergeIs;

      // 单次遍历处理所有合并逻辑
      sortedData.forEach((item, index) => {
        processor.initialize(item, index);
        if (index === 0) return;

        if (processor.processPrimary(index, sortedData)) {
          processor.processSecondary(index, sortedData);
        } else {
          processor.currentMergeIs = item.mergeIs;
        }
      });

      arr.splice(0, arr.length, ...sortedData);
    },
       // 分组排序方法
    prepareData(originData) {
      // 使用Map提高分组性能
      const groups = new Map();
      for (const item of originData) {
        const group = groups.get(item.markId) || [];
        group.push(item);
        groups.set(item.markId, group);
      }

      // 预计算排序权重避免重复计算
      return Array.from(groups.values()).flatMap(group => group.sort((a, b) => (b.mergeIs === '是') - (a.mergeIs === '是')));
    }
  }
};
</script>

mock数据

mock/index.js

export const mockData = [
  {
    name: '数据A',
    num: '9999999',
    type: 'AAA',
    mergeIs: '是',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'BBB',
    mergeIs: '是',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'CCC',
    mergeIs: '否',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'DDD',
    mergeIs: '否',
    markId: 'ITEM_001'
  },
  {
    name: '数据A',
    num: '9999999',
    type: 'EEE',
    mergeIs: '否',
    markId: 'ITEM_001'
  },
  {
    name: '数据B',
    num: '600',
    type: 'AAA',
    mergeIs: '是',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '9999999',
    type: 'BBB',
    mergeIs: '否',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '600',
    type: 'CCC',
    mergeIs: '是',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '9999999',
    type: 'DDD',
    mergeIs: '否',
    markId: 'ITEM_002'
  },
  {
    name: '数据B',
    num: '9999999',
    type: 'EEE',
    mergeIs: '否',
    markId: 'ITEM_002'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'AAA',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'BBB',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'CCC',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'DDD',
    mergeIs: '否',
    markId: 'ITEM_003'
  },
  {
    name: '数据C',
    num: '9999999',
    type: 'EEE',
    mergeIs: '否',
    markId: 'ITEM_003'
  }
];

5. 样式

<style lang="scss" scoped>
.con {
  min-height: calc(100vh - 160px);
  padding: 24px;
  border-radius: 8px;
  background-color: #fff;
}
.project-info-box {
  display: flex;
  flex-direction: column;
  width: 100%;
  height: 100%;
  padding-bottom: 20px;
}
.project-info {
  width: 100%;
  height: 60px;
  line-height: 60px;
  display: flex;
  justify-content: space-between;
  p {
    margin: 0;
  }
}
</style>

相关文章:

  • 从自动测量、8D响应到供应链协同的全链路质量管理数字化方案——全星QMS如何破解汽车行业质量困局
  • AI技术前沿:蓝耘元生代智算云快速入门教程详解,与其他云人工智能大模型深度对比
  • AI幻觉的生成原理与应对指南:六大中文模型横向解析
  • hash.
  • AI日报 - 2024年4月14日
  • PHP爬虫教程:使用cURL和Simple HTML DOM Parser
  • ECMAScript 11 新特性
  • [c语言日寄]空间复杂度
  • 典型操作系统内核架构
  • 《一文讲透》第7期:KWDB 巧用标签与索引优化查询性能
  • 《AI大模型应知应会100篇》 第16篇:AI安全与对齐:大模型的灵魂工程
  • 计算机网络参考模型
  • Spring的定时任务
  • 抖音IP属地可以随便选择地址吗?深度解析
  • Kaggle-Store Sales-(回归+多表合并+xgboost模型)
  • 视频分析设备平台EasyCVR打造阳光药房远程可视化智慧监管体系
  • 文档解析的技术难点有哪些?如何解决?
  • 我的NISP二级之路-05
  • Spark-SQL简介
  • virtualbox扩容
  • 饶权已任国家文物局局长
  • 单位被裁定补缴12年社保,滞纳金该谁出?
  • 税务部门曝光3起通过拆分经营骗享小规模纳税人税费优惠偷税案件
  • 加拿大财长:加拿大需要抗击美国关税
  • 云南洱源县4.8级地震:房屋受损442户,无人员伤亡报告
  • 湖南永州公安全面推行“项目警官制”,为重点项目建设护航