简单封装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
共享知识,共同进步,不足之处,请多多指教