概述
后台管理系统当中,最常见的莫过于表格,当出现大量数据的时候,一般采用分页加载的方式,更多的是表格组件结合分页组件实现分页加载数据,最近项目需求要根据表格高度,下拉到最底部然后加载更多数据,类似于手机app当中的我们常见的下拉触底加载数据的方式,本质上思路一致。
实现
基本原理
根据相关dom属性,总结出滚动条触底的一般公式为:
//target为滚动父级元素
target.scrollHeight === parseInt(target.clientHeight + target.scrollTop + '')
基本结构
App.vue
<template>
<div class='app'>
<el-table
v-loadMore="{
loadFn: loadMore,
isLock: isLock
}"
:height="400"
:data="tableData"
style="width: 100%"
>
<el-table-column prop="date" label="日期" width="180" show-overflow-tooltip> </el-table-column>
<el-table-column prop="name" label="姓名" width="180" show-overflow-tooltip> </el-table-column>
<el-table-column prop="address" label="地址" show-overflow-tooltip> </el-table-column>
<el-table-column prop="date" label="日期" width="180" show-overflow-tooltip> </el-table-column>
<el-table-column prop="name" label="姓名" width="180" show-overflow-tooltip> </el-table-column>
<el-table-column prop="address" label="地址" show-overflow-tooltip> </el-table-column>
<template #append v-if="tableData.length">
<div class="append">
<div class="load-more" v-if="hasMore">
<analyze-icon v-if="!loading" class="icon" value="load-more" />
<i class="el-icon-loading icon" v-else></i>
<span class="tips">{{ loading ? '表格数据加载中…' : '加载更多' }} </span>
</div>
<div class="no-more" v-else>
<el-divider class="divider">已加载全部</el-divider>
</div>
</div>
</template>
</el-table>
</div>
</template>
<script>
export default {
data(){
return {
requestStatus : 'FINISH',
currPage :1,
hasMore:false,
tableData : [
{
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
},
{
date: '2016-05-04',
name: '王小虎',
address: '上海市普陀区金沙江路 1517 弄'
},
{
date: '2016-05-01',
name: '王小虎',
address: '上海市普陀区金沙江路 1519 弄'
},
{
date: '2016-05-03',
name: '王小虎',
address: '上海市普陀区金沙江路 1516 弄'
}
];
}
},
computed:{
isLock() {
return this.requestStatus === 'PENDING';
},
loading() {
return this.requestStatus === 'PENDING';
}
},
created() {
this.tableData = new Array(40).fill(0).map((item) => {
return {
date: '2016-05-02',
name: '王小虎',
address: '上海市普陀区金沙江路 1518 弄'
};
});
console.log(this.tableData);
},
methods:{
loadMore() {
if (!this.hasMore) return;
this.currPage++;
this.getTableList();
},
getTableList() {
this.requestStatus ='PENDING';
getAnalyzeTableList({
id: this.$route.query.id as string,
bodyParams: {
currPage: this.currPage,
pageSize: 20,
}
})
.then((res) => {
this.hasMore = res.currPage < res.totalPage;
this.requestStatus = 'FINISH';
})
.catch(() => {
this.requestStatus ='FAILED';
});
},
}
}
</script>
全局指令
import Vue from 'vue';
const loadMoreOption = {
bind(el: any, binding: any, vnode: any) {
const selectWrap = el.querySelector('.el-table__body-wrapper');
selectWrap && selectWrap.addEventListener('scroll', handleScroll);
function handleScroll(e: MouseEvent) {
const target: HTMLElement = e.target as HTMLElement;
if (target.scrollHeight === parseInt(target.clientHeight + target.scrollTop + '')) {
binding?.value.loadFn();
}
}
vnode.componentInstance._handleScroll = handleScroll;
vnode.componentInstance._scrollWrap = selectWrap;
},
update(el: any, binding: any, vnode: any) {
// 组件数据更新所做的逻辑为优化频繁下拉触底请求数据的情况发生,上次结果出来后,才能发送下次请求
const handleScrollFn = vnode.componentInstance._handleScroll;
const selectWrap = vnode.componentInstance._scrollWrap;
if (binding.value.isLock) {
selectWrap.removeEventListener('scroll', handleScrollFn);
} else {
selectWrap.addEventListener('scroll', handleScrollFn);
}
}
};
Vue.directive('loadMore', loadMoreOption);