usePagingRequest 与 v-infinite-scroll 中后台列表滚动加载配套使用

286 阅读2分钟

usePagingRequest & v-infinite-scroll 中后台列表滚动加载配套使用

基于element-plus指令 v-infinite-scroll + 自定义hook函数usePagingRequest实现

1、指令安装

import { ElInfiniteScroll } from 'element-plus'

app.use(ElInfiniteScroll)

2、usePagingRequest hook函数设计

2.1、返回值设计

/**
 * @param loading 请求中
 * @param successStatus 请求成功
 * @param disabled 对应v-infinite-scroll指令属性infinite-scroll-disabled绑定值
 * @param noMore 分页数据全部加载完成
 * @param showRetryButton 是否显示数据加载失败重试按钮
 * @param query 请求参数
 * @param data 加载后的数据列表
 * @param load 数据加载方法
 * @param setLoading 主动设置请求中状态
 * @param setSuccessStatus 主动设置数据加载成功状态
 * @param setQuery 主动设置请求参数(增量设置非全部覆盖),用户搜索等查询条件
 */
export interface IUsePagingRequestReturnType<DataItem, RequestParams> {
  loading: Ref<boolean | undefined>
  successStatus: Ref<boolean | undefined>
  disabled: Ref<boolean>
  noMore: Ref<boolean>
  showRetryButton: Ref<boolean>
  query: Ref<UnwrapRef<RequestParams>>
  data: Ref<DataItem[]>
  load: () => Promise<IPagingResponseData<DataItem>> | undefined
  setLoading: (val: boolean) => boolean
  setSuccessStatus: (val: boolean) => boolean
  setQuery: (params: RequestParams, resetPageNo?: boolean) => void
}

2.2、使用案例

<div
  v-infinite-scroll="load"
  :infinite-scroll-disabled="disabled"
  :infinite-scroll-immediate="true"
>
  <ElRadioGroup v-model="checkedProductNo">
    <template v-for="item in data" :key="item.productNo">
      <ItemProduct
        :productName="item.productName"
        :productNo="item.productNo"
        :desc="item.desc"
        :categoryName="item.categoryName"
      />
    </template>
  </ElRadioGroup>
  <div :class="`open-product__loading ${noMore ? 'hidden' : ''}`" v-if="loading">
    <span>拼命加载中...</span>
  </div>
  <div class="open-product__retryload" v-if="showRetryButton" @click="load">
    <span>加载失败,点击</span>
    <ElButton type="primary" link>重试</ElButton>
  </div>
  <div class="open-product--nomore" v-if="noMore">没有更多了</div>
</div>
import type { IProductItem, IProductRequestParams } from '@/api/productManage/types'
import { getProductListApi } from '@/api/productManage'
import { usePagingRequest } from '@/hooks/web/usePagingRequest'

const { data, loading, successStatus, showRetryButton, noMore, disabled, load, setQuery } =
  usePagingRequest<IProductItem, IProductRequestParams>({
    apiMethod: getProductListApi,
    transformData: (data) => {
      // 这里可以使用map重组新数据列表
      return data
    },
    params: {
      pageNo: 1,
      pageSize: 20
    }
  })

2.3、代码实现

export function usePagingRequest<
  DataItem extends Recordable,
  RequestParams extends Recordable
>(options: {
  apiMethod: (...args: any) => Promise<IPagingResponseData<DataItem>>
  params: RequestParams
  transformData?: (data: DataItem[]) => DataItem[]
  pageNoName?: string
  pageSizeName?: string
}): IUsePagingRequestReturnType<DataItem, RequestParams> {
  const {
    apiMethod,
    params,
    transformData,
    pageNoName = 'pageNo',
    pageSizeName = 'pageSize'
  } = options

  const { loading, setLoading } = useLoading(false)
  const { status: successStatus, setStatus: setSuccessStatus } = useStatus<boolean>()
  const total = ref<number>(0)
  const data = ref<DataItem[]>([])
  const failLoadRetryRequestPageNoList = ref<number[]>([])
  const noMore = computed(() => total.value > 0 && data.value.length >= total.value)
  const showRetryButton = computed(() => !loading.value && !successStatus.value && !noMore.value)
  const disabled = computed(
    () => loading.value || noMore.value || failLoadRetryRequestPageNoList.value.length >= 5
  )

  const defaultParams = {
    ...cloneDeep(params),
    [pageNoName]: params[pageNoName] || 1,
    [pageSizeName]: params[pageSizeName] || 20
  }

  const query = ref<RequestParams>(defaultParams)
  const pageNo = computed(() => query.value[pageNoName])

  const setQuery = (params: RequestParams, resetPageNo = true) => {
    const newValue = {
      ...query.value,
      ...cloneDeep(params)
    }
    if (resetPageNo) newValue[pageNoName] = params[pageNoName] || 1
    query.value = {
      ...pickBy(newValue, isValidValue)
    }
  }

  const reqParams = computed(() => pickBy(query.value, isValidValue))

  const load = () => {
    if (loading.value) return

    setLoading(true)
    data.value = pageNo.value === 1 ? [] : data.value

    return apiMethod(reqParams.value)
      .then((res) => {
        setSuccessStatus(true)
        failLoadRetryRequestPageNoList.value = []
        total.value = res.total
        const records = res?.records || []

        const list = (transformData && transformData(records)) || records
        data.value = [...data.value, ...list] as UnwrapRef<DataItem[]>
        query.value[pageNoName] += 1

        return res
      })
      .catch((err) => {
        setSuccessStatus(false)
        failLoadRetryRequestPageNoList.value.push(pageNo.value)
        return Promise.reject(err)
      })
      .finally(() => (loading.value = false))
  }

  return {
    loading,
    setLoading,
    successStatus,
    setSuccessStatus,
    disabled,
    noMore,
    showRetryButton,
    query,
    setQuery,
    data,
    load
  }
}