189 lines
5.0 KiB
Vue
189 lines
5.0 KiB
Vue
<template>
|
|
<div class="base-list" ref="listRef">
|
|
<div v-if="loading && list.length === 0" class="loading-wrap">
|
|
<van-loading size="24px">加载中...</van-loading>
|
|
</div>
|
|
<template v-for="(item, index) in list" :key="item.id">
|
|
<slot :item="item" :index="index" />
|
|
</template>
|
|
<div v-if="loading && list.length > 0" class="loading-wrap">
|
|
<van-loading size="24px">加载中...</van-loading>
|
|
</div>
|
|
<div v-if="finished && list.length > 0" class="finished-text">{{ finishedText }}</div>
|
|
<div v-if="finished && list.length === 0 && !loading" class="empty-text">暂无数据</div>
|
|
<div ref="sentinel" class="sentinel"></div>
|
|
</div>
|
|
</template>
|
|
|
|
<script>
|
|
export default {
|
|
name: 'BaseList',
|
|
props: {
|
|
url: { type: String, required: true },
|
|
method: { type: String, default: 'get' },
|
|
params: { type: Object, default: () => ({}) },
|
|
pageSize: { type: Number, default: 10 },
|
|
finishedText: { type: String, default: '没有更多了' },
|
|
parseData: { type: Function, default: (res) => res.data.items },
|
|
cacheKey: { type: String, default: '' },
|
|
},
|
|
data() {
|
|
return {
|
|
loading: false,
|
|
finished: false,
|
|
list: [],
|
|
page: 1,
|
|
requestId: 0,
|
|
observer: null,
|
|
_cacheKey: null,
|
|
_dataCache: null,
|
|
}
|
|
},
|
|
emits: ['update:list', 'load', 'refresh'],
|
|
expose: ['refresh'],
|
|
watch: {
|
|
url() {
|
|
this.refresh()
|
|
},
|
|
params: {
|
|
handler() {
|
|
if (this._skipWatch) return
|
|
this.refresh()
|
|
},
|
|
deep: true
|
|
}
|
|
},
|
|
created() {
|
|
if (this.cacheKey) {
|
|
const cached = sessionStorage.getItem(this.cacheKey)
|
|
if (cached) {
|
|
try {
|
|
const { list, page, finished, params } = JSON.parse(cached)
|
|
if (JSON.stringify(this.params) === JSON.stringify(params)) {
|
|
this._dataCache = { list, page, finished }
|
|
}
|
|
} catch (e) { }
|
|
}
|
|
}
|
|
},
|
|
mounted() {
|
|
this.$nextTick(() => {
|
|
this.observer = new IntersectionObserver((entries) => {
|
|
if (entries[0].isIntersecting && !this.loading && !this.finished) {
|
|
this.loadMore()
|
|
}
|
|
}, { rootMargin: '100px' })
|
|
if (this.$refs.sentinel) {
|
|
this.observer.observe(this.$refs.sentinel)
|
|
}
|
|
})
|
|
window.addEventListener('scroll', this.onWindowScroll)
|
|
},
|
|
beforeUnmount() {
|
|
this.observer?.disconnect()
|
|
window.removeEventListener('scroll', this.onWindowScroll)
|
|
if (this.cacheKey && this.list.length > 0) {
|
|
sessionStorage.setItem(this.cacheKey, JSON.stringify({
|
|
list: this.list,
|
|
page: this.page,
|
|
finished: this.finished,
|
|
params: this.params
|
|
}))
|
|
}
|
|
},
|
|
methods: {
|
|
onWindowScroll() {
|
|
if (this.finished || this.loading) return
|
|
const el = this.$refs.sentinel
|
|
if (!el) return
|
|
const rect = el.getBoundingClientRect()
|
|
if (rect.top < window.innerHeight) {
|
|
this.loadMore()
|
|
}
|
|
},
|
|
loadMore() {
|
|
if (this.loading) return
|
|
if (this._dataCache && this._dataCache.params && JSON.stringify(this.params) === JSON.stringify(this._dataCache.params)) {
|
|
this.list = this._dataCache.list
|
|
this.page = this._dataCache.page
|
|
this.finished = this._dataCache.finished
|
|
this.loading = false
|
|
return
|
|
}
|
|
const currentRequestId = this.requestId
|
|
this.loading = true
|
|
const request = this.method === 'get' ? this.$get : this.$post
|
|
request(this.url, { page: this.page, pageSize: this.pageSize, ...this.params })
|
|
.then(res => {
|
|
if (currentRequestId !== this.requestId) return
|
|
const data = this.parseData(res)
|
|
// console.log(data);
|
|
const safeData = data || []
|
|
|
|
if (this.page === 1) {
|
|
this.list = safeData
|
|
} else {
|
|
this.list.push(...safeData)
|
|
}
|
|
this.page++
|
|
this.finished = safeData.length < this.pageSize
|
|
this.$emit('update:list', this.list)
|
|
this.$emit('load', this.list)
|
|
})
|
|
.catch(err => {
|
|
if (currentRequestId !== this.requestId) return
|
|
this.$showFailToast(err.message || '加载失败')
|
|
})
|
|
.finally(() => { this.loading = false })
|
|
},
|
|
refresh() {
|
|
this.requestId++
|
|
this.list = []
|
|
this.page = 1
|
|
this.finished = false
|
|
this.loading = false
|
|
this._skipWatch = true
|
|
this.loadMore()
|
|
this.$emit('refresh', this.list)
|
|
this._skipWatch = false
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.base-list {
|
|
min-height: 200px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.loading-wrap {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
padding: 20px;
|
|
width: 100%;
|
|
box-sizing: border-box;
|
|
}
|
|
|
|
.loading-wrap :deep(.van-loading) {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
flex-direction: row;
|
|
white-space: nowrap;
|
|
float: none !important;
|
|
}
|
|
|
|
.loading-wrap :deep(.van-loading__text) {
|
|
float: none !important;
|
|
}
|
|
|
|
.finished-text,
|
|
.empty-text {
|
|
text-align: center;
|
|
padding: 15px;
|
|
color: #969799;
|
|
font-size: 14px;
|
|
}
|
|
</style> |