element table encapsulation 2 - multi level header, header consolidation, row and column consolidation

Keywords: Javascript Front-end Vue Vue.js

This is an upgrade of the last encapsulated table, adding multi-level header and column consolidation functions. I put the code and reference articles at the bottom. Let's put some important nonsense on it, mainly my summary. You can take a look or slide directly to the code below. This time it's just a record to make it convenient for the future, so I won't make a public show of shame. If you can see it, just make a reference. Please don't reprint it.

design sketch:  

 

Summary:

  • As for the multi-level header, since the code mainly focuses on the position of the column (especially recursion), the table column is taken out and encapsulated into a component (MyColumn).
  • The first column of the rendering is also a secondary header. The difficulty here is that after hiding the secondary header (display:"none"), the latter secondary header will be filled. Therefore, the current solution for header row consolidation here is to obtain dom and directly set rowSpan=2; Moreover, it is written dead here at present, which is not conducive to expansion. It will be improved if possible in the future. At present, there is a bold idea to set a user-defined label (v - *) on the column. I don't know whether it can be realized.
  • The hover style after row and column merging is to collect online data for integration. My problem here is that at present, I can only select one item for merging, otherwise the style after hover will have problems. If you want to see relevant materials, slide to the bottom to find reference articles.
  • The zebra pattern of the table is also the zebra pattern style class name of the elemnt UI table added after getting the dom. Note: the table must be added with the stripe attribute. Method dependent, no zebra crossing without.
  • The for in loop will access the prototype of the object and should be used to traverse non array objects. It is not recommended to use this method to traverse arrays.   Give me a kind reminder. From the performance point of view, it loops more than the for loop array. If you accidentally loop the array, your vue will report an error, and the error may be more... Unspeakable feeling

code

Parent component:

<template>
  <div class="hello">
    <MyTable
      :tableTitle="MKTitle"
      :tableData="marketList"
      :page="pages"
      @getPageTable="getList"
      @getSize="getList"
      :mutiSelect ="mutiSelect"
    />
  </div>
</template>

<script>
import MyTable from '@/components/MyTable'
export default {
  name: 'HelloWorld',
  components: {
    MyTable
  },
  data () {
    return {
      marketList: [],
      MKTitle: [//Header data
        { label: 'project', prop: '', columns: [{ label: '', prop: 'price', merageRows: 2 }, { label: '', prop: 'type', merageRows: 2 }], },
        { label: 'Company', prop: 'unit' },
        {
          label: 'Current period', prop: 'period', formatter: (row) => {
            return row.period < 609 ? `<i style="color:red;font-style="normal;">${row.period}</i>` : `${row.period}`
          }
        },
        { label: 'Last period', prop: 'prePeriod' },
        { label: 'Ring ratio', prop: "chainRatio", },
        {
          label: 'sample size(%)', prop: '', columns: [{ label: 'total', prop: 'total' }, {
            label: 'Proportion', prop: 'percentage', render: (h, params) => {
              return h('el-tag', {
                props: { type: params.row.percentage > '15%' ? 'success' : params.row.percentage <= '0.6%' ? 'danger' : 'info' } // props of components
              }, params.row.percentage)
            }
          }]
        },
      ],
      MKList: [],//Tabular data
      pages: {//Paging data
        curPage: 1, // Paging - current page
        limit: 10,
        total: 0,
      },
      mutiSelect:false,
    }
  },
  created () {
    this.getList()
  },
  methods: {
    getList () {
      this.marketList = [//Tabular data
        {
          "id":"1212121",
          "price": "Medium and long term",
          "type": "5500V",
          "unit": "element/ton",
          "period": 675.1,
          "prePeriod": 671.6,
          "chainRatio": 3.5,
          "total": 79.8,
          "percentage": "15%"
        },
        {
          "id":"1212121",
          "price": "Medium and long term",
          "type": "5000V",
          "unit": "element/ton",
          "period": 609.1,
          "prePeriod": 608.6,
          "chainRatio": 0.5,
          "total": 165.2,
          "percentage": "36.6%"
        },
        {
          "id":"1212121",
          "price": "Medium and long term",
          "type": "4500V",
          "unit": "element/ton",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
        {
          "id":"1212121",
          "price": "goods in stock",
          "type": "5500V",
          "unit": "element/ton",
          "period": 675.1,
          "prePeriod": 671.6,
          "chainRatio": 3.5,
          "total": 79.8,
          "percentage": "15%"
        },
        {
          "id":"1212121",
          "price": "goods in stock",
          "type": "5000V",
          "unit": "element/ton",
          "period": 609.1,
          "prePeriod": 608.6,
          "chainRatio": 0.5,
          "total": 165.2,
          "percentage": "36.6%"
        },
        {
          "id":"1212121",
          "price": "Zhou Zong",
          "type": "5500V",
          "unit": "element/ton",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
        {
          "id":"1212121",
          "price": "Zhou Zong",
          "type": "5000V",
          "unit": "element/ton",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
        {
          "id":"1212121",
          "price": "Weight index",
          "type": "5000V",
          "unit": "spot",
          "period": 554.1,
          "prePeriod": '',
          "chainRatio": '',
          "total": 2.5,
          "percentage": "0.6%"
        },
      ]
      this.pages.total = 3;
    },    
  },
}
</script>

Table components:

<template>
  <div class="myTable">
    <el-table
      :data="tableData"
      stripe
      border
      style="width: 100%"
      @selection-change="SelectionChange"
      :header-cell-style="mergeMehod"
      :span-method="mergeCloumn"
      :row-class-name="rowClass"
      @cell-mouse-enter="cellMouseEnter"
      @cell-mouse-leave="cellMouseLeave"
      ref="myTable"
    >
      <!--  -->
      <el-table-column
        v-if="mutiSelect"
        type="selection"
        style="width: 55px;"
      >
      </el-table-column>
      <MyColumn
        v-for="(item,i) in tableTitle"
        :key="i"
        :myCol="item"
      />
      <slot name="handleColumn"></slot>
    </el-table>
    <el-pagination
      class="fy"
      background
      @size-change="getSize"
      hide-on-single-page
      @current-change="getCurChange"
      :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"
    >
    </el-pagination>
  </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
      }
    },
    tableTitle: {
      type: Array,
      default: () => {
        return []
      }
    },
    page: {
      type: Object,
      default: function () {
        return {
          limit: 2,
        }
      }
    }
  },
  data () {
    return {
      total: 100,
      mergeData: null,
      sameRowArr: [],
      curRowArr: [],
    }
  },
  created () {
    this.mergeData = this.dataMerge(this.tableData, ['price'])
    this.sameRowArr = this.getSameRow(this.tableData, 'price')
    this.stripe()
    /* ---------------------------------------------------------------------------------------------- */
  },
  methods: {
    SelectionChange (val) {//Select multiple lines, which is useless for the time being
      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 ({ column, rowIndex, columnIndex }) { //Merge header
      // The secondary headings of the first row and the second column are hidden, the subsequent or appended to the previous one, and the last one is empty
      if (rowIndex === 1) {
        if (columnIndex === 0 || columnIndex === 1) {
          return { display: 'none' }
        }
      }
      // Merge the first line of the title into two lines
      if (rowIndex === 0) {
        if (!this.mutiSelect && columnIndex === 0 || this.mutiSelect && columnIndex === 1) {
          this.$nextTick(() => {
            if (document.getElementsByClassName(column.id).length !== 0) {
              document.getElementsByClassName(column.id)[0].setAttribute('rowSpan', 2);
              return false;
            }
          });
          return column;
        }
      }
    },
    mergeCloumn ({ column, rowIndex }) {
      //Judge consolidated columns
      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: the final output of the merged data. pos: records the number of rows occupied by each column, which can be understood as the number of rows occupied by each column to be merged. Except for the first column, there are several rows to be merged, and the other columns are 0
      let [mergeData, pos] = [{}, {}]
      //Cyclic data
      for (let i in tableData) {
        //Loop the objects in the data to see how many key s there are
        for (let j in tableData[i]) {
          //If there is only one piece of data, the default value is 1. There is no need to merge
          if (i == 0) {
            mergeData[j] = [1];
            pos[j] = 0;
          } else {
            let [prev, next] = [tableData[i - 1], tableData[i]];
            //Judge whether the previous level exists. If so, judge whether the two key s are consistent
            //Judge whether there is an array that only allows those columns that need to be merged
            if (next && prev[j] == next[j] && ((!mergeCol || mergeCol.length == 0) || mergeCol.includes(j))) {
              //If the current data is equivalent to the data of the previous level, the array will be added with 1, and a 0 will be added after the array
              mergeData[j][pos[j]] += 1;
              mergeData[j].push(0)
            } else {
              mergeData[j].push(1);
              pos[j] = i;
            }
          }
        }
      }
      return mergeData
    },
    // Merge row highlighting------------------------------
    getSameRow (tableData, mergeCol) {
      let sameRowArr = [], sId = 0;
      //Cyclic data
      for (let i in tableData) {
        tableData[i].index = i;
        if (i == 0) {
          sameRowArr.push([i])
        } else {
          let [prev, next] = [tableData[i - 1], tableData[i]];
          if (next && next[mergeCol] === prev[mergeCol]) {
            sameRowArr[sId].push(i)
          } else {
            sId = sId + 1
            sameRowArr.push([i])
          }
        }
      }
      return sameRowArr
    },
    rowClass ({ rowIndex }) {
      // Same line
      let temArr = this.curRowArr
      for (let i = 0; i < temArr.length; i++) {
        if (rowIndex == temArr[i]) {
          return 'row_class'
        }
      }
    },
    cellMouseEnter (row) {
      this.sameRowArr.forEach((arr) => {
        if (arr.indexOf(row.index) != -1) {
          this.curRowArr = arr
        }
      })
    },
    cellMouseLeave () {
      this.curRowArr = []
      this.stripe()
    },
    // zebra crossing
    stripe () {
      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>
.el-table {
  /deep/.row_class {
    background: #f5f7fa;
  }
  /deep/.row_class td.el-table__cell {
    background: #f5f7fa !important;
  }
}

.fy {
  margin: 10px auto 0;
  text-align: center;
}
</style>

Column components:

<template>
  <el-table-column
    :label="myCol.label"
    :prop="myCol.prop"
    :width="myCol.width"
    :max-height="myCol.height"
    show-overflow-tooltip
    align="center"
  >
    <!-- Non multilevel header -->
    <template slot-scope="scope">
      <!-- wrong render column -->
      <template v-if="!myCol.render">
        <!-- column formatter -->
        <template v-if="myCol.formatter">
          <span v-html="myCol.formatter(scope.row, myCol)">111111</span>
        </template>
        <!-- Ordinary column -->
        <template v-else>
          {{scope.row[myCol.prop]}}
        </template>
      </template>
      <!-- render column -->
      <template v-else>
        <MyRenderDom
          :column="myCol"
          :row="scope.row"
          :render="myCol.render"
          :index="scope.$index"
        />
      </template>
    </template>
    <!-- Multi level header -->
    <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 component
      functional: true,//Set to true to indicate that the component is a function component
      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>

Reference article:

vue+elementui table recursively generates multi-level headers_ tsw529 blog - CSDN blog_ vue multi level header

element table columns automatically merge cells and multiple columns with the same value_ iscolor blog - CSDN blog

elementui table cell merging, header cell merging and header slash separation_ Blog of CH-EN - CSDN blog

Posted by xjake88x on Fri, 22 Oct 2021 03:55:09 -0700