使用element 2.14 实现表格虚拟滚动组件
下述代码为组件实现代码复制即可食用,默认只展示 一屏数据加两条
全选存在些许问题, 使用row-key时,如果行过多滚动时会不会很流畅
特别需要注意的是 行高必须要保持一致
<template> <div class="t-table" :id="TTableId"> <el-table ref="el-table" :tooltip-effect="tooltipEffect" :data="tableData" border :height="height" :row-class-name="rowClassName" :key="tableKey" :row-key="getRowKey" @select-all=" val => { this.selectAll(val, !isAll); } " @select="select" @current-change="handleCurrentChange" :highlight-current-row="highlightCurrentRow" :header-cell-style="headerCellStyle" :cell-style="cellStyle" :empty-text="emptyText" @row-click=" (row, column, event) => this.rowClick(row, column, event, 'table') " > <el-table-column type="index" v-if="index" align="center" fixed> <template slot-scope="scope"> {{ start > 0 ? scope.$index + start + 1 : scope.$index + 1 }} </template> </el-table-column> <el-table-column type="selection" fixed :reserve-selection="true" :width="$TableWidth.px46" v-if="selection" :selectable="rowSelectable" ></el-table-column> <slot></slot> </el-table> </div> </template> <script> export default { components: { }, name: "TTable", props: { //表格数据列表 saveDATA: { typeof: Array, default: [] }, //表格高度 height: { typeof: Number, default: 400 }, //一行高度 为了计算高度,需要行高固定且不能出现换行数据 itemHeight: { typeof: Number, default: 49 }, rowClassName: { typeof: Function, default: () => {} }, tooltipEffect: { typeof: String, default: "" }, //行key 必须填写saveDATA里面的变量名 rowKey: { typeof: String, default: "" }, //是否显示复选框列 selection: { typeof: Boolean, default: false }, //是否显示序号列 index: { typeof: Boolean, default: false }, //行禁选方法 rowSelectable: { typeof: Function, default: () => {} }, //是否可以单选 highlightCurrentRow: { typeof: Boolean, default: false }, //表头样式 headerCellStyle: { typeof: Object }, //表格样式 cellStyle: { typeof: Object }, //无数据文字 emptyText: { typeof: String, default: "" }, colDisable: { typeof: Array }, //组件id 如果一个页面需要使用多个ttable组件 需要给此参数赋唯一值 TTableId: { typeof: String, default: "t_table" } }, data() { return { tableData: [], //展示数据列表 tableRef: null, // 设置了滚动的那个盒子 tableWarp: null, // 被设置的transform元素 fixLeft: null, // 固定左侧--设置的transform元素 fixRight: null, // 固定右侧--设置的transform元素 tableFixedLeft: null, // 左侧固定列所在的盒子 tableFixedRight: null, // 右侧固定列所在的盒子 scrollTop: 0, //滚动高度 scrollNum: 0, //当前滚动了几屏数据 scrollTop / (itemHeight * pageList) scrollRowNum: 0, //记录当前滚动了多少条数据 start: 0, //默认截取数据开始位数 end: this.pageList + 2, //默认截取数据结束位数 selectList: [], //当前选中的列表 isAll: false, //是否全选 tableKey: "", //表格key currentRow: "", //单选数据 }; }, watch: { //每滚动一条数据则更改展示数据源并且更改容器偏移量 scrollRowNum(newV) { this.start = newV - 1; this.end = this.pageList + newV + 1; if (this.start <= 0) this.start = 0; this.tableData = this.saveDATA.slice(this.start, this.end); if (this.currentRow) this.setCurrentRow(this.currentRow); requestAnimationFrame(() => { // 计算偏移量 let translateY = `translateY(${this.start * this.itemHeight}px)`; this.tableWarp.style.transform = translateY; if (this.fixLeft) { this.fixLeft.style.transform = translateY; } if (this.fixRight) { this.fixRight.style.transform = translateY; } this.doLayout(); }); }, saveDATA: { handler(val) { this.init(); this.initMounted(); }, deep: true // 深度监听 }, pageList() { this.init(); this.initMounted(); } }, created() { }, mounted() { this.$nextTick(() => { this.tableSetRef = this.GetTableRef(); }); }, methods: { clearIndex() { this.start = 0; this.end = this.pageList + 2; this.scrollNum = 0; if (this.tableRef) this.tableRef.scrollTop = 0; }, //初始化表格内容盒子 initMounted() { this.$nextTick(() => { let box = document.getElementById(this.TTableId); // 设置了滚动的盒子 this.tableRef = this.$refs["el-table"].bodyWrapper; // 左侧固定列所在的盒子 this.tableFixedLeft = box.querySelector( ".el-table .el-table__fixed .el-table__fixed-body-wrapper" ); // 右侧固定列所在的盒子 this.tableFixedRight = box.querySelector( ".el-table .el-table__fixed-right .el-table__fixed-body-wrapper" ); /** * fixed-left | 主体 | fixed-right */ // 创建内容盒子divWarpPar并且高度设置为所有数据所需要的总高度 let dwp = box.getElementsByClassName("divWarpPar")[0]; if (dwp) { dwp.style.height = this.saveDATA.length * this.itemHeight + "px"; } else { let divWarpPar = document.createElement("div"); divWarpPar.className = "divWarpPar"; // 如果这里还没获取到saveDATA数据就渲染会导致内容盒子高度为0,可以通过监听saveDATA的长度后再设置一次高度 divWarpPar.style.height = this.saveDATA.length * this.itemHeight + "px"; // 新创建的盒子divWarpChild let divWarpChild = document.createElement("div"); divWarpChild.className = "fix-warp divWarpChild"; // 把tableRef的第一个子元素移动到新创建的盒子divWarpChild中 divWarpChild.append(this.tableRef.children[0]); // 把divWarpChild添加到divWarpPar中,最把divWarpPar添加到tableRef中 divWarpPar.append(divWarpChild); this.tableRef.append(divWarpPar); } // left改造 let dlp = box.getElementsByClassName("divLeftPar")[0]; if (dlp) { dlp.style.height = this.saveDATA.length * this.itemHeight + "px"; } else { let divLeftPar = document.createElement("div"); divLeftPar.className = "divLeftPar"; divLeftPar.style.height = this.saveDATA.length * this.itemHeight + "px"; let divLeftChild = document.createElement("div"); divLeftChild.className = "fix-left"; this.tableFixedLeft && divLeftChild.append(this.tableFixedLeft.children[0]); divLeftPar.append(divLeftChild); this.tableFixedLeft && this.tableFixedLeft.append(divLeftPar); } // right改造 let drp = box.getElementsByClassName("divRightPar")[0]; if (drp) { drp.style.height = this.saveDATA.length * this.itemHeight + "px"; } else { let divRightPar = document.createElement("div"); divRightPar.className = "divRightPar"; divRightPar.style.height = this.saveDATA.length * this.itemHeight + "px"; let divRightChild = document.createElement("div"); divRightChild.className = "fix-right"; this.tableFixedRight && divRightChild.append(this.tableFixedRight.children[0]); divRightPar.append(divRightChild); this.tableFixedRight && this.tableFixedRight.append(divRightPar); } // 被设置的transform元素 this.tableWarp = box.querySelector( ".el-table .el-table__body-wrapper .fix-warp" ); this.fixLeft = box.querySelector( ".el-table .el-table__fixed .el-table__fixed-body-wrapper .fix-left" ); this.fixRight = box.querySelector( ".el-table .el-table__fixed-right .el-table__fixed-body-wrapper .fix-right" ); this.tableRef.addEventListener("scroll", this.onScroll); }); }, // 初始化数据 init() { this.clearIndex(); this.tableData = this.saveDATA.slice(this.start, this.end); }, // 滚动事件 onScroll() { //解决固定行 与 非固定行 滚动时错位问题 let body = document.getElementById(this.TTableId); let fixedBox = body.getElementsByClassName( "el-table__fixed-body-wrapper" ); if (fixedBox) { for (let i = 0; i < fixedBox.length; i++) { const element = fixedBox[i]; element.scrollTop = this.tableRef.scrollTop; } } //, this.scrollTop = this.tableRef.scrollTop; //获取顶部距离 this.scrollNum = Math.floor(this.scrollTop / (this.itemHeight * this.pageList)) + 1; //计算滚动到几屏数据 this.scrollRowNum = Math.ceil(this.scrollTop / this.itemHeight); //计算已经滚动了多少条数据 //移除掉滚动时遗留的hover-row if (this.GetTableRef()) { const elements = body.getElementsByClassName( "hover-row" ); for (let i = 0; i < elements.length; i++) { elements[i].classList.remove("hover-row"); } } }, //获取表格ref GetTableRef() { return this.$refs["el-table"]; }, //刷新表格key RefreshTableKey() { this.tableKey = this.$common.GetNextStr(); this.clearIndex(); this.initMounted(); }, //设置行key getRowKey(row) { return this.$common.GetValueByPath(row, this.rowKey); }, //单行选择触发事件 select(val) { this.selectList = val; this.$emit("selection-change", val); }, //全选触发事件 selectAll(val, isAll) { this.isAll = isAll; if (isAll) { let list = JSON.parse(JSON.stringify(this.saveDATA)); if (this.rowSelectable) this.selectList = list.filter((m, i) => this.rowSelectable(m, i)); else this.selectList = list; let dList = this.selectList.filter( m => val.findIndex(k => JSON.stringify(k) == JSON.stringify(m)) == -1 ); dList.forEach(row => { this.GetTableRef().toggleRowSelection(row); }); } else { this.clearSelection(); } this.$emit("selection-change", this.selectList); }, //清空表格 clearSelection() { this.GetTableRef().clearSelection(); this.selectList = []; this.$emit("selection-change", this.selectList); }, //单行选中触发事件 handleCurrentChange(val) { this.currentRow = val; this.$emit("current-change", val); }, //设置指定行为单行选中 setCurrentRow(row) { this.currentRow = row; this.GetTableRef().setCurrentRow(row); }, rowClick(row, column, event) { this.$emit("row-click", row, column, event); }, //重新布局表格 doLayout() { this.GetTableRef().doLayout(); }, }, computed: { pageList() { return Math.ceil(this.height / this.itemHeight); } } }; </script> <style> .t-table .el-table__body-wrapper { overflow: auto !important; background-blend-mode: overlay !important; } </style>
下面为调用示例
<template> <div class="t-table" id="t_table"> <TTable :saveDATA="saveDATA" :height="200"> <el-table-column fixed label="id" prop="id"></el-table-column> <el-table-column fixed label="姓名" prop="name"></el-table-column> <el-table-column label="年龄" prop="age"></el-table-column> <el-table-column label="地址" prop="address" min-width="300" ></el-table-column> <el-table-column label="地址" prop="address" min-width="300" ></el-table-column> <el-table-column label="地址" prop="address" min-width="300" ></el-table-column> <el-table-column label="地址" prop="address" min-width="300" ></el-table-column> <el-table-column label="地址" prop="address" min-width="300" ></el-table-column> <el-table-column label="地址" prop="address" min-width="300" ></el-table-column> </TTable> </div> </template> <script> import TTable from "../../../src/components/TTable.vue"; export default { components: { TTable, }, props: {}, data() { return { saveDATA: [], // 所有数据 }; }, watch: {}, created() { }, mounted() { this.init(); }, methods: { init() { this.saveDATA = []; for (let i = 0; i < 2000; i++) { this.saveDATA.push({ id: i, name: `张三${i}`, age: i, address: "重庆市渝中区两路口阿士大夫撒多富士达浪费的水立方as的裂缝阿萨德立法技术放两三分啥地方了as的裂缝as了巅峰就啥的立法啥的立法阿斯蒂芬了as的裂缝就啥砥砺奋进阿萨德理发店发了" }); } } } }; </script>