vant3
import { Toast } from 'vant'
import { computed, ref } from 'vue'
export interface ListRequestResult {
list: Array<any>
total: number
}
export interface ListRequestResultError {
err: unknown
}
type RequestFunType = () => Promise<ListRequestResult | ListRequestResultError>
type BwListOption = {
pullingText?: string
loosingText?: string
loadingText?: string
successText?: string
disabled?: boolean
requestBeginPage: number
}
export class BwListClass {
private request: RequestFunType
readonly height: string
readonly options?: BwListOption = {
pullingText: '下拉即可刷新...',
loosingText: '释放即可刷新...',
loadingText: '加载中...',
successText: '加载完成',
disabled: false,
requestBeginPage: 0
}
readonly refreshing = ref(false)
readonly pageNumber = ref(0)
readonly list = ref<Array<any>>([])
readonly finished = ref(false)
readonly loading = ref(false)
private errorFlag = ref(false)
readonly errorMsg = ref('')
loadMoreError = ref(false)
readonly empty = computed(() => {
return (
!this.refreshing.value &&
this.pageNumber.value == this.options!.requestBeginPage &&
this.list.value.length == 0 &&
!this.errorFlag.value
)
})
constructor(
request: RequestFunType,
height: string,
options?: BwListOption
) {
this.request = request
this.height = height
if (options) {
this.options = { ...this.options, ...options }
}
if (options?.requestBeginPage) {
this.pageNumber = ref(this.options!.requestBeginPage)
}
}
onRefresh = () => {
if (this.refreshing.value == false) {
this.refreshing.value = true
}
this.loadData()
}
load = () => {
this.loading.value = true
this.pageNumber.value++
this.loadData()
}
loadData = () => {
let lastPage = this.pageNumber.value
if (this.refreshing.value) {
this.pageNumber.value = this.options!.requestBeginPage
}
if (this.loading.value) {
lastPage--
}
this.request &&
this.request()
.then((res: ListRequestResult | ListRequestResultError) => {
this.errorFlag.value = false
if ((res as ListRequestResultError).err !== undefined) {
console.log('ListRequestResultError', res)
if (
this.loading &&
this.pageNumber.value !=
this.options!.requestBeginPage
) {
this.loadMoreError.value = true
} else {
this.errorFlag.value = true
}
this.refreshing.value = false
this.loading.value = false
console.log('lastPage', lastPage, this.pageNumber.value)
this.pageNumber.value = lastPage
} else {
let arr = (res as ListRequestResult).list
let total = (res as ListRequestResult).total
if (this.refreshing.value) {
this.list.value = []
this.refreshing.value = false
this.finished.value = false
}
this.list.value = this.list.value.concat(arr)
this.loading.value = false
console.log(this.list.value.length, total)
if (
this.list.value.length >= total ||
this.list.value.length == 0
) {
this.finished.value = true
}
}
})
.catch((err: any) => {
console.log(err)
if (this.refreshing.value) {
Toast(`下拉刷新失败: ${err.msg}`)
}
if (this.loading.value) {
Toast(`加载更多失败: ${err.msg}`)
}
if (
this.loading &&
this.pageNumber.value != this.options!.requestBeginPage
) {
this.loadMoreError.value = true
} else {
this.errorMsg.value =
(err.msg || '') + '-请求服务失败,点击重试!2'
this.errorFlag.value = true
}
this.refreshing.value = false
this.loading.value = false
console.log('lastPage2', lastPage, this.pageNumber.value)
this.pageNumber.value = lastPage
})
}
}
<template>
<div :style="navStyle" ref="root" class="view" @scroll="onScroll">
<van-pull-refresh
v-model="res.refreshing.value"
:pullingText="res.options?.pullingText"
:loosingText="res.options?.loosingText"
:loadingText="res.options?.loadingText"
:successText="res.options?.successText"
:disabled="res.options?.disabled || res.loading.value"
@refresh="res.onRefresh"
>
<van-list
class="listHeight"
v-model:loading="res.loading.value"
v-model:error="res.loadMoreError.value"
:finished="res.finished.value"
error-text="请求失败,点击重新加载"
:finished-text="
!res.finished.value || res.list.value.length == 0
? ''
: '没有更多了'
"
:immediate-check="false"
@load="res.load"
>
<div
class="item-list"
v-for="item in res.list.value"
:key="item.toString()"
>
<slot :item="item"></slot>
</div>
<!-- 下拉刷新空数据 -->
<van-empty
v-if="res.empty.value"
:image="emptyImage"
description="暂无数据, 下拉刷新!"
/>
</van-list>
</van-pull-refresh>
</div>
</template>
<script setup lang="ts">
/******************************************************/
import { computed, onMounted, watch, ref } from 'vue'
import { BwListClass } from './index'
import { emptyImage, errorImage } from '@/common/img'
import userScroll from '@/common/minScroll'
/******************************************************/
const props = defineProps<{ res: BwListClass }>()
const minHeight = computed(() => {
let height = props.res.height
if (height.indexOf('calc') != -1) {
height = `${height.substring(0, height.length - 1)} - 30px)`
} else {
height = `${height} - 30px`
}
// console.log('minHeight', height)
return height
})
const navStyle = {
'--height': props.res.height,
'--minHeight': minHeight.value
}
/******************************************************/
//滚动传递给父组件使用
const root = ref()
const { scrollTop, onScroll, toTop } = userScroll(root)
defineExpose({ scrollTop, onScroll, toTop })
</script>
<style scoped lang="less">
.view {
overflow-y: scroll;
overflow-x: hidden;
padding: 0px 15px;
height: var(--height);
.listHeight {
min-height: var(--minHeight);
}
}
</style>
import { ref, onActivated, onDeactivated, Ref } from 'vue'
export default function userScroll(root: Ref<Element>) {
const scrollTop = ref(0)
onActivated(() => {
root.value.scrollTo({
top: scrollTop.value
})
})
function onScroll(e: any) {
scrollTop.value = e.srcElement.scrollTop
}
function toTop() {
if (root) {
root.value.scrollTo({
top: 0
})
}
}
return { scrollTop, onScroll, toTop }
}
import { shallowRef } from 'vue'
export const emptyImage = shallowRef(
new URL('@/assets/image/common/empty.png', import.meta.url).href
)
export const errorImage = shallowRef(
new URL('@/assets/image/common/error.png', import.meta.url).href
)
export const attachmentImage = shallowRef(
new URL('@/assets/image//detail/attachment.png', import.meta.url).href
)
使用
<!-- 待办 已办列表 -->
<template>
<div class="backlog">
<bw-nav-bar :title="title"></bw-nav-bar>
<bw-list ref="bwlist" :res="bwListModel">
<template #default="slotProps">
<div>{{slotProps.item}}</div>
</template>
</bw-list>
</div>
</template>
<script setup lang="ts">
/******************************************************/
import { ref } from 'vue'
import { toDoList, doneList } from '@/common/api.js'
import { BwListClass } from '@/components/bw-list'
/******************************************************/
const height = 'calc(100vh - env(safe-area-inset-top) - 46px)'
const bwListModel: BwListClass = new BwListClass(requestList, height)
/******************************************************/
onMounted(() => {
bwListModel.onRefresh()
})
/******************************************************/
function requestList() {
let _res = (res: any) => {
return Promise.resolve({
list: res.data.result,
total: parseInt(res.data.count)
})
}
let _err = (err: unknown) => {
return Promise.reject(err)
}
//此处可以替换自己的网络请求
if (listType.value == ListType.done) {
return doneList({
offset: bwListModel.pageNumber.value
})
.then(_res)
.catch(_err)
} else {
return toDoList({
offset: bwListModel.pageNumber.value
})
.then(_res)
.catch(_err)
}
}
</script>
<style lang="less" scoped>
.backlog {
height: 100vh;
width: 100vw;
}
</style>