Pārlūkot izejas kodu

z-paging插件更新

zhouql 2 nedēļas atpakaļ
vecāks
revīzija
4b2c29864f

+ 19 - 0
uni_modules/z-paging/changelog.md

@@ -1,3 +1,22 @@
+## 2.8.8(2025-08-29)
+1.`新增` props:`in-swiper-slot`,用以解决在vue3+(微信小程序或QQ小程序)中,`scrollIntoViewById`和`scrollIntoViewByIndex`因无法获取节点信息导致滚动到指定view无效的问题。  
+2.`修复` 在vue2中缓存模式无效的问题。  
+3.`修复` 聊天记录模式在键盘弹出后,底部聊天输入框依然可以滚动的问题。  
+4.`修复` 部分老版本`webview`中,`#right`位置不正确的问题。  
+5.`修复` 在快手小程序+安卓中滚动到底部可能多次触发的问题。  
+6.`修复` 在微信小程序+虚拟列表中滚动到顶部偶现无效的问题。  
+7.`修复` 方法`reload(true)`调用时`refresher-complete-delay`无效的问题。  
+8.`优化` 底部安全区域展示逻辑和性能。   
+## 2.8.7(2025-05-30)
+1.`新增` props:`layout-only`,支持仅使用基础布局。  
+2.`新增` `goF2`方法,支持手动触发进入二楼。  
+3.`新增` `@scrollDirectionChange`事件,支持监听列表滚动方向改变。  
+4.`新增` props:`paging-class`,支持直接设置`z-paging`的`class`。  
+5.`新增` `addKeyboardHeightChangeListener`方法,支持手动添加键盘高度变化监听。  
+6.`修复` `scrollIntoViewById`方法在存在`slot=top`或局部区域滚动时,滚动的位置不准确的问题。  
+7.`优化` 重构底部安全区域处理逻辑,修改为占位view的方式,处理方案更灵活并支持自定义底部安全区域颜色。  
+8.`优化` 兼容在`nvue`+`vue3`中使用`waterfall`。  
+9.`优化` 规范`types`中对`style`类型的约束。  
 ## 2.8.6(2025-03-17)
 1.`新增` 聊天记录模式流式输出(类似chatGPT回答)演示demo。  
 2.`新增` z-paging及其公共子组件支持`HBuilderX`代码文档提示。  

+ 4 - 1
uni_modules/z-paging/components/z-paging/css/z-paging-main.css

@@ -52,6 +52,10 @@
 	/* #endif */
 }
 
+.zp-right {
+	right: 0;
+}
+
 .zp-scroll-view-super {
 	flex: 1;
 	overflow: hidden;
@@ -218,7 +222,6 @@
 }
 
 .zp-safe-area-inset-bottom {
-	position: absolute;
 	/* #ifndef APP-PLUS */
 	height: env(safe-area-inset-bottom);
 	/* #endif */

+ 10 - 6
uni_modules/z-paging/components/z-paging/js/modules/chat-record-mode.js

@@ -111,12 +111,7 @@ export default {
 		}
 	},
 	mounted() {
-		// 监听键盘高度变化(H5、百度小程序、抖音小程序、飞书小程序不支持)
-		// #ifndef H5 || MP-BAIDU || MP-TOUTIAO
-		if (this.useChatRecordMode) {
-			uni.onKeyboardHeightChange(this._handleKeyboardHeightChange);
-		}
-		// #endif
+		this.addKeyboardHeightChangeListener();
 	},
 	methods: {
 		// 添加聊天记录
@@ -129,6 +124,15 @@ export default {
 		doChatRecordLoadMore() {
 			this.useChatRecordMode && this._onLoadingMore('click');
 		},
+		// 手动添加键盘高度变化监听
+		addKeyboardHeightChangeListener() {
+			// 监听键盘高度变化(H5、百度小程序、抖音小程序、飞书小程序不支持)
+			// #ifndef H5 || MP-BAIDU || MP-TOUTIAO
+			if (this.useChatRecordMode) {
+				uni.onKeyboardHeightChange(this._handleKeyboardHeightChange);
+			}
+			// #endif
+		},
 		// 处理键盘高度变化
 		_handleKeyboardHeightChange(res) {
 			this.$emit('keyboardHeightChange', res);

+ 25 - 15
uni_modules/z-paging/components/z-paging/js/modules/data-handle.js

@@ -157,7 +157,8 @@ export default {
 			listRendering: false,
 			isHandlingRefreshToPage: false,
 			isFirstPageAndNoMore: false,
-			totalDataChangeThrow: true
+			totalDataChangeThrow: true,
+			addDataFromTopBufferedInsert: u.useBufferedInsert(this._addDataFromTop)
 		}
 	},
 	computed: {
@@ -182,7 +183,9 @@ export default {
 	},
 	watch: {
 		totalData(newVal, oldVal) {
-			this._totalDataChange(newVal, oldVal, this.totalDataChangeThrow);
+			// 触发totalData改变事件时是否触发emit列表更新事件,如果是从缓存中设置则必须触发,否则根据totalDataChangeThrow的规则判断
+			const eventThrow = this.isSettingCacheList ? true : this.totalDataChangeThrow;
+			this._totalDataChange(newVal, oldVal, eventThrow);
 			this.totalDataChangeThrow = true;
 		},
 		currentData(newVal, oldVal) {
@@ -299,17 +302,10 @@ export default {
 		},
 		// 从顶部添加数据,不会影响分页的pageNo和pageSize
 		addDataFromTop(data, toTop = true, toTopWithAnimate = true) {
-			// 数据是否拼接到顶部,如果是聊天记录模式并且列表没有倒置,则应该拼接在底部
-			let addFromTop = !this.isChatRecordModeAndNotInversion;
-			data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : (addFromTop ? data.reverse() : data);
-			// #ifndef APP-NVUE
-			this.finalUseVirtualList && this._setCellIndex(data, 'top')
-			// #endif
-			
-			this.totalData = addFromTop ? [...data, ...this.totalData] : [...this.totalData, ...data];
-			if (toTop) {
-				u.delay(() => this.useChatRecordMode ? this.scrollToBottom(toTopWithAnimate) : this.scrollToTop(toTopWithAnimate));
-			}
+			// 如果使用了虚拟列表,则需要对短时间内的大量数据进行整合然后一次性添加,避免设置虚拟列表cellIndex时候key冲突的问题,否则正常调用
+			(this.finalUseVirtualList ? this.addDataFromTopBufferedInsert : this._addDataFromTop)(
+				data, toTop, toTopWithAnimate
+			);
 		},
 		// 重新设置列表数据,调用此方法不会影响pageNo和pageSize,也不会触发请求。适用场景:当需要删除列表中某一项时,将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时,请使用此方法,请勿直接修改page中:list.sync绑定的数组)
 		resetTotalData(data) {
@@ -547,7 +543,7 @@ export default {
 				return;
 			}
 			this._doCheckScrollViewShouldFullHeight(newVal);
-			if(!this.realTotalData.length && !newVal.length){
+			if (!this.realTotalData.length && !newVal.length) {
 				eventThrow = false;
 			}
 			this.realTotalData = newVal;
@@ -613,7 +609,7 @@ export default {
 					this.totalData = [...this.totalData, ...newVal];
 					// 此处是为了解决在微信小程序中,在某些情况下滚动到底部加载更多后滚动位置直接变为最底部的问题,因此需要通过代码强制滚动回加载更多前的位置
 					// #ifdef MP-WEIXIN
-					if (!this.isIos && !this.refresherOnly && !this.usePageScroll && newVal.length) {
+					if (!this.isIos && !this.isOnly && !this.usePageScroll && newVal.length) {
 						this.loadingMoreTimeStamp = u.getTime();
 						this.$nextTick(() => {
 							this.scrollToY(currentScrollTop);
@@ -659,6 +655,20 @@ export default {
 			const resultPagingList = totalPagingList.splice(pageNoIndex, finalPageNoIndex - pageNoIndex);
 			u.delay(() => callback(resultPagingList), localPagingLoadingTime)
 		},
+		// 从顶部添加数据,不会影响分页的pageNo和pageSize
+		_addDataFromTop(data, toTop = true, toTopWithAnimate = true) {
+			// 数据是否拼接到顶部,如果是聊天记录模式并且列表没有倒置,则应该拼接在底部
+			let addFromTop = !this.isChatRecordModeAndNotInversion;
+			data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : (addFromTop ? data.reverse() : data);
+			// #ifndef APP-NVUE
+			this.finalUseVirtualList && this._setCellIndex(data, 'top')
+			// #endif
+			
+			this.totalData = addFromTop ? [...data, ...this.totalData] : [...this.totalData, ...data];
+			if (toTop) {
+				u.delay(() => this.useChatRecordMode ? this.scrollToBottom(toTopWithAnimate) : this.scrollToTop(toTopWithAnimate));
+			}
+		},
 		// 存储列表缓存数据
 		_saveLocalCache(data) {
 			uni.setStorageSync(this.finalCacheKey, data);

+ 1 - 1
uni_modules/z-paging/components/z-paging/js/modules/empty.js

@@ -108,7 +108,7 @@ export default {
 		},
 		// 是否展示空数据图
 		showEmpty() {
-			if (this.refresherOnly || this.hideEmptyView || this.realTotalData.length) return false;
+			if (this.isOnly || this.hideEmptyView || this.realTotalData.length) return false;
 			if (this.autoHideEmptyViewWhenLoading) {
 				if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true;
 			} else {

+ 4 - 4
uni_modules/z-paging/components/z-paging/js/modules/load-more.js

@@ -255,10 +255,10 @@ export default {
 			}
 			// emit scrolltolower
 			this._emitScrollEvent('scrolltolower');
-			// 如果是只使用下拉刷新 或者 禁用底部加载更多 或者 底部加载更多不是默认状态或加载失败状态 或者 是加载中状态 或者 空数据图已经展示了,则return,不触发内部加载更多逻辑
-			if (this.refresherOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading || this.showEmpty) return;
+			// 如果是只使用布局或下拉刷新 或者 禁用底部加载更多 或者 底部加载更多不是默认状态或加载失败状态 或者 是加载中状态 或者 空数据图已经展示了,则return,不触发内部加载更多逻辑
+			if (this.isOnly || !this.loadingMoreEnabled || !(this.loadingStatus === Enum.More.Default || this.loadingStatus === Enum.More.Fail) || this.loading || this.showEmpty) return;
 			// #ifdef MP-WEIXIN
-			if (!this.isIos && !this.refresherOnly && !this.usePageScroll) {
+			if (!this.isIos && !this.isOnly && !this.usePageScroll) {
 				const currentTimestamp = u.getTime();
 				// 在非ios平台+scroll-view中节流处理
 				if (this.loadingMoreTimeStamp > 0 && currentTimestamp - this.loadingMoreTimeStamp < 100) {
@@ -352,7 +352,7 @@ export default {
 		_showLoadingMore(type) {
 			if (!this.showLoadingMoreWhenReload && (!(this.loadingStatus === Enum.More.Default ? this.nShowBottom : true) || !this.realTotalData.length)) return false;
 			if (((!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading) && !this.showLoadingMore) || 
-			(!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) || this.refresherOnly) {
+			(!this.loadingMoreEnabled && (!this.showLoadingMoreWhenReload || this.isUserPullDown || this.loadingStatus !== Enum.More.Loading)) || this.isOnly) {
 				return false;
 			}
 			if (this.useChatRecordMode && type !== 'Loading') return false;

+ 34 - 3
uni_modules/z-paging/components/z-paging/js/modules/nvue.js

@@ -67,6 +67,7 @@ export default {
 			nShowRefresherRevealHeight: 0,
 			nOldShowRefresherRevealHeight: -1,
 			nRefresherWidth: u.rpx2px(750),
+			nListHeight: 0,
 			nF2Opacity: 0
 		}
 	},
@@ -130,10 +131,39 @@ export default {
 		// 列表滚动时触发
 		_nOnScroll(e) {
 			this.$emit('scroll', e);
-			const contentOffsetY = -e.contentOffset.y;
-			this.oldScrollTop = contentOffsetY;
+			const scrollTop = -e.contentOffset.y;
+			const scrollHeight = e.contentSize.height;
+			
+			if (this.watchScrollDirectionChange) {
+				// 计算scroll-view滚动方向,正常情况下上次滚动的oldScrollTop大于当前scrollTop即为向上滚动,反之为向下滚动
+				let direction = this.oldScrollTop > scrollTop ? 'top' : 'bottom';
+				// 此处为解决在iOS中,滚动到顶部因bounce的影响回弹导致滚动方向为bottom的问题:如果滚动到顶部了并且scrollTop小于顶部滚动区域,则强制设置direction为top
+				if (scrollTop <= 0) {
+					direction = 'top';
+				}
+				// 此处为解决在iOS中,滚动到底部因bounce的影响回弹导致滚动方向为top的问题:如果滚动到底部了并且scrollTop超过底部滚动区域,则强制设置direction为bottom
+				if (scrollTop > this.lastScrollHeight - this.nListHeight - 1) {
+					direction = 'bottom';
+				}
+				// emit 列表滚动方向改变事件
+				if (direction !== this.lastScrollDirection) {
+					this.$emit('scrollDirectionChange', direction);
+					this.lastScrollDirection = direction;
+				}
+				// 当scrollHeight变化时,需要延迟100毫秒设置lastScrollHeight,如果直接根据scrollHeight的话,因为此时数据还未改变,会导致滚动方向从bottom变为top
+				if (this.lastScrollHeight !== scrollHeight && !this.setContentHeightPending) {
+					// 因此处会多次触发,因此加个标识确保在延时期间仅触发一次
+					this.setContentHeightPending = true;
+					u.delay(() => {
+						this.lastScrollHeight = scrollHeight;
+						this.setContentHeightPending = false;
+					})
+				}
+			}
+			
+			this.oldScrollTop = scrollTop;
 			this.nListIsDragging = e.isDragging;
-			this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1);
+			this._checkShouldShowBackToTop(scrollTop, scrollTop - 1);
 		},
 		// 列表滚动结束
 		_nOnScrollend(e) {
@@ -146,6 +176,7 @@ export default {
 			// 判断是否滚动到底部了
 			this._getNodeClientRect('.zp-n-list').then(node => {
 				if (node) {
+					this.nListHeight = node[0].height;
 					if (e?.contentSize?.height + e?.contentOffset?.y <= node[0].height) {
 						this._emitScrollEvent('scrolltolower');
 					}

+ 9 - 5
uni_modules/z-paging/components/z-paging/js/modules/refresher.js

@@ -298,7 +298,7 @@ export default {
 			return u.addUnit(this.refresherThreshold, this.unit);
 		},
 		finalRefresherEnabled() {
-			if (this.useChatRecordMode) return false;
+			if (this.layoutOnly || this.useChatRecordMode) return false;
 			if (this.privateRefresherEnabled === -1) return this.refresherEnabled;
 			return this.privateRefresherEnabled === 1;
 		},
@@ -383,6 +383,10 @@ export default {
 		updateCustomRefresherHeight() {
 			u.delay(() => this.$nextTick(this._updateCustomRefresherHeight));
 		},
+		// 进入二楼
+		goF2() {
+			this._handleGoF2();
+		},
 		// 关闭二楼
 		closeF2() {
 			this._handleCloseF2();
@@ -546,7 +550,7 @@ export default {
 			this.refresherReachMaxAngle = true;
 			this.isTouchEnded = true;
 			const refresherThreshold = this.finalRefresherThreshold;
-			if (moveDis >= refresherThreshold && (this.refresherStatus === Enum.Refresher.ReleaseToRefresh || this.refresherStatus === Enum.Refresher.GoF2)) {
+			if (moveDis >= refresherThreshold && [Enum.Refresher.ReleaseToRefresh, Enum.Refresher.GoF2].indexOf(this.refresherStatus) >= 0) {
 				// 如果是松手进入二楼状态,则触发进入二楼
 				if (this.refresherStatus === Enum.Refresher.GoF2) {
 					this._handleGoF2();
@@ -615,8 +619,8 @@ export default {
 		// 下拉刷新结束
 		_refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) {
 			if (this.loadingType === Enum.LoadingType.Refresher) {
-				// 计算当前下拉刷新结束需要延迟的时间
-				const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0;
+				// 计算当前下拉刷新结束需要延迟的时间(用户主动下拉刷新或reload时显示下拉刷新view才需要计算延迟时间)
+				const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.finalShowRefresherWhenReload)) ? this.refresherCompleteDelay : 0;
 				// 如果延迟时间大于0,则展示刷新结束状态,否则直接展示默认状态
 				const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default;
 				if (this.finalShowRefresherWhenReload) {
@@ -793,7 +797,7 @@ export default {
 		// 判断touch手势是否要触发
 		_touchDisabled() {
 			const checkOldScrollTop = this.oldScrollTop > 5;
-			return this.loading || this.isRefresherInComplete || this.useChatRecordMode || !this.refresherEnabled || !this.useCustomRefresher ||(this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop);
+			return this.loading || this.isRefresherInComplete || this.useChatRecordMode || this.layoutOnly || !this.refresherEnabled || !this.useCustomRefresher || (this.usePageScroll && this.useCustomRefresher && this.pageScrollTop > 10) || (!(this.usePageScroll && this.useCustomRefresher) && checkOldScrollTop);
 		},
 		// #endif
 		// 更新自定义下拉刷新view高度

+ 82 - 16
uni_modules/z-paging/components/z-paging/js/modules/scroller.js

@@ -48,6 +48,11 @@ export default {
 			type: String,
 			default: u.gc('scrollIntoView', '')
 		},
+		// z-paging是否使用swiper-item或其他父组件包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,scrollIntoViewById和scrollIntoViewByIndex因无法获取节点信息导致滚动到指定view无效的问题
+		inSwiperSlot: {
+			type: Boolean,
+			default: false
+		},
 	},
 	data() {
 		return {
@@ -63,6 +68,9 @@ export default {
 			privateScrollWithAnimation: -1,
 			cacheScrollNodeHeight: -1,
 			superContentHeight: 0,
+			lastScrollHeight: 0,
+			lastScrollDirection: '',
+			setContentHeightPending: false
 		}
 	},
 	watch: {
@@ -251,7 +259,7 @@ export default {
 		_onScrollToLower(e) {
 			(!e.detail || !e.detail.direction || e.detail.direction === 'bottom') 
 			&& this.toBottomLoadingMoreEnabled
-			&& this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom')
+			&& this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom');
 		},
 		// 滚动到顶部
 		_scrollToTop(animate = true, isPrivate = true) {
@@ -299,6 +307,13 @@ export default {
 				this.scrollTop = 0;
 				this.oldScrollTop = this.scrollTop;
 			});
+			u.delay(() => {
+				this.scrollTop = this.oldScrollTop;
+				this.$nextTick(() => {
+					this.scrollTop = 0;
+					this.oldScrollTop = this.scrollTop;
+				});
+			}, 500)
 		},
 		// 滚动到底部
 		async _scrollToBottom(animate = true) {
@@ -367,11 +382,29 @@ export default {
 					}
 					return;
 					// #endif
-					this._getNodeClientRect('#' + sel.replace('#', ''), this.$parent).then((node) => {
+					// 获取指定view的节点信息
+					let inDom = false;
+					// 在vue3+(微信小程序或QQ小程序)中,无法获取节点信息导致滚动到指定view无效的问题
+					// 通过uni.createSelectorQuery().in(this.$parent)来解决此问题
+					// #ifdef VUE3
+					// #ifdef MP-WEIXIN || MP-QQ
+					if (this.inSwiperSlot) {
+						inDom = this.$parent;
+					}
+					// #endif
+					// #endif
+					this._getNodeClientRect('#' + sel.replace('#', ''), inDom).then((node) => {
 						if (node) {
-							let nodeTop = node[0].top;
-							this._scrollIntoViewByNodeTop(nodeTop, offset, animate);
-							finishCallback && finishCallback();
+							// 获取zp-scroll-view-container的节点信息
+							this._getNodeClientRect('.zp-scroll-view-container').then((svContainerNode) => {
+								if (svContainerNode) {
+									// 滚动的top为指定view的top减zp-scroll-view-container的top,因为指定view的top是相对于整个窗口的,需要考虑相对的位置关系
+									this._scrollIntoViewByNodeTop(node[0].top - svContainerNode[0].top, offset, animate);
+									finishCallback && finishCallback();
+								}
+							});
+						} else {
+							u.consoleErr(`无法获取${sel}的节点信息,请检查!`);
 						}
 					});
 				});
@@ -425,25 +458,56 @@ export default {
 		// scroll-view滚动中
 		_scroll(e) {
 			this.$emit('scroll', e);
-			const { scrollTop, scrollLeft } = e.detail;
+			const { scrollTop, scrollLeft, scrollHeight } = e.detail;
+			
+			if (this.watchScrollDirectionChange) {
+				// 计算scroll-view滚动方向,正常情况下上次滚动的oldScrollTop大于当前scrollTop即为向上滚动,反之为向下滚动
+				let direction = this.oldScrollTop > scrollTop ? 'top' : 'bottom';
+				// 此处为解决在iOS中,滚动到顶部因bounce的影响回弹导致滚动方向为bottom的问题:如果滚动到顶部了并且scrollTop小于顶部滚动区域,则强制设置direction为top
+				// 此外发现在h5中下拉刷新时direction有概率被判断为bottom(oldScrollTop > scrollTop),因为下拉刷新时会禁止scroll-view滚动,则以此为依据强制设置direction为top
+				if (scrollTop <= 0 || !this.scrollEnable) {
+					direction = 'top';
+				}
+				// 此处为解决在iOS中,滚动到底部因bounce的影响回弹导致滚动方向为top的问题:如果滚动到底部了并且scrollTop超过底部滚动区域,则强制设置direction为bottom
+				if (scrollTop > this.lastScrollHeight - this.scrollViewHeight - 1 && this.scrollEnable) {
+					direction = 'bottom';
+				}
+				// emit 列表滚动方向改变事件
+				if (direction !== this.lastScrollDirection) {
+					this.$emit('scrollDirectionChange', direction);
+					this.lastScrollDirection = direction;
+				}
+				// 当scrollHeight变化时,需要延迟100毫秒设置lastScrollHeight,如果直接根据scrollHeight的话,因为此时数据还未改变,会导致滚动方向从bottom变为top
+				if (this.lastScrollHeight !== scrollHeight && !this.setContentHeightPending) {
+					// 因此处会多次触发,因此加个标识确保在延时期间仅触发一次
+					this.setContentHeightPending = true;
+					u.delay(() => {
+						this.lastScrollHeight = scrollHeight;
+						this.setContentHeightPending = false;
+					})
+				}
+			}
+			
 			// #ifndef APP-NVUE
 			this.finalUseVirtualList && this._updateVirtualScroll(scrollTop, this.oldScrollTop - scrollTop);
 			// #endif
 			this.oldScrollTop = scrollTop;
 			this.oldScrollLeft = scrollLeft;
-			// 滚动区域内容的总高度 - 当前滚动的scrollTop = 当前滚动区域的顶部与内容底部的距离
-			const scrollDiff = e.detail.scrollHeight - this.oldScrollTop;
 			// 在非ios平台滚动中,再次验证一下是否滚动到了底部。因为在一些安卓设备中,有概率滚动到底部不触发@scrolltolower事件,因此添加双重检测逻辑
-			!this.isIos && this._checkScrolledToBottom(scrollDiff);
+			// 排除快手的情况,因为在快手安卓中双重检测会导致滚动到底部事件多次触发
+			// #ifndef MP-KUAISHOU
+			if (!this.isIos) {
+				// 滚动区域内容的总高度 - 当前滚动的scrollTop = 当前滚动区域的顶部与内容底部的距离
+				const scrollDiff = e.detail.scrollHeight - this.oldScrollTop;
+				this._checkScrolledToBottom(scrollDiff);
+			}
+			// #endif
 		},
 		// emit scrolltolower/scrolltoupper事件
 		_emitScrollEvent(type) {
-		    const reversedType = type === 'scrolltolower' ? 'scrolltoupper' : 'scrolltolower';
-		    const eventType = this.useChatRecordMode && !this.isChatRecordModeAndNotInversion
-		        ? reversedType
-		        : type;
-		    
-		    this.$emit(eventType);
+			const reversedType = type === 'scrolltolower' ? 'scrolltoupper' : 'scrolltolower';
+			const eventType = this.useChatRecordMode && !this.isChatRecordModeAndNotInversion ? reversedType : type;
+			this.$emit(eventType);
 		},
 		// 更新内置的scroll-view是否启用滚动动画
 		_updatePrivateScrollWithAnimation(animate) {
@@ -521,7 +585,9 @@ export default {
 			this._doCheckScrollViewShouldFullHeight(this.realTotalData);
 			const node = `.zp-page-${type}`;
 			const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`;
-			let safeAreaInsetBottomAdd = this.safeAreaInsetBottom;
+			// 是否设置底部安全区域间距,仅当开启底部安全区域并且slot=bottom不存在的时候才处理,如果slot=bottom存在则直接在bottom底部插入占位view
+			// 如果useSafeAreaPlaceholder为true,这里也不需要额外通过marginBottom设置底部安全区域了
+			const safeAreaInsetBottomAdd = this.safeAreaInsetBottom && !this.zSlots.bottom && !this.useSafeAreaPlaceholder;
 			this.$nextTick(() => {
 				let delayTime = 0;
 				// #ifdef MP-BAIDU || APP-NVUE

+ 2 - 18
uni_modules/z-paging/components/z-paging/js/modules/virtual-list.js

@@ -79,7 +79,7 @@ export default {
 			type: String,
 			default: u.gc('virtualCellIdPrefix', '')
 		},
-		// 虚拟列表是否使用swiper-item包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题
+		// 虚拟列表是否使用swiper-item或其他父组件包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题
 		// 仅vue3+(微信小程序或QQ小程序)+非内置列表写法虚拟列表有效,其他情况此属性设置任何值都无效,所以如果您在swiper-item内使用z-paging的非内置虚拟列表写法,将此属性设置为true即可
 		virtualInSwiperSlot: {
 			type: Boolean,
@@ -89,7 +89,6 @@ export default {
 	data() {
 		return {
 			virtualListKey: u.getInstanceId(),
-			virtualPageHeight: 0,
 			virtualCellHeight: 0,
 			virtualScrollTimeStamp: 0,
 			
@@ -108,7 +107,6 @@ export default {
 				fixed: 0,
 				dynamic: 0
 			},
-			pagingOrgTop: -1,
 			updateVirtualListFromDataChange: false
 		}
 	},
@@ -149,7 +147,7 @@ export default {
 			return this.cellKeyName;
 		},
 		finalVirtualPageHeight(){
-			return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight;
+			return this.scrollViewHeight > 0 ? this.scrollViewHeight : this.windowHeight;
 		},
 		finalFixedCellHeight() {
 			return u.convertToPx(this.fixedCellHeight);
@@ -271,20 +269,6 @@ export default {
 			}
 			// #endif
 		},
-		// 初始化虚拟列表
-		_virtualListInit() {
-			this.$nextTick(() => {
-				u.delay(() => {
-					// 获取虚拟列表滚动区域的高度
-					this._getNodeClientRect('.zp-scroll-view').then(node => {
-						if (node) {
-							this.pagingOrgTop = node[0].top;
-							this.virtualPageHeight = node[0].height;
-						}
-					});
-				});
-			})
-		},
 		// cellHeightMode为fixed时获取第一个cell高度
 		_updateFixedCellHeight() {
 			if (!this.finalFixedCellHeight) {

+ 1 - 1
uni_modules/z-paging/components/z-paging/js/z-paging-constant.js

@@ -2,7 +2,7 @@
 
 export default {
 	// 当前版本号
-	version: '2.8.6',
+	version: '2.8.8',
 	// 延迟操作的通用时间
 	delayTime: 100,
 	// 请求失败时候全局emit使用的key

+ 52 - 29
uni_modules/z-paging/components/z-paging/js/z-paging-main.js

@@ -60,6 +60,8 @@ export default {
 			checkScrolledToBottomTimeOut: null,
 			cacheTopHeight: -1,
 			statusBarHeight: systemInfo.statusBarHeight,
+			scrollViewHeight: 0,
+			pagingOrgTop: -1,
 
 			// --------------状态&判断---------------
 			insideOfPaging: -1,
@@ -94,6 +96,11 @@ export default {
 			type: Object,
 			default: u.gc('pagingStyle', {}),
 		},
+		// 设置z-paging的class,优先级低于pagingStyle和height、width、maxWidth、bgColor
+		pagingClass: {
+			type: [String, Array, Object],
+			default: u.gc('pagingClass', ''),
+		},
 		// z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100%
 		height: {
 			type: String,
@@ -184,6 +191,16 @@ export default {
 			type: Boolean,
 			default: u.gc('watchTouchDirectionChange', false)
 		},
+		// 是否监听列表滚动方向改变,默认为否
+		watchScrollDirectionChange: {
+			type: Boolean,
+			default: u.gc('watchScrollDirectionChange', false)
+		},
+		// 是否只使用基础布局,设置为true后将关闭mounted自动请求数据、关闭下拉刷新和滚动到底部加载更多,强制隐藏空数据图。默认为否
+		layoutOnly: {
+			type: Boolean,
+			default: u.gc('layoutOnly', false)
+		},
 		// z-paging中布局的单位,默认为rpx
 		unit: {
 			type: String,
@@ -192,7 +209,7 @@ export default {
 	},
 	created() {
 		// 组件创建时,检测是否开始加载状态
-		if (this.createdReload && !this.refresherOnly && this.auto) {
+		if (this.createdReload && !this.isOnly && this.auto) {
 			this._startLoading();
 			this.$nextTick(this._preReload);
 		}
@@ -201,7 +218,7 @@ export default {
 		this.active = true;
 		this.wxsPropType = u.getTime().toString();
 		this.renderJsIgnore;
-		if (!this.createdReload && !this.refresherOnly && this.auto) {
+		if (!this.createdReload && !this.isOnly && this.auto) {
 			// 开始预加载
 			u.delay(() => this.$nextTick(this._preReload), 0);
 		}
@@ -211,20 +228,20 @@ export default {
 		// #ifdef H5 || MP
 		delay = c.delayTime;
 		// #endif
+		this.systemInfo = u.getSystemInfoSync();
 		this.$nextTick(() => {
 			// 初始化systemInfo
 			this.systemInfo = u.getSystemInfoSync();
 			// 初始化z-paging高度
 			!this.usePageScroll && this.autoHeight  && this._setAutoHeight();
-			// #ifdef MP-KUAISHOU
-			this._setFullScrollViewInHeight();
-			// #endif
 			this.loaded = true;
 			u.delay(() => {
 				// 更新fixed模式下z-paging的布局,主要是更新windowTop、windowBottom
 				this.updateFixedLayout();
 				// 更新缓存中z-paging整个内容容器高度
 				this._updateCachedSuperContentHeight();
+				// 更新z-paging中scroll-view高度
+				this._updateScrollViewHeight();
 			});
 		})
 		// 初始化页面滚动模式下slot="top"、slot="bottom"高度
@@ -237,8 +254,10 @@ export default {
 				this.isTouchmoving = true;
 			})
 		}
-		// 监听uni.$emit中全局emit的complete error等事件
-		this._onEmit();
+		if (!this.layoutOnly) {
+			// 监听uni.$emit中全局emit的complete error等事件
+			this._onEmit();
+		}
 		// #ifdef APP-NVUE
 		if (!this.isIos && !this.useChatRecordMode) {
 			this.nLoadingMoreFixedHeight = true;
@@ -246,10 +265,6 @@ export default {
 		// 在nvue中更新nvue下拉刷新view容器的宽度,而不是写死默认的750rpx,需要考虑列表宽度不是铺满屏幕的情况
 		this._nUpdateRefresherWidth();
 		// #endif
-		// #ifndef APP-NVUE
-		// 虚拟列表模式时,初始化数据
-		this.finalUseVirtualList && this._virtualListInit();
-		// #endif
 		// #ifndef APP-PLUS
 		this.$nextTick(() => {
 			// 非app平台中,在通过获取css设置的底部安全区域占位view高度设置bottom距离后,更新页面滚动底部高度
@@ -320,7 +335,10 @@ export default {
 			}
 			return this.pagingContentStyle;
 		},
-		
+		// 最终的当前开启安全区域适配后,是否使用placeholder形式实现。如果slot=bottom存在,则应当交由固定在底部的view处理,因此需排除此情况
+		finalUseSafeAreaPlaceholder() {
+			return this.useSafeAreaPlaceholder && !this.zSlots.bottom;
+		},
 		renderJsIgnore() {
 			if ((this.usePageScroll && this.useChatRecordMode) || (!this.refresherEnabled && this.scrollable) || !this.useCustomRefresher) {
 				this.$nextTick(() => {
@@ -335,19 +353,19 @@ export default {
 		},
 		windowBottom() {
 			if (!this.systemInfo) return 0;
-			let windowBottom = this.systemInfo.windowBottom || 0;
-			// 如果开启底部安全区域适配并且不使用placeholder的形式体现并且不是聊天记录模式(因为聊天记录模式在keyboardHeight计算初已添加了底部安全区域),在windowBottom添加底部安全区域高度
-			if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder && !this.useChatRecordMode) {
-				windowBottom += this.safeAreaBottom;
-			}
-			return windowBottom;
+			return this.systemInfo.windowBottom || 0;
 		},
+		// 是否是ios+h5
 		isIosAndH5() {
 			// #ifndef H5
 			return false;
 			// #endif
 			return this.isIos;
-		}
+		},
+		// 是否是只使用基础布局或者只使用下拉刷新
+		isOnly() {
+			return this.layoutOnly || this.refresherOnly;
+		},
 	},
 	methods: {
 		// 当前版本号
@@ -434,20 +452,25 @@ export default {
 				}
 			} catch (e) {}
 		},
-		// #ifdef MP-KUAISHOU
-		// 设置scroll-view内容器的最小高度等于scroll-view的高度(为了解决在快手小程序中内容较少时scroll-view内容器高度无法铺满scroll-view的问题)
-		async _setFullScrollViewInHeight() {
-			try {
-				// 如果需要铺满全屏,则计算当前全屏可是区域的高度
-				const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
-				scrollViewNode && this.$set(this.scrollViewInStyle, 'min-height', scrollViewNode[0].height + 'px');
-			} catch (e) {}
+		// 更新scroll-view高度
+		async _updateScrollViewHeight() {
+			const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
+			if (scrollViewNode) {
+				const scrollViewNodeHeight = scrollViewNode[0].height;
+				this.scrollViewHeight = scrollViewNodeHeight;
+				this.pagingOrgTop =  scrollViewNode[0].top;
+				// 设置scroll-view内容器的最小高度等于scroll-view的高度(为了解决在快手小程序中内容较少时scroll-view内容器高度无法铺满scroll-view的问题)
+				// #ifdef MP-KUAISHOU
+				this.$set(this.scrollViewInStyle, 'min-height', scrollViewNodeHeight + 'px');
+				// #endif
+			}
 		},
-		// #endif
 		// 组件销毁后续处理
 		_handleUnmounted() {
 			this.active = false;
-			this._offEmit();
+			if (!this.layoutOnly) {
+				this._offEmit();
+			}
 			// 取消监听键盘高度变化事件(H5、百度小程序、抖音小程序、飞书小程序、QQ小程序、快手小程序不支持)
 			// #ifndef H5 || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU
 			this.useChatRecordMode && uni.offKeyboardHeightChange(this._handleKeyboardHeightChange);

+ 21 - 1
uni_modules/z-paging/components/z-paging/js/z-paging-utils.js

@@ -207,6 +207,25 @@ function deepCopy(obj) {
 	return newObj;
 }
 
+// 对短时间内重复插入的数据进行整合,并一次性插入
+function useBufferedInsert(fn, delay = 50) {
+	let buffer = [];
+	let timer = null;
+	let latestArgs = [];
+	return function insertBuffered(data, ...args) {
+		const newData = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data;
+		buffer.push(...newData);
+		latestArgs = args;
+		if (!timer) {
+			timer = setTimeout(() => {
+				fn(buffer.length === 1 ? buffer[0] : buffer, ...latestArgs);
+				buffer = [];
+				timer = null;
+			}, buffer.length === 1 ? 10 : delay);
+		}
+	};
+}
+
 // ------------------ 私有方法 ------------------------
 // 处理全局配置
 function _handleDefaultConfig() {
@@ -298,5 +317,6 @@ export default {
 	addUnit,
 	deepCopy,
 	rpx2px,
-	getSystemInfoSync
+	getSystemInfoSync,
+	useBufferedInsert
 };

+ 156 - 20
uni_modules/z-paging/components/z-paging/z-paging.vue

@@ -4,7 +4,7 @@
   / /_____| |_) | (_| | (_| | | | | | (_| |
  /___|    | .__/ \__,_|\__, |_|_| |_|\__, |
           |_|          |___/         |___/ 
-v2.8.6 (2025-03-17)
+v2.8.8 (2025-08-29)
 @author ZXLee <admin@zxlee.cn>
 -->
 <!-- 文档地址:https://z-paging.zxlee.cn -->
@@ -14,19 +14,21 @@ v2.8.6 (2025-03-17)
 
 <template name="z-paging">
 	<!-- #ifndef APP-NVUE -->
-	<view :class="{'z-paging-content':true,'z-paging-content-full':!usePageScroll,'z-paging-content-fixed':!usePageScroll&&fixed,'z-paging-content-page':usePageScroll,'z-paging-reached-top':renderPropScrollTop<1,'z-paging-use-chat-record-mode':useChatRecordMode}" :style="[finalPagingStyle]">
+	<view :class="[{'z-paging-content':true,'z-paging-content-full':!usePageScroll,'z-paging-content-fixed':!usePageScroll&&fixed,'z-paging-content-page':usePageScroll,'z-paging-reached-top':renderPropScrollTop<1,'z-paging-use-chat-record-mode':useChatRecordMode}, pagingClass]" :style="[finalPagingStyle]">
 		<!-- #ifndef APP-PLUS -->
-		<view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom"></view>
+		<view v-if="cssSafeAreaInsetBottom===-1" class="zp-safe-area-inset-bottom" style="position: absolute"/>
 		<!-- #endif -->
 		<!-- 二楼view -->
 		<view v-if="showF2 && showRefresherF2" @touchmove.stop.prevent class="zp-f2-content" :style="[{'transform': f2Transform, 'transition': `transform .2s linear`, 'height': superContentHeight + 'px', 'z-index': f2ZIndex}]">
 			<slot name="f2"/>
 		</view>
 		<!-- 顶部固定的slot -->
-		<slot v-if="!usePageScroll&&zSlots.top" name="top" />
-		<view class="zp-page-top" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.top" :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]">
-			<slot name="top" />
-		</view>
+		<template v-if="zSlots.top">
+			<slot v-if="!usePageScroll" name="top" />
+			<view v-else class="zp-page-top" @touchmove.stop.prevent :style="[{'top':`${windowTop}px`,'z-index':topZIndex}]">
+				<slot name="top" />
+			</view>
+		</template>
 		<view :class="{'zp-view-super':true,'zp-scroll-view-super':!usePageScroll}" :style="[finalScrollViewStyle]">
 			<view v-if="zSlots.left" :class="{'zp-page-left':true,'zp-absoulte':finalIsOldWebView}">
 				<slot name="left" />
@@ -58,7 +60,7 @@ v2.8.6 (2025-03-17)
 						:change:prop="pagingWxs.propObserver" :prop="wxsPropType"
 						:data-refresherThreshold="finalRefresherThreshold" :data-refresherF2Enabled="refresherF2Enabled" :data-refresherF2Threshold="finalRefresherF2Threshold" :data-isIos="isIos"
 						:data-loading="loading||isRefresherInComplete" :data-useChatRecordMode="useChatRecordMode" 
-						:data-refresherEnabled="refresherEnabled" :data-useCustomRefresher="useCustomRefresher" :data-pageScrollTop="wxsPageScrollTop"
+						:data-refresherEnabled="finalRefresherEnabled" :data-useCustomRefresher="useCustomRefresher" :data-pageScrollTop="wxsPageScrollTop"
 						:data-scrollTop="wxsScrollTop" :data-refresherMaxAngle="refresherMaxAngle" :data-refresherNoTransform="refresherNoTransform"
 						:data-refresherAecc="refresherAngleEnableChangeContinued" :data-usePageScroll="usePageScroll" :data-watchTouchDirectionChange="watchTouchDirectionChange"
 						:data-oldIsTouchmoving="isTouchmoving" :data-refresherOutRate="finalRefresherOutRate" :data-refresherPullRate="finalRefresherPullRate" :data-hasTouchmove="hasTouchmove"
@@ -100,7 +102,7 @@ v2.8.6 (2025-03-17)
 										<view class="zp-list-container" :style="[innerListStyle]">
 											<template v-if="finalUseVirtualList">
 												<view class="zp-list-cell" :style="[innerCellStyle]" :id="`${fianlVirtualCellIdPrefix}-${item[virtualCellIndexKey]}`" v-for="(item,index) in virtualList" :key="item['zp_unique_index']" @click="_innerCellClick(item,virtualTopRangeIndex+index)">
-													<view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第103行中注释这一行,并打开下面一行注释</view>
+													<view v-if="useCompatibilityMode">使用兼容模式请在组件源码z-paging.vue第105行中注释这一行,并打开下面一行注释</view>
 													<!-- <zp-public-virtual-cell v-if="useCompatibilityMode" :extraData="extraData" :item="item" :index="virtualTopRangeIndex+index" /> -->
 													<slot v-else name="cell" :item="item" :index="virtualTopRangeIndex+index"/>
 												</view>
@@ -140,7 +142,11 @@ v2.8.6 (2025-03-17)
 									<slot v-else-if="loadingStatus===M.Fail&&zSlots.loadingMoreFail&&showLoadingMore&&loadingMoreEnabled&&!useChatRecordMode" name="loadingMoreFail" />
 									<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMore&&showDefaultLoadingMoreText&&!(loadingStatus===M.NoMore&&!showLoadingMoreNoMoreView)&&loadingMoreEnabled&&!useChatRecordMode" :zConfig="zLoadMoreConfig" />
 									<!-- #endif -->
-									<view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder&&!useChatRecordMode" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
+									<!-- 底部安全区域useSafeAreaPlaceholder模式占位,此时占位不再固定在底部而是跟随页面一起滚动 -->
+									<!-- 如果底部slot=bottom存在,占位区域会插入在slot=bottom下方,不再跟随页面滚动,因此这里就没必要显示了 -->
+									<!-- 聊天记录模式因为列表倒置,此处不需要显示底部安全区域,另行处理 -->
+									
+									<view v-if="safeAreaInsetBottom&&finalUseSafeAreaPlaceholder&&!useChatRecordMode" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
 								</view>
 								<!-- 空数据图 -->
 								<view v-if="showEmpty" :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}" :style="[emptyViewSuperStyle,chatRecordRotateStyle]">
@@ -161,10 +167,37 @@ v2.8.6 (2025-03-17)
 		</view>
 		<!-- 底部固定的slot -->
 		<view class="zp-page-bottom-container" :style="{'background': bottomBgColor}">
-			<slot v-if="!usePageScroll&&zSlots.bottom" name="bottom" />
-			<view class="zp-page-bottom" @touchmove.stop.prevent v-else-if="usePageScroll&&zSlots.bottom" :style="[{'bottom': `${windowBottom}px`}]">
-				<slot name="bottom" />
-			</view>
+			<template v-if="zSlots.bottom">
+				<!-- 非页面滚动底部插槽(父容器开启flex,中间列表设置了flex:1,通过中间列表撑开固定在底部) -->
+				<slot v-if="!usePageScroll" name="bottom" />
+				<!-- 页面滚动底部插槽(通过position: fixed固定在底部) -->
+				<view v-else class="zp-page-bottom" @touchmove.stop.prevent :style="[{'bottom': `${windowBottom}px`, 'background': bottomBgColor}]">
+					<slot name="bottom" />
+					<!-- 页面滚动底部安全区域占位(仅slot=bottom存在时展示在slot=bottom插入的view下方,当slot=bottom不存在时,通过控制容器的marginBottom设置底部安全区域间距) -->
+					<template v-if="safeAreaInsetBottom">
+						<!-- 如果是App,则使用style中的safeAreaBottom设置高度,非APP使用class,因为class中的env(safe-area-inset-bottom)在部分app中无效 -->
+						<!-- #ifdef APP-PLUS -->
+						<view :style="[{height:safeAreaBottom+'px'}]" />
+						<!-- #endif -->
+						<!-- #ifndef APP-PLUS -->
+						<view class="zp-safe-area-inset-bottom" />
+						<!-- #endif -->
+					</template>
+				</view>
+			</template>
+			<!-- 非页面滚动底部安全区域占位(无论slot=bottom是否存在)-->
+			<!-- 如果useSafeAreaPlaceholder开启了并且slot=bottom不存在就不显示这个占位view了,因为此时useSafeAreaPlaceholder会是跟随滚动的状态 -->
+			<!-- 聊天记录模式因为列表倒置,此处不需要显示底部安全区域,另行处理 -->
+			<template v-if="safeAreaInsetBottom&&!usePageScroll&&!(finalUseSafeAreaPlaceholder)&&!useChatRecordMode">
+				<!-- 如果是App,则使用style中的safeAreaBottom设置高度,非APP使用class,因为class中的env(safe-area-inset-bottom)在部分app中无效 -->
+				<!-- #ifdef APP-PLUS -->
+				<view :style="[{height:safeAreaBottom+'px'}]" />
+				<!-- #endif -->
+				<!-- #ifndef APP-PLUS -->
+				<view class="zp-safe-area-inset-bottom" />
+				<!-- #endif -->
+			</template>
+			
 			<!-- 聊天记录模式底部占位 -->
 			<template v-if="useChatRecordMode&&autoAdjustPositionWhenChat">
 				<view :style="[{height:chatRecordModeSafeAreaBottom+'px'}]" />
@@ -183,7 +216,7 @@ v2.8.6 (2025-03-17)
 	</view>
 	<!-- #endif -->
 	<!-- #ifdef APP-NVUE -->
-	<component ref="z-paging-content" :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="{'z-paging-content-fixed':fixed&&!usePageScroll}" :scrollable="false">
+	<component ref="z-paging-content" :is="finalNvueSuperListIs" :style="[finalPagingStyle]" :class="[{'z-paging-content-fixed':fixed&&!usePageScroll}, pagingClass]" :scrollable="false">
 		<!-- 二楼view -->
 		<view v-if="showF2 && showRefresherF2" ref="zp-n-f2" class="zp-f2-content" @touchmove.stop.prevent :style="[{'height': superContentHeight + 'px', 'width': nRefresherWidth + 'px', 'opacity': nF2Opacity}]">
 			<slot name="f2"/>
@@ -201,7 +234,9 @@ v2.8.6 (2025-03-17)
 			<view v-if="zSlots.left" class="zp-page-left">
 				<slot name="left" />
 			</view>
-			<component :is="finalNvueListIs" ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},usePageScroll?scrollViewStyle:{},chatRecordRotateStyle]" :alwaysScrollableVertical="true"
+			<!-- 因在nvue+vue3+waterfall中,使用<component is="waterfall" />设置的瀑布流无效,因此此处只能单独判断finalNvueListIs等于waterfall时,直接写<waterfall />标签暂时解决 -->
+			<!-- 下方的v-if和v-else中的代码完全一致,仅标签不同,等待官方解决后再统一,已提issue:https://ask.dcloud.net.cn/question/168505 -->
+			<component v-if="finalNvueListIs !== 'waterfall'" :is="finalNvueListIs" ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},usePageScroll?scrollViewStyle:{},chatRecordRotateStyle]" :alwaysScrollableVertical="true"
 				:fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop"
 				:scrollable="finalScrollable" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth"
 				:column-gap="nWaterfallColumnGap" :left-gap="nWaterfallLeftGap" :right-gap="nWaterfallRightGap" :pagingEnabled="nvuePagingEnabled" :offset-accuracy="offsetAccuracy"
@@ -253,7 +288,7 @@ v2.8.6 (2025-03-17)
 					<slot name="loading" />
 				</component>
 				<!-- 上拉加载更多view -->
-				<component :is="nViewIs" v-if="!refresherOnly&&loadingMoreEnabled&&!showEmpty">
+				<component :is="nViewIs" v-if="!isOnly&&loadingMoreEnabled&&!showEmpty">
 					<!-- 聊天记录模式加载更多loading(滚动到顶部加载更多或无更多数据时显示) -->
 					<template v-if="useChatRecordMode&&realTotalData.length>=defaultPageSize&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)&&realTotalData.length&&isChatRecordModeAndInversion">
 						<view :style="[chatRecordRotateStyle]">
@@ -271,7 +306,10 @@ v2.8.6 (2025-03-17)
 						<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
 						<slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" />
 						<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" />
-						<view v-if="safeAreaInsetBottom&&useSafeAreaPlaceholder&&!useChatRecordMode" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
+						<!-- 底部安全区域useSafeAreaPlaceholder模式占位,此时占位不再固定在底部而是跟随页面一起滚动 -->
+						<!-- 如果底部slot=bottom存在,占位区域会插入在slot=bottom下方,不再跟随页面滚动,因此这里就没必要显示了 -->
+						<!-- 聊天记录模式因为列表倒置,此处不需要显示底部安全区域,另行处理 -->
+						<view v-if="safeAreaInsetBottom&&finalUseSafeAreaPlaceholder&&!useChatRecordMode" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
 					</view>
 				</component>
 				<!-- 空数据图 -->
@@ -286,6 +324,94 @@ v2.8.6 (2025-03-17)
 				</component>
 				<component :is="nViewIs" v-if="!hideNvueBottomTag" ref="zp-n-list-bottom-tag" class="zp-n-list-bottom-tag"></component>
 			</component>
+			<waterfall v-else :is="finalNvueListIs" ref="zp-n-list" :id="nvueListId" :style="[{'flex': 1,'top':isIos?'0px':'-1px'},usePageScroll?scrollViewStyle:{},chatRecordRotateStyle]" :alwaysScrollableVertical="true"
+				:fixFreezing="nFixFreezing" :show-scrollbar="showScrollbar" :loadmoreoffset="finalLowerThreshold" :enable-back-to-top="enableBackToTop"
+				:scrollable="finalScrollable" :bounce="nvueBounce" :column-count="nWaterfallColumnCount" :column-width="nWaterfallColumnWidth"
+				:column-gap="nWaterfallColumnGap" :left-gap="nWaterfallLeftGap" :right-gap="nWaterfallRightGap" :pagingEnabled="nvuePagingEnabled" :offset-accuracy="offsetAccuracy"
+				@loadmore="_nOnLoadmore" @scroll="_nOnScroll" @scrollend="_nOnScrollend">
+				<refresh v-if="(zSlots.top?cacheTopHeight!==-1:true)&&finalNvueRefresherEnabled" class="zp-n-refresh" :style="[nvueRefresherStyle]" :display="nRefresherLoading?'show':'hide'" @refresh="_nOnRrefresh" @pullingdown="_nOnPullingdown">
+					<view ref="zp-n-refresh-container" class="zp-n-refresh-container" :style="[{background:refresherBackground,width:nRefresherWidth}]" id="zp-n-refresh-container">
+						<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
+						<!-- 下拉刷新view -->
+						<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
+						<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
+						<slot v-else-if="(nScopedSlots?nScopedSlots:zSlots).refresher" :refresherStatus="refresherStatus" name="refresher" />
+						<z-paging-refresh ref="refresh" v-else :status="refresherStatus" :defaultThemeStyle="finalRefresherThemeStyle" :isIos="isIos"
+							:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
+							:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
+							:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
+							:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
+					</view>
+				</refresh>
+				<component :is="nViewIs" v-if="isIos&&!useChatRecordMode?oldScrollTop>10:true" ref="zp-n-list-top-tag" class="zp-n-list-top-tag" style="margin-top: -1rpx;" :style="[{height:finalNvueRefresherEnabled?'0px':'1px'}]"></component>
+				<component :is="nViewIs" v-if="nShowRefresherReveal" ref="zp-n-list-refresher-reveal" :style="[{transform:`translateY(-${nShowRefresherRevealHeight}px)`},{background:refresherBackground}]">
+					<view v-if="useRefresherStatusBarPlaceholder" class="zp-custom-refresher-status-bar-placeholder" :style="[{'height': `${statusBarHeight}px`}]" />
+					<!-- 下拉刷新view -->
+					<slot v-if="zSlots.refresherComplete&&refresherStatus===R.Complete" name="refresherComplete" />
+					<slot v-else-if="zSlots.refresherF2&&refresherStatus===R.GoF2" name="refresherF2" />
+					<slot v-else-if="(nScopedSlots?nScopedSlots:$slots).refresher" :refresherStatus="R.Loading" name="refresher" />
+					<z-paging-refresh ref="refresh" v-else :status="R.Loading" :defaultThemeStyle="finalRefresherThemeStyle" :isIos="isIos"
+						:defaultText="finalRefresherDefaultText" :pullingText="finalRefresherPullingText" :refreshingText="finalRefresherRefreshingText" :completeText="finalRefresherCompleteText" :goF2Text="finalRefresherGoF2Text"
+						:defaultImg="refresherDefaultImg" :pullingImg="refresherPullingImg" :refreshingImg="refresherRefreshingImg" :completeImg="refresherCompleteImg" :refreshingAnimated="refresherRefreshingAnimated"
+						:showUpdateTime="showRefresherUpdateTime" :updateTimeKey="refresherUpdateTimeKey" :updateTimeTextMap="finalRefresherUpdateTimeTextMap"
+						:imgStyle="refresherImgStyle" :titleStyle="refresherTitleStyle" :updateTimeStyle="refresherUpdateTimeStyle" :unit="unit" />
+				</component>
+				<!-- 内置列表 -->
+				<template v-if="finalUseInnerList">
+					<component :is="nViewIs">
+						<slot name="header"/>
+					</component>	
+					<component :is="nViewIs" class="zp-list-cell" v-for="(item,index) in realTotalData" :key="finalCellKeyName.length?item[finalCellKeyName]:index">
+						<slot name="cell" :item="item" :index="index"/>
+					</component>
+					<component :is="nViewIs">
+						<slot name="footer"/>
+					</component>	
+				</template>
+				<template v-else>
+					<slot />
+				</template>
+				<!-- 全屏Loading -->
+				<component :is="nViewIs" v-if="showLoading&&zSlots.loading&&!loadingFullFixed" :class="{'z-paging-content-fixed':usePageScroll}" style="flex:1" :style="[chatRecordRotateStyle]">
+					<slot name="loading" />
+				</component>
+				<!-- 上拉加载更多view -->
+				<component :is="nViewIs" v-if="!isOnly&&loadingMoreEnabled&&!showEmpty">
+					<!-- 聊天记录模式加载更多loading(滚动到顶部加载更多或无更多数据时显示) -->
+					<template v-if="useChatRecordMode&&realTotalData.length>=defaultPageSize&&(loadingStatus!==M.NoMore||zSlots.chatNoMore)&&realTotalData.length&&isChatRecordModeAndInversion">
+						<view :style="[chatRecordRotateStyle]">
+							<slot v-if="loadingStatus===M.NoMore&&zSlots.chatNoMore" name="chatNoMore" />
+							<template v-else>
+								<slot v-if="zSlots.chatLoading" :loadingMoreStatus="loadingStatus" name="chatLoading" />
+								<z-paging-load-more v-else @doClick="_onLoadingMore('click')" :zConfig="zLoadMoreConfig" />
+							</template>
+						</view>
+					</template>
+					
+					<view :style="nLoadingMoreFixedHeight?{height:loadingMoreCustomStyle&&loadingMoreCustomStyle.height?loadingMoreCustomStyle.height:loadingMoreFixedHeight}:{}">
+						<slot v-if="showLoadingMoreDefault" name="loadingMoreDefault" />
+						<slot v-else-if="showLoadingMoreLoading" name="loadingMoreLoading" />
+						<slot v-else-if="showLoadingMoreNoMore" name="loadingMoreNoMore" />
+						<slot v-else-if="showLoadingMoreFail" name="loadingMoreFail" />
+						<z-paging-load-more @doClick="_onLoadingMore('click')" v-else-if="showLoadingMoreCustom" :zConfig="zLoadMoreConfig" />
+						<!-- 底部安全区域useSafeAreaPlaceholder模式占位,此时占位不再固定在底部而是跟随页面一起滚动 -->
+						<!-- 如果底部slot=bottom存在,占位区域会插入在slot=bottom下方,不再跟随页面滚动,因此这里就没必要显示了 -->
+						<!-- 聊天记录模式因为列表倒置,此处不需要显示底部安全区域,另行处理 -->
+						<view v-if="safeAreaInsetBottom&&finalUseSafeAreaPlaceholder&&!useChatRecordMode" class="zp-safe-area-placeholder" :style="[{height:safeAreaBottom+'px'}]" />
+					</view>
+				</component>
+				<!-- 空数据图 -->
+				<component :is="nViewIs" v-if="showEmpty" :class="{'z-paging-content-fixed':usePageScroll}" :style="[{flex:emptyViewCenter?1:0},emptyViewSuperStyle,chatRecordRotateStyle]">
+					<view :class="{'zp-empty-view':true,'zp-empty-view-center':emptyViewCenter}">
+						<slot v-if="zSlots.empty" name="empty" :isLoadFailed="isLoadFailed" />
+						<z-paging-empty-view v-else :emptyViewImg="finalEmptyViewImg" :emptyViewText="finalEmptyViewText" :showEmptyViewReload="finalShowEmptyViewReload" 
+						:emptyViewReloadText="finalEmptyViewReloadText" :isLoadFailed="isLoadFailed" :emptyViewStyle="emptyViewStyle" :emptyViewTitleStyle="emptyViewTitleStyle" 
+						:emptyViewImgStyle="emptyViewImgStyle" :emptyViewReloadStyle="emptyViewReloadStyle" :emptyViewZIndex="emptyViewZIndex" :emptyViewFixed="emptyViewFixed" :unit="unit"
+						@reload="_emptyViewReload" @viewClick="_emptyViewClick" />
+					</view>
+				</component>
+				<component :is="nViewIs" v-if="!hideNvueBottomTag" ref="zp-n-list-bottom-tag" class="zp-n-list-bottom-tag"></component>
+			</waterfall>
 			<view v-if="zSlots.right" class="zp-page-right">
 				<slot name="right" />
 			</view>
@@ -293,6 +419,11 @@ v2.8.6 (2025-03-17)
 		<!-- 底部固定的slot -->
 		<view class="zp-page-bottom-container" :style="{'background': bottomBgColor}">
 			<slot name="bottom" />
+			<!-- 非页面滚动底部安全区域占位(无论slot=bottom是否存在)-->
+			<!-- 如果useSafeAreaPlaceholder开启了并且slot=bottom不存在就不显示这个占位view了,因为此时useSafeAreaPlaceholder会是跟随滚动的状态 -->
+			<!-- 聊天记录模式因为列表倒置,此处不需要显示底部安全区域,另行处理 -->
+			<view v-if="safeAreaInsetBottom&&!usePageScroll&&!(finalUseSafeAreaPlaceholder)&&!useChatRecordMode" :style="[{height:safeAreaBottom+'px'}]" />
+			
 			<!-- 聊天记录模式底部占位 -->
 			<template v-if="useChatRecordMode&&autoAdjustPositionWhenChat">
 				<view :style="[{height:chatRecordModeSafeAreaBottom+'px'}]" />
@@ -327,11 +458,14 @@ v2.8.6 (2025-03-17)
 	 * @property {Boolean} autoFullHeight 使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为true
 	 * @property {String} defaultThemeStyle loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认为black
 	 * @property {Object} pagingStyle 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替
+	 * @property {String|Array|Object} pagingClass 设置z-paging的class,优先级低于pagingStyle和height、width、maxWidth、bgColor
 	 * @property {String} height z-paging的高度,优先级低于pagingStyle中设置的height,传字符串,如100px、100rpx、100%
 	 * @property {String} width z-paging的宽度,优先级低于pagingStyle中设置的width,传字符串,如100px、100rpx、100%
 	 * @property {String} maxWidth z-paging的最大宽度,优先级低于pagingStyle中设置的max-width,默认为空
 	 * @property {String} bgColor z-paging的背景色(为css中的background,因此也可以设置渐变,背景图片等),优先级低于pagingStyle中设置的background-color
 	 * @property {Boolean} watchTouchDirectionChange 是否监听列表触摸方向改变,默认为false
+	 * @property {Boolean} watchScrollDirectionChange 是否监听列表滚动方向改变,默认为false
+	 * @property {Boolean} layoutOnly 是否只使用基础布局,设置为true后将关闭mounted自动请求数据、关闭下拉刷新和滚动到底部加载更多,强制隐藏空数据图。默认为否
 	 * @property {Number|String} delay 调用complete后延迟处理的时间,单位为毫秒,优先级高于min-delay,默认为0
 	 * @property {Number|String} minDelay 触发@query后最小延迟处理的时间,单位为毫秒,优先级低于delay,默认为0
 	 * @property {Boolean} callNetworkReject 请求失败是否触发reject,默认为true
@@ -451,7 +585,8 @@ v2.8.6 (2025-03-17)
 	 * @property {String} virtualCellIdPrefix 虚拟列表cell id的前缀
 	 * @property {Boolean} useInnerList 是否在z-paging内部循环渲染列表(使用内置列表),默认为false
 	 * @property {Boolean} forceCloseInnerList 强制关闭inner-list,默认为false
-	 * @property {Boolean} virtualInSwiperSlot 虚拟列表是否使用swiper-item包裹,默认为false
+	 * @property {Boolean} virtualInSwiperSlot 虚拟列表是否使用swiper-item或其他父组件包裹,默认为false
+	 * @property {Boolean} inSwiperSlot z-paging是否使用swiper-item或其他父组件包裹,默认为否
 	 * @property {String} cellKeyName 内置列表cell的key名称(仅nvue有效)
 	 * @property {Object} innerListStyle innerList样式
 	 * @property {Object} innerCellStyle innerCell样式
@@ -515,7 +650,8 @@ v2.8.6 (2025-03-17)
 	 * @event {Function} scrolltoupper z-paging内置的scroll-view/list-view/waterfall滚动顶部时触发
 	 * @event {Function} scrollend z-paging内置的list滚动结束时触发
 	 * @event {Function} contentHeightChanged z-paging中内容高度改变时触发
-	 * @event {Function} touchDirectionChange 监听列表触摸方向改变
+	 * @event {Function} touchDirectionChange 监听列表触摸方向改变(nvue无效)
+	 * @event {Function} scrollDirectionChange 监听列表滚动方向改变(页面滚动无效)
 	 * @example <z-paging ref="paging" v-model="dataList" @query="queryList"></z-paging>
 	 */
 	export default {

+ 41 - 43
uni_modules/z-paging/package.json

@@ -2,7 +2,7 @@
   "id": "z-paging",
   "name": "z-paging",
   "displayName": "【z-paging下拉刷新、上拉加载】高性能,全平台兼容。支持虚拟列表,分页全自动处理",
-  "version": "2.8.6",
+  "version": "2.8.8",
   "description": "超简单、低耦合!使用wxs+renderjs实现。支持自定义下拉刷新、上拉加载更多、虚拟列表、下拉进入二楼、自动管理空数据图、无闪动聊天分页、本地分页、国际化等数百项配置",
   "keywords": [
     "下拉刷新",
@@ -13,7 +13,8 @@
 ],
   "repository": "https://github.com/SmileZXLee/uni-z-paging",
   "engines": {
-    "HBuilderX": "^3.0.7"
+    "HBuilderX": "^3.0.7",
+    "uni-app": "^4.07"
   },
   "dcloudext": {
     "sale": {
@@ -33,55 +34,52 @@
       "permissions": "无"
     },
     "npmurl": "https://www.npmjs.com/package/z-paging",
-    "type": "component-vue"
+    "type": "component-vue",
+    "darkmode": "√",
+    "i18n": "√",
+    "widescreen": "√"
   },
   "uni_modules": {
     "dependencies": [],
     "encrypt": [],
     "platforms": {
       "cloud": {
-        "tcb": "y",
-        "aliyun": "y",
-        "alipay": "n"
+        "tcb": "",
+        "aliyun": "",
+        "alipay": ""
       },
       "client": {
-        "App": {
-            "app-vue": "y",
-            "app-nvue": "y",
-            "app-harmony": "u",
-            "app-uvue": "u"
-        },
-        "H5-mobile": {
-          "Safari": "y",
-          "Android Browser": "y",
-          "微信浏览器(Android)": "y",
-          "QQ浏览器(Android)": "y"
-        },
-        "H5-pc": {
-          "Chrome": "y",
-          "IE": "y",
-          "Edge": "y",
-          "Firefox": "y",
-          "Safari": "y"
-        },
-        "小程序": {
-          "微信": "y",
-          "阿里": "y",
-          "百度": "y",
-          "字节跳动": "y",
-          "QQ": "y",
-          "钉钉": "y",
-          "快手": "y",
-          "飞书": "y",
-          "京东": "y"
-        },
-        "快应用": {
-          "华为": "y",
-          "联盟": "y"
-        },
-        "Vue": {
-          "vue2": "y",
-          "vue3": "y"
+        "uni-app": {
+          "vue": {
+            "vue2": "√",
+            "vue3": "√"
+          },
+          "web": {
+            "safari": "√",
+            "chrome": "√"
+          },
+          "app": {
+            "vue": "√",
+            "nvue": "√",
+            "android": "√",
+            "ios": "√",
+            "harmony": "√"
+          },
+          "mp": {
+            "weixin": "√",
+            "alipay": "√",
+            "toutiao": "√",
+            "baidu": "√",
+            "kuaishou": "√",
+            "jd": "√",
+            "harmony": "-",
+            "qq": "√",
+            "lark": "√"
+          },
+          "quickapp": {
+            "huawei": "√",
+            "union": "-"
+          }
         }
       }
     }

+ 2 - 2
uni_modules/z-paging/readme.md

@@ -4,7 +4,7 @@
     <img alt="logo" src="https://z-paging.zxlee.cn/img/title-logo.png" height="100" style="margin-bottom: 50px;" />
 </p>
 
-[![version](https://img.shields.io/badge/version-2.8.6-blue)](https://github.com/SmileZXLee/uni-z-paging) [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
+[![version](https://img.shields.io/badge/version-2.8.8-blue)](https://github.com/SmileZXLee/uni-z-paging) [![license](https://img.shields.io/github/license/SmileZXLee/uni-z-paging)](https://en.wikipedia.org/wiki/MIT_License)
 <img height="0" width="0" src="https://api.z-notify.zxlee.cn/v1/public/statistics/8293556910106066944/addOnly?from=uni" />
 
 `z-paging-x`现已支持uniapp x,持续完善中,插件地址👉🏻 [https://ext.dcloud.net.cn/plugin?name=z-paging-x](https://ext.dcloud.net.cn/plugin?name=z-paging-x)  
@@ -19,7 +19,7 @@
 * 【低耦合,低侵入】分页自动管理。在page中无需处理任何分页相关逻辑,无需在data中定义任何分页相关变量,全由z-paging内部处理。
 * 【超灵活,支持各种类型自定义】支持自定义下拉刷新,自定义上拉加载更多等各种自定义效果;支持使用内置自动分页,同时也支持通过监听下拉刷新和滚动到底部事件自行处理;支持使用自带全屏布局规范,同时也支持将z-paging自由放在任意容器中。
 * 【功能丰富】支持国际化,支持自定义且自动管理空数据图,支持主题模式切换,支持本地分页,支持无闪动聊天分页模式,支持展示最后更新时间,支持吸顶效果,支持内部scroll-view滚动与页面滚动,支持一键滚动到顶部,支持下拉进入二楼等诸多功能。
-* 【全平台兼容】支持vue&nvue,vue2&vue3,js&ts,支持h5、app、鸿蒙Next及各家小程序。
+* 【全平台兼容】支持vue&nvue,vue2&vue3,js&ts,支持h5、app、鸿蒙Next及各家小程序。
 * 【高性能】在app-vue、h5、微信小程序、QQ小程序上使用wxs+renderjs在视图层实现下拉刷新;支持虚拟列表,轻松渲染百万级列表数据!
 
 *** 

+ 1 - 1
uni_modules/z-paging/types/comps/z-paging-cell.d.ts

@@ -5,7 +5,7 @@ declare interface ZPagingCellProps {
   /**
    * z-paging-cell样式
    */
-  cellStyle?: Record<string, any>
+  cellStyle?: Partial<CSSStyleDeclaration>
 }
 
 // ****************************** Slots ******************************

+ 4 - 4
uni_modules/z-paging/types/comps/z-paging-empty-view.d.ts

@@ -32,23 +32,23 @@ declare interface ZPagingEmptyViewProps {
    * 空数据图样式,可设置空数据view的top等
    * - 如果空数据图不是fixed布局,则此处是`margin-top`
    */
-  emptyViewStyle?: Record<string, any>;
+  emptyViewStyle?: Partial<CSSStyleDeclaration>;
 
   /**
    * 空数据图img样式
    */
-  emptyViewImgStyle?: Record<string, any>;
+  emptyViewImgStyle?: Partial<CSSStyleDeclaration>;
 
   /**
    * 空数据图描述文字样式
    */
-  emptyViewTitleStyle?: Record<string, any>;
+  emptyViewTitleStyle?: Partial<CSSStyleDeclaration>;
 
   /**
    * 空数据图重新加载按钮样式
    * @since 1.6.7
    */
-  emptyViewReloadStyle?: Record<string, any>;
+  emptyViewReloadStyle?: Partial<CSSStyleDeclaration>;
 
   /**
    * 是否显示空数据图重新加载按钮(无数据时)

+ 1 - 1
uni_modules/z-paging/types/comps/z-paging-swiper-item.d.ts

@@ -60,7 +60,7 @@ declare interface ZPagingSwiperItemProps {
   /**
    * innerList样式
    */
-  innerListStyle?: Record<string, any>
+  innerListStyle?: Partial<CSSStyleDeclaration>
 }
 
 // ****************************** Methods ******************************

+ 1 - 1
uni_modules/z-paging/types/comps/z-paging-swiper.d.ts

@@ -17,7 +17,7 @@ declare interface ZPagingSwiperProps {
   /**
    * z-paging-swiper样式
    */
-  swiperStyle?: Record<string, any>
+  swiperStyle?: Partial<CSSStyleDeclaration>
 }
 
 

+ 74 - 18
uni_modules/z-paging/types/comps/z-paging.d.ts

@@ -33,6 +33,11 @@ declare global {
      * 列表触摸的方向,top代表用户将列表向上移动(scrollTop不断减小),bottom代表用户将列表向下移动(scrollTop不断增大)
      */
     type TouchDirection = 'top' | 'bottom';
+
+    /**
+     * 列表滚动的方向,top代表用户将列表向上移动(scrollTop不断减小),bottom代表用户将列表向下移动(scrollTop不断增大)
+     */
+    type ScrollDirection = 'top' | 'bottom';
   }
 
   namespace ZPagingParams {
@@ -266,7 +271,13 @@ declare interface ZPagingProps {
   /**
    * 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替。
    */
-  pagingStyle?: Record<string, any>
+  pagingStyle?: Partial<CSSStyleDeclaration>
+
+  /**
+   * 设置z-paging的class,优先级低于pagingStyle和height、width、maxWidth、bgColor
+   * @since 2.8.7
+   */
+  pagingClass?: string | string[] | Record<string, boolean>
 
   /**
    * z-paging的高度,优先级低于paging-style中设置的height
@@ -299,6 +310,21 @@ declare interface ZPagingProps {
    */
   watchTouchDirectionChange?: boolean
 
+  /**
+   * 是否监听列表滚动方向改变
+   * @default false
+   * @since 2.8.7
+   */
+  watchScrollDirectionChange?: boolean
+  
+  /**
+   * 是否只使用基础布局
+   * - 设置为true后将关闭mounted自动请求数据、关闭下拉刷新和滚动到底部加载更多,强制隐藏空数据图
+   * @default false
+   * @since 2.8.7
+   */
+  layoutOnly?: boolean
+
   /**
    * 调用complete后延迟处理的时间,单位为毫秒,优先级高于min-delay
    * @default 0
@@ -460,18 +486,18 @@ declare interface ZPagingProps {
   /**
    * 自定义下拉刷新中左侧图标的样式
    */
-  refresherImgStyle?: Record<string, any>
+  refresherImgStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 自定义下拉刷新中右侧状态描述文字的样式
    */
-  refresherTitleStyle?: Record<string, any>
+  refresherTitleStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 自定义下拉刷新中右侧最后更新时间文字的样式
    * - show-refresher-update-time为true时有效
    */
-  refresherUpdateTimeStyle?: Record<string, any>
+  refresherUpdateTimeStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 是否实时监听下拉刷新中进度,并通过@refresherTouchmove传递给父组件
@@ -712,18 +738,18 @@ declare interface ZPagingProps {
    * 自定义底部加载更多样式;如:{'background':'red'} 
    * - 此属性无法修改文字样式,修改文字样式请使用loading-more-title-custom-style
    */
-  loadingMoreCustomStyle?: Record<string, any>
+  loadingMoreCustomStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 自定义底部加载更多文字样式;如:{'color':'red'}
    * @since 2.1.7
    */
-  loadingMoreTitleCustomStyle?: Record<string, any>
+  loadingMoreTitleCustomStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 自定义底部加载更多加载中动画样式
    */
-  loadingMoreLoadingIconCustomStyle?: Record<string, any>
+  loadingMoreLoadingIconCustomStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 自定义底部加载更多加载中动画图标类型
@@ -826,7 +852,7 @@ declare interface ZPagingProps {
   /**
    * 自定义底部没有更多数据的分割线样式
    */
-  loadingMoreNoMoreLineCustomStyle?: Record<string, any>
+  loadingMoreNoMoreLineCustomStyle?: Partial<CSSStyleDeclaration>
 
   // ******************** 空数据与加载失败配置 ********************
   /**
@@ -889,28 +915,28 @@ declare interface ZPagingProps {
   /**
    * 空数据图父view样式
    */
-  emptyViewSuperStyle?: Record<string, any>
+  emptyViewSuperStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 空数据图样式,可设置空数据view的top等,如::empty-view-style="{'top':'100rpx'}"
    */
-  emptyViewStyle?: Record<string, any>
+  emptyViewStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 空数据图img样式
    */
-  emptyViewImgStyle?: Record<string, any>
+  emptyViewImgStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 空数据图描述文字样式
    */
-  emptyViewTitleStyle?: Record<string, any>
+  emptyViewTitleStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 空数据图重新加载按钮样式
    * @since 1.6.7
    */
-  emptyViewReloadStyle?: Record<string, any>
+  emptyViewReloadStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * 是否显示空数据图重新加载按钮(无数据时)
@@ -1014,7 +1040,7 @@ declare interface ZPagingProps {
   /**
    * 点击返回顶部按钮的自定义样式
    */
-  backToTopStyle?: Record<string, any>
+  backToTopStyle?: Partial<CSSStyleDeclaration>
 
   // ******************** 虚拟列表&内置列表配置 ********************
   /**
@@ -1090,13 +1116,21 @@ declare interface ZPagingProps {
   forceCloseInnerList?: boolean
 
   /**
-   * 虚拟列表是否使用swiper-item包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题
+   * 虚拟列表是否使用swiper-item或其他父组件包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题
    * - 仅vue3+(微信小程序或QQ小程序)+非内置列表写法虚拟列表有效,其他情况此属性设置任何值都无效,所以如果您在swiper-item内使用z-paging的非内置虚拟列表写法,将此属性设置为true即可
    * @default false
    * @since 2.8.6
    */
   virtualInSwiperSlot?: boolean
 
+  /**
+   * z-paging是否使用swiper-item或其他父组件包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,scrollIntoViewById和scrollIntoViewByIndex因无法获取节点信息导致滚动到指定view无效的问题
+   * - 仅vue3+(微信小程序或QQ小程序)且需要调用scrollIntoViewById或scrollIntoViewByIndex方法时有效,其他情况此属性设置任何值都无效
+   * @default false
+   * @since 2.8.8
+   */
+  inSwiperSlot?: boolean
+
   /**
    * 内置列表cell的key名称(仅nvue有效,在nvue中开启use-inner-list时必须填此项)
    * @since 2.2.7
@@ -1106,13 +1140,13 @@ declare interface ZPagingProps {
   /**
    * innerList样式
    */
-  innerListStyle?: Record<string, any>
+  innerListStyle?: Partial<CSSStyleDeclaration>
 
   /**
    * innerCell样式
    * @since 2.2.8
    */
-  innerCellStyle?: Record<string, any>
+  innerCellStyle?: Partial<CSSStyleDeclaration>
 
   // ******************** 本地分页配置 ********************
   /**
@@ -1541,6 +1575,15 @@ declare interface ZPagingProps {
    * @since 2.3.0
    */
   onTouchDirectionChange?: (direction: ZPagingEnums.TouchDirection) => void
+
+  /**
+   * 监听列表滚动方向改变
+   * - 页面滚动无效
+   * - 必须同时设置:watch-scroll-direction-change="true"
+   * @param direction 列表滚动的方向,top代表用户将列表向上移动(scrollTop不断减小),bottom代表用户将列表向下移动(scrollTop不断增大)
+   * @since 2.8.7
+   */
+  onScrollDirectionChange?: (direction: ZPagingEnums.ScrollDirection) => void
 }
 
 
@@ -1840,6 +1883,13 @@ declare interface _ZPagingRef<T = any> {
    * @since 2.6.1
    */
   updateCustomRefresherHeight: () => void;
+  
+  /**
+   * 手动进入二楼
+   *
+   * @since 2.8.7
+   */
+  goF2: () => void;
 
   /**
    * 手动关闭二楼
@@ -1954,6 +2004,13 @@ declare interface _ZPagingRef<T = any> {
    */
   addChatRecordData: (data: _Arrayable<T>, scrollToBottom?: boolean, animate?: boolean) => void;
 
+  /**
+   * 手动添加键盘高度变化监听
+   * 
+   * @since 2.8.7
+   */
+  addKeyboardHeightChangeListener: () => void;
+
   // ******************** 滚动到指定位置方法 ********************
   /**
    * 滚动到顶部
@@ -1971,7 +2028,6 @@ declare interface _ZPagingRef<T = any> {
 
   /**
    * 滚动到指定view
-   * - vue中有效,若此方法无效,请使用scrollIntoViewByNodeTop
    *
    * @param id 需要滚动到的view的id值,不包含"#"
    * @param [offset=0] 偏移量,单位为px