vxeTable 表格合并

64 阅读4分钟

vxeTable官网(Vxe Table v4)

vxe-table 是基于 Vue.js 开发的高性能表格组件,功能丰富,支持大数据量渲染、虚拟滚动、排序、筛选、分页、编辑、导出等多种功能。它不是 Vue 官方自带的组件,而是由社区开发和维护的第三方开源项目。适合需要复杂表格功能的中大型项目使用。

小编在开发过程中遇到的vxeTable 横向合并+纵向合并 展示例图如下:

image.png

如果要实现上面例图就需要使用到vxeTable中的span-method属性

image.png

  • || 是“或者”,满足任意一个条件即为真;
  • && 是“并且”,必须同时满足所有条件才为真;
  • 在判断一个变量是否等于多个不同值时,通常用 ||,而不是 &&
<template>
  <vxe-table
    :data="tableData"
    :columns="tableColumns"
    :span-method="spanMethod"
    size="small"
    max-height="400"
  >
  </vxe-table>
</template>

<script>
import vue from 'vue'
export default {
  name: 'CS',
  data () {
    return {
      tableData: []
      tableColumns: [
      { field: 'A', title: '申报校区', width: '150', align: 'center' },
      { field: 'B', title: '申报部门', width: '150', align: 'center' },
      { field: 'C', title: '项目名称', width: '150', align: 'center' },
      {
        field: 'D',
        title: '项目总预算',
        width: '170',
        align: 'right',
        formatter: 'money'
      },
      {
        title: '基本信息',
        visible: true,
        align: 'center',
        children: [
          { field: 'E', title: '校区校长', width: '120', align: 'center' },
          { field: 'F', title: '项目负责人', width: '120', align: 'center' },
          { field: 'G', title: '申报人', width: '120', align: 'center' }
        ]
      },
      {
        title: '预算信息',
        visible: true,
        align: 'center',
        children: [
          { field: 'H', title: '子项目名称', width: '120', align: 'center' },
          {
            field: 'I',
            title: '子项目必要性说明',
            width: '160',
            align: 'center'
          },
          { field: 'J', title: '资金性质', width: '120', align: 'center' },
          { field: 'K', title: '部门经济分类', width: '120', align: 'center' },
          {
            field: 'L',
            title: '申报数',
            width: '120',
            align: 'right',
            formatter: 'money'
          }
        ]
      },
      {
        title: '采购信息明细',
        visible: true,
        align: 'center',
        children: [
          { field: 'M', title: '采购名称', width: '120', align: 'center' },
          { field: 'N', title: '采购品目', width: '120', align: 'center' },
          { field: 'O', title: '规格型号', width: '120', align: 'center' },
          {
            field: 'P',
            title: '单价',
            width: '120',
            align: 'right',
            formatter: 'money'
          },
          { field: 'Q', title: '采购数量', width: '120', align: 'center' },
          {
            field: 'R',
            title: '采购金额',
            width: '120',
            align: 'right',
            formatter: 'money'
          }
        ]
      },
      { field: 'S', title: '审核状态', width: '120', align: 'center' },
      { field: 'T', title: '流程节点', width: '120', align: 'center' },
      { field: 'U', title: '项目开始时间', width: '120', align: 'center' },
      { field: 'V', title: '项目结束时间', width: '120', align: 'center' },
      { field: 'W', title: '是否政府采购', width: '120', align: 'center' },
      { field: 'X', title: '项目概况', width: '120', align: 'center' },
      { field: 'Y', title: '年度', width: '120', align: 'center' },
      { field: 'Z', title: '政策依据', width: '120', align: 'center' },
      { field: 'AA', title: '测算标准', width: '120', align: 'center' },
      {
        title: '预算信息',
        visible: true,
        align: 'center',
        children: [
          { field: 'AB', title: '子项年度', width: '120', align: 'center' },
          {
            field: 'AC',
            title: '关联财政二级项目名称',
            width: '180',
            align: 'center'
          },
          {
            field: 'AD',
            title: '关联财政子项目名称',
            width: '160',
            align: 'center'
          }
        ]
      }
    ],
    }
  },
  created () {
    this.getTableData()
  },
  methods: {
    // 自定义合并函数,返回计算后的值 (不能用于虚拟滚动、展开行,不建议用于固定列、树形结构)
    spanMethod ({ row, _rowIndex, column, visibleData }) {
      // 横向合并字段数组
      const horizontalFields = ['A', 'B', 'C']
      // 竖向合并字段数组
      // 这里定义依据字段映射关系,key 是依据字段,value 是要合并的字段
      const verticalFields = {
        A: ['A', 'B'],
        pId: ['C', 'D', 'E', 'F', 'G', 'S', 'T', 'U', 'V', 'W', 'X'],
        bId: ['Y', 'Z', 'AA'],
        bgtId: ['H', 'I', 'K', 'L', 'AB', 'AC', 'AD']
      }
      const cellValue = row[column.property]

      if (cellValue === null || cellValue === '') {
        return { rowspan: 1, colspan: 1 }
      }
      
      // 先计算横向合并的 colspan,默认 1
      let colspan = 1
      const hIndex = horizontalFields.indexOf(column.property)
      if (hIndex !== -1) {
        // 只有值为 '合计' 或 '小计' 才合并
        if (cellValue === '合计' || cellValue === '小计') {
          // 判断左边一列是否和当前列值相同,且不为 null 或 ''
          if (hIndex > 0) {
            const leftField = horizontalFields[hIndex - 1]
            const leftValue = row[leftField]
            if (
              leftValue !== null &&
              leftValue !== '' &&
              leftValue === cellValue
            ) {
              // 当前单元格隐藏
              colspan = 0
            }
          }

          if (colspan !== 0) {
            // 计算向右合并的 colspan
            for (let i = hIndex + 1; i < horizontalFields.length; i++) {
              const nextValue = row[horizontalFields[i]]
              if (
                nextValue !== null &&
                nextValue !== '' &&
                (nextValue === '合计' || nextValue === '小计') &&
                nextValue === cellValue
              ) {
                colspan++
              } else {
                break
              }
            }
          }
        }
      }

      // 计算竖向合并的 rowspan,默认 1
      let rowspan = 1
      let foundBasisField = null
      for (const basisField in verticalFields) {
        if (verticalFields[basisField].includes(column.property)) {
          foundBasisField = basisField
          break
        }
      }

      if (foundBasisField) {
        // 如果竖向合并字段数组,依据字段key对应的值为 null 或 undefined,则不支持合并,直接返回
        if (
          row[foundBasisField] === '' ||
          row[foundBasisField] === null ||
          row[foundBasisField] === 'null' ||
          row[foundBasisField] === undefined ||
          row[foundBasisField] === 'undefined'
        ) {
          return { rowspan: 1, colspan }
        }
        
        // 如果当前单元格值是 '小计' 或 '合计',不做竖向合并,直接返回
        if (cellValue === '小计' || cellValue === '合计') {
          return { rowspan: 1, colspan }
        }
        const prevRow = visibleData[_rowIndex - 1]
        const currRow = row

        if (
          prevRow &&
          prevRow[foundBasisField] === currRow[foundBasisField] &&
          prevRow[column.property] === cellValue
        ) {
          // 上一行相同,隐藏当前单元格
          return { rowspan: 0, colspan: 0 }
        } else {
          // 计算向下连续相同单元格数
          let countRowspan = 1
          for (let i = _rowIndex + 1; i < visibleData.length; i++) {
            const nextRow = visibleData[i]
            if (
              nextRow[foundBasisField] === currRow[foundBasisField] &&
              nextRow[column.property] === cellValue
            ) {
              countRowspan++
            } else {
              break
            }
          }
          rowspan = countRowspan
        }
      }

      return { rowspan, colspan }
    },
    async getTableData () {
      this.tableData = []
    }
  }
}
</script>