element table 动态合并单元格

3,273 阅读8分钟

方法一

效果图:

avator

优点

  • 利用三维数组,单元格实现合并行或列

缺点

  • 但是myMappings是写死的,不是动态生成的
<template>
  <div>
    <el-table
      :show-header="false"
      :highlight-current-row="false"
      :data="tableData"
      :span-method="tableSpanMethod"
      border
      style="width: 100%"
    >
      <el-table-column prop="0" />
      <el-table-column prop="1" />
      <el-table-column prop="2" />
      <el-table-column prop="3" />
      <el-table-column prop="4" />
    </el-table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        ['BOARD_1', '', 'BOARD_2', '', 'FAN'],
        ['BOARD_3', '', 'BOARD_4', '', ''],
        ['PWR', '', '', '', '']
      ]
    }
  },
  methods: {
    tableSpanMethod({ row, column, rowIndex, columnIndex }) {
      var myMappings = [
        [[1, 2], [0, 0], [1, 2], [0, 0], [2, 1]],
        [[1, 2], [0, 0], [1, 2], [0, 0], [0, 0]],
        [[1, 5], [1, 1], [1, 1], [1, 1], [1, 1]]
      ]

      return myMappings[rowIndex][columnIndex]
    }
  }
}
</script>

方法二

element table 行合并通用方法

效果图

优点

  • 只需要传递需合并字段的prop值,就能实现表格的动态合并

缺点

  • 【产品小类】和【频段】是独立合并的
  • 应该先按【产品小类】合并,再按【频段】合并
  • 现在把【小类1】和【小类2】的【频段2】都合并到一起了
<template>
  <el-table size="small" :data="data" :span-method="objectSpanMethod" :summary-method="getSummaries" show-summary border>
    <el-table-column
      v-for="column in columns"
      :key="column.prop"
      :prop="column.prop"
      :label="column.label"
      :width="column.width"
      show-overflow-tooltip
    />
  </el-table>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        {
          subName: '小类1',
          freq: '频段1',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '2',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类1',
          freq: '频段1',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '5',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类1',
          freq: '频段2',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '6',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类1',
          freq: '频段2',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '9',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类2',
          freq: '频段2',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '9',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        }
      ],
      columns: [
        { prop: 'subName', label: '产品小类', width: '100' },
        { prop: 'freq', label: '频段', width: '80' },
        { prop: 'itemName', label: '设备号' },
        { prop: 'pillarNo', label: '位置', width: '200' },
        { prop: 'totalValue', label: '测试总数', width: '80' },
        { prop: 'dataValue', label: '故障数', width: '80' },
        { prop: 'dataPerc', label: '故障率', width: '80' },
        { prop: 'startDatetime', label: '统计开始时间', width: '135' },
        { prop: 'endDatetime', label: '统计结束时间', width: '135' }
      ]
    }
  },
  computed: {
    data() {
      return this.mergeTableRow(this.tableData, ['subName', 'freq'])
    }
  },
  methods: {
    mergeTableRow(data, merge) {
      if (!merge || merge.length === 0) {
        return data
      }
      merge.forEach((m) => {
        const mList = {}
        data = data.map((v, index) => {
          const rowVal = v[m]
          if (mList[rowVal] && mList[rowVal].newIndex === index) {
            mList[rowVal]['num']++
            mList[rowVal]['newIndex']++
            data[mList[rowVal]['index']][m + '-span'].rowspan++
            v[m + '-span'] = {
              rowspan: 0,
              colspan: 0
            }
          } else {
            mList[rowVal] = { num: 1, index: index, newIndex: index + 1 }
            v[m + '-span'] = {
              rowspan: 1,
              colspan: 1
            }
          }
          return v
        })
      })
      return data
    },
    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      const span = column['property'] + '-span'
      if (row[span]) {
        return row[span]
      }
    },
    getSummaries(param) {
      const { columns, data } = param
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = '总计'
          return
        }
        const values = data.map(item => Number(item[column.property]))
        if (!values.every(value => isNaN(value))) {
          sums[index] = values.reduce((prev, curr) => {
            const value = Number(curr)
            if (!isNaN(value)) {
              return prev + curr
            } else {
              return prev
            }
          }, 0)
        } else {
          sums[index] = '--'
        }
      })
      sums[6] = (sums[5] * 100 / sums[4]).toFixed(2) + '%'
      return sums
    }
  }
}
</script>

方法三

效果图

优点

  • 可以先按【产品小类】合并,再按【频段】合并,完全实现定制化合并

缺点

  • myMappings还未实现函数传参,知道效果图后,才能计算出myMappings
<template>
  <el-table size="small" :data="tableData" :span-method="objectSpanMethod" :summary-method="getSummaries" show-summary border>
    <el-table-column
      v-for="column in columns"
      :key="column.prop"
      :prop="column.prop"
      :label="column.label"
      :width="column.width"
      show-overflow-tooltip
    />
  </el-table>
</template>

<script>
export default {
  data() {
    return {
      tableData: [
        ['小类1', '频段1', '设备号1', '位置1', '100', '2', '1%', '2020-04-13 00:00:00', '2020-04-14 00:00:00'],
        ['小类1', '频段1', '设备号1', '位置1', '100', '2', '1%', '2020-04-13 00:00:00', '2020-04-14 00:00:00'],
        ['小类1', '频段2', '设备号1', '位置1', '100', '2', '1%', '2020-04-13 00:00:00', '2020-04-14 00:00:00'],
        ['小类1', '频段2', '设备号1', '位置1', '100', '2', '1%', '2020-04-13 00:00:00', '2020-04-14 00:00:00'],
        ['小类2', '频段2', '设备号1', '位置1', '100', '2', '1%', '2020-04-13 00:00:00', '2020-04-14 00:00:00']
      ],
      columns: [
        { prop: '0', label: '产品小类', width: '100' },
        { prop: '1', label: '频段', width: '80' },
        { prop: '2', label: '设备号' },
        { prop: '3', label: '位置', width: '200' },
        { prop: '4', label: '测试总数', width: '80' },
        { prop: '5', label: '故障数', width: '80' },
        { prop: '6', label: '故障率', width: '80' },
        { prop: '7', label: '统计开始时间', width: '135' },
        { prop: '8', label: '统计结束时间', width: '135' }
      ]
    }
  },
  methods: {
    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      /**
       * myMappings是一个5*9的二维数组,每个元素的值均是[x, y],
       * x代表rowspan,y代表colspan,[x, y]为合并后该单元格左上角的坐标值
       * [4, 1]表示,行向下合并4个单元格,列保持不变,仍为1个单元格
       * [0, 0]表示,该单元格被别的单元格吞并了,不显示
       * [1, 1]表示,显示原有单元格,单元格的行和列均不进行合并
       */
      var myMappings = [
        [[4, 1], [2, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1]],
        [[0, 0], [0, 0], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1]],
        [[0, 0], [2, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1]],
        [[0, 0], [0, 0], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1]],
        [[1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1], [1, 1]]
      ]

      return myMappings[rowIndex][columnIndex]
    },
    getSummaries(param) {
      const { columns, data } = param
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = '总计'
          return
        }
        const values = data.map(item => Number(item[column.property]))
        if (!values.every(value => isNaN(value))) {
          sums[index] = values.reduce((prev, curr) => {
            const value = Number(curr)
            if (!isNaN(value)) {
              return prev + curr
            } else {
              return prev
            }
          }, 0)
        } else {
          sums[index] = '--'
        }
      })
      sums[6] = (sums[5] * 100 / sums[4]).toFixed(2) + '%'
      return sums
    }
  }
}
</script>

改进方法三

效果图

优点

  • 先按【产品小类】合并,再按【频段】合并,且myMappings已实现动态生成

缺点

  • myMappings生成方法不能接受传参,只能定制化合并单元格
<template>
  <el-table 
    size="small" 
    :data="tableData" 
    :span-method="objectSpanMethod" 
    :summary-method="getSummaries" 
    show-summary 
    border>
      <el-table-column
        v-for="column in columns"
        :key="column.prop"
        :prop="column.prop"
        :label="column.label"
        :width="column.width"
        show-overflow-tooltip
      />
  </el-table>
</template>

<script>
export default {
  data() {
    return {
      originTableData: [
        {
          subName: '小类1',
          freq: '频段1',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '2',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类1',
          freq: '频段1',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '5',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类1',
          freq: '频段2',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '6',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类1',
          freq: '频段2',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '9',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        },
        {
          subName: '小类2',
          freq: '频段2',
          itemName: '设备号1',
          pillarNo: '位置1',
          totalValue: '100',
          dataValue: '9',
          dataPerc: '1%',
          startDatetime: '2020-04-13 00:00:00',
          endDatetime: '2020-04-14 00:00:00'
        }
      ],
      columns: [
        { prop: '0', label: '产品小类', width: '100' },
        { prop: '1', label: '频段', width: '80' },
        { prop: '2', label: '设备号' },
        { prop: '3', label: '位置', width: '200' },
        { prop: '4', label: '测试总数', width: '80' },
        { prop: '5', label: '故障数', width: '80' },
        { prop: '6', label: '故障率', width: '80' },
        { prop: '7', label: '统计开始时间', width: '135' },
        { prop: '8', label: '统计结束时间', width: '135' }
      ],
      map:[]
    }
  },
  computed: {
    tableData() {
      // 格式化接口返回的表格数据,每一项,对象转数组
      let formatTableData = []
      this.originTableData.map(item => {
          formatTableData.push(Object.values(item))
      })
      return formatTableData
    }
  },
  methods: {
    originMap() {
      // 初始化一个5X9的二维数组,每一项的值均为[1,1]
      const rows = this.originTableData.length
      var arr = new Array()
      for(let i=0; i<rows; i++){
          arr[i] = new Array()
          for(let j=0; j<9; j++){
              arr[i][j] = [1,1]
          }
      }
      return arr
    },
    mergedMap() {
      /**
       * 动态生成myMappings
       * --------------------------------步骤1------------------------------------
       * 合并第0列的单元格时,从表格最后一行遍历到第一行,修改originMap的初始值
       * 如果tab[n][0] === tab[n-1][0],则第n-1行第0列的X坐标 += 第n行第0列的X坐标
       * 再将第n行第0列的X坐标和Y坐标,均赋值为[0, 0]
       * [0, 0]表示该单元格不显示,因为被合并了
       * --------------------------------步骤2------------------------------------
       * 合并第1列的单元格时,需考虑第0列上下两行的X坐标的值是否相等
       * 只有tab[n][0] === tab[n-1][0]时, 继续以步骤1的方法遍历,修改originMap的初始值
       */
      const tab = this.tableData
      const map = this.originMap()
      const rows = this.originTableData.length - 1
      // 合并第0列
      for(var r=rows; r>0; r--) {
        if(tab[r][0] === tab[r-1][0]) {
          map[r-1][0][0] += map[r][0][0]
          map[r][0] = [0, 0]
        }
      }
      // 合并第1列,与第0列的值有关
      for(var r=rows; r>0; r--) {
        if(tab[r][1] === tab[r-1][1] && tab[r][0] === tab[r-1][0]) {
          map[r-1][1][0] += map[r][1][0]
          map[r][1] = [0, 0]
        }
      }
      return map
    },
    objectSpanMethod({ row, column, rowIndex, columnIndex }) {
      var myMappings = this.mergedMap()
      return myMappings[rowIndex][columnIndex]
    },
    getSummaries(param) {
      const { columns, data } = param
      const sums = []
      columns.forEach((column, index) => {
        if (index === 0) {
          sums[index] = '总计'
          return
        }
        const values = data.map(item => Number(item[column.property]))
        if (!values.every(value => isNaN(value))) {
          sums[index] = values.reduce((prev, curr) => {
            const value = Number(curr)
            if (!isNaN(value)) {
              return prev + curr
            } else {
              return prev
            }
          }, 0)
        } else {
          sums[index] = '--'
        }
      })
      sums[6] = (sums[5] * 100 / sums[4]).toFixed(2) + '%'
      return sums
    }
  }
}
</script>