el-table 的在数据量比较大的情况下,会卡顿,因为数据量太大,渲染速度跟不上。可以通过触底加载、定时器和请求动画帧、虚拟列表、vxe-table、vue-virtual-scroller 这些方案解决。

一、触底加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<template>
<!-- 触底加载 -->
<div class="box">
<el-table
v-el-table-infinite-scroll="load"
height="480"
:data="tableData"
border
style="width: 80%"
v-loading="loading"
element-loading-text="数据量太大啦,客官稍后..."
element-loading-spinner="el-icon-loading"
element-loading-background="rgba(255, 255, 255, 0.5)"
:header-cell-style="{
height: '24px',
lineHeight: '24px',
color: '#606266',
background: '#F5F5F5',
fontWeight: 'bold',
}"
>
<el-table-column type="index" label="序号"></el-table-column>
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="name" label="名字"></el-table-column>
<el-table-column prop="value" label="对应值"></el-table-column>
</el-table>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<script>
function averageFn(arr) {
let i = 0;
let result = [];
while (i < arr.length) {
// 一次截取10个用于分堆
result.push(arr.slice(i, i + 10));
// 这10个截取完,再准备截取下10个
i = i + 10;
}
return result;
}
export default {
data() {
return {
allTableData: [],
tableData: [],
loading: false,
};
},
// 第一步,请求大量数据时候,转成二维数组,分堆分组分块存储
async created() {
this.loading = true;
const res = await axios.get("XXXX");
this.allTableData = averageFn(res.data.data);
// this.originalAllTableData = this.allTableData // 也可以存一份原始值,留作备用,都行的
this.loading = false;
this.load();
},
methods: {
async load() {
console.log("自动多次执行之,首次执行会根据高度去计算要执行几次合适");
// 第四步,触底加载相当于把二维数组的每一项取出来用,用完时return停止即可
if (this.allTableData.length == 0) {
console.log("没数据啦");
return;
}
// 第二步,加载的时候,把二维数组的第一项取出来,拼接到要展示的表格数据中去
let arr = this.allTableData[0];
this.tableData = this.tableData.concat(arr);
// 第三步,拼接展示以后,再把二维数组的第一项的数据删除即可
this.allTableData.shift();
},
},
};
</script>

二、 定时器请求动画帧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<div v-if="activeName == 'aaa'">
<el-button
style="margin-bottom: 12px"
size="small"
type="primary"
:loading="loading"
@click="plan"
>点击请求加载</el-button
>
<el-table
height="300"
:data="arr"
border
style="width: 80%"
:header-cell-style="{
height: '24px',
lineHeight: '24px',
color: '#606266',
background: '#F5F5F5',
fontWeight: 'bold',
}"
>
<el-table-column type="index" label="序号"></el-table-column>
<el-table-column prop="id" label="ID"></el-table-column>
<el-table-column prop="name" label="名字"></el-table-column>
<el-table-column prop="value" label="对应值"></el-table-column>
</el-table>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
function averageFn(arr) {
let i = 0; // 从第0个开始
let result = []; // 定义结果,结果是二维数组
while (i < arr.length) {
// 从原始数组的第一项开始遍历
result.push(arr.slice(i, i + 10)); // 一次截取10个用于分堆
i = i + 10;
}
return result;
}

async created() {
await this.getData()
},
methods: {
//
async getData() {
for (let i = 0; i < 100000; i++) {
await this.list.push({ name: 'aaa', index: i })
}
},
/**
* 请求动画帧
* */
async plan() {
this.loading = true;
const res = await axios.get("xxxxx");
this.loading = false;
// 1. 将大数据量分堆
let twoDArr = averageFn(res.data.data);
// 2. 定义一个函数,专门用来做赋值渲染(使用二维数组中的每一项)
const use2DArrItem = (page) => {
// 4. 从第一项,取到最后一项
if (page > twoDArr.length - 1) {
console.log("每一项都获取完了");
return;
}
// 5. 使用请求动画帧的方式
requestAnimationFrame(() => {
// 6. 取出一项,就拼接一项(concat也行)
this.arr = [...this.arr, ...twoDArr[page]];
// 7. 这一项搞定,继续下一项
page = page + 1;
// 8. 直至完毕(递归调用,注意结束条件)
use2DArrItem(page);
});
};
// 3. 从二维数组中的第一项,第一堆开始获取并渲染(数组的第一项即索引为0)
use2DArrItem(0);
},
},

三、虚拟列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<template>
<!-- 虚拟列表容器,类似“窗口”,窗口的高度取决于一次展示几条数据
比如窗口只能看到10条数据,一条40像素,10条400像素
故,窗口的高度为400像素,注意要开定位和滚动条 -->
<div
class="virtualListWrap"
ref="virtualListWrap"
@scroll="handleScroll"
:style="{ height: itemHeight * count + 'px' }"
>
<!-- 占位dom元素,其高度为所有的数据的总高度 -->
<div
class="placeholderDom"
:style="{ height: allListData.length * itemHeight + 'px' }"
></div>
<!-- 内容区,展示10条数据,注意其定位的top值是变化的 -->
<div class="contentList" :style="{ top: topVal }">
<!-- 每一条(项)数据 -->
<div
v-for="(item, index) in showListData"
:key="index"
class="itemClass"
:style="{ height: itemHeight + 'px' }"
>
{{ item.name }}
</div>
</div>
<!-- 加载中部分 -->
<div class="loadingBox" v-show="loading">
<i class="el-icon-loading"></i>
&nbsp;&nbsp;<span>loading...........</span>
</div>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
<script>
function throttle(fn, wait) {
var pre = Date.now();
return function () {
var context = this;
var args = arguments;
var now = Date.now();
if (now - pre >= wait) {
fn.apply(context, args);
pre = Date.now();
}
};
}
import axios from "axios";
export default {
data() {
return {
allListData: [], // 所有的数据,比如这个数组存放了十万条数据
itemHeight: 40, // 每一条(项)的高度,比如40像素
count: 10, // 一屏展示几条数据
start: 0, // 开始位置的索引
end: 10, // 结束位置的索引
topVal: 0, // 父元素滚动条滚动,更改子元素对应top定位的值,确保联动
loading: false,
};
},
computed: {
// 从所有的数据allListData中截取需要展示的数据showListData
showListData: function () {
return this.allListData.slice(this.start, this.end);
},
},
async created() {
this.loading = true;
const res = await axios.get("xxxxxx");
this.allListData = res.data.data;
this.loading = false;
},
methods: {
handleScroll() {
throttle(this.s(), 500);
},
s() {
/**
* 获取在垂直方向上,滚动条滚动了多少像素距离Element.scrollTop
*
* 滚动的距离除以每一项的高度,即为滚动到了多少项,当然,要取个整数
* 例:滚动4米,一步长0.8米,滚动到第几步,4/0.8 = 第5步(取整好计算)
*
* 又因为我们一次要展示10项,所以知道了起始位置项,再加上结束位置项,
* 就能得出区间了【起始位置, 起始位置 + size项数】==【起始位置, 结束位置】
* */
const scrollTop = this.$refs.virtualListWrap.scrollTop;
this.start = Math.floor(scrollTop / this.itemHeight);
this.end = this.start + this.count;
/**
* 动态更改定位的top值,确保联动,动态展示相应内容
* */
this.topVal = this.$refs.virtualListWrap.scrollTop + "px";
},
},
};
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<style scoped lang="less">
// 虚拟列表容器盒子
.virtualListWrap {
box-sizing: border-box;
width: 240px;
border: solid 1px #000000;
// 开启滚动条
overflow-y: auto;
// 开启相对定位
position: relative;

.contentList {
width: 100%;
height: auto;
// 搭配使用绝对定位
position: absolute;
top: 0;
left: 0;

.itemClass {
box-sizing: border-box;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
}

// 奇偶行改一个颜色
.itemClass:nth-child(even) {
background: #c7edcc;
}

.itemClass:nth-child(odd) {
background: pink;
}
}

.loadingBox {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: 100%;
height: 100%;
background-color: rgba(255, 255, 255, 0.64);
color: green;
display: flex;
justify-content: center;
align-items: center;
}
}
</style>

四、vxe-table

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="box">
<vxe-table
border
show-overflow
ref="xTable1"
height="300"
:row-config="{ isHover: true }"
:loading="loading"
>
<vxe-column type="seq"></vxe-column>
<vxe-column field="id" title="ID"></vxe-column>
<vxe-column field="name" title="名字"></vxe-column>
<vxe-column field="value" title="对应值"></vxe-column>
</vxe-table>
</div>
</template>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<script>
import axios from "axios";
export default {
data() {
return {
loading: false,
};
},
async created() {
this.loading = true;
const res = await axios.get("xxxxxxx");
this.loading = false;
this.render(res.data.data);
},
methods: {
render(data) {
this.$nextTick(() => {
const $table = this.$refs.xTable1;
$table.loadData(data);
});
},
},
};
</script>

五、vue-virtual-scroller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<template>
<div class="main-container">
<DynamicScroller
class="scroller"
:min-item-size="54"
:items="list"
:item-size="30"
key-field="index"
v-slot="{ item }"
>
<div class="user">{{ item.index + 1 }} -- {{ item.name }}</div>
<!-- <template v-slot="{ item }">
<div class="user">
{{ item.index + 1 }} -- {{ item.name }}
</div>
</template> -->
</DynamicScroller>
</div>
</template>
1
2
3
4
5
6
7
<script>
export default {
props: {
list: Array,
},
}
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<style scoped>
/* .main-container {
height: 200px;
} */

.scroller {
height: 300px;
overflow: scroll;
}

.user {
height: 30px;
padding: 0 12px;
display: flex;
align-items: center;
}
</style>

父组件使用:

1
2
3
4
5
<div
style="height: 200px;width: 300px;margin:20px;border: 1px solid #333;overflow: auto;"
>
<v-table :list="list" v-if="activeName == 'eee'"></v-table>
</div>