ch-tgr-h5/src/components/BaseList.vue
2026-05-25 15:41:09 +08:00

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>