| <template> | |
| 	<view class="yingbing-scroller" | |
| 	:refreshState="refreshState" :change:refreshState="pulldownwxs.refreshStateWatcher" | |
| 	:pulldownable="pulldownable" :change:pulldownable="pulldownwxs.pulldownableWatcher" | |
| 	:pullupable="pullupable" :change:pullupable="pulldownwxs.pullupableWatcher" | |
| 	@touchstart="pulldownwxs.touchstart" | |
| 	@touchmove="pulldownwxs.touchmove" | |
| 	@touchend="pulldownwxs.touchend" | |
| 	@touchcancel="pulldownwxs.touchcancel"> | |
| 		<view class="yingbing-scroller-wrapper"> | |
| 			<view class="yingbing-scroller-refresh"> | |
| 				<refresh-loading size="30" :visible="pulldownStatus == 'end' ? false : Boolean(pulldownStatus)" :color="loadingColor"></refresh-loading> | |
| 				<text class="yingbing-scroller-refresh-text" :style="{color: loadingColor}">{{ pulldownText }}</text> | |
| 			</view> | |
| 			<view class="yingbing-scroll"> | |
| 				<scroll-view | |
| 				scroll-anchoing | |
| 				class="yingbing-scroll-view" | |
| 				scroll-y | |
| 				:refresher-enabled="false" | |
| 				:scroll-into-view="scrollIntoViewId" | |
| 				:scroll-with-animation="scrollWithAnimation" | |
| 				:show-scrollbar="false" | |
| 				:scroll-top="scrollTop" | |
| 				:lower-threshold="100" | |
| 				@scroll="handleScroll" @scrolltolower="handleScrolltolower" | |
| 				@scrolltoupper="handleScrolltoupper"> | |
| 					<view :id="'yingbing-scroll-item_' + index" v-for="(item, index) in data" :key="item.index + '_' + item.current"> | |
| 						<!-- #ifdef MP --> | |
| 						<slot :name="'wx:' + index"></slot> | |
| 						<!-- #endif --> | |
| 						<!-- #ifndef MP --> | |
| 						<slot :item="item" :index="index"></slot> | |
| 						<!-- #endif --> | |
| 					</view> | |
| 					<view class="yingbing-scroller-refresh" v-if="loadmoreable" @tap="handleScrolltolower"> | |
| 						<refresh-loading size="30" :visible="loadmoreStatus == 'end' ? false : Boolean(loadmoreStatus)" :color="loadingColor"></refresh-loading> | |
| 						<text class="yingbing-scroller-refresh-text">{{loadmoreText}}</text> | |
| 					</view> | |
| 				</scroll-view> | |
| 			</view> | |
| 			<view class="yingbing-scroller-refresh"> | |
| 				<refresh-loading size="30" :visible="pullupStatus == 'end' ? false : Boolean(pullupStatus)" :color="loadingColor"></refresh-loading> | |
| 				<text class="yingbing-scroller-refresh-text" :style="{color: loadingColor}">{{ pullupText }}</text> | |
| 			</view> | |
| 		</view> | |
| 	</view> | |
| </template> | |
| 
 | |
| <script> | |
| 	import RefreshLoading from '../loading/loading.vue' | |
| 	const readyHeight = 80 | |
| 	export default { | |
| 		options: { | |
| 			addGlobalClass: true, | |
| 			virtualHost: true,//将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定 | |
| 		}, | |
| 		components: { | |
| 			RefreshLoading | |
| 		}, | |
| 		inject: ['getPrevChapterDefaultText', 'getNextChapterDefaultText', 'getChapterReadyText', 'getChapterLoadingText', 'getChapterSuccessText', 'getChapterFailText', 'getPrevChapterEndText', 'getNextChapterEndText'], | |
| 		props: { | |
| 			data: { | |
| 				type: Array, | |
| 				default () { | |
| 					return new Array | |
| 				} | |
| 			}, | |
| 			loadingColor: { | |
| 				type: String, | |
| 				default: '#333' | |
| 			}, | |
| 			autoplay: { | |
| 				type: Boolean, | |
| 				default: false | |
| 			}, | |
| 			pulldownable: { | |
| 				type: Boolean, | |
| 				default: false | |
| 			}, | |
| 			pullupable: { | |
| 				type: Boolean, | |
| 				default: false | |
| 			}, | |
| 			loadmoreable: { | |
| 				type: Boolean, | |
| 				default: false | |
| 			} | |
| 		}, | |
| 		computed: { | |
| 			chapterReadyText () { | |
| 				return this.getChapterReadyText() | |
| 			}, | |
| 			chapterLoadingText () { | |
| 				return this.getChapterLoadingText() | |
| 			}, | |
| 			chapterSuccessText () { | |
| 				return this.getChapterSuccessText() | |
| 			}, | |
| 			chapterFailText () { | |
| 				return this.getChapterFailText() | |
| 			}, | |
| 			prevChapterDefaultText () { | |
| 				return this.getPrevChapterDefaultText() | |
| 			}, | |
| 			prevChapterEndText() { | |
| 				return this.getPrevChapterEndText() | |
| 			}, | |
| 			nextChapterDefaultText() { | |
| 				return this.getNextChapterDefaultText() | |
| 			}, | |
| 			nextChapterEndText() { | |
| 				return this.getNextChapterEndText() | |
| 			}, | |
| 			pulldownText () { | |
| 				return this.pulldownStatus == 'ready' ? this.chapterReadyText : this.pulldownStatus == 'success' ? this.chapterSuccessText : this.pulldownStatus == 'fail' ? this.chapterFailText :  this.pulldownStatus == 'end' ? this.prevChapterEndText : this.prevChapterDefaultText | |
| 			}, | |
| 			pullupText () { | |
| 				return this.pullupStatus == 'ready' ? this.chapterReadyText : this.pullupStatus == 'success' ? this.chapterSuccessText : this.pullupStatus == 'fail' ? this.chapterFailText :  this.pullupStatus == 'end' ? this.nextChapterEndText : this.nextChapterDefaultText | |
| 			}, | |
| 			loadmoreText () { | |
| 				return this.loadmoreStatus == 'ready' ? this.chapterLoadingText : this.loadmoreStatus == 'success' ? this.chapterSuccessText : this.loadmoreStatus == 'fail' ? this.chapterFailText :  this.loadmoreStatus == 'end' ? this.nextChapterEndText : this.nextChapterDefaultText | |
| 			} | |
| 		}, | |
| 		data () { | |
| 			return { | |
| 				refreshState: '', | |
| 				pulldownStatus: '', | |
| 				pullupStatus: '', | |
| 				loadmoreStatus: '', | |
| 				isLoadmore: false, | |
| 				scrollIntoViewId: '', | |
| 				scrollWithAnimation: false, | |
| 				scrollTop: 0, | |
| 				scrollTopRecord: 0 | |
| 			} | |
| 		}, | |
| 		created() { | |
| 			this.startAutoplay() | |
| 		}, | |
| 		beforeDestroy() { | |
| 			this.stopAutoplay() | |
| 		}, | |
| 		methods: { | |
| 			handleScroll (e) { | |
| 				this.scrollTopRecord = e.detail.scrollTop | |
| 				this.$emit('scroll', e) | |
| 			}, | |
| 			handleScrolltolower (e) { | |
| 				if ( !this.loadmoreable || this.isLoadmore || this.loadmoreStatus == 'end' ) return | |
| 				this.isLoadmore = true | |
| 				this.loadmoreStatus = 'ready' | |
| 				this.$emit('loadmore', (state) => { | |
| 					this.loadmoreStatus = state | |
| 					this.isLoadmore = false | |
| 				}) | |
| 			}, | |
| 			handleScrolltoupper (e) { | |
| 				this.$emit('scrolltoupper', e) | |
| 			}, | |
| 			pulldown () { | |
| 				if ( this.pulldownStatus == 'end' ) { | |
| 					this.stopRefresh() | |
| 					return | |
| 				} | |
| 				this.$emit('pulldown', (state) => { | |
| 					this.pulldownStatus = state | |
| 					this.stopRefresh() | |
| 				}) | |
| 			}, | |
| 			pullup () { | |
| 				if ( this.pullupStatus == 'end' ) { | |
| 					this.stopRefresh() | |
| 					return | |
| 				} | |
| 				this.$emit('pullup', (state) => { | |
| 					this.pullupStatus = state | |
| 					this.stopRefresh() | |
| 				}) | |
| 			}, | |
| 			startPulldown () { | |
| 				this.pulldownStatus = 'ready' | |
| 				this.refreshState = '' | |
| 				this.$nextTick(function () { | |
| 					this.refreshState = 'pulldown' | |
| 				}) | |
| 			}, | |
| 			startPullup () { | |
| 				this.pullupStatus = 'ready' | |
| 				this.refreshState = '' | |
| 				this.$nextTick(function () { | |
| 					this.refreshState = 'pullup' | |
| 				}) | |
| 			}, | |
| 			stopRefresh () { | |
| 				this.refreshState = '' | |
| 				this.$nextTick(function () { | |
| 					this.refreshState = 'stop' | |
| 				}) | |
| 			}, | |
| 			resetRefresh () { | |
| 				this.pulldownStatus = '' | |
| 				this.pullupStatus = '' | |
| 				this.refreshState = '' | |
| 				this.loadmoreState = '' | |
| 				this.isLoadmore = false | |
| 			}, | |
| 			scrollToIndex (index, animated = false) { | |
| 				this.stopAutoplay() | |
| 				this.scrollIntoViewId = '' | |
| 				this.scrollWithAnimation = animated | |
| 				this.$nextTick(function () { | |
| 					this.scrollIntoViewId = 'yingbing-scroll-item_' + index | |
| 					this.$nextTick(function () { | |
| 						this.scrollWithAnimation = false | |
| 						setTimeout(() => { | |
| 							this.startAutoplay() | |
| 						}, 400) | |
| 					}) | |
| 				}) | |
| 			}, | |
| 			getItemRect (index) { | |
| 				return new Promise(resolve => { | |
| 					uni.createSelectorQuery().in(this).select('#yingbing-scroll-item_' + index).boundingClientRect(data => { | |
| 						resolve(data) | |
| 					}).exec(); | |
| 				}) | |
| 			}, | |
| 			pullingdown (threshold) { | |
| 				if ( this.pulldownStatus != 'end' ) { | |
| 					// #ifndef APP-NVUE | |
| 					if ( threshold >= readyHeight ) { | |
| 						this.pulldownStatus = 'ready' | |
| 					} else { | |
| 						this.pulldownStatus = 'pull' | |
| 					} | |
| 					// #endif | |
| 					// #ifdef APP-NVUE | |
| 					if ( threshold >= 195 ) { | |
| 						this.pulldownStatus = 'ready' | |
| 					} else { | |
| 						this.pulldownStatus = 'pull' | |
| 					} | |
| 					// #endif | |
| 				} | |
| 			}, | |
| 			pullingup (threshold) { | |
| 				if ( this.pullupStatus != 'end' ) { | |
| 					// #ifndef APP-NVUE | |
| 					if ( threshold >= readyHeight ) { | |
| 						this.pullupStatus = 'ready' | |
| 					} else { | |
| 						this.pullupStatus = 'pull' | |
| 					} | |
| 					// #endif | |
| 					// #ifdef APP-NVUE | |
| 					if ( threshold >= 195 ) { | |
| 						this.pullupStatus = 'ready' | |
| 					} else { | |
| 						this.pullupStatus = 'pull' | |
| 					} | |
| 					// #endif | |
| 				} | |
| 			}, | |
| 			setScrollTop (top) { | |
| 				this.scrollTop = top | |
| 				this.scrollTopRecord = top | |
| 			}, | |
| 			startAutoplay () { | |
| 				this.stopAutoplay() | |
| 				this.scrollTop = this.scrollTopRecord | |
| 				if ( this.autoplay && this.data.length > 0 ) { | |
| 					this.autoplayTimer = setInterval(() => { | |
| 						if ( this.scrollTop - this.scrollTopRecord > 2 ) this.scrollTop = this.scrollTopRecord//如果scrollTop比滚动距离大2像素,说明发生过等待章节加载,需要重新赋值scrollTop真实的滚动距离 | |
| 						this.scrollTop += 1 | |
| 					}, 20) | |
| 				} | |
| 			}, | |
| 			stopAutoplay () { | |
| 				if ( this.autoplayTimer ) { | |
| 					clearInterval(this.autoplayTimer) | |
| 					this.autoplayTimer = null | |
| 				} | |
| 			} | |
| 		}, | |
| 		watch: { | |
| 			autoplay () { | |
| 				this.startAutoplay() | |
| 			} | |
| 		} | |
| 	} | |
| </script> | |
| <!-- #ifdef APP-VUE || H5 || MP-WEIXIN || MP-QQ --> | |
| <script module="pulldownwxs" lang="wxs" src="./pulldown.wxs"></script> | |
| <!-- #endif --> | |
| 
 | |
| <style scoped> | |
| 	/* #ifdef APP-VUE || H5 */ | |
| 	/deep/ .yingbing-scroll-view .uni-scroll-view::-webkit-scrollbar { | |
| 		display: none; | |
| 		width: 0 !important; | |
| 		height: 0 !important; | |
| 		-webkit-appearance: none; | |
| 		background: transparent; | |
| 	} | |
| 	/* #endif */ | |
| 	/* #ifdef MP */ | |
| 	/deep/ ::-webkit-scrollbar { | |
| 		display: none; | |
| 		width: 0 !important; | |
| 		height: 0 !important; | |
| 		-webkit-appearance: none; | |
| 		background: transparent; | |
| 	} | |
| 	/* #endif */ | |
| 	.yingbing-scroller { | |
| 		/* #ifndef APP-NVUE */ | |
| 		height: 100%; | |
| 		/* #endif */ | |
| 		/* #ifdef APP-NVUE */ | |
| 		flex: 1; | |
| 		/* #endif */ | |
| 		position: relative; | |
| 		overflow: hidden; | |
| 	} | |
| 	.yingbing-scroller-wrapper { | |
| 		height: calc(100% + 160px); | |
| 		position: absolute; | |
| 		top: -80px; | |
| 		left: 0; | |
| 		right: 0; | |
| 		display: flex; | |
| 		flex-direction: column; | |
| 	} | |
| 	.yingbing-scroller-refresh { | |
| 		height: 80px; | |
| 		padding: 20px 0; | |
| 		box-sizing: border-box; | |
| 		display: flex; | |
| 		align-items: center; | |
| 		justify-content: center; | |
| 	} | |
| 	.yingbing-scroller-refresh-text { | |
| 		font-size: 13px; | |
| 		margin-left: 10px; | |
| 	} | |
| 	.yingbing-scroll { | |
| 		flex: 1; | |
| 		position: relative; | |
| 	} | |
| 	.yingbing-scroll-view { | |
| 		position: absolute; | |
| 		top: 0; | |
| 		left: 0; | |
| 		right: 0; | |
| 		bottom: 0; | |
| 	} | |
| </style> |