Element UI 之 Table 树形数据合并行的实现

一、需求说明

Table 内容为树形结构,但需要合并收费项目重复列,具有子项的项目可展开和收起。如图:

二、遇到问题

1、表格数据格式

[{
    
    
    id: 1,
    name: '篮网',
    item: '投篮(%)',
    value: Number.parseInt(Math.random() * 100),
    children: [{
    
    
      id: 311,
      name: '詹姆斯·哈登',
      item: '投篮(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 312,
      name: '詹姆斯·哈登',
      item: '三分(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 313,
      name: '詹姆斯·哈登',
      item: '罚球(%)',
      value: Number.parseInt(Math.random() * 100)
    }]
  }, {
    
    
    id: 2,
    name: '篮网',
    item: '三分(%)',
    value: Number.parseInt(Math.random() * 100),
    children: [{
    
    
      id: 321,
      name: '詹姆斯·哈登',
      item: '投篮(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 322,
      name: '詹姆斯·哈登',
      item: '三分(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 323,
      name: '詹姆斯·哈登',
      item: '罚球(%)',
      value: Number.parseInt(Math.random() * 100)
    }]
  }, {
    
    
    id: 3,
    name: '篮网',
    item: '罚球(%)',
    value: Number.parseInt(Math.random() * 100),
    children: [{
    
    
      id: 31,
      name: '詹姆斯·哈登',
      item: '投篮(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 32,
      name: '詹姆斯·哈登',
      item: '三分(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 33,
      name: '詹姆斯·哈登',
      item: '罚球(%)',
      value: Number.parseInt(Math.random() * 100)
    }]
  }]

2、表格未合并列前

3、期待合并效果

4、实际合并效果(注意观察项目列是否正确)

5、造成原因

通过打印第一列渲染内容,发现表格树形结构项无论是否展开、是父节点还是子节点,都是顺序执行渲染。这样,Table 标签的 rowspan 或者 colspan 属性所执行的合并行或合并列,合并机制都是按照渲染内容顺序来执行的。则没法做到我们想要的先合并父节点,再合并子节点。

三、解决思路

1、为达到先合并父节点,再合并子节点效果,可将上文提供的数据转为以下数据格式(相同父节点列表中最后一项保留子节点数据)。

[{
    
    
    id: 1,
    name: '篮网',
    item: '投篮(%)',
    value: Number.parseInt(Math.random() * 100)
  }, {
    
    
    id: 2,
    name: '篮网',
    item: '三分(%)',
    value: Number.parseInt(Math.random() * 100)
  }, {
    
    
    id: 3,
    name: '篮网',
    item: '罚球(%)',
    value: Number.parseInt(Math.random() * 100),
    children: [{
    
    
      id: 31,
      name: '詹姆斯·哈登',
      item: '投篮(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 32,
      name: '詹姆斯·哈登',
      item: '三分(%)',
      value: Number.parseInt(Math.random() * 100)
    }, {
    
    
      id: 33,
      name: '詹姆斯·哈登',
      item: '罚球(%)',
      value: Number.parseInt(Math.random() * 100)
    }]
  }]


2、可这样一来,发现表格无法展开。但若添加 default-expand-all 则能看到展开项,但依然无法切换树形表格是否展开与收起,造成原因是因为父节点列表中第一项没有 children 数据。

3、所幸的是,Element UI 给我们提供了 toggleRowExpansion 方法,我们可以通过给数据打标识和调用 toggleRowExpansion 方法来实现树形表格的展开与收起。

四、Demo 演示及代码


在真正的业务中,后端给前端的数据更可能是具有相关标识的扁平化数据。本文代码中,也是从扁平化数组整合为符合操作需求的树形结构数据。

// mock.js
export const originTableData = [
  {
    
     id: 1, parentId: 0, name: '篮网', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 2, parentId: 0, name: '篮网', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 3, parentId: 0, name: '篮网', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 31, parentId: 3, name: '詹姆斯·哈登', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 32, parentId: 3, name: '詹姆斯·哈登', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 33, parentId: 3, name: '詹姆斯·哈登', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 34, parentId: 3, name: '凯文·杜兰特', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 35, parentId: 3, name: '凯文·杜兰特', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 36, parentId: 3, name: '凯文·杜兰特', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 37, parentId: 3, name: '凯里·欧文', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 38, parentId: 3, name: '凯里·欧文', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 39, parentId: 3, name: '凯里·欧文', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 4, parentId: 0, name: '湖人', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 5, parentId: 0, name: '湖人', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 6, parentId: 0, name: '湖人', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 61, parentId: 6, name: '勒布朗·詹姆斯', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 62, parentId: 6, name: '勒布朗·詹姆斯', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 63, parentId: 6, name: '勒布朗·詹姆斯', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 64, parentId: 6, name: '安东尼·戴维斯', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 65, parentId: 6, name: '安东尼·戴维斯', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 66, parentId: 6, name: '安东尼·戴维斯', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 67, parentId: 6, name: '亚历克斯·卡鲁索', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 68, parentId: 6, name: '亚历克斯·卡鲁索', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 69, parentId: 6, name: '亚历克斯·卡鲁索', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 7, parentId: 0, name: '快船', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 8, parentId: 0, name: '快船', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 9, parentId: 0, name: '快船', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 91, parentId: 9, name: '保罗·乔治', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 92, parentId: 9, name: '保罗·乔治', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 93, parentId: 9, name: '保罗·乔治', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 94, parentId: 9, name: '科怀·伦纳德', item: '投篮(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 95, parentId: 9, name: '科怀·伦纳德', item: '三分(%)', value: Number.parseInt(Math.random() * 100) },
  {
    
     id: 96, parentId: 9, name: '科怀·伦纳德', item: '罚球(%)', value: Number.parseInt(Math.random() * 100) }
]
<template>
  <el-table
    ref="table"
    border
    row-key="id"
    :data="formatTableData"
    :indent="30"
    :span-method="objectSpanMethod"
    style="width: 700px;"
  >
    <el-table-column
      prop="name"
      label="球队"
      width="200"
    >
      <template v-slot="{ row }">
        <div class="cell-content">
          <span>{
   
   { row.name }}</span>
          <span class="arrow-icon" @click="toggleRowExpansion(row)">
            <i
              v-if="checkHasChildren(row)"
              :class="row.isExpand ? 'el-icon-caret-top' : 'el-icon-caret-bottom'"
            />
          </span>
        </div>
      </template>
    </el-table-column>
    <el-table-column
      prop="item"
      label="项目"
      width="180"
      align="center"
    />
    <el-table-column
      prop="value"
      label="数据"
      align="center"
    />
  </el-table>
</template>

<script>
import { originTableData } from '../mock'

export default {
  name: 'ExampleTable',
  data () {
    return {
      originTableData
    }
  },
  computed: {
    formatTableData () {
      const data = []
      this.originTableData.forEach(originItem => {
        const { id, parentId, name, item, value } = originItem
        const children = this.originTableData.filter(item => item.parentId === id)

        if (parentId === 0) {
          data.push({
            id,
            name,
            item,
            value,
            children,
            isExpand: false
          })
        }
      })

      return data
    }
  },
  methods: {
    checkHasChildren (row) {
      return this.formatTableData.filter(item => item.name === row.name)
        .some(item => item.children.length > 0)
    },
    toggleRowExpansion (row) {
      row.isExpand = !row.isExpand // 此行代码有副作用,但目前只想到这样实现
      const rowList = this.originTableData.filter(item => item.name === row.name)
      const expansionRow = rowList[rowList.length - 1]
      this.$refs.table && this.$refs.table.toggleRowExpansion(expansionRow, row.isExpand)
    },
    objectSpanMethod ({ rowIndex, columnIndex }) {
      if (columnIndex === 0) {
        if (rowIndex % 3 === 0) {
          return {
            rowspan: 3,
            colspan: 1
          }
        } else {
          return {
            rowspan: 0,
            colspan: 0
          }
        }
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.cell-content {
  display: flex;
  justify-content: space-between;

  .arrow-icon {
    cursor: pointer;
  }
}

/deep/ .el-table__row.el-table__row--level-1 > td:first-child .cell .cell-content {
  padding-left: 30px;
}
</style>

猜你喜欢

转载自blog.csdn.net/qq_41548644/article/details/117676282
今日推荐