Vant 下拉刷新加载更多列表封装 ts vite vue3

535 阅读1分钟

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 //请求页码开始下标

}

  


//bw-list需要的类

export class BwListClass {

private request: RequestFunType //请求数据接口

readonly height: string //列表页面style高度

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(() => {

// console.log('onActivated--minScroll', scrollTop.value)

root.value.scrollTo({

top: scrollTop.value

})

})

  


function onScroll(e: any) {

// console.log(e.srcElement.scrollTop)

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>