点我在线体验 Demo
先看一下效果, 一共 3600 X 100 = 36W 个单元格基本感受不到卡顿, 而且每个单元格点击可以编辑, 支持固定头和固定列
项目源代码地址 GitHub https://github.com/ColdDay/vue-fast-table
解决问题核心点: 横向滚动加载, 竖向滚动加载
项目背景
笔者最近在做广告排期功能, 需要进行点位预占, 大的合同可能需要对多个资源排期, 周期可能到几年这样, 然后我们的页面交互是这样
横向每个月 30 个单元格, 最多的 3 年, 36 个月, 每行 36*30=1080 个单元格
竖向 100 个资源, 总共约10W 个单元格, 然后每个单元格里面会有一个输入框, 一个库存总数, 所以总数是 20W 个, 内网使用, 接口请求根本不是问题, 可以浏览器渲染就扛不住了接口回来之后会出现几十秒的白屏, 整个页面处于卡死状态
这还不算, 加载出之后页面操作也是非常卡, 滑动延迟严重, 页面基本处于瘫痪状态
之前的功能是基于 jQuery 开发的, 项目重构用的 vue,UI 采用了 ElementUI,ElmentUI 中的表格在数据量较大是有严重的性能问题, 最直接的表现就是白屏时间较长, 而且会出现渲染错乱
所以就想着自己实现一个表格, 解决卡顿问题
实现思路
表格拆分, 动态加载
表格横向按月拆分, 每个月份单独一个 table, 月份 table 外层放一个占位 div, 根据横向滚动位置控制展示
竖向按资源拆分, 同样包裹一个占位 div, 按照滚动位置动态加载, 始终保持 dom 数量上线
动态编辑, 按需生成编辑输入框
不同的标签在浏览器渲染时性能是不一样的, 比如 input 这种标签就比 span 等标签重许多, 所以不能满屏 input
方案就是点击单元格展示输入框, 焦点丢失移除, 此处的展示非 display 控制显示隐藏, 而是 v-if 控制 dom 是否加载
代码分解
固定头
- <div class="table-head">
- <div class="module"
- v-bind:style="{ transform:'translateX('+ scrollLeft +'px)'}"
- v-for="(item, index) in monthData" v-bind:key="index">
- <table cellspacing="0" cellpadding="0">
- <thead>
- <tr>
- <td colspan="30">{{item.month}}</td>
- </tr>
- <tr>
- <td width="100"
- v-for="(d_item, d_index) in item.days" v-bind:key="d_index"
- style="min-width:100px">{{d_item}}</td>
- </tr>
- </thead>
- </table>
- </div>
- </div>
固定列
- <div class="table-fix-cloumns">
- <div class="module fix-left-top">
- <table width="100" cellspacing="0" cellpadding="0">
- <thead>
- <tr>
- <td > 位置 </td>
- </tr>
- <tr>
- <td>position</td>
- </tr>
- </thead>
- </table>
- </div>
- <div class="module" v-bind:style="{ transform:'translateY('+ scrollTop +'px)'}">
- <table width="100" cellspacing="0" cellpadding="0">
- <thead>
- <tr v-for="(item, index) in projectData" v-bind:key="index">
- <td>{{item.name}}</td>
- </tr>
- </thead>
- </table>
- </div>
- </div>
表体
- <div class="table-body" @scroll="tableScroll" style="height: 300px">
- <div class="module"
- style="width:3000px;"
- v-for="(item, index) in monthData" v-bind:key="index">
- <div class="content"
- v-if="Math.abs(index - curModule) <3">
- <div class="row"
- style="height:30px"
- v-for="(p_item, p_index) in projectData"
- v-bind:key="p_index">
- <table width="3000"
- v-if="Math.abs(p_index - curRow) <20"
- cellspacing="0" cellpadding="0">
- <tbody>
- <tr>
- <td
- @click="clickTd(p_index,item.month, d_item, $event)"
- v-for="(d_item, d_index) in item.days" v-bind:key="d_index">
- <span v-if="!originProjectData[p_index][''+item.month][''+d_item]['show']">{{originProjectData[p_index][''+item.month][''+d_item]['last']}}</span>
- <input
- @blur="blurTd(p_index,item.month, d_item)"
- v-if="originProjectData[p_index][''+item.month][''+d_item]['show']"
- v-model="originProjectData[p_index][''+item.month][''+d_item]['last']"
- v-focus="originProjectData[p_index][''+item.month][''+d_item]['focus']"/>
- </td>
- </tr>
- </tbody>
- </table>
- </div>
- </div>
- </div>
- </div>
经过如上优化, 完美解决表格卡顿问题, 但是我并没有封装组件, 原因如下
. 插件封装后会有很多限制, 不能再用 vue 那种模板写法, 用 JSON 传入数据, 自定义内容不是很灵活
. 可以根据自己的应用场景自行修改拓展, 代码已经很简洁
. 比较懒
如果你有类似需求可以试一下我这个, 也欢迎 Star
来源: https://juejin.im/post/5c8e51bff265da67f51b42c6