下拉刷新组件封装

一:在components文件下创建refresh文件夹(refresh/index)

<template>
	<scroll-view style="height: 100%;" :style="{'background-color': bgColor}"
		:scroll-y="allow_scroll_y" :refresher-enabled="false"
		@scroll="scroll" @scrolltolower="bottomTolower"
	>
		<!-- <view style="height: var(--status-bar-height);"></view> -->
		<view class="refresh-moudle"
			@touchstart="touchStart($event)" 
			@touchmove="touchMove($event)" 
			@touchend="touchEnd($event)" 
			:style="{'margin-top': showTopLoad ? `calc(${top}px - 180rpx)` : '0'}">
			<!-- :style="{transform: 'translate3d(0,' + top + 'px, 0)'}"
		> -->
			  <view class="pull-refresh" :style="{'display': showTopLoad? 'block': 'none'}">
				<slot name="pull-refresh">
				  <view class="down-tip" v-if="dropDownState==1">
					<uv-icon :color="contentColor" class="down-img" name="arrow-downward"></uv-icon>
					<!-- <image v-if="dropDownInfo.downImg" class="down-img" :src="dropDownInfo.downImg"></image> -->
					<view class="down-text" :style="{'color': contentColor}">{{downText}}</view>
				  </view>
				  <view class="up-tip" v-if="dropDownState==2">
					<uv-icon :color="contentColor" class="up-img" name="arrow-downward"></uv-icon>
					<!-- <image v-if="dropDownInfo.upImg" class="up-img" :src="dropDownInfo.upImg"></image> -->
					<view class="up-text" :style="{'color': contentColor}">{{upText}}</view>
				  </view>
				  <view class="refresh-tip" v-if="dropDownState==3">
					  <!-- <div class="loader"></div> -->
					  <uv-loading-icon mode="spinner" :color="contentColor" :textColor="contentColor"></uv-loading-icon>
					<!-- <image v-if="dropDownInfo.refreshImg" class="refresh-img" :src="dropDownInfo.refreshImg"></image> -->
					<view class="refresh-text" style="margin-left: 15px;" :style="{'color': contentColor}">{{refreshText}}</view>
				  </view>
				</slot>
			  </view>
			<slot></slot>
			<view v-if="!showTopLoad" style="width: 100%; margin: 10rpx 0;">
				<slot name="push-refresh">
					<view class="flex" v-if="!showMore">
						<view class="refresh-text" style="margin-left: 15px;" :style="{'color': contentColor}">没有更多数据了</view>
					</view>
					<view class="flex" v-else-if="pushLoading">
						<uv-loading-icon mode="spinner" :color="contentColor" ></uv-loading-icon>
						<view class="refresh-text" style="margin-left: 15px;" :style="{'color': contentColor}">加载中...</view>
					</view>
					<view class="flex" v-else>
						<uv-icon :color="contentColor" class="up-img" name="arrow-upward"></uv-icon>
						<view class="up-text" :style="{'color': contentColor}">上拉显示更多</view>
					</view>
				</slot>
			</view>
		</view>
	</scroll-view>
</template>

<script>
	export default {	
        name: "refreshCustom",
		props: {
			bgColor: {
				type: String,
				default: '#A03AFF'
			},
			loadColor: {
				type: String,
				default: 'rgb(60,15,95)'
			},
			contentColor: {
				type: String,
				default: 'rgb(60,15,95)'
			},
			downText: {
				type: String,
				default: '下拉刷新'
			},
			upText: {
				type: String,
				default: '释放立即刷新'
			},
			refreshText: {
				type: String,
				default: '刷新中...'
			},
			/**
			 * 显示下拉功能,true:下拉刷新,false:上拉刷新
			 */
			showTopLoad: {
				type: Boolean,
				default: true
			},
			/**
			 * 下拉刷新中,是否可以继续加载数据
			 */
			showMore: {
				type: Boolean,
				default: false
			}
		},
		data () {
			return {
			  defaultOffset: 80, // 下拉偏移高度, 如果要改建议相应的修改.releshMoudle的margin-top和.down-tip, .up-tip, .refresh-tip的height
			  top: 0,
			  startY: 0,
			  isDropDown: false, // 是否下拉
			  isRefreshing: false, // 是否正在刷新
			  dropDownState: 1, // 显示1:下拉可以刷新, 2:松开立即刷新, 3:正在刷新数据中...
			  
			  scrollTop: 0,
			  allow_scroll_y: true,//解决因ios环境下scrollview的scrolltop可下拉为负数带来的体验问题
			  allowTouchMove: true, //控制是否允许touchMove事件
			  
			  pushLoading: false,
			}
		},
		methods: {
			scroll: function(e) {
				this.$nextTick(function() {
					this.scrollTop = e.detail.scrollTop
				});
			},
			/**
			 * 触摸开始,手指点击屏幕时
			 * @param {object} e Touch 对象包含的属性
			 */
			touchStart (e) {
				if (!this.showTopLoad) {
					return
				}
				this.startY = e.changedTouches[0].pageY
				this.startScrollTop = this.scrollTop				
				this.allowTouchMove = true //控制是否允许touchMove事件
			},
			
			/**
			 * 接触点改变,滑动时
			 * @param {object} e Touch 对象包含的属性
			 */
			touchMove (e) {
				//控制是否允许touchMove事件
				if(!this.allowTouchMove) {
					return
				}
				
				//解决因ios环境下scrollview的scrolltop可下拉为负数带来的体验问题
				this.$nextTick(function() {
					if(this.scrollTop<0) {
						this.allow_scroll_y = false
					} else {
						this.allow_scroll_y = true
					}
				});
				
				if(this.scrollTop <= 0) {
					if (e.changedTouches[0].pageY > this.startY) {
						// 下拉
						this.isDropDown = true
						if (!this.isRefreshing) {
						  // 获取拉取的间隔差  当前移动的y点          初始的y点        初始顶部距离
						  let diff = e.changedTouches[0].pageY - this.startY -  this.startScrollTop
						  this.top = Math.pow(diff, 0.8) + (this.dropDownState === 3 ? this.defaultOffset : 0);
						  if (this.top >= this.defaultOffset) {
							this.dropDownState = 2
							e.preventDefault();
						  } else { 
							this.dropDownState = 1
							// 去掉会导致ios无法刷新
							e.preventDefault();
						  }
						}
					} else {
						this.isDropDown = false
						this.dropDownState = 1
					}
				}
			},
			
			/**
			 * 触摸结束,手指离开屏幕时
			 * @param {object} e Touch 对象包含的属性
			 */
			touchEnd (e) {
			  //解决因ios环境下scrollview的scrolltop可下拉为负数带来的体验问题	
			  this.$nextTick(function() {
				  this.allow_scroll_y = true
			  });
			  
			  if (this.isDropDown && !this.isRefreshing) {
				if (this.top >= this.defaultOffset) {
				  // do refresh
				  this.isRefreshing = true
				  this.refresh();
				} else {
				  // cancel refresh
				  this.isRefreshing = false
				  this.isDropDown = false
				  this.dropDownState = 1
				  this.top = 0
				}
			  }
			},
			
			/**
			 * 刷新
			 */
			refresh () {
			  this.dropDownState = 3
			  this.top = this.defaultOffset
			  // 获取数据,结束正在刷新动画
			  this.$emit('onRefresh', { data: 'data123', cb: () => {
				  this.refreshDone()
			  }});
			  		  
			  setTimeout(() => {
				this.refreshDone()
			  }, 30000);
			},
			
			/**
			 * 刷新完成
			 */
			refreshDone () {
			  this.isRefreshing = false
			  this.isDropDown = false
			  this.dropDownState = 1
			  this.top = 0
			},
			/**
			 * 上拉方法
			 */
			bottomTolower() {
				console.log('--- bottomTolowerbottomTolower')
				this.pushLoading = true
				this.$emit('onBottomRefresh', { cb: () => {
					this.pushLoading = false
				}})
				setTimeout(() => {
					this.pushLoading = false
				}, 30000);
			}
		}
	}
</script>

<style>
	/* HTML: <div class="loader"></div> */
	.loader {
	  width: 25px;
	  aspect-ratio: 1;
	  display: grid;
	  border-radius: 50%;
	  /* background:
	    linear-gradient(0deg ,rgb(0 0 0/50%) 30%,#0000 0 70%,rgb(0 0 0/100%) 0) 50%/8% 100%,
	    linear-gradient(90deg,rgb(0 0 0/25%) 30%,#0000 0 70%,rgb(0 0 0/75% ) 0) 50%/100% 8%; */
	  /* background:
	    linear-gradient(0deg ,rgb(60 15 95/50%) 30%,#0000 0 70%,rgb(60 15 95/100%) 0) 50%/8% 100%,
	    linear-gradient(90deg,rgb(60 15 95/25%) 30%,#0000 0 70%,rgb(60 15 95/75% ) 0) 50%/100% 8%; */
	  background:
	    linear-gradient(0deg ,rgb(60 15 95/50%) 30%,#0000 0 70%,rgb(60 15 95/100%) 0) 50%/8% 100%,
	    linear-gradient(90deg,rgb(60 15 95/25%) 30%,#0000 0 70%,rgb(60 15 95/75% ) 0) 50%/100% 8%;
	  background-repeat: no-repeat;
	  animation: l23 1s infinite steps(12);
	}
	.loader::before,
	.loader::after {
	   content: "";
	   grid-area: 1/1;
	   border-radius: 50%;
	   background: inherit;
	   opacity: 0.915;
	   transform: rotate(30deg);
	}
	.loader::after {
	   opacity: 0.83;
	   transform: rotate(60deg);
	}
	@keyframes l23 {
	  100% {transform: rotate(1turn)}
	}
</style>

<style lang="scss" scoped>
	$height: 180rpx;
	.refresh-moudle {
		width: 100%;
		margin-top: -$height;
		-webkit-overflow-scrolling: touch; /* ios5+ */
		.pull-refresh {
			width: 100%;
			color: #999;
			transition-duration: 200ms;
			font-size: 28upx;
			.down-tip,
			.up-tip,
			.refresh-tip {
				display: flex;
				align-items: center;
				justify-content: center;
				height: $height;
			}
			.down-img,
			.up-img,
			.refresh-img{
				width: 50upx;
				height: 50upx;
				margin-right: 30upx;
			}
			.down-img {
			  transform: rotate(0deg);
			  animation: anticlockwise 0.8s ease;
			}
			@keyframes anticlockwise {
			  0% {
				transform: rotate(-180deg);
			  }
			  100% {
				transform: rotate(0deg);
			  }
			}
			.up-img {
			  transform: rotate(180deg);
			  animation: clockwise 0.8s ease;
			}
			@keyframes clockwise {
			  0% {
				transform: rotate(0deg);
			  }
			  100% {
				transform: rotate(-180deg);
			  }
			}
			.refresh-img {
			  animation: rotating 1.5s linear infinite;
			}
			@keyframes rotating {
			  0% {
				transform: rotate(0deg);
			  }
			  100% {
				transform: rotate(1turn);
			  }
			}
		}
	}
</style>

一:如何使用

bgColor  loadColor  contentColor这三个颜色自定义

<refresh @onRefresh="onRefresh" :bgColor="null" :loadColor="'RGBA(41, 184, 118, 1)'"
					:contentColor="'RGBA(160, 237, 193, 1)'">
    /*列表内容*/
</refresh>

<script setup>
import Refresh from '@/components/refresh/index.vue'

async function onRefresh(option) {
	console.log('onRefresh ---', option);
	const {
		cb,
		data
	} = option;

	try {
		await getData('1', '1', '10', token), //将要刷新的数据放这里,确保异步的请求完成
	} catch (error) {
		console.error('Error fetching backpack list:', error);
	} finally {
		cb && cb(); // 在完成后调用回调
	}
}

</script>

下拉刷新是移动端 APP 中常见的交互方式,可以提高用户体验,因此我们可以将其封装成一个组件,方便在多个页面中复用。下面是一个简单的 Vue 下拉刷新组件的实现: ```html <template> <div class="pull-refresh-wrapper"> <div class="pull-refresh-status" :class="{'loading': isLoading}"> <div class="pull-refresh-icon"></div> <div class="pull-refresh-text">{{ text }}</div> </div> <div class="pull-refresh-content" ref="content"> <slot></slot> </div> </div> </template> <script> export default { props: { threshold: { type: Number, default: 80 }, text: { type: String, default: '下拉刷新' } }, data() { return { startY: 0, isLoading: false, isDragging: false }; }, mounted() { this.$refs.content.addEventListener('touchstart', this.handleTouchStart); this.$refs.content.addEventListener('touchmove', this.handleTouchMove); this.$refs.content.addEventListener('touchend', this.handleTouchEnd); }, beforeDestroy() { this.$refs.content.removeEventListener('touchstart', this.handleTouchStart); this.$refs.content.removeEventListener('touchmove', this.handleTouchMove); this.$refs.content.removeEventListener('touchend', this.handleTouchEnd); }, methods: { handleTouchStart(e) { this.startY = e.touches[0].clientY; }, handleTouchMove(e) { if (this.isLoading) { return; } const currentY = e.touches[0].clientY; const distance = currentY - this.startY; if (distance > 0 && this.$refs.content.scrollTop === 0) { this.isDragging = true; e.preventDefault(); if (distance >= this.threshold) { this.text = '松开刷新'; } else { this.text = '下拉刷新'; } } else { this.isDragging = false; } }, handleTouchEnd() { if (this.isLoading || !this.isDragging) { return; } if (this.text === '松开刷新') { this.isLoading = true; this.text = '正在刷新'; this.$emit('refresh', () => { this.isLoading = false; this.text = '下拉刷新'; }); } else { this.text = '下拉刷新'; } this.isDragging = false; } } }; </script> <style> .pull-refresh-wrapper { height: 100%; overflow: hidden; } .pull-refresh-status { height: 80px; display: flex; align-items: center; justify-content: center; font-size: 14px; color: #666; } .pull-refresh-icon { width: 16px; height: 16px; margin-right: 8px; border-radius: 50%; border: 2px solid #666; border-top-color: transparent; animation: spin 0.6s linear infinite; } .pull-refresh-text { white-space: nowrap; } .pull-refresh-content { height: calc(100% - 80px); overflow-y: auto; } .loading .pull-refresh-icon { border-color: #42b983; border-top-color: transparent; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> ``` 在上面的代码中,我们使用了 touch 事件来实现下拉刷新。当用户下拉到一定程度时,组件会触发 refresh 事件,我们可以在该事件中进行数据的异步请求。在请求完成后,将 isLoading 设为 false 即可停止 loading 状态。 另外,我们使用了 slot 来插入需要刷新的内容,并监听了 touchstart、touchmove 和 touchend 事件来处理下拉刷新的逻辑。其中,handleTouchMove 方法用于判断下拉距离是否达到阈值,并改变刷新文本的内容;handleTouchEnd 方法用于处理下拉刷新的触发事件,如果下拉距离达到阈值,则触发 refresh 事件,并将 isLoading 设为 true,同时改变刷新文本的内容为“正在刷新”。在事件处理函数中,我们使用了箭头函数来确保 this 指向正确。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值