taro 封装scrollview组件

591 阅读3分钟

简单封装ScrollView组件,实现页面上下拉加载数据

  • bindscrolltoupper:滚动到顶部/左边时触发
  • bindscrolltolower:滚动到底部/右边时触发

PageScrollView组件

import { Image, ScrollView, ScrollViewProps, View } from "@tarojs/components"
import { useState } from "react"
import Loading from "../Loading"
import "./index.less"
interface IProps extends ScrollViewProps {
  children?: React.ReactNode,
  height?: string,
  className?: string,
  loading?: boolean,
  data?: any[],
  noDataText?: string,
  showBackTop?: boolean,
  onLoadMore?: () => void,
  onRefresh?: () => void
  onBackTop?: () => void
}
const PageScrollView: React.FC<IProps> = (props) => {
  const { children, height, className, onLoadMore, onRefresh, loading = false, data, noDataText = "暂无数据", showBackTop = false, onBackTop } = props

  const [scrollTop, setScrollTop] = useState(0);

  const renderLoading = () => {
    return (
      <View className="scroll-view-loading">
        <Loading type="snake"></Loading>
      </View>
    )
  }

  const renderNoData = () => {
    return (
      <View className="scroll-view-no-data">
        <View >
          <Image className="scroll-view-no-data-img" src="http://oss-prd-vvip-data.oss-cn-shanghai.aliyuncs.com/el/vvip_images/no_data.jpg"></Image>
        </View>
        <View className="scroll-view-no-data-text">{noDataText}</View>
      </View>
    )
  }

  const renderBackTop = () => {

    return (
      <View className="scroll-view-back-top" onClick={() => goBackTop()}>
        <Image className="scroll-view-back-top-icon" src="http://oss-prd-vvip-data.oss-cn-shanghai.aliyuncs.com/el/vvip_images/back_top.png"></Image>
      </View>
    )
  }
  // 解决弹窗弹起时,滚动条回顶部的问题
  let scrollTopCache = 0
  const onScroll = (e) => {
    scrollTopCache = e.detail.scrollTop
    // setScrollTop(scrollTopCache)
  }
  const goBackTop = () => {
    setScrollTop(scrollTop == 0 ? -1 : 0)
    if (scrollTop > 100) {
      onBackTop && onBackTop()
    }
  }

  return (
    <>
      <ScrollView
        className={`${className}`}
        scrollY
        upper-threshold="50"
        lower-threshold="50"
        {...props}
        scrollTop={scrollTop}
        onScroll={onScroll}
        onScrollToUpper={onRefresh}
        onScrollToLower={onLoadMore}
        style={{ height: height }}
      >
        {/* 插槽内容 */}
        {children}
        {/* 加载中 */}
        {loading && renderLoading()}
        {/* 无数据 */}
        {!loading && !data?.length && renderNoData()}
        {/* 回到顶部 */}
        {showBackTop && renderBackTop()}
      </ScrollView>
    </>
  )
}

export default PageScrollView

样式

.scroll-view-loading{
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  height: 80rpx;
}
.scroll-view-no-data{
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
  margin-top: 100rpx;
  &-img{
    width: 300rpx;
    height: 300rpx;
  }
  &-text{
    font-size: 28rpx;
    color: #999;
  }
}

.scroll-view-back-top{
  position: fixed;
  bottom: 100rpx;
  right: 30rpx;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 80rpx;
  height: 80rpx;
  background-color: #ccc;
  border-radius: 50%;
  box-shadow: 0 0 10rpx rgba(0,0,0,.1);
  &-icon{
    width: 40rpx;
    height: 40rpx;
  }
}

loading 组件

import { View } from "@tarojs/components"
import "./index.less"

type loadingType = "snake" | "spin"
interface IProps {
  type?: loadingType,
}
const Loading: React.FC<IProps> = (props) => {
  const { type = "spin" } = props

  const renderSnake = () => {
    return (
      <View className="al-snake-loading">
        {
          [1, 2, 3, 4, 5].map((_, index) => {
            return <View key={index} className="al-snake-loading-item"></View>
          })
        }
      </View>
    )
  }
  const renderSpin = () => {
    return (
      <View className="al-spin-loading"></View>
    )
  }
  return (
    <>
      {type === "snake" && renderSnake()}
      {type === "spin" && renderSpin()}
    </>
  )
}

export default Loading

样式

.al-snake-loading{
  position: relative;
  width: 100px;
  height: 20px;

  &-item{
    position: absolute;
    width: 20px;
    height: 20px;
    background-color: #3cefff;
    opacity: 0.5;
    border-radius: 20px;
    animation: snake 1s infinite ease-in-out;
    &:nth-child(1){
      left: 0px;
      animation-delay: 0s;
    }
    &:nth-child(2){
      left: 20px;
      animation-delay: 0.2s;
    }
    &:nth-child(3){
      left: 40px;
      animation-delay: 0.4s;
    }
    &:nth-child(4){
      left: 60px;
      animation-delay: 0.6s;
    }
    &:nth-child(5){
      left: 80px;
      animation-delay: 0.8s;
    }
  }
}

.al-spin-loading{
  border: 3px solid hsla(185, 100%, 62%, 0.2);
  border-top-color: #3cefff;
  border-radius: 50%;
  width: 60px;
  height: 60px;
  animation: spin 1s linear infinite;
}

@keyframes snake {
  0% {
    opacity: 0.3;
    transform: translateY(0px);
    box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.1);
  }
  50% {
    opacity: 1;
    transform: translateY(-10px);
    background-color: #fc2f70;
    box-shadow: 0px 20px 3px rgba(0, 0, 0, 0.05);
  }
  100% {
    opacity: 0.3;
    transform: translateY(0px);
    box-shadow: 0px 0px 3px rgba(0, 0, 0, 0.1);
  }
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

使用

import api from "@/api";
import PageScrollView from "@/components/PageScrollView";
import { View } from "@tarojs/components";
import { useAsyncEffect } from "ahooks";
import { useRef, useState } from "react";
import "./index.less";
const page = () => {
  const queryParams = useRef<any>({
    storeId: "27",
    baCode: "",
    brand: "EL",
    customerSID: "",
    festivalId: "1864153287945132",
    page: 0,
    pageSize: 11,
  });
  const [orderList, setOrderList] = useState<any>([]);
  const [page, setPage] = useState(0);
  const [isLastPage, setIsLastPage] = useState(false);
  const [loading, setLoading] = useState(false);
  const loadData = async (page: number) => {
    if (isLastPage && page > 0) return;
    setLoading(true);
    setTimeout(async () => {
      const { data: { data: { content = [] } = {} } = {} } = await api.order.unGiftOrder({ ...queryParams.current, page });

      if (content.length < queryParams.current.pageSize) setIsLastPage(true);
      if (page === 0) {
        setOrderList(content);
      } else {
        setOrderList((prevList) => [...prevList, ...content]);
      }
      setLoading(false);
      setPage(page + 1);
    }, 2000);
  }
  useAsyncEffect(async () => {
    await loadData(0);
  }, [])
  return (
    <View className="page">
      <PageScrollView height="100vh" data={orderList} onRefresh={() => loadData(0)} onBackTop={() => loadData(0)} loading={loading} onLoadMore={() => loadData(page)}>
        {
          orderList.map((item, index) => {
            return <View className="card" key={index}>{index}-{item.orderCode}</View>
          })
        }
      </PageScrollView>
    </View>
  )
}

export default page

共享知识,共同进步,不足之处,请多多指教