Bootstrap

vue2 ----原生手撸table之多维数组

需求:二级表格,红框内容动态并且多个,至少10个一级,其中二级15列,即动态列数至少150列,条数最少20

交互大概就是表格中效果https://data.eastmoney.com/bbsj/
在这里插入图片描述

咋一看结构,需求并不难,无非就是交互时需要计算鼠标悬停时的坐标并将对应行列背景色进行修改;

原始数据结构
这个需求的数据结构就是需要将表头和表内容分开就好处理的多,后端返的数据结构是这样的一个三维数组,所以需要前端去处理数据结构,也可以让后端去处理。。。
在这里插入图片描述
处理数据结构
采用的方式:将数据第一条作为表头,将三维数组扁平为一维数组,这里需要注意表头的value一定要跟数组扁平后的key一样

//构建表头
export function initArry(k) {
  let newlist = []
  k.map((ite, i) => {
    let newl = ite.fmList.map((k, j) => {
      return {
        name: k.name,
        value: 'fullRecords' + i + 'fmList' + j + 'value',
        width: '19px',
      }
    })
    newlist.push({
      label: ite.storeName,
      storeId: ite.storeId,
      children: [
        {
          name: '名称',
          value: 'fullRecords' + i + 'materialName',
          width: '150px',
        },
        ...newl,
      ],
    })
  })
  return newlist
}
// 对象扁平化
export function flatObj(o) {
  if (typeof o !== 'object')
    throw new Error(`TypeError: need a object type but get a ${typeof o}`)

  const res = {}
  const flat = (obj, preKey = '') => {
    Object.entries(obj).forEach(([key, value]) => {
      // preKey默认是'', 如果是递归入口 preKey有值 需要加 . 或者 [] 分割
      let newKey = key
      if (preKey) {
        newKey = `${preKey}${Array.isArray(obj) ? `${newKey}` : `${newKey}`}`
      }

      // 引用类型继续递归拍平, 基本类型设置到结果对象上
      if (value && typeof value === 'object') {
        return flat(value, newKey)
      }
      res[newKey] = value
    })
  }

  flat(o)
  return res
}

一、el-table方案

	<el-table
      v-loading="loading"
      :data="tableData"
      :row-height="30"
      fit
      :cell-class-name="tableCellClassName"
      :cell-style="selectedCellStyle"
      @cell-mouse-enter="hoverCall"
      @cell-mouse-leave="cellMouseLeave"
    >
      <el-table-column
        label="开始时间"
        prop="startTime"
        header-align="center"
        align="center"
        width="130"
      ></el-table-column>
      <el-table-column
        label="结束时间"
        prop="endTime"
        header-align="center"
        align="center"
        width="130"
      ></el-table-column>
      <el-table-column
        v-for="(item, idx2) in tableConfig"
        :key="idx2"
        :label="item.label"
        header-align="center"
        align="center"
      >
        <el-table-column
          v-for="(detail, idx3) in item.children"
          :key="idx3"
          :label="detail.name"
          :width="detail.width"
          :prop="detail.value"
          header-align="center"
          align="center"
        ></el-table-column>
      </el-table-column>
    </el-table> 
hoverCall: function (row, column, cell, event) {
  this.getCellIndex = column.columnIndex
},
cellMouseLeave() {
  this.getCellIndex = null
},
// 为列对象(cell)设置索引值
tableCellClassName({ column, columnIndex }) {
  column.columnIndex = columnIndex
},
// 设置列对象(cell)的样式(style),只在文本显示时才有效果
selectedCellStyle({ column, columnIndex, row, rowIndex }) {
  if (this.getCellIndex === columnIndex) {
    return {
      'background-color': 'RGBA(230, 241, 253, 1) !important',
    }
  }
},

弊端:页面进行卡顿,后端接口返回80ms,但是前端处理完结构再渲染至浏览器长达4s,渲染的数据还需要计算鼠标悬停等交互。。。直接卡的起飞

痛点是交互性能尊嘟尊嘟很不丝滑【😂】需要悬停2s才能渲染上,如果100条数据需要5s才能渲染上【裂开】

【另外,产品新增需求,将每个数据里有特殊项的背景色改为蓝色,意味着又要去查找并且判断】有时会导致整个页面会直接崩掉。。。

二、原生纯手撸
最后尝试好几种解决办法性能效果都不太显著,决定开启手撸之旅,代码如下:

<template>
  <div class="box">
    <div class="table">
      <div v-if="header.length != 0" class="head">
        <span class="flex_c gd">开始时间</span>
        <span class="flex_c gd">结束时间</span>
        <div v-for="(item, index) in header" :key="index" class="two">
          <div class="flex_c td">{{ item.label }}</div>
          <div class="t_d">
            <span
              v-for="(ktem, kndex) in item.children"
              :key="kndex"
              :style="{ width: ktem.width }"
              class="t"
            >
              {{ ktem.name }}
            </span>
          </div>
        </div>
      </div>
      <div class="body">
        <!-- <el-empty description="暂无数据"></el-empty> -->
        <div v-if="date.length == 0" class="no-data-space">
          <div class="no-data">
            <img style="height: 150px" src="@/assets/store/data.png" alt="" />
            <span>暂无数据</span>
          </div>
        </div>
        <div v-for="(item, i) in date" v-else :key="i" class="bo_cont">
          <span class="flex_c gd s td_li" @mouseover="mouseover(0, null)">
            {{ item.startTime }}
          </span>
          <span class="flex_c gd s td_li" @mouseover="mouseover(1, null)">
            {{ item.endTime }}
          </span>
          <div v-for="(htem, hi) in header" :key="hi" class="bo_child">
            <span
              v-for="(kte, k) in htem.children"
              :key="k"
              :style="{ width: kte.width }"
              :class="{
                bg: item[kte.value] == '0',
                t: true,
                td_li: true,
                c_blue: ['合格', '不合格', '不判定'].includes(kte.name),
                bg:
                  !['物料', '合格', '不合格', '不判定'].includes(kte.name) &&
                  !['关', '0'].includes(item[kte.value]),
              }"
              @mouseover="mouseover(hi, k)"
              @click="gotoQuality(kte.name, item[kte.value], item)"
            >
              {{ item[kte.value] }}
            </span>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
  export default {
    props: {
      header: {
        type: Array,
        default: () => {
          return []
        },
      },
      date: {
        type: Array,
        default: () => {
          return []
        },
      },
    },
    methods: {
      /**
       * 移入悬停增加列背景色
       * hi 表头序号
       * index 列数 不为null--即二级表头
       */
      mouseover(hi, index) {
        let k = hi + 2
        let trs = this.$el.querySelectorAll('.bo_cont')
        let tds = document.querySelectorAll('.td_li')
        for (let i = 0; i < tds.length; i++) {
          tds[i].onmouseover = () => {
            trs.forEach((el, i) => {
              if (index == null) {
                el.children[hi].style.backgroundColor = 'RGBA(230, 241, 253, 1)'
              } else {
                el.children[k].children[index].style.backgroundColor =
                  'RGBA(230, 241, 253, 1)'
              }
            })
          }
          tds[i].onmouseout = () => {
            trs.forEach((el, i) => {
              if (index == null) {
                el.children[hi].style.backgroundColor = ''
              } else {
                el.children[k].children[index].style.backgroundColor = ''
              }
            })
          }
        }
      },
      gotoQuality(label, value, row) {
        if (!['合格', '不合格', '不判定'].includes(label)) return
        if (value == null) return
        this.$emit('goto', row)
      },
    },
  }
</script>

最终还是原生解决了我的场景需求,从渲染需要4s多到现在的80ms,做到与后端接口数据返回同步,鼠标悬停修改列背景色也是真的很丝滑,如同本篇开始链接一样丝滑,不得不感叹,还是原生nb,并且易扩展也不会出现崩的现象,😄开心ing

💗💗💗写在最后:
文笔有限,不妥的地方望见谅
如果本篇文章对你有帮助,麻烦点一下赞哟

如果你有更好的建议或者意见,也可以在评论区留言,互相学习!!!

;