这次是对上次封装表格的一次升级,增加了多级表头,列合并功能。我将代码和参考文章放在最下面。上面就放一些可能有点重要的废话吧,主要是我的一些总结。可以看看也可以直接滑到下方代码。此次同样只是做个记录,给以后一个方便,因此我也不公开献丑了。如果您能看到,做个参考就行,请勿转载。

效果图: 

elementui 表格添加子集前面的列合并 element表头合并_数据

总结:

  • 关于多级表头,由于代码主要集中在列的位置(尤其是需要递归),因此将表格列单独拿出来封装成了一个组件(MyColumn)。
  • 效果图的第一列也是一个二级表头,此处难点是隐藏二级标题后(display:"none"),后一个二级标题会进行补位,因此表头行合并这里目前的解决方式是获取dom,直接设置rowSpan=2;而且目前此处是写死的,也不利于扩展,后续有可能的话会改进,目前有个大胆的想法就是列上设置自定义标签(v-*)不知道能不能实现。
  • 行列合并且之后的hover样式就是搜集网上的资料进行了一下整合,这里我的问题是目前只能选择一项进行合并,不然hover后的样式会出问题。想看相关资料滑到最下方找参考文章。
  • 表格的斑马纹也是拿到dom后添加的elemnt UI表格的斑马纹样式类名,注意:表格必须加上stripe属性。方法依赖,不加就没有斑马线。
  • 新增了isMerge属性,不配置isMerge属性,普通表格可正常使用。
  • 新增了table的key值,主要是之前的需求变更,表格头部部分改变,又要保留原版(这里靠下拉框用户选择控制)。因此根据选择,对tableTilte进行了重新赋值,这个切换开始是正确的,就直接上线了。然而,上线后我又测试了一次,发现第一轮切换DOM没问题,第二轮切换少两列,第三轮少四列,然后依次类推。。。。。。直到一列都没剩下。后来我尝试看数据,发现获取到的数据是没问题,然而视图不对。于是尝试解决。首先使用$forceUpdate() 强制刷新,然并卵。。。。之后开始各种姿势给数组重新赋值,然并卵。。。。就在我放弃准备两套DOM来回show的时候,偶然看见一个哥们说可以修改key值,我就试了下,bug修复。然而最近又发现因为修改key值,视图刷新。生命周期(目前只测试created)会执行两次。开发时需要注意
  • for in循环会访问该对象的原型,应该用在非数组对象的遍历上,不建议使用该方法遍历数组。 给个善意的提醒吧,从表现上来看就是比for循环数组是多循环一次,然后你不慎用来循环数组的话,你的vue就会报错,错误可能还会比较。。。说不上的感觉

代码

父组件:

<template>
  <div class="hello">
    <MyTable
      :tableTitle="MKTitle"
      :tableData="marketList"
      :page="pages"
      :isMerge="true"
      mergeName="projectName"
      @getPageTable="getList"
      @getSize="getList"
      :mutiSelect ="mutiSelect"
      :key="tableKey"
    />
  </div>
</template>

<script>
import MyTable from '@/components/MyTable'
export default {
  name: 'HelloWorld',
  components: {
    MyTable
  },
  data () {
    return {
      tableKey: 0,//table的key值
      marketList: [],
      MKTitle: [//表头数据
        { label: '项目', prop: '', columns: [{ label: '', prop: 'price', isGroup: 'project' }, { label: '', prop: 'type', isGroup: 'project' }], },
        { label: '单位', prop: 'unit' },
        {
          label: '本期', prop: 'period', formatter: (row) => {
            return row.period < 609 ? `<i style="color:red;font-style:normal;">${row.period}</i>` : `${row.period}`
          }
        },
        { label: '上期', prop: 'prePeriod' },
        { label: '环比', prop: "chainRatio", },
        {
          label: '样本量(%)', prop: '', columns: [{ label: '总量', prop: 'total' }, {
            label: '占比', prop: 'percentage', render: (h, params) => {
              return h('el-tag', {
                props: { type: params.row.percentage > '15%' ? 'success' : params.row.percentage <= '0.6%' ? 'danger' : 'info' } // 组件的props
              }, params.row.percentage)
            }
          }]
        },
      ],
      MKList: [],//表格数据
      pages: {//分页数据
        curPage: 1, // 分页-当前页
        limit: 10,
        total: 0,
      },
      mutiSelect:false,
    }
  },
  created () {
    this.getList()
  },
  methods: {
    getList () {
      this.marketList = [//表格数据
        {
          "id":"1212121",
          "price": "中长期",
          "type": "5500V",
          "unit": "元/吨",
          "period": 675.1,
          "prePeriod": 671.6,
          "chainRatio": 3.5,
          "total": 79.8,
          "percentage": "15%"
        },
        {
          "id":"1212121",
          "price": "中长期",
          "type": "5000V",
          "unit": "元/吨",
          "period": 609.1,
          "prePeriod": 608.6,
          "chainRatio": 0.5,
          "total": 165.2,
          "percentage": "36.6%"
        },
        {
          "id":"1212121",
          "price": "中长期",
          "type": "4500V",
          "unit": "元/吨",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
        {
          "id":"1212121",
          "price": "现货",
          "type": "5500V",
          "unit": "元/吨",
          "period": 675.1,
          "prePeriod": 671.6,
          "chainRatio": 3.5,
          "total": 79.8,
          "percentage": "15%"
        },
        {
          "id":"1212121",
          "price": "现货",
          "type": "5000V",
          "unit": "元/吨",
          "period": 609.1,
          "prePeriod": 608.6,
          "chainRatio": 0.5,
          "total": 165.2,
          "percentage": "36.6%"
        },
        {
          "id":"1212121",
          "price": "周综合",
          "type": "5500V",
          "unit": "元/吨",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
        {
          "id":"1212121",
          "price": "周综合",
          "type": "5000V",
          "unit": "元/吨",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
        {
          "id":"1212121",
          "price": "权重指数",
          "type": "5000V",
          "unit": "点",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
      ]
      this.pages.total = 3;
    },    
  },
}
</script>

表格组件:

<template>
  <div class="myTable">
    <el-table :data="tableData" border stripe style="width: 100%" :header-cell-style="mergeMehod" :row-class-name="rowClass"
      :span-method="mergeCloumn" @selection-change="SelectionChange" @cell-mouse-enter="cellMouseEnter" @cell-mouse-leave="cellMouseLeave">
      <el-table-column v-if="mutiSelect" type="selection" style="width: 55px;" />
      <MyColumn v-for="(item,i) in tableTitle" :key="i" :my-col="item" />
      <slot name="handleColumn" />
    </el-table>
    <el-pagination class="fy" background hide-on-single-page :page-size.sync="page.limit" :page-sizes="[2,5,10,20]" :current-page.sync="page.curPage"
      layout="total, sizes, prev, pager, next,jumper" :total="page.total" @size-change="getSize" @current-change="getCurChange" />
  </div>
</template>

<script>
import MyColumn from './MyColumn'
export default {
  name: 'MyTable',
  components: {
    MyColumn,
  },
  props: {
    tableData: {
      type: Array,
      default: () => {
        return []
      }
    },
    mutiSelect: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    isMerge: {
      type: Boolean,
      default: () => {
        return false
      }
    },
    mergeName: {
      type: [String, Array],
      default: () => {
        return ''
      }
    },
    tableTitle: {
      type: Array,
      default: () => {
        return []
      }
    },
    page: {
      type: Object,
      default: function () {
        return {
          limit: 2,
        }
      }
    }
  },
  data () {
    return {
      total: 100,
      mergeData: null,
      sameRowArr: [],
      curRowArr: [],
    }
  },
  watch: {
    tableTitle: {
      deep: true,  // 深度监听
      immediate: true,
      handler (newVal) {
        return newVal
      }
    },
    tableData: {
      deep: true,  // 深度监听
      immediate: true,
      handler (newVal) {
        if (this.isMerge) {
          this.mergeData = this.dataMerge(newVal, [this.mergeName])
          this.sameRowArr = this.getSameRow(newVal, this.mergeName)
          this.getStripe()
        }
      }
    },
    deep: true
  },
  created () {

  },
  methods: {
    SelectionChange (val) {//多行选中,暂时没用上
      console.log(val, 'ppo')
    },
    getSize (val) {
      this.$set(this.page, 'curPage', 1)
      this.$emit('getPageTable', this.page.curPage, val)
    },
    getCurChange (val) {
      this.$emit('getPageTable', val, this.page.limit)
    },
    mergeMehod ({ row, column, rowIndex, columnIndex }) { //合并表头
      if (!this.isMerge) return
      for (let i = 0; i < row.length - 1; i++) {
        if (row[i].level === row[i + 1].level && row[i].labelClassName && row[i].labelClassName === row[i + 1].labelClassName && column.label === '') {
          return { display: 'none' }
        }
      }
      if (rowIndex === 0) {
        if (columnIndex === 0) {
          this.$nextTick(() => {
            if (document.getElementsByClassName(column.id).length !== 0) {
              document.getElementsByClassName(column.id)[0].setAttribute('rowSpan', 2);
              return false;
            }
          });
          return column;
        }
      }
    },
    mergeCloumn ({ row, column, rowIndex, columnIndex }) {
      if (!this.isMerge) return
      if (this.mergeData) {
        //判断合并列
        let _row = column.property ? this.mergeData[column.property][rowIndex] : 1
        let _col = _row > 0 ? 1 : 0;
        return {
          rowspan: _row,
          colspan: _col
        }
      }
    },
    dataMerge: (tableData, mergeCol) => {
      // mergeData:最终输出合并的数据,
      // pos:记录每个列最终占了几行,可以理解成需要合并的每一列占据的行数,除第一列就是有几行合并就是几,其他列都是0
      let [mergeData, pos] = [{}, {}]
      //循环数据
      for (let i in tableData) {
        //循环数据内对象,查看有多少key
        for (let j in tableData[i]) {
          //如果只有一条数据时默认为1即可,无需合并
          if (i == 0) {
            mergeData[j] = [1];
            pos[j] = 0;
          } else {
            let [prev, next] = [tableData[i - 1], tableData[i]];
            //判断上一级别是否存在 ,如果存在判断两者key是否一致
            //判断是否有数组规定只允许那几列需要合并单元格的
            if (next && prev[j] == next[j] && ((!mergeCol || mergeCol.length == 0) || mergeCol.includes(j))) {
              //如果当前与上一级数据相当,数组就加1 数组后面就添加一个0
              mergeData[j][pos[j]] += 1;
              mergeData[j].push(0)
            } else {
              mergeData[j].push(1);
              pos[j] = i;
            }
          }
        }
      }
      return mergeData;
    },
    // 合并行高亮------------------------------
    getSameRow (tableData, mergeCol) {
      let sameRowArr = [], sId = 0;
      //循环数据
      for (let i in tableData) {
        tableData[i].index = i;
        if (i == 0) {
          sameRowArr.push([i])
        } else {
          if (tableData[i][mergeCol] === tableData[i - 1][mergeCol]) {
            sameRowArr[sId].push(i)
          } else {
            sId = sId + 1;
            sameRowArr.push([i])
          }
        }
      }
      return sameRowArr
    },
    rowClass ({ rowIndex }) {
      // 同一行
      let temArr = this.curRowArr
      for (let i = 0; i < temArr.length; i++) {
        if (rowIndex == temArr[i]) {
          return 'row_class'
        }
      }
    },
    cellMouseEnter (row) {
      if (!this.isMerge) return
      this.sameRowArr.forEach((arr) => {
        if (arr.indexOf(row.index) != -1) {
          this.curRowArr = arr
        }
      })
    },
    cellMouseLeave () {
      this.curRowArr = []
      this.getStripe()
    },
    // 斑马线
    getStripe () {
      if (!this.isMerge) return
      this.$nextTick(() => {
        let tr = document.getElementsByClassName("el-table__row");
        for (let i = 0; i < tr.length; i++) {
          if (tr[i].classList.contains('el-table__row--striped')) tr[i].classList.remove('el-table__row--striped')
        }
        for (let i = 0; i < this.sameRowArr.length; i++) {
          if (i % 2 != 0) {
            for (let j = 0; j < this.sameRowArr[i].length; j++) {
              tr[this.sameRowArr[i][j]].classList.add('el-table__row--striped')
            }
          }
        }
      })
    }
  }
}
</script>
<style lang="scss"  scoped>
::v-deep .el-table .row_class td {
  background: #f5f7fa !important;
}
.fy {
  margin: 10px auto 0;
  text-align: center;
}
</style>

列组件:

<template>
<!-- show-overflow-tooltip属性,表格列单行显示,多出隐藏,鼠标放上去tootip显示 -->
  <el-table-column
    :label="myCol.label"
    :label-class-name="myCol.isGroup"
    :prop="myCol.prop"
    :width="myCol.width"
    :max-height="myCol.height"
    show-overflow-tooltip
    align="center"
  >
    <!-- 非多级表头 -->
    <template slot-scope="scope">
      <!-- 非render列 -->
      <template v-if="!myCol.render">
        <!-- 列formatter -->
        <template v-if="myCol.formatter">
          <span v-html="myCol.formatter(scope.row, myCol)"/>
        </template>
        <!-- 普通列 -->
        <template v-else>
          {{scope.row[myCol.prop]}}
        </template>
      </template>
      <!-- render列 -->
      <template v-else>
        <MyRenderDom
          :column="myCol"
          :row="scope.row"
          :render="myCol.render"
          :index="scope.$index"
        />
      </template>
    </template>
    <!-- 多级表头 -->
    <template v-for="(its,i) in myCol.columns">
      <MyColumn
        v-if="myCol.columns"
        :key="i"
        :myCol="its"
      > </MyColumn>
      <el-table-column
        v-else
        :key="i"
      >
      </el-table-column>
    </template>
  </el-table-column>
</template>

<script>
export default {
  name: 'MyColumn',
  components: {
    MyRenderDom: {//函数式组件
      functional: true,//设置为true 即表示该组件为一个函数组件
      props: {
        row: Object,
        render: Function,
        index: Number,
        column: {
          type: Object,
          default: null
        }
      },
      render: (h, ctx) => {
        const params = {
          row: ctx.props.row,
          index: ctx.props.index
        }
        if (ctx.props.column) params.column = ctx.props.column
        return ctx.props.render(h, params)
      }
    },
  },
  props: {
    myCol: {
      type: Object
    },
    mutiSelect: {
      type: Boolean,
      default: () => {
        return false
      }
    }
  },
  data () {
    return {

    }
  },
  created () {
  },
  methods: {

  }
}
</script>