You've already forked template-MP-ts
Initial commit
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
// [z-paging]点击返回顶部view模块
|
||||
import u from '.././z-paging-utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 自动显示点击返回顶部按钮,默认为否
|
||||
autoShowBackToTop: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoShowBackToTop', false)
|
||||
},
|
||||
// 点击返回顶部按钮显示/隐藏的阈值(滚动距离),单位为px,默认为400rpx
|
||||
backToTopThreshold: {
|
||||
type: [Number, String],
|
||||
default: u.gc('backToTopThreshold', '400rpx')
|
||||
},
|
||||
// 点击返回顶部按钮的自定义图片地址,默认使用z-paging内置的图片
|
||||
backToTopImg: {
|
||||
type: String,
|
||||
default: u.gc('backToTopImg', '')
|
||||
},
|
||||
// 点击返回顶部按钮返回到顶部时是否展示过渡动画,默认为是
|
||||
backToTopWithAnimate: {
|
||||
type: Boolean,
|
||||
default: u.gc('backToTopWithAnimate', true)
|
||||
},
|
||||
// 点击返回顶部按钮与底部的距离,注意添加单位px或rpx,默认为160rpx
|
||||
backToTopBottom: {
|
||||
type: [Number, String],
|
||||
default: u.gc('backToTopBottom', '160rpx')
|
||||
},
|
||||
// 点击返回顶部按钮的自定义样式
|
||||
backToTopStyle: {
|
||||
type: Object,
|
||||
default: u.gc('backToTopStyle', {}),
|
||||
},
|
||||
// iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向,默认为是
|
||||
enableBackToTop: {
|
||||
type: Boolean,
|
||||
default: u.gc('enableBackToTop', true)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 点击返回顶部的class
|
||||
backToTopClass: 'zp-back-to-top zp-back-to-top-hide',
|
||||
// 上次点击返回顶部的时间
|
||||
lastBackToTopShowTime: 0,
|
||||
// 点击返回顶部显示的class是否在展示中,使得按钮展示/隐藏过度效果更自然
|
||||
showBackToTopClass: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
backToTopThresholdUnitConverted() {
|
||||
return u.addUnit(this.backToTopThreshold, this.unit);
|
||||
},
|
||||
backToTopBottomUnitConverted() {
|
||||
return u.addUnit(this.backToTopBottom, this.unit);
|
||||
},
|
||||
finalEnableBackToTop() {
|
||||
return this.usePageScroll ? false : this.enableBackToTop;
|
||||
},
|
||||
finalBackToTopThreshold() {
|
||||
return u.convertToPx(this.backToTopThresholdUnitConverted);
|
||||
},
|
||||
finalBackToTopStyle() {
|
||||
const backToTopStyle = this.backToTopStyle;
|
||||
if (!backToTopStyle.bottom) {
|
||||
backToTopStyle.bottom = this.windowBottom + u.convertToPx(this.backToTopBottomUnitConverted) + 'px';
|
||||
}
|
||||
if(!backToTopStyle.position){
|
||||
backToTopStyle.position = this.usePageScroll ? 'fixed': 'absolute';
|
||||
}
|
||||
return backToTopStyle;
|
||||
},
|
||||
finalBackToTopClass() {
|
||||
return `${this.backToTopClass} zp-back-to-top-${this.unit}`;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击了返回顶部
|
||||
_backToTopClick() {
|
||||
let callbacked = false;
|
||||
this.$emit('backToTopClick', toTop => {
|
||||
(toTop === undefined || toTop === true) && this._handleToTop();
|
||||
callbacked = true;
|
||||
});
|
||||
// 如果用户没有禁止默认的返回顶部事件,则触发滚动到顶部
|
||||
this.$nextTick(() => {
|
||||
!callbacked && this._handleToTop();
|
||||
})
|
||||
},
|
||||
// 处理滚动到顶部(聊天记录模式中为滚动到底部)
|
||||
_handleToTop() {
|
||||
!this.backToTopWithAnimate && this._checkShouldShowBackToTop(0);
|
||||
!this.useChatRecordMode ? this.scrollToTop(this.backToTopWithAnimate) : this.scrollToBottom(this.backToTopWithAnimate);
|
||||
},
|
||||
// 判断是否要显示返回顶部按钮
|
||||
_checkShouldShowBackToTop(scrollTop) {
|
||||
if (!this.autoShowBackToTop) {
|
||||
this.showBackToTopClass = false;
|
||||
return;
|
||||
}
|
||||
if (scrollTop > this.finalBackToTopThreshold) {
|
||||
if (!this.showBackToTopClass) {
|
||||
// 记录当前点击返回顶部按钮显示的class生效了
|
||||
this.showBackToTopClass = true;
|
||||
this.lastBackToTopShowTime = new Date().getTime();
|
||||
// 当滚动到需要展示返回顶部的阈值内,则延迟300毫秒展示返回到顶部按钮
|
||||
u.delay(() => {
|
||||
this.backToTopClass = 'zp-back-to-top zp-back-to-top-show';
|
||||
}, 300)
|
||||
}
|
||||
} else {
|
||||
// 如果当前点击返回顶部按钮显示的class是生效状态并且滚动小于触发阈值,则隐藏返回顶部按钮
|
||||
if (this.showBackToTopClass) {
|
||||
this.backToTopClass = 'zp-back-to-top zp-back-to-top-hide';
|
||||
u.delay(() => {
|
||||
this.showBackToTopClass = false;
|
||||
}, new Date().getTime() - this.lastBackToTopShowTime < 500 ? 0 : 300)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
// [z-paging]聊天记录模式模块
|
||||
import u from '.././z-paging-utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 使用聊天记录模式,默认为否
|
||||
useChatRecordMode: {
|
||||
type: Boolean,
|
||||
default: u.gc('useChatRecordMode', false)
|
||||
},
|
||||
// 使用聊天记录模式时滚动到顶部后,列表垂直移动偏移距离。默认0rpx。单位px(暂时无效)
|
||||
chatRecordMoreOffset: {
|
||||
type: [Number, String],
|
||||
default: u.gc('chatRecordMoreOffset', '0rpx')
|
||||
},
|
||||
// 使用聊天记录模式时是否自动隐藏键盘:在用户触摸列表时候自动隐藏键盘,默认为是
|
||||
autoHideKeyboardWhenChat: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoHideKeyboardWhenChat', true)
|
||||
},
|
||||
// 使用聊天记录模式中键盘弹出时是否自动调整slot="bottom"高度,默认为是
|
||||
autoAdjustPositionWhenChat: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoAdjustPositionWhenChat', true)
|
||||
},
|
||||
// 使用聊天记录模式中键盘弹出时占位高度偏移距离。默认0rpx。单位px
|
||||
chatAdjustPositionOffset: {
|
||||
type: [Number, String],
|
||||
default: u.gc('chatAdjustPositionOffset', '0rpx')
|
||||
},
|
||||
// 使用聊天记录模式中键盘弹出时是否自动滚动到底部,默认为否
|
||||
autoToBottomWhenChat: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoToBottomWhenChat', false)
|
||||
},
|
||||
// 使用聊天记录模式中reload时是否显示chatLoading,默认为否
|
||||
showChatLoadingWhenReload: {
|
||||
type: Boolean,
|
||||
default: u.gc('showChatLoadingWhenReload', false)
|
||||
},
|
||||
// 在聊天记录模式中滑动到顶部状态为默认状态时,以加载中的状态展示,默认为是。若设置为否,则默认会显示【点击加载更多】,然后才会显示loading
|
||||
chatLoadingMoreDefaultAsLoading: {
|
||||
type: Boolean,
|
||||
default: u.gc('chatLoadingMoreDefaultAsLoading', true)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
// 键盘高度
|
||||
keyboardHeight: 0,
|
||||
// 键盘高度是否未改变,此时占位高度变化不需要动画效果
|
||||
isKeyboardHeightChanged: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalChatRecordMoreOffset() {
|
||||
return u.convertToPx(this.chatRecordMoreOffset);
|
||||
},
|
||||
finalChatAdjustPositionOffset() {
|
||||
return u.convertToPx(this.chatAdjustPositionOffset);
|
||||
},
|
||||
// 聊天记录模式旋转180度style
|
||||
chatRecordRotateStyle() {
|
||||
let cellStyle;
|
||||
// 在vue中,直接将列表倒置,因此在vue的cell中,也直接写style="transform: scaleY(-1)"转回来即可。
|
||||
// #ifndef APP-NVUE
|
||||
cellStyle = this.useChatRecordMode ? { transform: 'scaleY(-1)' } : {};
|
||||
// #endif
|
||||
|
||||
// 在nvue中,需要考虑数据量不满一页的情况,因为nvue中的list无法通过flex-end修改不满一页的起始位置,会导致不满一页时列表数据从底部开始,因此需要特别判断
|
||||
// 当数据不满一屏的时候,不进行列表倒置
|
||||
// #ifdef APP-NVUE
|
||||
cellStyle = this.useChatRecordMode ? { transform: this.isFirstPageAndNoMore ? 'scaleY(1)' : 'scaleY(-1)' } : {};
|
||||
// #endif
|
||||
|
||||
this.$emit('update:cellStyle', cellStyle);
|
||||
this.$emit('cellStyleChange', cellStyle);
|
||||
|
||||
// 在聊天记录模式中,如果列表没有倒置并且当前是第一页,则需要自动滚动到最底部
|
||||
this.$nextTick(() => {
|
||||
if (this.isFirstPage && this.isChatRecordModeAndNotInversion) {
|
||||
this.$nextTick(() => {
|
||||
// 这里多次触发滚动到底部是为了避免在某些情况下,即使是在nextTick但是cell未渲染完毕导致滚动到底部位置不正确的问题
|
||||
this._scrollToBottom(false);
|
||||
u.delay(() => {
|
||||
this._scrollToBottom(false);
|
||||
u.delay(() => {
|
||||
this._scrollToBottom(false);
|
||||
}, 50)
|
||||
}, 50)
|
||||
})
|
||||
}
|
||||
})
|
||||
return cellStyle;
|
||||
},
|
||||
// 是否是聊天记录列表并且有配置transform
|
||||
isChatRecordModeHasTransform() {
|
||||
return this.useChatRecordMode && this.chatRecordRotateStyle && this.chatRecordRotateStyle.transform;
|
||||
},
|
||||
// 是否是聊天记录列表并且列表未倒置
|
||||
isChatRecordModeAndNotInversion() {
|
||||
return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(1)';
|
||||
},
|
||||
// 是否是聊天记录列表并且列表倒置
|
||||
isChatRecordModeAndInversion() {
|
||||
return this.isChatRecordModeHasTransform && this.chatRecordRotateStyle.transform === 'scaleY(-1)';
|
||||
},
|
||||
// 最终的聊天记录模式中底部安全区域的高度,如果开启了底部安全区域并且键盘未弹出,则添加底部区域高度
|
||||
chatRecordModeSafeAreaBottom() {
|
||||
return this.safeAreaInsetBottom && !this.keyboardHeight ? this.safeAreaBottom : 0;
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// 监听键盘高度变化(H5、百度小程序、抖音小程序、飞书小程序不支持)
|
||||
// #ifndef H5 || MP-BAIDU || MP-TOUTIAO
|
||||
if (this.useChatRecordMode) {
|
||||
uni.onKeyboardHeightChange(this._handleKeyboardHeightChange);
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// 添加聊天记录
|
||||
addChatRecordData(data, toBottom = true, toBottomWithAnimate = true) {
|
||||
if (!this.useChatRecordMode) return;
|
||||
this.isTotalChangeFromAddData = true;
|
||||
this.addDataFromTop(data, toBottom, toBottomWithAnimate);
|
||||
},
|
||||
// 手动触发滚动到顶部加载更多,聊天记录模式时有效
|
||||
doChatRecordLoadMore() {
|
||||
this.useChatRecordMode && this._onLoadingMore('click');
|
||||
},
|
||||
// 处理键盘高度变化
|
||||
_handleKeyboardHeightChange(res) {
|
||||
this.$emit('keyboardHeightChange', res);
|
||||
if (this.autoAdjustPositionWhenChat) {
|
||||
this.isKeyboardHeightChanged = true;
|
||||
this.keyboardHeight = res.height > 0 ? res.height + this.finalChatAdjustPositionOffset : res.height;
|
||||
}
|
||||
if (this.autoToBottomWhenChat && this.keyboardHeight > 0) {
|
||||
u.delay(() => {
|
||||
this.scrollToBottom(false);
|
||||
u.delay(() => {
|
||||
this.scrollToBottom(false);
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
// [z-paging]通用布局相关模块
|
||||
import u from '.././z-paging-utils'
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
const weexDom = weex.requireModule('dom');
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
systemInfo: null,
|
||||
cssSafeAreaInsetBottom: -1,
|
||||
isReadyDestroy: false,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 顶部可用距离
|
||||
windowTop() {
|
||||
if (!this.systemInfo) return 0;
|
||||
// 暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634
|
||||
// 感谢litangyu!!https://github.com/SmileZXLee/uni-z-paging/issues/25
|
||||
// #ifdef VUE3 && H5
|
||||
const pageHeadNode = document.getElementsByTagName("uni-page-head");
|
||||
if (!pageHeadNode.length) return 0;
|
||||
// #endif
|
||||
return this.systemInfo.windowTop || 0;
|
||||
},
|
||||
// 底部安全区域高度
|
||||
safeAreaBottom() {
|
||||
if (!this.systemInfo) return 0;
|
||||
let safeAreaBottom = 0;
|
||||
// #ifdef APP-PLUS
|
||||
safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0 ;
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
safeAreaBottom = Math.max(this.cssSafeAreaInsetBottom, 0);
|
||||
// #endif
|
||||
return safeAreaBottom;
|
||||
},
|
||||
// 是否是比较老的webview,在一些老的webview中,需要进行一些特殊处理
|
||||
isOldWebView() {
|
||||
// #ifndef APP-NVUE || MP-KUAISHOU
|
||||
try {
|
||||
const systemInfos = u.getSystemInfoSync(true).system.split(' ');
|
||||
const deviceType = systemInfos[0];
|
||||
const version = parseInt(systemInfos[1]);
|
||||
if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) {
|
||||
return true;
|
||||
}
|
||||
} catch(e) {
|
||||
return false;
|
||||
}
|
||||
// #endif
|
||||
return false;
|
||||
},
|
||||
// 当前组件的$slots,兼容不同平台
|
||||
zSlots() {
|
||||
// #ifdef VUE2
|
||||
|
||||
// #ifdef MP-ALIPAY
|
||||
return this.$slots;
|
||||
// #endif
|
||||
|
||||
return this.$scopedSlots || this.$slots;
|
||||
// #endif
|
||||
|
||||
return this.$slots;
|
||||
},
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.isReadyDestroy = true;
|
||||
},
|
||||
// #ifdef VUE3
|
||||
unmounted() {
|
||||
this.isReadyDestroy = true;
|
||||
},
|
||||
// #endif
|
||||
methods: {
|
||||
// 更新fixed模式下z-paging的布局
|
||||
updateFixedLayout() {
|
||||
this.fixed && this.$nextTick(() => {
|
||||
this.systemInfo = u.getSystemInfoSync();
|
||||
})
|
||||
},
|
||||
// 获取节点尺寸
|
||||
_getNodeClientRect(select, inDom = true, scrollOffset = false) {
|
||||
if (this.isReadyDestroy) {
|
||||
return Promise.resolve(false);
|
||||
};
|
||||
// nvue中获取节点信息
|
||||
// #ifdef APP-NVUE
|
||||
select = select.replace(/[.|#]/g, '');
|
||||
const ref = this.$refs[select];
|
||||
return new Promise((resolve, reject) => {
|
||||
if (ref) {
|
||||
weexDom.getComponentRect(ref, option => {
|
||||
resolve(option && option.result ? [option.size] : false);
|
||||
})
|
||||
} else {
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
return;
|
||||
// #endif
|
||||
|
||||
// vue中获取节点信息
|
||||
//#ifdef MP-ALIPAY
|
||||
inDom = false;
|
||||
//#endif
|
||||
|
||||
/*
|
||||
inDom可能是true、false,也可能是具体的dom节点
|
||||
如果inDom不为false,则使用uni.createSelectorQuery().in()进行查询,如果inDom为true,则in中的是this,否则in中的为具体的dom
|
||||
如果inDom为false,则使用uni.createSelectorQuery()进行查询
|
||||
*/
|
||||
let res = !!inDom ? uni.createSelectorQuery().in(inDom === true ? this : inDom) : uni.createSelectorQuery();
|
||||
scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect();
|
||||
return new Promise((resolve, reject) => {
|
||||
res.exec(data => {
|
||||
resolve((data && data != '' && data != undefined && data.length) ? data : false);
|
||||
});
|
||||
});
|
||||
},
|
||||
// 获取slot="left"和slot="right"宽度并且更新布局
|
||||
_updateLeftAndRightWidth(targetStyle, parentNodePrefix) {
|
||||
this.$nextTick(() => {
|
||||
let delayTime = 0;
|
||||
// #ifdef MP-BAIDU
|
||||
delayTime = 10;
|
||||
// #endif
|
||||
setTimeout(() => {
|
||||
['left','right'].map(position => {
|
||||
this._getNodeClientRect(`.${parentNodePrefix}-${position}`).then(res => {
|
||||
this.$set(targetStyle, position, res ? res[0].width + 'px' : '0px');
|
||||
});
|
||||
})
|
||||
}, delayTime)
|
||||
})
|
||||
},
|
||||
// 通过获取css设置的底部安全区域占位view高度设置bottom距离(直接通过systemInfo在部分平台上无法获取到底部安全区域)
|
||||
_getCssSafeAreaInsetBottom(success) {
|
||||
this._getNodeClientRect('.zp-safe-area-inset-bottom').then(res => {
|
||||
this.cssSafeAreaInsetBottom = res ? res[0].height : -1;
|
||||
res && success && success();
|
||||
});
|
||||
},
|
||||
// 同步获取系统信息,兼容不同平台(供z-paging-swiper使用)
|
||||
_getSystemInfoSync(useCache = false) {
|
||||
return u.getSystemInfoSync(useCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,736 @@
|
||||
// [z-paging]数据处理模块
|
||||
import u from '.././z-paging-utils'
|
||||
import c from '.././z-paging-constant'
|
||||
import Enum from '.././z-paging-enum'
|
||||
import interceptor from '../z-paging-interceptor'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 自定义初始的pageNo,默认为1
|
||||
defaultPageNo: {
|
||||
type: Number,
|
||||
default: u.gc('defaultPageNo', 1),
|
||||
observer: function(newVal) {
|
||||
this.pageNo = newVal;
|
||||
},
|
||||
},
|
||||
// 自定义pageSize,默认为10
|
||||
defaultPageSize: {
|
||||
type: Number,
|
||||
default: u.gc('defaultPageSize', 10),
|
||||
validator: (value) => {
|
||||
if (value <= 0) u.consoleErr('default-page-size必须大于0!');
|
||||
return value > 0;
|
||||
}
|
||||
},
|
||||
// 为保证数据一致,设置当前tab切换时的标识key,并在complete中传递相同key,若二者不一致,则complete将不会生效
|
||||
dataKey: {
|
||||
type: [Number, String, Object],
|
||||
default: u.gc('dataKey', null),
|
||||
},
|
||||
// 使用缓存,若开启将自动缓存第一页的数据,默认为否。请注意,因考虑到切换tab时不同tab数据不同的情况,默认仅会缓存组件首次加载时第一次请求到的数据,后续的下拉刷新操作不会更新缓存。
|
||||
useCache: {
|
||||
type: Boolean,
|
||||
default: u.gc('useCache', false)
|
||||
},
|
||||
// 使用缓存时缓存的key,用于区分不同列表的缓存数据,useCache为true时必须设置,否则缓存无效
|
||||
cacheKey: {
|
||||
type: String,
|
||||
default: u.gc('cacheKey', null)
|
||||
},
|
||||
// 缓存模式,默认仅会缓存组件首次加载时第一次请求到的数据,可设置为always,即代表总是缓存,每次列表刷新(下拉刷新、调用reload等)都会更新缓存
|
||||
cacheMode: {
|
||||
type: String,
|
||||
default: u.gc('cacheMode', Enum.CacheMode.Default)
|
||||
},
|
||||
// 自动注入的list名,可自动修改父view(包含ref="paging")中对应name的list值
|
||||
autowireListName: {
|
||||
type: String,
|
||||
default: u.gc('autowireListName', '')
|
||||
},
|
||||
// 自动注入的query名,可自动调用父view(包含ref="paging")中的query方法
|
||||
autowireQueryName: {
|
||||
type: String,
|
||||
default: u.gc('autowireQueryName', '')
|
||||
},
|
||||
// 获取分页数据Function,功能与@query类似。若设置了fetch则@query将不再触发
|
||||
fetch: {
|
||||
type: Function,
|
||||
default: null
|
||||
},
|
||||
// fetch的附加参数,fetch配置后有效
|
||||
fetchParams: {
|
||||
type: Object,
|
||||
default: u.gc('fetchParams', null)
|
||||
},
|
||||
// z-paging mounted后自动调用reload方法(mounted后自动调用接口),默认为是
|
||||
auto: {
|
||||
type: Boolean,
|
||||
default: u.gc('auto', true)
|
||||
},
|
||||
// 用户下拉刷新时是否触发reload方法,默认为是
|
||||
reloadWhenRefresh: {
|
||||
type: Boolean,
|
||||
default: u.gc('reloadWhenRefresh', true)
|
||||
},
|
||||
// reload时自动滚动到顶部,默认为是
|
||||
autoScrollToTopWhenReload: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoScrollToTopWhenReload', true)
|
||||
},
|
||||
// reload时立即自动清空原list,默认为是,若立即自动清空,则在reload之后、请求回调之前页面是空白的
|
||||
autoCleanListWhenReload: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoCleanListWhenReload', true)
|
||||
},
|
||||
// 列表刷新时自动显示下拉刷新view,默认为否
|
||||
showRefresherWhenReload: {
|
||||
type: Boolean,
|
||||
default: u.gc('showRefresherWhenReload', false)
|
||||
},
|
||||
// 列表刷新时自动显示加载更多view,且为加载中状态,默认为否
|
||||
showLoadingMoreWhenReload: {
|
||||
type: Boolean,
|
||||
default: u.gc('showLoadingMoreWhenReload', false)
|
||||
},
|
||||
// 组件created时立即触发reload(可解决一些情况下先看到页面再看到loading的问题),auto为true时有效。为否时将在mounted+nextTick后触发reload,默认为否
|
||||
createdReload: {
|
||||
type: Boolean,
|
||||
default: u.gc('createdReload', false)
|
||||
},
|
||||
// 本地分页时上拉加载更多延迟时间,单位为毫秒,默认200毫秒
|
||||
localPagingLoadingTime: {
|
||||
type: [Number, String],
|
||||
default: u.gc('localPagingLoadingTime', 200)
|
||||
},
|
||||
// 自动拼接complete中传过来的数组(使用聊天记录模式时无效)
|
||||
concat: {
|
||||
type: Boolean,
|
||||
default: u.gc('concat', true)
|
||||
},
|
||||
// 请求失败是否触发reject,默认为是
|
||||
callNetworkReject: {
|
||||
type: Boolean,
|
||||
default: u.gc('callNetworkReject', true)
|
||||
},
|
||||
// 父组件v-model所绑定的list的值
|
||||
value: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
type: Array,
|
||||
default: function() {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
data (){
|
||||
return {
|
||||
currentData: [],
|
||||
totalData: [],
|
||||
realTotalData: [],
|
||||
totalLocalPagingList: [],
|
||||
dataPromiseResultMap: {
|
||||
reload: null,
|
||||
complete: null,
|
||||
localPaging: null
|
||||
},
|
||||
isSettingCacheList: false,
|
||||
pageNo: 1,
|
||||
currentRefreshPageSize: 0,
|
||||
isLocalPaging: false,
|
||||
isAddedData: false,
|
||||
isTotalChangeFromAddData: false,
|
||||
privateConcat: true,
|
||||
myParentQuery: -1,
|
||||
firstPageLoaded: false,
|
||||
pagingLoaded: false,
|
||||
loaded: false,
|
||||
isUserReload: true,
|
||||
fromEmptyViewReload: false,
|
||||
queryFrom: '',
|
||||
listRendering: false,
|
||||
isHandlingRefreshToPage: false,
|
||||
isFirstPageAndNoMore: false,
|
||||
totalDataChangeThrow: true
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pageSize() {
|
||||
return this.defaultPageSize;
|
||||
},
|
||||
finalConcat() {
|
||||
return this.concat && this.privateConcat;
|
||||
},
|
||||
finalUseCache() {
|
||||
if (this.useCache && !this.cacheKey) {
|
||||
u.consoleErr('use-cache为true时,必须设置cache-key,否则缓存无效!');
|
||||
}
|
||||
return this.useCache && !!this.cacheKey;
|
||||
},
|
||||
finalCacheKey() {
|
||||
return this.cacheKey ? `${c.cachePrefixKey}-${this.cacheKey}` : null;
|
||||
},
|
||||
isFirstPage() {
|
||||
return this.pageNo === this.defaultPageNo;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
totalData(newVal, oldVal) {
|
||||
this._totalDataChange(newVal, oldVal, this.totalDataChangeThrow);
|
||||
this.totalDataChangeThrow = true;
|
||||
},
|
||||
currentData(newVal, oldVal) {
|
||||
this._currentDataChange(newVal, oldVal);
|
||||
},
|
||||
useChatRecordMode(newVal, oldVal) {
|
||||
if (newVal) {
|
||||
this.nLoadingMoreFixedHeight = false;
|
||||
}
|
||||
},
|
||||
value: {
|
||||
handler(newVal) {
|
||||
// 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用
|
||||
if (newVal !== this.totalData) {
|
||||
this.totalDataChangeThrow = false;
|
||||
this.totalData = newVal;
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// #ifdef VUE3
|
||||
modelValue: {
|
||||
handler(newVal) {
|
||||
// 当v-model绑定的数据源被更改时,此时数据源改变不emit input事件,避免循环调用
|
||||
if (newVal !== this.totalData) {
|
||||
this.totalDataChangeThrow = false;
|
||||
this.totalData = newVal;
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// 请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否成功(默认为是)
|
||||
complete(data, success = true) {
|
||||
this.customNoMore = -1;
|
||||
return this.addData(data, success);
|
||||
},
|
||||
//【保证数据一致】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为dataKey,需与:data-key绑定的一致,第三个参数为是否成功(默认为是)
|
||||
completeByKey(data, dataKey = null, success = true) {
|
||||
if (dataKey !== null && this.dataKey !== null && dataKey !== this.dataKey) {
|
||||
this.isFirstPage && this.endRefresh();
|
||||
return new Promise(resolve => resolve());
|
||||
}
|
||||
this.customNoMore = -1;
|
||||
return this.addData(data, success);
|
||||
},
|
||||
//【通过total判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为total(列表总数),第三个参数为是否成功(默认为是)
|
||||
completeByTotal(data, total, success = true) {
|
||||
if (total == 'undefined') {
|
||||
this.customNoMore = -1;
|
||||
} else {
|
||||
const dataTypeRes = this._checkDataType(data, success, false);
|
||||
data = dataTypeRes.data;
|
||||
success = dataTypeRes.success;
|
||||
if (total >= 0 && success) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.$nextTick(() => {
|
||||
let nomore = false;
|
||||
const realTotalDataCount = this.pageNo == this.defaultPageNo ? 0 : this.realTotalData.length;
|
||||
const dataLength = this.privateConcat ? data.length : 0;
|
||||
let exceedCount = realTotalDataCount + dataLength - total;
|
||||
// 没有更多数据了
|
||||
if (exceedCount >= 0) {
|
||||
nomore = true;
|
||||
// 仅截取total内部分的数据
|
||||
exceedCount = this.defaultPageSize - exceedCount;
|
||||
if (this.privateConcat && exceedCount > 0 && exceedCount < data.length) {
|
||||
data = data.splice(0, exceedCount);
|
||||
}
|
||||
}
|
||||
this.completeByNoMore(data, nomore, success).then(res => resolve(res)).catch(() => reject());
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
return this.addData(data, success);
|
||||
},
|
||||
//【自行判断是否有更多数据】请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging处理,第一个参数为请求结果数组,第二个参数为是否没有更多数据,第三个参数为是否成功(默认是是)
|
||||
completeByNoMore(data, nomore, success = true) {
|
||||
if (nomore != 'undefined') {
|
||||
this.customNoMore = nomore == true ? 1 : 0;
|
||||
}
|
||||
return this.addData(data, success);
|
||||
},
|
||||
// 请求结束且请求失败时调用,支持传入请求失败原因
|
||||
completeByError(errorMsg) {
|
||||
this.customerEmptyViewErrorText = errorMsg;
|
||||
return this.complete(false);
|
||||
},
|
||||
// 与上方complete方法功能一致,新版本中设置服务端回调数组请使用complete方法
|
||||
addData(data, success = true) {
|
||||
if (!this.fromCompleteEmit) {
|
||||
this.disabledCompleteEmit = true;
|
||||
this.fromCompleteEmit = false;
|
||||
}
|
||||
const currentTimeStamp = u.getTime();
|
||||
const disTime = currentTimeStamp - this.requestTimeStamp;
|
||||
let minDelay = this.minDelay;
|
||||
if (this.isFirstPage && this.finalShowRefresherWhenReload) {
|
||||
minDelay = Math.max(400, minDelay);
|
||||
}
|
||||
const addDataDalay = (this.requestTimeStamp > 0 && disTime < minDelay) ? minDelay - disTime : 0;
|
||||
this.$nextTick(() => {
|
||||
u.delay(() => {
|
||||
this._addData(data, success, false);
|
||||
}, this.delay > 0 ? this.delay : addDataDalay)
|
||||
})
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dataPromiseResultMap.complete = { resolve, reject };
|
||||
});
|
||||
},
|
||||
// 从顶部添加数据,不会影响分页的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));
|
||||
}
|
||||
},
|
||||
// 重新设置列表数据,调用此方法不会影响pageNo和pageSize,也不会触发请求。适用场景:当需要删除列表中某一项时,将删除对应项后的数组通过此方法传递给z-paging。(当出现类似的需要修改列表数组的场景时,请使用此方法,请勿直接修改page中:list.sync绑定的数组)
|
||||
resetTotalData(data) {
|
||||
this.isTotalChangeFromAddData = true;
|
||||
data = Object.prototype.toString.call(data) !== '[object Array]' ? [data] : data;
|
||||
this.totalData = data;
|
||||
},
|
||||
// 设置本地分页数据,请求结束(成功或者失败)调用此方法,将请求的结果传递给z-paging作分页处理(若调用了此方法,则上拉加载更多时内部会自动分页,不会触发@query所绑定的事件)
|
||||
setLocalPaging(data, success = true) {
|
||||
this.isLocalPaging = true;
|
||||
this.$nextTick(() => {
|
||||
this._addData(data, success, true);
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dataPromiseResultMap.localPaging = { resolve, reject };
|
||||
});
|
||||
},
|
||||
// 重新加载分页数据,pageNo会恢复为默认值,相当于下拉刷新的效果(animate为true时会展示下拉刷新动画,默认为false)
|
||||
reload(animate = this.showRefresherWhenReload) {
|
||||
if (animate) {
|
||||
this.privateShowRefresherWhenReload = animate;
|
||||
this.isUserPullDown = true;
|
||||
}
|
||||
if (!this.showLoadingMoreWhenReload) {
|
||||
this.listRendering = true;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this._preReload(animate, false);
|
||||
})
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dataPromiseResultMap.reload = { resolve, reject };
|
||||
});
|
||||
},
|
||||
// 刷新列表数据,pageNo和pageSize不会重置,列表数据会重新从服务端获取。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致
|
||||
refresh() {
|
||||
return this._handleRefreshWithDisPageNo(this.pageNo - this.defaultPageNo + 1);
|
||||
},
|
||||
// 刷新列表数据至指定页,例如pageNo=5时则代表刷新列表至第5页,此时pageNo会变为5,列表会展示前5页的数据。必须保证@query绑定的方法中的pageNo和pageSize和传给服务端的一致
|
||||
refreshToPage(pageNo) {
|
||||
this.isHandlingRefreshToPage = true;
|
||||
return this._handleRefreshWithDisPageNo(pageNo + this.defaultPageNo - 1);
|
||||
},
|
||||
// 手动更新列表缓存数据,将自动截取v-model绑定的list中的前pageSize条覆盖缓存,请确保在list数据更新到预期结果后再调用此方法
|
||||
updateCache() {
|
||||
if (this.finalUseCache && this.totalData.length) {
|
||||
this._saveLocalCache(this.totalData.slice(0, Math.min(this.totalData.length, this.pageSize)));
|
||||
}
|
||||
},
|
||||
// 清空分页数据
|
||||
clean() {
|
||||
this._reload(true);
|
||||
this._addData([], true, false);
|
||||
},
|
||||
// 清空分页数据
|
||||
clear() {
|
||||
this.clean();
|
||||
},
|
||||
// reload之前的一些处理
|
||||
_preReload(animate = this.showRefresherWhenReload, isFromMounted = true, retryCount = 0) {
|
||||
const showRefresher = this.finalRefresherEnabled && this.useCustomRefresher;
|
||||
// #ifndef APP-NVUE
|
||||
// 如果获取slot="refresher"高度失败,则不触发reload,直到获取slot="refresher"高度成功
|
||||
if (this.customRefresherHeight === -1 && showRefresher) {
|
||||
u.delay(() => {
|
||||
retryCount ++;
|
||||
// 如果重试次数是10的倍数(也就是每500毫秒),尝试重新获取一下slot="refresher"高度
|
||||
// 此举是为了解决在某些特殊情况下,z-paging组件mounted了,但是未展示在用户面前,(比如在tabbar页面中,未切换到对应tabbar但是通过代码让z-paging展示了,此时控制台会报Error: Not Found:Page,因为这时候去获取dom节点信息获取不到)
|
||||
// 当用户在某个时刻让此z-paging展示在面前时,即可顺利获取到slot="refresher"高度,递归停止
|
||||
if (retryCount % 10 === 0) {
|
||||
this._updateCustomRefresherHeight();
|
||||
}
|
||||
this._preReload(animate, isFromMounted, retryCount);
|
||||
}, c.delayTime / 2);
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
this.isUserReload = true;
|
||||
this.loadingType = Enum.LoadingType.Refresher;
|
||||
if (animate) {
|
||||
this.privateShowRefresherWhenReload = animate;
|
||||
// #ifndef APP-NVUE
|
||||
if (this.useCustomRefresher) {
|
||||
this._doRefresherRefreshAnimate();
|
||||
} else {
|
||||
this.refresherTriggered = true;
|
||||
}
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this.refresherStatus = Enum.Refresher.Loading;
|
||||
this.refresherRevealStackCount ++;
|
||||
u.delay(() => {
|
||||
this._getNodeClientRect('zp-n-refresh-container', false).then((node) => {
|
||||
if (node) {
|
||||
let nodeHeight = node[0].height;
|
||||
this.nShowRefresherReveal = true;
|
||||
this.nShowRefresherRevealHeight = nodeHeight;
|
||||
u.delay(() => {
|
||||
this._nDoRefresherEndAnimation(0, -nodeHeight, false, false);
|
||||
u.delay(() => {
|
||||
this._nDoRefresherEndAnimation(nodeHeight, 0);
|
||||
}, 10)
|
||||
}, 10)
|
||||
}
|
||||
this._reload(false, isFromMounted);
|
||||
this._doRefresherLoad(false);
|
||||
});
|
||||
}, this.pagingLoaded ? 10 : 100)
|
||||
return;
|
||||
// #endif
|
||||
} else {
|
||||
this._refresherEnd(false, false, false, false);
|
||||
}
|
||||
this._reload(false, isFromMounted);
|
||||
},
|
||||
// 重新加载分页数据
|
||||
_reload(isClean = false, isFromMounted = false, isUserPullDown = false) {
|
||||
this.isAddedData = false;
|
||||
this.insideOfPaging = -1;
|
||||
this.cacheScrollNodeHeight = -1;
|
||||
this.pageNo = this.defaultPageNo;
|
||||
this._cleanRefresherEndTimeout();
|
||||
!this.privateShowRefresherWhenReload && !isClean && this._startLoading(true);
|
||||
this.firstPageLoaded = true;
|
||||
this.isTotalChangeFromAddData = false;
|
||||
if (!this.isSettingCacheList) {
|
||||
this.totalData = [];
|
||||
}
|
||||
if (!isClean) {
|
||||
this._emitQuery(this.pageNo, this.defaultPageSize, isUserPullDown ? Enum.QueryFrom.UserPullDown : Enum.QueryFrom.Reload);
|
||||
let delay = 0;
|
||||
// #ifdef MP-TOUTIAO
|
||||
delay = 5;
|
||||
// #endif
|
||||
u.delay(this._callMyParentQuery, delay);
|
||||
if (!isFromMounted && this.autoScrollToTopWhenReload) {
|
||||
let checkedNRefresherLoading = true;
|
||||
// #ifdef APP-NVUE
|
||||
checkedNRefresherLoading = !this.nRefresherLoading;
|
||||
// #endif
|
||||
checkedNRefresherLoading && this._scrollToTop(false);
|
||||
}
|
||||
}
|
||||
// #ifdef APP-NVUE
|
||||
this.$nextTick(() => {
|
||||
this.nShowBottom = this.realTotalData.length > 0;
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
// 处理服务端返回的数组
|
||||
_addData(data, success, isLocal) {
|
||||
this.isAddedData = true;
|
||||
this.fromEmptyViewReload = false;
|
||||
this.isTotalChangeFromAddData = true;
|
||||
this.refresherTriggered = false;
|
||||
this._endSystemLoadingAndRefresh();
|
||||
const tempIsUserPullDown = this.isUserPullDown;
|
||||
if (this.showRefresherUpdateTime && this.isFirstPage) {
|
||||
u.setRefesrherTime(u.getTime(), this.refresherUpdateTimeKey);
|
||||
this.$refs.refresh && this.$refs.refresh.updateTime();
|
||||
}
|
||||
if (!isLocal && tempIsUserPullDown && this.isFirstPage) {
|
||||
this.isUserPullDown = false;
|
||||
}
|
||||
this.listRendering = true;
|
||||
this.$nextTick(() => {
|
||||
u.delay(() => this.listRendering = false);
|
||||
})
|
||||
let dataTypeRes = this._checkDataType(data, success, isLocal);
|
||||
data = dataTypeRes.data;
|
||||
success = dataTypeRes.success;
|
||||
let delayTime = c.delayTime;
|
||||
if (this.useChatRecordMode) delayTime = 0;
|
||||
this.loadingForNow = false;
|
||||
u.delay(() => {
|
||||
this.pagingLoaded = true;
|
||||
this.$nextTick(()=>{
|
||||
!isLocal && this._refresherEnd(delayTime > 0, true, tempIsUserPullDown);
|
||||
})
|
||||
})
|
||||
if (this.isFirstPage) {
|
||||
this.isLoadFailed = !success;
|
||||
this.$emit('isLoadFailedChange', this.isLoadFailed);
|
||||
if (this.finalUseCache && success && (this.cacheMode === Enum.CacheMode.Always ? true : this.isSettingCacheList)) {
|
||||
this._saveLocalCache(data);
|
||||
}
|
||||
}
|
||||
this.isSettingCacheList = false;
|
||||
if (success) {
|
||||
if (!(this.privateConcat === false && !this.isHandlingRefreshToPage && this.loadingStatus === Enum.More.NoMore)) {
|
||||
this.loadingStatus = Enum.More.Default;
|
||||
}
|
||||
if (isLocal) {
|
||||
// 如果当前是本地分页,则必然是由setLocalPaging方法触发,此时直接本地加载第一页数据即可。后续本地分页加载更多方法由滚动到底部加载更多事件处理
|
||||
this.totalLocalPagingList = data;
|
||||
const localPageNo = this.defaultPageNo;
|
||||
const localPageSize = this.queryFrom !== Enum.QueryFrom.Refresh ? this.defaultPageSize : this.currentRefreshPageSize;
|
||||
this._localPagingQueryList(localPageNo, localPageSize, 0, res => {
|
||||
u.delay(() => {
|
||||
this.completeByTotal(res, this.totalLocalPagingList.length);;
|
||||
}, 0)
|
||||
})
|
||||
} else {
|
||||
// 如果当前不是本地分页,则按照正常分页逻辑进行数据处理&emit数据
|
||||
let dataChangeDelayTime = 0;
|
||||
// #ifdef APP-NVUE
|
||||
if (this.privateShowRefresherWhenReload && this.finalNvueListIs === 'waterfall') {
|
||||
dataChangeDelayTime = 150;
|
||||
}
|
||||
// #endif
|
||||
u.delay(() => {
|
||||
this._currentDataChange(data, this.currentData);
|
||||
this._callDataPromise(true, this.totalData);
|
||||
}, dataChangeDelayTime)
|
||||
}
|
||||
if (this.isHandlingRefreshToPage) {
|
||||
this.isHandlingRefreshToPage = false;
|
||||
this.pageNo = this.defaultPageNo + Math.ceil(data.length / this.pageSize) - 1;
|
||||
if (data.length % this.pageSize !== 0) {
|
||||
this.customNoMore = 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._currentDataChange(data, this.currentData);
|
||||
this._callDataPromise(false);
|
||||
this.loadingStatus = Enum.More.Fail;
|
||||
this.isHandlingRefreshToPage = false;
|
||||
if (this.loadingType === Enum.LoadingType.LoadMore) {
|
||||
this.pageNo --;
|
||||
}
|
||||
}
|
||||
},
|
||||
// 所有数据改变时调用
|
||||
_totalDataChange(newVal, oldVal, eventThrow=true) {
|
||||
if ((!this.isUserReload || !this.autoCleanListWhenReload) && this.firstPageLoaded && !newVal.length && oldVal.length) {
|
||||
return;
|
||||
}
|
||||
this._doCheckScrollViewShouldFullHeight(newVal);
|
||||
if(!this.realTotalData.length && !newVal.length){
|
||||
eventThrow = false;
|
||||
}
|
||||
this.realTotalData = newVal;
|
||||
// emit列表更新事件
|
||||
if (eventThrow) {
|
||||
this.$emit('input', newVal);
|
||||
// #ifdef VUE3
|
||||
this.$emit('update:modelValue', newVal);
|
||||
// #endif
|
||||
this.$emit('update:list', newVal);
|
||||
this.$emit('listChange', newVal);
|
||||
this._callMyParentList(newVal);
|
||||
}
|
||||
this.firstPageLoaded = false;
|
||||
this.isTotalChangeFromAddData = false;
|
||||
this.$nextTick(() => {
|
||||
u.delay(()=>{
|
||||
// emit z-paging内容区域高度改变事件
|
||||
this._getNodeClientRect('.zp-paging-container-content').then(res => {
|
||||
res && this.$emit('contentHeightChanged', res[0].height);
|
||||
});
|
||||
}, c.delayTime * (this.isIos ? 1 : 3))
|
||||
// #ifdef APP-NVUE
|
||||
// 在nvue中延时600毫秒展示底部加载更多,避免底部加载更多太早加载闪一下的问题
|
||||
u.delay(() => {
|
||||
this.nShowBottom = true;
|
||||
}, c.delayTime * 6, 'nShowBottomDelay');
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
// 当前数据改变时调用
|
||||
_currentDataChange(newVal, oldVal) {
|
||||
newVal = [...newVal];
|
||||
// #ifndef APP-NVUE
|
||||
this.finalUseVirtualList && this._setCellIndex(newVal, 'bottom');
|
||||
// #endif
|
||||
if (this.isFirstPage && this.finalConcat) {
|
||||
this.totalData = [];
|
||||
}
|
||||
// customNoMore:-1代表交由z-paging自行判断;1代表没有更多了;0代表还有更多数据
|
||||
if (this.customNoMore !== -1) {
|
||||
// 如果customNoMore等于1 或者 customNoMore不是0并且新增数组长度为0(也就是不是明确的还有更多数据并且新增的数组长度为0),则没有更多数据了
|
||||
if (this.customNoMore === 1 || (this.customNoMore !== 0 && !newVal.length)) {
|
||||
this.loadingStatus = Enum.More.NoMore;
|
||||
}
|
||||
} else {
|
||||
// 如果新增的数据数组长度为0 或者 新增的数组长度小于默认的pageSize,则没有更多数据了
|
||||
if (!newVal.length || (newVal.length && newVal.length < this.defaultPageSize)) {
|
||||
this.loadingStatus = Enum.More.NoMore;
|
||||
}
|
||||
}
|
||||
if (!this.totalData.length) {
|
||||
// #ifdef APP-NVUE
|
||||
// 如果在聊天记录模式+nvue中,并且数据不满一页时需要将列表倒序,因为此时没有将列表旋转180度,数组中第0条数据应当在最底下显示
|
||||
if (this.useChatRecordMode && this.finalConcat && this.isFirstPage && this.loadingStatus === Enum.More.NoMore) {
|
||||
newVal.reverse();
|
||||
}
|
||||
// #endif
|
||||
this.totalData = newVal;
|
||||
} else {
|
||||
if (this.finalConcat) {
|
||||
const currentScrollTop = this.oldScrollTop;
|
||||
this.totalData = [...this.totalData, ...newVal];
|
||||
// 此处是为了解决在微信小程序中,在某些情况下滚动到底部加载更多后滚动位置直接变为最底部的问题,因此需要通过代码强制滚动回加载更多前的位置
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!this.isIos && !this.refresherOnly && !this.usePageScroll && newVal.length) {
|
||||
this.loadingMoreTimeStamp = u.getTime();
|
||||
this.$nextTick(() => {
|
||||
this.scrollToY(currentScrollTop);
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
} else {
|
||||
this.totalData = newVal;
|
||||
}
|
||||
}
|
||||
this.privateConcat = true;
|
||||
},
|
||||
// 根据pageNo处理refresh操作
|
||||
_handleRefreshWithDisPageNo(pageNo) {
|
||||
if (!this.isHandlingRefreshToPage && !this.realTotalData.length) return this.reload();
|
||||
if (pageNo >= 1) {
|
||||
this.loading = true;
|
||||
this.privateConcat = false;
|
||||
const totalPageSize = pageNo * this.pageSize;
|
||||
this.currentRefreshPageSize = totalPageSize;
|
||||
// 如果调用refresh时是本地分页,则在组件内部自己处理分页逻辑,不emit query相关事件
|
||||
if (this.isLocalPaging && this.isHandlingRefreshToPage) {
|
||||
this._localPagingQueryList(this.defaultPageNo, totalPageSize, 0, res => {
|
||||
this.complete(res);
|
||||
})
|
||||
} else {
|
||||
// emit query相关事件
|
||||
this._emitQuery(this.defaultPageNo, totalPageSize, Enum.QueryFrom.Refresh);
|
||||
this._callMyParentQuery(this.defaultPageNo, totalPageSize);
|
||||
}
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dataPromiseResultMap.reload = { resolve, reject };
|
||||
});
|
||||
},
|
||||
// 本地分页请求
|
||||
_localPagingQueryList(pageNo, pageSize, localPagingLoadingTime, callback) {
|
||||
pageNo = Math.max(1, pageNo);
|
||||
pageSize = Math.max(1, pageSize);
|
||||
const totalPagingList = [...this.totalLocalPagingList];
|
||||
const pageNoIndex = (pageNo - 1) * pageSize;
|
||||
const finalPageNoIndex = Math.min(totalPagingList.length, pageNoIndex + pageSize);
|
||||
const resultPagingList = totalPagingList.splice(pageNoIndex, finalPageNoIndex - pageNoIndex);
|
||||
u.delay(() => callback(resultPagingList), localPagingLoadingTime)
|
||||
},
|
||||
// 存储列表缓存数据
|
||||
_saveLocalCache(data) {
|
||||
uni.setStorageSync(this.finalCacheKey, data);
|
||||
},
|
||||
// 通过缓存数据填充列表数据
|
||||
_setListByLocalCache() {
|
||||
this.totalData = uni.getStorageSync(this.finalCacheKey) || [];
|
||||
this.isSettingCacheList = true;
|
||||
},
|
||||
// 修改父view的list
|
||||
_callMyParentList(newVal) {
|
||||
if (this.autowireListName.length) {
|
||||
const myParent = u.getParent(this.$parent);
|
||||
if (myParent && myParent[this.autowireListName]) {
|
||||
myParent[this.autowireListName] = newVal;
|
||||
}
|
||||
}
|
||||
},
|
||||
// 调用父view的query
|
||||
_callMyParentQuery(customPageNo = 0, customPageSize = 0) {
|
||||
if (this.autowireQueryName) {
|
||||
if (this.myParentQuery === -1) {
|
||||
const myParent = u.getParent(this.$parent);
|
||||
if (myParent && myParent[this.autowireQueryName]) {
|
||||
this.myParentQuery = myParent[this.autowireQueryName];
|
||||
}
|
||||
}
|
||||
if (this.myParentQuery !== -1) {
|
||||
customPageSize > 0 ? this.myParentQuery(customPageNo, customPageSize) : this.myParentQuery(this.pageNo, this.defaultPageSize);
|
||||
}
|
||||
}
|
||||
},
|
||||
// emit query事件
|
||||
_emitQuery(pageNo, pageSize, from){
|
||||
this.queryFrom = from;
|
||||
this.requestTimeStamp = u.getTime();
|
||||
const [lastItem] = this.realTotalData.slice(-1);
|
||||
if (this.fetch) {
|
||||
const fetchParams = interceptor._handleFetchParams({pageNo, pageSize, from, lastItem: lastItem || null}, this.fetchParams);
|
||||
const fetchResult = this.fetch(fetchParams);
|
||||
if (!interceptor._handleFetchResult(fetchResult, this, fetchParams)) {
|
||||
u.isPromise(fetchResult) ? fetchResult.then(res => {
|
||||
this.complete(res);
|
||||
}).catch(err => {
|
||||
this.complete(false);
|
||||
}) : this.complete(fetchResult)
|
||||
}
|
||||
} else {
|
||||
this.$emit('query', ...interceptor._handleQuery(pageNo, pageSize, from, lastItem || null));
|
||||
}
|
||||
},
|
||||
// 触发数据改变promise
|
||||
_callDataPromise(success, totalList) {
|
||||
for (const key in this.dataPromiseResultMap) {
|
||||
const obj = this.dataPromiseResultMap[key];
|
||||
if (!obj) continue;
|
||||
success ? obj.resolve({ totalList, noMore: this.loadingStatus === Enum.More.NoMore }) : this.callNetworkReject && obj.reject(`z-paging-${key}-error`);
|
||||
}
|
||||
},
|
||||
// 检查complete data的类型
|
||||
_checkDataType(data, success, isLocal) {
|
||||
const dataType = Object.prototype.toString.call(data);
|
||||
if (dataType === '[object Boolean]') {
|
||||
success = data;
|
||||
data = [];
|
||||
} else if (dataType !== '[object Array]') {
|
||||
data = [];
|
||||
if (dataType !== '[object Undefined]' && dataType !== '[object Null]') {
|
||||
u.consoleErr(`${isLocal ? 'setLocalPaging' : 'complete'}参数类型不正确,第一个参数类型必须为Array!`);
|
||||
}
|
||||
}
|
||||
return { data, success };
|
||||
},
|
||||
}
|
||||
}
|
||||
144
uni_modules/z-paging/components/z-paging/js/modules/empty.js
Normal file
144
uni_modules/z-paging/components/z-paging/js/modules/empty.js
Normal file
@@ -0,0 +1,144 @@
|
||||
// [z-paging]空数据图view模块
|
||||
import u from '.././z-paging-utils'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 是否强制隐藏空数据图,默认为否
|
||||
hideEmptyView: {
|
||||
type: Boolean,
|
||||
default: u.gc('hideEmptyView', false)
|
||||
},
|
||||
// 空数据图描述文字,默认为“没有数据哦~”
|
||||
emptyViewText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('emptyViewText', null)
|
||||
},
|
||||
// 是否显示空数据图重新加载按钮(无数据时),默认为否
|
||||
showEmptyViewReload: {
|
||||
type: Boolean,
|
||||
default: u.gc('showEmptyViewReload', false)
|
||||
},
|
||||
// 加载失败时是否显示空数据图重新加载按钮,默认为是
|
||||
showEmptyViewReloadWhenError: {
|
||||
type: Boolean,
|
||||
default: u.gc('showEmptyViewReloadWhenError', true)
|
||||
},
|
||||
// 空数据图点击重新加载文字,默认为“重新加载”
|
||||
emptyViewReloadText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('emptyViewReloadText', null)
|
||||
},
|
||||
// 空数据图图片,默认使用z-paging内置的图片
|
||||
emptyViewImg: {
|
||||
type: String,
|
||||
default: u.gc('emptyViewImg', '')
|
||||
},
|
||||
// 空数据图“加载失败”描述文字,默认为“很抱歉,加载失败”
|
||||
emptyViewErrorText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('emptyViewErrorText', null)
|
||||
},
|
||||
// 空数据图“加载失败”图片,默认使用z-paging内置的图片
|
||||
emptyViewErrorImg: {
|
||||
type: String,
|
||||
default: u.gc('emptyViewErrorImg', '')
|
||||
},
|
||||
// 空数据图样式
|
||||
emptyViewStyle: {
|
||||
type: Object,
|
||||
default: u.gc('emptyViewStyle', {})
|
||||
},
|
||||
// 空数据图容器样式
|
||||
emptyViewSuperStyle: {
|
||||
type: Object,
|
||||
default: u.gc('emptyViewSuperStyle', {})
|
||||
},
|
||||
// 空数据图img样式
|
||||
emptyViewImgStyle: {
|
||||
type: Object,
|
||||
default: u.gc('emptyViewImgStyle', {})
|
||||
},
|
||||
// 空数据图描述文字样式
|
||||
emptyViewTitleStyle: {
|
||||
type: Object,
|
||||
default: u.gc('emptyViewTitleStyle', {})
|
||||
},
|
||||
// 空数据图重新加载按钮样式
|
||||
emptyViewReloadStyle: {
|
||||
type: Object,
|
||||
default: u.gc('emptyViewReloadStyle', {})
|
||||
},
|
||||
// 空数据图片是否铺满z-paging,默认为否,即填充满z-paging内列表(滚动区域)部分。若设置为否,则为填铺满整个z-paging
|
||||
emptyViewFixed: {
|
||||
type: Boolean,
|
||||
default: u.gc('emptyViewFixed', false)
|
||||
},
|
||||
// 空数据图片是否垂直居中,默认为是,若设置为否即为从空数据容器顶部开始显示。emptyViewFixed为false时有效
|
||||
emptyViewCenter: {
|
||||
type: Boolean,
|
||||
default: u.gc('emptyViewCenter', true)
|
||||
},
|
||||
// 加载中时是否自动隐藏空数据图,默认为是
|
||||
autoHideEmptyViewWhenLoading: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoHideEmptyViewWhenLoading', true)
|
||||
},
|
||||
// 用户下拉列表触发下拉刷新加载中时是否自动隐藏空数据图,默认为是
|
||||
autoHideEmptyViewWhenPull: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoHideEmptyViewWhenPull', true)
|
||||
},
|
||||
// 空数据view的z-index,默认为9
|
||||
emptyViewZIndex: {
|
||||
type: Number,
|
||||
default: u.gc('emptyViewZIndex', 9)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
customerEmptyViewErrorText: ''
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalEmptyViewImg() {
|
||||
return this.isLoadFailed ? this.emptyViewErrorImg : this.emptyViewImg;
|
||||
},
|
||||
finalShowEmptyViewReload() {
|
||||
return this.isLoadFailed ? this.showEmptyViewReloadWhenError : this.showEmptyViewReload;
|
||||
},
|
||||
// 是否展示空数据图
|
||||
showEmpty() {
|
||||
if (this.refresherOnly || this.hideEmptyView || this.realTotalData.length) return false;
|
||||
if (this.autoHideEmptyViewWhenLoading) {
|
||||
if (this.isAddedData && !this.firstPageLoaded && !this.loading) return true;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return !this.autoHideEmptyViewWhenPull && !this.isUserReload;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 点击了空数据view重新加载按钮
|
||||
_emptyViewReload() {
|
||||
let callbacked = false;
|
||||
this.$emit('emptyViewReload', reload => {
|
||||
if (reload === undefined || reload === true) {
|
||||
this.fromEmptyViewReload = true;
|
||||
this.reload().catch(() => {});
|
||||
}
|
||||
callbacked = true;
|
||||
});
|
||||
// 如果用户没有禁止默认的点击重新加载刷新列表事件,则触发列表重新刷新
|
||||
this.$nextTick(() => {
|
||||
if (!callbacked) {
|
||||
this.fromEmptyViewReload = true;
|
||||
this.reload().catch(() => {});
|
||||
}
|
||||
})
|
||||
},
|
||||
// 点击了空数据view
|
||||
_emptyViewClick() {
|
||||
this.$emit('emptyViewClick');
|
||||
},
|
||||
}
|
||||
}
|
||||
113
uni_modules/z-paging/components/z-paging/js/modules/i18n.js
Normal file
113
uni_modules/z-paging/components/z-paging/js/modules/i18n.js
Normal file
@@ -0,0 +1,113 @@
|
||||
// [z-paging]i18n模块
|
||||
import { initVueI18n } from '@dcloudio/uni-i18n'
|
||||
import messages from '../../i18n/index.js'
|
||||
const { t } = initVueI18n(messages)
|
||||
|
||||
import u from '.././z-paging-utils'
|
||||
import c from '.././z-paging-constant'
|
||||
import interceptor from '../z-paging-interceptor'
|
||||
|
||||
export default {
|
||||
computed: {
|
||||
finalLanguage() {
|
||||
try {
|
||||
const local = uni.getLocale();
|
||||
const language = this.systemInfo.appLanguage;
|
||||
return local === 'auto' ? interceptor._handleLanguage2Local(language, this._language2Local(language)) : local;
|
||||
} catch (e) {
|
||||
// 如果获取系统本地语言异常,则默认返回中文,uni.getLocale在部分低版本HX或者cli中可能报找不到的问题
|
||||
return 'zh-Hans';
|
||||
}
|
||||
},
|
||||
// 最终的下拉刷新默认状态的文字
|
||||
finalRefresherDefaultText() {
|
||||
return this._getI18nText('zp.refresher.default', this.refresherDefaultText);
|
||||
},
|
||||
// 最终的下拉刷新下拉中的文字
|
||||
finalRefresherPullingText() {
|
||||
return this._getI18nText('zp.refresher.pulling', this.refresherPullingText);
|
||||
},
|
||||
// 最终的下拉刷新中文字
|
||||
finalRefresherRefreshingText() {
|
||||
return this._getI18nText('zp.refresher.refreshing', this.refresherRefreshingText);
|
||||
},
|
||||
// 最终的下拉刷新完成文字
|
||||
finalRefresherCompleteText() {
|
||||
return this._getI18nText('zp.refresher.complete', this.refresherCompleteText);
|
||||
},
|
||||
// 最终的下拉刷新上次更新时间文字
|
||||
finalRefresherUpdateTimeTextMap() {
|
||||
return {
|
||||
title: t('zp.refresherUpdateTime.title'),
|
||||
none: t('zp.refresherUpdateTime.none'),
|
||||
today: t('zp.refresherUpdateTime.today'),
|
||||
yesterday: t('zp.refresherUpdateTime.yesterday')
|
||||
};
|
||||
},
|
||||
// 最终的继续下拉进入二楼文字
|
||||
finalRefresherGoF2Text() {
|
||||
return this._getI18nText('zp.refresher.f2', this.refresherGoF2Text);
|
||||
},
|
||||
// 最终的底部加载更多默认状态文字
|
||||
finalLoadingMoreDefaultText() {
|
||||
return this._getI18nText('zp.loadingMore.default', this.loadingMoreDefaultText);
|
||||
},
|
||||
// 最终的底部加载更多加载中文字
|
||||
finalLoadingMoreLoadingText() {
|
||||
return this._getI18nText('zp.loadingMore.loading', this.loadingMoreLoadingText);
|
||||
},
|
||||
// 最终的底部加载更多没有更多数据文字
|
||||
finalLoadingMoreNoMoreText() {
|
||||
return this._getI18nText('zp.loadingMore.noMore', this.loadingMoreNoMoreText);
|
||||
},
|
||||
// 最终的底部加载更多加载失败文字
|
||||
finalLoadingMoreFailText() {
|
||||
return this._getI18nText('zp.loadingMore.fail', this.loadingMoreFailText);
|
||||
},
|
||||
// 最终的空数据图title
|
||||
finalEmptyViewText() {
|
||||
return this.isLoadFailed ? this.finalEmptyViewErrorText : this._getI18nText('zp.emptyView.title', this.emptyViewText);
|
||||
},
|
||||
// 最终的空数据图reload title
|
||||
finalEmptyViewReloadText() {
|
||||
return this._getI18nText('zp.emptyView.reload', this.emptyViewReloadText);
|
||||
},
|
||||
// 最终的空数据图加载失败文字
|
||||
finalEmptyViewErrorText() {
|
||||
return this.customerEmptyViewErrorText || this._getI18nText('zp.emptyView.error', this.emptyViewErrorText);
|
||||
},
|
||||
// 最终的系统loading title
|
||||
finalSystemLoadingText() {
|
||||
return this._getI18nText('zp.systemLoading.title', this.systemLoadingText);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 获取当前z-paging的语言
|
||||
getLanguage() {
|
||||
return this.finalLanguage;
|
||||
},
|
||||
// 获取国际化转换后的文本
|
||||
_getI18nText(key, value) {
|
||||
const dataType = Object.prototype.toString.call(value);
|
||||
if (dataType === '[object Object]') {
|
||||
const nextValue = value[this.finalLanguage];
|
||||
if (nextValue) return nextValue;
|
||||
} else if (dataType === '[object String]') {
|
||||
return value;
|
||||
}
|
||||
return t(key);
|
||||
},
|
||||
// 系统language转i18n local
|
||||
_language2Local(language) {
|
||||
const formatedLanguage = language.toLowerCase().replace(new RegExp('_', ''), '-');
|
||||
if (formatedLanguage.indexOf('zh') !== -1) {
|
||||
if (formatedLanguage === 'zh' || formatedLanguage === 'zh-cn' || formatedLanguage.indexOf('zh-hans') !== -1) {
|
||||
return 'zh-Hans';
|
||||
}
|
||||
return 'zh-Hant';
|
||||
}
|
||||
if (formatedLanguage.indexOf('en') !== -1) return 'en';
|
||||
return language;
|
||||
}
|
||||
}
|
||||
}
|
||||
374
uni_modules/z-paging/components/z-paging/js/modules/load-more.js
Normal file
374
uni_modules/z-paging/components/z-paging/js/modules/load-more.js
Normal file
@@ -0,0 +1,374 @@
|
||||
// [z-paging]滚动到底部加载更多模块
|
||||
import u from '.././z-paging-utils'
|
||||
import Enum from '.././z-paging-enum'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 自定义底部加载更多样式
|
||||
loadingMoreCustomStyle: {
|
||||
type: Object,
|
||||
default: u.gc('loadingMoreCustomStyle', {})
|
||||
},
|
||||
// 自定义底部加载更多文字样式
|
||||
loadingMoreTitleCustomStyle: {
|
||||
type: Object,
|
||||
default: u.gc('loadingMoreTitleCustomStyle', {})
|
||||
},
|
||||
// 自定义底部加载更多加载中动画样式
|
||||
loadingMoreLoadingIconCustomStyle: {
|
||||
type: Object,
|
||||
default: u.gc('loadingMoreLoadingIconCustomStyle', {})
|
||||
},
|
||||
// 自定义底部加载更多加载中动画图标类型,可选flower或circle,默认为flower
|
||||
loadingMoreLoadingIconType: {
|
||||
type: String,
|
||||
default: u.gc('loadingMoreLoadingIconType', 'flower')
|
||||
},
|
||||
// 自定义底部加载更多加载中动画图标图片
|
||||
loadingMoreLoadingIconCustomImage: {
|
||||
type: String,
|
||||
default: u.gc('loadingMoreLoadingIconCustomImage', '')
|
||||
},
|
||||
// 底部加载更多加载中view是否展示旋转动画,默认为是
|
||||
loadingMoreLoadingAnimated: {
|
||||
type: Boolean,
|
||||
default: u.gc('loadingMoreLoadingAnimated', true)
|
||||
},
|
||||
// 是否启用加载更多数据(含滑动到底部加载更多数据和点击加载更多数据),默认为是
|
||||
loadingMoreEnabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('loadingMoreEnabled', true)
|
||||
},
|
||||
// 是否启用滑动到底部加载更多数据,默认为是
|
||||
toBottomLoadingMoreEnabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('toBottomLoadingMoreEnabled', true)
|
||||
},
|
||||
// 滑动到底部状态为默认状态时,以加载中的状态展示,默认为否。若设置为是,可避免滚动到底部看到默认状态然后立刻变为加载中状态的问题,但分页数量未超过一屏时,不会显示【点击加载更多】
|
||||
loadingMoreDefaultAsLoading: {
|
||||
type: Boolean,
|
||||
default: u.gc('loadingMoreDefaultAsLoading', false)
|
||||
},
|
||||
// 滑动到底部"默认"文字,默认为【点击加载更多】
|
||||
loadingMoreDefaultText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('loadingMoreDefaultText', null)
|
||||
},
|
||||
// 滑动到底部"加载中"文字,默认为【正在加载...】
|
||||
loadingMoreLoadingText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('loadingMoreLoadingText', null)
|
||||
},
|
||||
// 滑动到底部"没有更多"文字,默认为【没有更多了】
|
||||
loadingMoreNoMoreText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('loadingMoreNoMoreText', null)
|
||||
},
|
||||
// 滑动到底部"加载失败"文字,默认为【加载失败,点击重新加载】
|
||||
loadingMoreFailText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('loadingMoreFailText', null)
|
||||
},
|
||||
// 当没有更多数据且分页内容未超出z-paging时是否隐藏没有更多数据的view,默认为否
|
||||
hideNoMoreInside: {
|
||||
type: Boolean,
|
||||
default: u.gc('hideNoMoreInside', false)
|
||||
},
|
||||
// 当没有更多数据且分页数组长度少于这个值时,隐藏没有更多数据的view,默认为0,代表不限制。
|
||||
hideNoMoreByLimit: {
|
||||
type: Number,
|
||||
default: u.gc('hideNoMoreByLimit', 0)
|
||||
},
|
||||
// 是否显示默认的加载更多text,默认为是
|
||||
showDefaultLoadingMoreText: {
|
||||
type: Boolean,
|
||||
default: u.gc('showDefaultLoadingMoreText', true)
|
||||
},
|
||||
// 是否显示没有更多数据的view
|
||||
showLoadingMoreNoMoreView: {
|
||||
type: Boolean,
|
||||
default: u.gc('showLoadingMoreNoMoreView', true)
|
||||
},
|
||||
// 是否显示没有更多数据的分割线,默认为是
|
||||
showLoadingMoreNoMoreLine: {
|
||||
type: Boolean,
|
||||
default: u.gc('showLoadingMoreNoMoreLine', true)
|
||||
},
|
||||
// 自定义底部没有更多数据的分割线样式
|
||||
loadingMoreNoMoreLineCustomStyle: {
|
||||
type: Object,
|
||||
default: u.gc('loadingMoreNoMoreLineCustomStyle', {})
|
||||
},
|
||||
// 当分页未满一屏时,是否自动加载更多,默认为否(nvue无效)
|
||||
insideMore: {
|
||||
type: Boolean,
|
||||
default: u.gc('insideMore', false)
|
||||
},
|
||||
// 距底部/右边多远时(单位px),触发 scrolltolower 事件,默认为100rpx
|
||||
lowerThreshold: {
|
||||
type: [Number, String],
|
||||
default: u.gc('lowerThreshold', '100rpx')
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
M: Enum.More,
|
||||
// 底部加载更多状态
|
||||
loadingStatus: Enum.More.Default,
|
||||
// 在渲染之后的底部加载更多状态
|
||||
loadingStatusAfterRender: Enum.More.Default,
|
||||
// 底部加载更多时间戳
|
||||
loadingMoreTimeStamp: 0,
|
||||
// 底部加载更多slot
|
||||
loadingMoreDefaultSlot: null,
|
||||
// 是否展示底部加载更多
|
||||
showLoadingMore: false,
|
||||
// 是否是开发者自定义的加载更多,-1代表交由z-paging自行判断;1代表没有更多了;0代表还有更多数据
|
||||
customNoMore: -1,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 底部加载更多配置
|
||||
zLoadMoreConfig() {
|
||||
return {
|
||||
status: this.loadingStatusAfterRender,
|
||||
defaultAsLoading: this.loadingMoreDefaultAsLoading || (this.useChatRecordMode && this.chatLoadingMoreDefaultAsLoading),
|
||||
defaultThemeStyle: this.finalLoadingMoreThemeStyle,
|
||||
customStyle: this.loadingMoreCustomStyle,
|
||||
titleCustomStyle: this.loadingMoreTitleCustomStyle,
|
||||
iconCustomStyle: this.loadingMoreLoadingIconCustomStyle,
|
||||
loadingIconType: this.loadingMoreLoadingIconType,
|
||||
loadingIconCustomImage: this.loadingMoreLoadingIconCustomImage,
|
||||
loadingAnimated: this.loadingMoreLoadingAnimated,
|
||||
showNoMoreLine: this.showLoadingMoreNoMoreLine,
|
||||
noMoreLineCustomStyle: this.loadingMoreNoMoreLineCustomStyle,
|
||||
defaultText: this.finalLoadingMoreDefaultText,
|
||||
loadingText: this.finalLoadingMoreLoadingText,
|
||||
noMoreText: this.finalLoadingMoreNoMoreText,
|
||||
failText: this.finalLoadingMoreFailText,
|
||||
hideContent: !this.loadingMoreDefaultAsLoading && this.listRendering,
|
||||
unit: this.unit,
|
||||
isChat: this.useChatRecordMode,
|
||||
chatDefaultAsLoading: this.chatLoadingMoreDefaultAsLoading
|
||||
};
|
||||
},
|
||||
// 最终的底部加载更多主题
|
||||
finalLoadingMoreThemeStyle() {
|
||||
return this.loadingMoreThemeStyle.length ? this.loadingMoreThemeStyle : this.defaultThemeStyle;
|
||||
},
|
||||
// 最终的底部加载更多触发阈值
|
||||
finalLowerThreshold() {
|
||||
return u.convertToPx(this.lowerThreshold);
|
||||
},
|
||||
// 是否显示默认状态下的底部加载更多
|
||||
showLoadingMoreDefault() {
|
||||
return this._showLoadingMore('Default');
|
||||
},
|
||||
// 是否显示加载中状态下的底部加载更多
|
||||
showLoadingMoreLoading() {
|
||||
return this._showLoadingMore('Loading');
|
||||
},
|
||||
// 是否显示没有更多了状态下的底部加载更多
|
||||
showLoadingMoreNoMore() {
|
||||
return this._showLoadingMore('NoMore');
|
||||
},
|
||||
// 是否显示加载失败状态下的底部加载更多
|
||||
showLoadingMoreFail() {
|
||||
return this._showLoadingMore('Fail');
|
||||
},
|
||||
// 是否显示自定义状态下的底部加载更多
|
||||
showLoadingMoreCustom() {
|
||||
return this._showLoadingMore('Custom');
|
||||
},
|
||||
// 底部加载更多固定高度
|
||||
loadingMoreFixedHeight() {
|
||||
return u.addUnit('80rpx', this.unit);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 页面滚动到底部时通知z-paging进行进一步处理
|
||||
pageReachBottom() {
|
||||
!this.useChatRecordMode && this.toBottomLoadingMoreEnabled && this._onLoadingMore('toBottom');
|
||||
},
|
||||
// 手动触发上拉加载更多(非必须,可依据具体需求使用)
|
||||
doLoadMore(type) {
|
||||
this._onLoadingMore(type);
|
||||
},
|
||||
// 通过@scroll事件检测是否滚动到了底部(顺带检测下是否滚动到了顶部)
|
||||
_checkScrolledToBottom(scrollDiff, checked = false) {
|
||||
// 如果当前scroll-view高度未获取,则获取其高度
|
||||
if (this.cacheScrollNodeHeight === -1) {
|
||||
// 获取当前scroll-view高度
|
||||
this._getNodeClientRect('.zp-scroll-view').then((res) => {
|
||||
if (res) {
|
||||
const scrollNodeHeight = res[0].height;
|
||||
// 缓存当前scroll-view高度,如果获取过了不再获取
|
||||
this.cacheScrollNodeHeight = scrollNodeHeight;
|
||||
// // scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离)
|
||||
if (scrollDiff - scrollNodeHeight <= this.finalLowerThreshold) {
|
||||
// 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件
|
||||
this._onLoadingMore('toBottom');
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// scrollDiff - this.cacheScrollNodeHeight = 当前滚动区域的顶部与内容底部的距离 - scroll-view高度 = 当前滚动区域的底部与内容底部的距离(也就是最终的与底部的距离)
|
||||
if (scrollDiff - this.cacheScrollNodeHeight <= this.finalLowerThreshold) {
|
||||
// 如果与底部的距离小于阈值,则判断为滚动到了底部,触发滚动到底部事件
|
||||
this._onLoadingMore('toBottom');
|
||||
} else if (scrollDiff - this.cacheScrollNodeHeight <= 500 && !checked) {
|
||||
// 如果与底部的距离小于500px,则获取当前滚动的位置,延迟150毫秒重复上述步骤再次检测(避免@scroll触发时获取的scrollTop不正确导致的其他问题,此时获取的scrollTop不一定可信)。防止因为部分性能较差安卓设备@scroll采样率过低导致的滚动到底部但是依然没有触发的问题
|
||||
u.delay(() => {
|
||||
this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => {
|
||||
if (res) {
|
||||
this.oldScrollTop = res[0].scrollTop;
|
||||
const newScrollDiff = res[0].scrollHeight - this.oldScrollTop;
|
||||
this._checkScrolledToBottom(newScrollDiff, true);
|
||||
}
|
||||
})
|
||||
}, 150, 'checkScrolledToBottomDelay')
|
||||
}
|
||||
// 检测一下是否已经滚动到了顶部了,因为在安卓中滚动到顶部时scrollTop不一定为0(和滚动到底部一样的原因),所以需要在scrollTop小于150px时,通过获取.zp-scroll-view的scrollTop再判断一下
|
||||
if (this.oldScrollTop <= 150 && this.oldScrollTop !== 0) {
|
||||
u.delay(() => {
|
||||
// 这里再判断一下是否确实已经滚动到顶部了,如果已经滚动到顶部了,则不用再判断了,再次判断的原因是可能150毫秒之后oldScrollTop才是0
|
||||
if (this.oldScrollTop !== 0) {
|
||||
this._getNodeClientRect('.zp-scroll-view', true, true).then((res) => {
|
||||
// 如果150毫秒后.zp-scroll-view的scrollTop为0,则认为已经滚动到了顶部了
|
||||
if (res && res[0].scrollTop === 0 && this.oldScrollTop !== 0) {
|
||||
this._onScrollToUpper();
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 150, 'checkScrolledToTopDelay')
|
||||
}
|
||||
}
|
||||
},
|
||||
// 触发加载更多时调用,from:toBottom-滑动到底部触发;click-点击加载更多触发
|
||||
_onLoadingMore(from = 'click') {
|
||||
// 如果是ios并且是滚动到底部的,则在滚动到底部时候尝试将列表设置为禁止滚动然后设置为允许滚动,以禁止底部bounce的效果
|
||||
if (this.isIos && from === 'toBottom' && !this.scrollToBottomBounceEnabled && this.scrollEnable) {
|
||||
this.scrollEnable = false;
|
||||
this.$nextTick(() => {
|
||||
this.scrollEnable = true;
|
||||
})
|
||||
}
|
||||
// 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;
|
||||
// #ifdef MP-WEIXIN
|
||||
if (!this.isIos && !this.refresherOnly && !this.usePageScroll) {
|
||||
const currentTimestamp = u.getTime();
|
||||
// 在非ios平台+scroll-view中节流处理
|
||||
if (this.loadingMoreTimeStamp > 0 && currentTimestamp - this.loadingMoreTimeStamp < 100) {
|
||||
this.loadingMoreTimeStamp = 0;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// #endif
|
||||
// 处理加载更多数据
|
||||
this._doLoadingMore();
|
||||
},
|
||||
// 处理开始加载更多
|
||||
_doLoadingMore() {
|
||||
if (this.pageNo >= this.defaultPageNo && this.loadingStatus !== Enum.More.NoMore) {
|
||||
this.pageNo ++;
|
||||
this._startLoading(false);
|
||||
if (this.isLocalPaging) {
|
||||
// 如果是本地分页,则在组件内部对数据进行分页处理,不触发@query事件
|
||||
this._localPagingQueryList(this.pageNo, this.defaultPageSize, this.localPagingLoadingTime, res => {
|
||||
this.completeByTotal(res, this.totalLocalPagingList.length);
|
||||
this.queryFrom = Enum.QueryFrom.LoadMore;
|
||||
})
|
||||
} else {
|
||||
// emit @query相关加载更多事件
|
||||
this._emitQuery(this.pageNo, this.defaultPageSize, Enum.QueryFrom.LoadMore);
|
||||
this._callMyParentQuery();
|
||||
}
|
||||
// 设置当前加载状态为底部加载更多状态
|
||||
this.loadingType = Enum.LoadingType.LoadMore;
|
||||
}
|
||||
},
|
||||
// (预处理)判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view
|
||||
_preCheckShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode) {
|
||||
if (this.loadingStatus === Enum.More.NoMore && this.hideNoMoreByLimit > 0 && newVal.length) {
|
||||
this.showLoadingMore = newVal.length > this.hideNoMoreByLimit;
|
||||
} else if ((this.loadingStatus === Enum.More.NoMore && this.hideNoMoreInside && newVal.length) || (this.insideMore && this.insideOfPaging !== false && newVal.length)) {
|
||||
this.$nextTick(() => {
|
||||
this._checkShowNoMoreInside(newVal, scrollViewNode, pagingContainerNode);
|
||||
})
|
||||
if (this.insideMore && this.insideOfPaging !== false && newVal.length) {
|
||||
this.showLoadingMore = newVal.length;
|
||||
}
|
||||
} else {
|
||||
this.showLoadingMore = newVal.length;
|
||||
}
|
||||
},
|
||||
// 判断当没有更多数据且分页内容未超出z-paging时是否显示没有更多数据的view
|
||||
async _checkShowNoMoreInside(totalData, oldScrollViewNode, oldPagingContainerNode) {
|
||||
try {
|
||||
const scrollViewNode = oldScrollViewNode || await this._getNodeClientRect('.zp-scroll-view');
|
||||
// 在页面滚动模式下
|
||||
if (this.usePageScroll) {
|
||||
if (scrollViewNode) {
|
||||
// 获取滚动内容总高度
|
||||
const scrollViewTotalH = scrollViewNode[0].top + scrollViewNode[0].height;
|
||||
// 如果滚动内容总高度小于窗口高度,则认为内容未超出z-paging
|
||||
this.insideOfPaging = scrollViewTotalH < this.windowHeight;
|
||||
// 如果需要没有更多数据时,隐藏底部加载更多view,并且内容未超过z-paging,则隐藏底部加载更多
|
||||
if (this.hideNoMoreInside) {
|
||||
this.showLoadingMore = !this.insideOfPaging;
|
||||
}
|
||||
// 如果需要内容未超过z-paging时自动加载更多,则触发加载更多
|
||||
this._updateInsideOfPaging();
|
||||
}
|
||||
} else {
|
||||
// 在scroll-view滚动模式下
|
||||
const pagingContainerNode = oldPagingContainerNode || await this._getNodeClientRect('.zp-paging-container-content');
|
||||
// 获取滚动内容总高度
|
||||
const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0;
|
||||
// 获取z-paging内置scroll-view高度
|
||||
const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0;
|
||||
// 如果滚动内容总高度小于z-paging内置scroll-view高度,则认为内容未超出z-paging
|
||||
this.insideOfPaging = pagingContainerH < scrollViewH;
|
||||
if (this.hideNoMoreInside) {
|
||||
this.showLoadingMore = !this.insideOfPaging;
|
||||
}
|
||||
// 如果需要内容未超过z-paging时自动加载更多,则触发加载更多
|
||||
this._updateInsideOfPaging();
|
||||
}
|
||||
} catch (e) {
|
||||
// 如果发生了异常,判断totalData数组长度为0,则认为内容未超出z-paging
|
||||
this.insideOfPaging = !totalData.length;
|
||||
if (this.hideNoMoreInside) {
|
||||
this.showLoadingMore = !this.insideOfPaging;
|
||||
}
|
||||
// 如果需要内容未超过z-paging时自动加载更多,则触发加载更多
|
||||
this._updateInsideOfPaging();
|
||||
}
|
||||
},
|
||||
// 是否要展示上拉加载更多view
|
||||
_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) {
|
||||
return false;
|
||||
}
|
||||
if (this.useChatRecordMode && type !== 'Loading') return false;
|
||||
if (!this.zSlots) return false;
|
||||
if (type === 'Custom') {
|
||||
return this.showDefaultLoadingMoreText && !(this.loadingStatus === Enum.More.NoMore && !this.showLoadingMoreNoMoreView);
|
||||
}
|
||||
const res = this.loadingStatus === Enum.More[type] && this.zSlots[`loadingMore${type}`] && (type === 'NoMore' ? this.showLoadingMoreNoMoreView : true);
|
||||
if (res) {
|
||||
// #ifdef APP-NVUE
|
||||
if (!this.isIos) {
|
||||
this.nLoadingMoreFixedHeight = false;
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
return res;
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
// [z-paging]loading相关模块
|
||||
import u from '.././z-paging-utils'
|
||||
import Enum from '.././z-paging-enum'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 第一次加载后自动隐藏loading slot,默认为是
|
||||
autoHideLoadingAfterFirstLoaded: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoHideLoadingAfterFirstLoaded', true)
|
||||
},
|
||||
// loading slot是否铺满屏幕并固定,默认为否
|
||||
loadingFullFixed: {
|
||||
type: Boolean,
|
||||
default: u.gc('loadingFullFixed', false)
|
||||
},
|
||||
// 是否自动显示系统Loading:即uni.showLoading,若开启则将在刷新列表时(调用reload、refresh时)显示,下拉刷新和滚动到底部加载更多不会显示,默认为false。
|
||||
autoShowSystemLoading: {
|
||||
type: Boolean,
|
||||
default: u.gc('autoShowSystemLoading', false)
|
||||
},
|
||||
// 显示系统Loading时是否显示透明蒙层,防止触摸穿透,默认为是(H5、App、微信小程序、百度小程序有效)
|
||||
systemLoadingMask: {
|
||||
type: Boolean,
|
||||
default: u.gc('systemLoadingMask', true)
|
||||
},
|
||||
// 显示系统Loading时显示的文字,默认为"加载中"
|
||||
systemLoadingText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('systemLoadingText', null)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false,
|
||||
loadingForNow: false,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// loading状态
|
||||
loadingStatus(newVal) {
|
||||
this.$emit('loadingStatusChange', newVal);
|
||||
this.$nextTick(() => {
|
||||
this.loadingStatusAfterRender = newVal;
|
||||
})
|
||||
if (this.useChatRecordMode) {
|
||||
if (this.isFirstPage && (newVal === Enum.More.NoMore || newVal === Enum.More.Fail)) {
|
||||
this.isFirstPageAndNoMore = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
this.isFirstPageAndNoMore = false;
|
||||
},
|
||||
loading(newVal){
|
||||
if (newVal) {
|
||||
this.loadingForNow = newVal;
|
||||
}
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
// 是否显示loading
|
||||
showLoading() {
|
||||
if (this.firstPageLoaded || !this.loading || !this.loadingForNow) return false;
|
||||
if (this.finalShowSystemLoading) {
|
||||
// 显示系统loading
|
||||
uni.showLoading({
|
||||
title: this.finalSystemLoadingText,
|
||||
mask: this.systemLoadingMask
|
||||
})
|
||||
}
|
||||
return this.autoHideLoadingAfterFirstLoaded ? (this.fromEmptyViewReload ? true : !this.pagingLoaded) : this.loadingType === Enum.LoadingType.Refresher;
|
||||
},
|
||||
// 最终的是否显示系统loading
|
||||
finalShowSystemLoading() {
|
||||
return this.autoShowSystemLoading && this.loadingType === Enum.LoadingType.Refresher;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 处理开始加载更多状态
|
||||
_startLoading(isReload = false) {
|
||||
if ((this.showLoadingMoreWhenReload && !this.isUserPullDown) || !isReload) {
|
||||
this.loadingStatus = Enum.More.Loading;
|
||||
}
|
||||
this.loading = true;
|
||||
},
|
||||
// 停止系统loading和refresh
|
||||
_endSystemLoadingAndRefresh(){
|
||||
this.finalShowSystemLoading && uni.hideLoading();
|
||||
!this.useCustomRefresher && uni.stopPullDownRefresh();
|
||||
// #ifdef APP-NVUE
|
||||
this.usePageScroll && uni.stopPullDownRefresh();
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
}
|
||||
268
uni_modules/z-paging/components/z-paging/js/modules/nvue.js
Normal file
268
uni_modules/z-paging/components/z-paging/js/modules/nvue.js
Normal file
@@ -0,0 +1,268 @@
|
||||
// [z-paging]nvue独有部分模块
|
||||
import u from '.././z-paging-utils'
|
||||
import c from '.././z-paging-constant'
|
||||
import Enum from '.././z-paging-enum'
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
const weexAnimation = weex.requireModule('animation');
|
||||
// #endif
|
||||
export default {
|
||||
props: {
|
||||
// #ifdef APP-NVUE
|
||||
// nvue中修改列表类型,可选值有list、waterfall和scroller,默认为list
|
||||
nvueListIs: {
|
||||
type: String,
|
||||
default: u.gc('nvueListIs', 'list')
|
||||
},
|
||||
// nvue waterfall配置,仅在nvue中且nvueListIs=waterfall时有效,配置参数详情参见:https://uniapp.dcloud.io/component/waterfall
|
||||
nvueWaterfallConfig: {
|
||||
type: Object,
|
||||
default: u.gc('nvueWaterfallConfig', {})
|
||||
},
|
||||
// nvue 控制是否回弹效果,iOS不支持动态修改
|
||||
nvueBounce: {
|
||||
type: Boolean,
|
||||
default: u.gc('nvueBounce', true)
|
||||
},
|
||||
// nvue中通过代码滚动到顶部/底部时,是否加快动画效果(无滚动动画时无效),默认为否
|
||||
nvueFastScroll: {
|
||||
type: Boolean,
|
||||
default: u.gc('nvueFastScroll', false)
|
||||
},
|
||||
// nvue中list的id
|
||||
nvueListId: {
|
||||
type: String,
|
||||
default: u.gc('nvueListId', '')
|
||||
},
|
||||
// nvue中refresh组件的样式
|
||||
nvueRefresherStyle: {
|
||||
type: Object,
|
||||
default: u.gc('nvueRefresherStyle', {})
|
||||
},
|
||||
// nvue中是否按分页模式(类似竖向swiper)显示List,默认为false
|
||||
nvuePagingEnabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('nvuePagingEnabled', false)
|
||||
},
|
||||
// 是否隐藏nvue列表底部的tagView,此view用于标识滚动到底部位置,若隐藏则滚动到底部功能将失效,在nvue中实现吸顶+swiper功能时需将最外层z-paging的此属性设置为true。默认为否
|
||||
hideNvueBottomTag: {
|
||||
type: Boolean,
|
||||
default: u.gc('hideNvueBottomTag', false)
|
||||
},
|
||||
// nvue中控制onscroll事件触发的频率:表示两次onscroll事件之间列表至少滚动了10px。注意,将该值设置为较小的数值会提高滚动事件采样的精度,但同时也会降低页面的性能
|
||||
offsetAccuracy: {
|
||||
type: Number,
|
||||
default: u.gc('offsetAccuracy', 10)
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
nRefresherLoading: false,
|
||||
nListIsDragging: false,
|
||||
nShowBottom: true,
|
||||
nFixFreezing: false,
|
||||
nShowRefresherReveal: false,
|
||||
nLoadingMoreFixedHeight: false,
|
||||
nShowRefresherRevealHeight: 0,
|
||||
nOldShowRefresherRevealHeight: -1,
|
||||
nRefresherWidth: u.rpx2px(750),
|
||||
nF2Opacity: 0
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// #ifdef APP-NVUE
|
||||
nScopedSlots() {
|
||||
// #ifdef VUE2
|
||||
return this.$scopedSlots;
|
||||
// #endif
|
||||
// #ifdef VUE3
|
||||
return null;
|
||||
// #endif
|
||||
},
|
||||
nWaterfallColumnCount() {
|
||||
if (this.finalNvueListIs !== 'waterfall') return 0;
|
||||
return this._nGetWaterfallConfig('column-count', 2);
|
||||
},
|
||||
nWaterfallColumnWidth() {
|
||||
return this._nGetWaterfallConfig('column-width', 'auto');
|
||||
},
|
||||
nWaterfallColumnGap() {
|
||||
return this._nGetWaterfallConfig('column-gap', 'normal');
|
||||
},
|
||||
nWaterfallLeftGap() {
|
||||
return this._nGetWaterfallConfig('left-gap', 0);
|
||||
},
|
||||
nWaterfallRightGap() {
|
||||
return this._nGetWaterfallConfig('right-gap', 0);
|
||||
},
|
||||
nViewIs() {
|
||||
const is = this.finalNvueListIs;
|
||||
return is === 'scroller' || is === 'view' ? 'view' : is === 'waterfall' ? 'header' : 'cell';
|
||||
},
|
||||
nSafeAreaBottomHeight() {
|
||||
return this.safeAreaInsetBottom ? this.safeAreaBottom : 0;
|
||||
},
|
||||
finalNvueListIs() {
|
||||
if (this.usePageScroll) return 'view';
|
||||
const nvueListIsLowerCase = this.nvueListIs.toLowerCase();
|
||||
if (['list','waterfall','scroller'].indexOf(nvueListIsLowerCase) !== -1) return nvueListIsLowerCase;
|
||||
return 'list';
|
||||
},
|
||||
finalNvueSuperListIs() {
|
||||
return this.usePageScroll ? 'view' : 'scroller';
|
||||
},
|
||||
finalNvueRefresherEnabled() {
|
||||
return this.finalNvueListIs !== 'view' && this.finalRefresherEnabled && !this.nShowRefresherReveal && !this.useChatRecordMode;
|
||||
},
|
||||
// #endif
|
||||
},
|
||||
mounted(){
|
||||
// #ifdef APP-NVUE
|
||||
//旋转屏幕时更新宽度
|
||||
uni.onWindowResize((res) => {
|
||||
// this._nUpdateRefresherWidth();
|
||||
})
|
||||
// #endif
|
||||
},
|
||||
methods: {
|
||||
// #ifdef APP-NVUE
|
||||
// 列表滚动时触发
|
||||
_nOnScroll(e) {
|
||||
this.$emit('scroll', e);
|
||||
const contentOffsetY = -e.contentOffset.y;
|
||||
this.oldScrollTop = contentOffsetY;
|
||||
this.nListIsDragging = e.isDragging;
|
||||
this._checkShouldShowBackToTop(contentOffsetY, contentOffsetY - 1);
|
||||
},
|
||||
// 列表滚动结束
|
||||
_nOnScrollend(e) {
|
||||
this.$emit('scrollend', e);
|
||||
|
||||
// 判断是否滚动到顶部了
|
||||
if (e?.contentOffset?.y >= 0) {
|
||||
this._emitScrollEvent('scrolltoupper');
|
||||
}
|
||||
// 判断是否滚动到底部了
|
||||
this._getNodeClientRect('.zp-n-list').then(node => {
|
||||
if (node) {
|
||||
if (e?.contentSize?.height + e?.contentOffset?.y <= node[0].height) {
|
||||
this._emitScrollEvent('scrolltolower');
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
// 下拉刷新刷新中
|
||||
_nOnRrefresh() {
|
||||
if (this.nShowRefresherReveal) return;
|
||||
// 进入刷新状态
|
||||
this.nRefresherLoading = true;
|
||||
if (this.refresherStatus === Enum.Refresher.GoF2) {
|
||||
this._handleGoF2();
|
||||
this.$nextTick(() => {
|
||||
this._nRefresherEnd();
|
||||
})
|
||||
} else {
|
||||
this.refresherStatus = Enum.Refresher.Loading;
|
||||
this._doRefresherLoad();
|
||||
}
|
||||
|
||||
},
|
||||
// 下拉刷新下拉中
|
||||
_nOnPullingdown(e) {
|
||||
if (this.refresherStatus === Enum.Refresher.Loading || (this.isIos && !this.nListIsDragging)) return;
|
||||
this._emitTouchmove(e);
|
||||
let { viewHeight, pullingDistance } = e;
|
||||
// 更新下拉刷新状态
|
||||
// 下拉刷新距离超过阈值
|
||||
if (pullingDistance >= viewHeight) {
|
||||
// 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
|
||||
// (pullingDistance - viewHeight) + this.finalRefresherThreshold 不等同于pullingDistance,此处是为了兼容不同平台下拉相同距离pullingDistance不一致的问题,pullingDistance仅与viewHeight互相关联
|
||||
this.refresherStatus = this.refresherF2Enabled && (pullingDistance - viewHeight) + this.finalRefresherThreshold >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
|
||||
} else {
|
||||
// 下拉刷新距离未超过阈值,显示默认状态
|
||||
this.refresherStatus = Enum.Refresher.Default;
|
||||
}
|
||||
},
|
||||
// 下拉刷新结束
|
||||
_nRefresherEnd(doEnd = true) {
|
||||
if (doEnd) {
|
||||
this._nDoRefresherEndAnimation(0, -this.nShowRefresherRevealHeight);
|
||||
!this.usePageScroll && this.$refs['zp-n-list'].resetLoadmore();
|
||||
this.nRefresherLoading = false;
|
||||
}
|
||||
},
|
||||
// 执行主动触发下拉刷新动画
|
||||
_nDoRefresherEndAnimation(height, translateY, animate = true, checkStack = true) {
|
||||
// 清除下拉刷新相关timeout
|
||||
this._cleanRefresherCompleteTimeout();
|
||||
this._cleanRefresherEndTimeout();
|
||||
|
||||
if (!this.finalShowRefresherWhenReload) {
|
||||
// 如果reload不需要自动展示下拉刷新view,则在complete duration结束后再把下拉刷新状态设置回默认
|
||||
this.refresherEndTimeout = u.delay(() => {
|
||||
this.refresherStatus = Enum.Refresher.Default;
|
||||
}, this.refresherCompleteDuration);
|
||||
return;
|
||||
}
|
||||
// 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可
|
||||
const stackCount = this.refresherRevealStackCount;
|
||||
if (height === 0 && checkStack) {
|
||||
this.refresherRevealStackCount --;
|
||||
if (stackCount > 1) return;
|
||||
this.refresherEndTimeout = u.delay(() => {
|
||||
this.refresherStatus = Enum.Refresher.Default;
|
||||
}, this.refresherCompleteDuration);
|
||||
}
|
||||
if (stackCount > 1) {
|
||||
this.refresherStatus = Enum.Refresher.Loading;
|
||||
}
|
||||
|
||||
const duration = animate ? 200 : 0;
|
||||
if (this.nOldShowRefresherRevealHeight !== height) {
|
||||
if (height > 0) {
|
||||
this.nShowRefresherReveal = true;
|
||||
}
|
||||
// 展示下拉刷新view
|
||||
weexAnimation.transition(this.$refs['zp-n-list-refresher-reveal'], {
|
||||
styles: {
|
||||
height: `${height}px`,
|
||||
transform: `translateY(${translateY}px)`,
|
||||
},
|
||||
duration,
|
||||
timingFunction: 'linear',
|
||||
needLayout: true,
|
||||
delay: 0
|
||||
})
|
||||
}
|
||||
u.delay(() => {
|
||||
if (animate) {
|
||||
this.nShowRefresherReveal = height > 0;
|
||||
}
|
||||
}, duration > 0 ? duration - 60 : 0);
|
||||
this.nOldShowRefresherRevealHeight = height;
|
||||
},
|
||||
// 滚动到底部加载更多
|
||||
_nOnLoadmore() {
|
||||
if (this.nShowRefresherReveal || !this.totalData.length) return;
|
||||
this.useChatRecordMode ? this.doChatRecordLoadMore() : this._onLoadingMore('toBottom');
|
||||
},
|
||||
// 获取nvue waterfall单项配置
|
||||
_nGetWaterfallConfig(key, defaultValue) {
|
||||
return this.nvueWaterfallConfig[key] || defaultValue;
|
||||
},
|
||||
// 更新nvue 下拉刷新view容器的宽度
|
||||
_nUpdateRefresherWidth() {
|
||||
u.delay(() => {
|
||||
this.$nextTick(()=>{
|
||||
this._getNodeClientRect('.zp-n-list').then(node => {
|
||||
if (node) {
|
||||
this.nRefresherWidth = node[0].width || this.nRefresherWidth;
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
831
uni_modules/z-paging/components/z-paging/js/modules/refresher.js
Normal file
831
uni_modules/z-paging/components/z-paging/js/modules/refresher.js
Normal file
@@ -0,0 +1,831 @@
|
||||
// [z-paging]下拉刷新view模块
|
||||
import u from '.././z-paging-utils'
|
||||
import c from '.././z-paging-constant'
|
||||
import Enum from '.././z-paging-enum'
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
const weexAnimation = weex.requireModule('animation');
|
||||
// #endif
|
||||
export default {
|
||||
props: {
|
||||
// 下拉刷新的主题样式,支持black,white,默认black
|
||||
refresherThemeStyle: {
|
||||
type: String,
|
||||
default: u.gc('refresherThemeStyle', '')
|
||||
},
|
||||
// 自定义下拉刷新中左侧图标的样式
|
||||
refresherImgStyle: {
|
||||
type: Object,
|
||||
default: u.gc('refresherImgStyle', {})
|
||||
},
|
||||
// 自定义下拉刷新中右侧状态描述文字的样式
|
||||
refresherTitleStyle: {
|
||||
type: Object,
|
||||
default: u.gc('refresherTitleStyle', {})
|
||||
},
|
||||
// 自定义下拉刷新中右侧最后更新时间文字的样式(show-refresher-update-time为true时有效)
|
||||
refresherUpdateTimeStyle: {
|
||||
type: Object,
|
||||
default: u.gc('refresherUpdateTimeStyle', {})
|
||||
},
|
||||
// 在微信小程序和QQ小程序中,是否实时监听下拉刷新中进度,默认为否
|
||||
watchRefresherTouchmove: {
|
||||
type: Boolean,
|
||||
default: u.gc('watchRefresherTouchmove', false)
|
||||
},
|
||||
// 底部加载更多的主题样式,支持black,white,默认black
|
||||
loadingMoreThemeStyle: {
|
||||
type: String,
|
||||
default: u.gc('loadingMoreThemeStyle', '')
|
||||
},
|
||||
// 是否只使用下拉刷新,设置为true后将关闭mounted自动请求数据、关闭滚动到底部加载更多,强制隐藏空数据图。默认为否
|
||||
refresherOnly: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherOnly', false)
|
||||
},
|
||||
// 自定义下拉刷新默认状态下回弹动画时间,单位为毫秒,默认为100毫秒,nvue无效
|
||||
refresherDefaultDuration: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherDefaultDuration', 100)
|
||||
},
|
||||
// 自定义下拉刷新结束以后延迟回弹的时间,单位为毫秒,默认为0
|
||||
refresherCompleteDelay: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherCompleteDelay', 0)
|
||||
},
|
||||
// 自定义下拉刷新结束回弹动画时间,单位为毫秒,默认为300毫秒(refresherEndBounceEnabled为false时,refresherCompleteDuration为设定值的1/3),nvue无效
|
||||
refresherCompleteDuration: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherCompleteDuration', 300)
|
||||
},
|
||||
// 自定义下拉刷新中是否允许列表滚动,默认为是
|
||||
refresherRefreshingScrollable: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherRefreshingScrollable', true)
|
||||
},
|
||||
// 自定义下拉刷新结束状态下是否允许列表滚动,默认为否
|
||||
refresherCompleteScrollable: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherCompleteScrollable', false)
|
||||
},
|
||||
// 是否使用自定义的下拉刷新,默认为是,即使用z-paging的下拉刷新。设置为false即代表使用uni scroll-view自带的下拉刷新,h5、App、微信小程序以外的平台不支持uni scroll-view自带的下拉刷新
|
||||
useCustomRefresher: {
|
||||
type: Boolean,
|
||||
default: u.gc('useCustomRefresher', true)
|
||||
},
|
||||
// 自定义下拉刷新下拉帧率,默认为40,过高可能会出现抖动问题
|
||||
refresherFps: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherFps', 40)
|
||||
},
|
||||
// 自定义下拉刷新允许触发的最大下拉角度,默认为40度,当下拉角度小于设定值时,自定义下拉刷新动画不会被触发
|
||||
refresherMaxAngle: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherMaxAngle', 40)
|
||||
},
|
||||
// 自定义下拉刷新的角度由未达到最大角度变到达到最大角度时,是否继续下拉刷新手势,默认为否
|
||||
refresherAngleEnableChangeContinued: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherAngleEnableChangeContinued', false)
|
||||
},
|
||||
// 自定义下拉刷新默认状态下的文字
|
||||
refresherDefaultText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('refresherDefaultText', null)
|
||||
},
|
||||
// 自定义下拉刷新松手立即刷新状态下的文字
|
||||
refresherPullingText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('refresherPullingText', null)
|
||||
},
|
||||
// 自定义下拉刷新刷新中状态下的文字
|
||||
refresherRefreshingText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('refresherRefreshingText', null)
|
||||
},
|
||||
// 自定义下拉刷新刷新结束状态下的文字
|
||||
refresherCompleteText: {
|
||||
type: [String, Object],
|
||||
default: u.gc('refresherCompleteText', null)
|
||||
},
|
||||
// 自定义继续下拉进入二楼文字
|
||||
refresherGoF2Text: {
|
||||
type: [String, Object],
|
||||
default: u.gc('refresherGoF2Text', null)
|
||||
},
|
||||
// 自定义下拉刷新默认状态下的图片
|
||||
refresherDefaultImg: {
|
||||
type: String,
|
||||
default: u.gc('refresherDefaultImg', null)
|
||||
},
|
||||
// 自定义下拉刷新松手立即刷新状态下的图片,默认与refresherDefaultImg一致
|
||||
refresherPullingImg: {
|
||||
type: String,
|
||||
default: u.gc('refresherPullingImg', null)
|
||||
},
|
||||
// 自定义下拉刷新刷新中状态下的图片
|
||||
refresherRefreshingImg: {
|
||||
type: String,
|
||||
default: u.gc('refresherRefreshingImg', null)
|
||||
},
|
||||
// 自定义下拉刷新刷新结束状态下的图片
|
||||
refresherCompleteImg: {
|
||||
type: String,
|
||||
default: u.gc('refresherCompleteImg', null)
|
||||
},
|
||||
// 自定义下拉刷新刷新中状态下是否展示旋转动画
|
||||
refresherRefreshingAnimated: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherRefreshingAnimated', true)
|
||||
},
|
||||
// 是否开启自定义下拉刷新刷新结束回弹效果,默认为是
|
||||
refresherEndBounceEnabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherEndBounceEnabled', true)
|
||||
},
|
||||
// 是否开启自定义下拉刷新,默认为是
|
||||
refresherEnabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherEnabled', true)
|
||||
},
|
||||
// 设置自定义下拉刷新阈值,默认为80rpx
|
||||
refresherThreshold: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherThreshold', '80rpx')
|
||||
},
|
||||
// 设置系统下拉刷新默认样式,支持设置 black,white,none,none 表示不使用默认样式,默认为black
|
||||
refresherDefaultStyle: {
|
||||
type: String,
|
||||
default: u.gc('refresherDefaultStyle', 'black')
|
||||
},
|
||||
// 设置自定义下拉刷新区域背景
|
||||
refresherBackground: {
|
||||
type: String,
|
||||
default: u.gc('refresherBackground', 'transparent')
|
||||
},
|
||||
// 设置固定的自定义下拉刷新区域背景
|
||||
refresherFixedBackground: {
|
||||
type: String,
|
||||
default: u.gc('refresherFixedBackground', 'transparent')
|
||||
},
|
||||
// 设置固定的自定义下拉刷新区域高度,默认为0
|
||||
refresherFixedBacHeight: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherFixedBacHeight', 0)
|
||||
},
|
||||
// 设置自定义下拉刷新下拉超出阈值后继续下拉位移衰减的比例,范围0-1,值越大代表衰减越多。默认为0.65(nvue无效)
|
||||
refresherOutRate: {
|
||||
type: Number,
|
||||
default: u.gc('refresherOutRate', 0.65)
|
||||
},
|
||||
// 是否开启下拉进入二楼功能,默认为否
|
||||
refresherF2Enabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherF2Enabled', false)
|
||||
},
|
||||
// 下拉进入二楼阈值,默认为200rpx
|
||||
refresherF2Threshold: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherF2Threshold', '200rpx')
|
||||
},
|
||||
// 下拉进入二楼动画时间,单位为毫秒,默认为200毫秒
|
||||
refresherF2Duration: {
|
||||
type: [Number, String],
|
||||
default: u.gc('refresherF2Duration', 200)
|
||||
},
|
||||
// 下拉进入二楼状态松手后是否弹出二楼,默认为是
|
||||
showRefresherF2: {
|
||||
type: Boolean,
|
||||
default: u.gc('showRefresherF2', true)
|
||||
},
|
||||
// 设置自定义下拉刷新下拉时实际下拉位移与用户下拉距离的比值,默认为0.75,即代表若用户下拉10px,则实际位移为7.5px(nvue无效)
|
||||
refresherPullRate: {
|
||||
type: Number,
|
||||
default: u.gc('refresherPullRate', 0.75)
|
||||
},
|
||||
// 是否显示最后更新时间,默认为否
|
||||
showRefresherUpdateTime: {
|
||||
type: Boolean,
|
||||
default: u.gc('showRefresherUpdateTime', false)
|
||||
},
|
||||
// 如果需要区别不同页面的最后更新时间,请为不同页面的z-paging的`refresher-update-time-key`设置不同的字符串
|
||||
refresherUpdateTimeKey: {
|
||||
type: String,
|
||||
default: u.gc('refresherUpdateTimeKey', 'default')
|
||||
},
|
||||
// 下拉刷新时下拉到“松手立即刷新”或“松手进入二楼”状态时是否使手机短振动,默认为否(h5无效)
|
||||
refresherVibrate: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherVibrate', false)
|
||||
},
|
||||
// 下拉刷新时是否禁止下拉刷新view跟随用户触摸竖直移动,默认为否。注意此属性只是禁止下拉刷新view移动,其他下拉刷新逻辑依然会正常触发
|
||||
refresherNoTransform: {
|
||||
type: Boolean,
|
||||
default: u.gc('refresherNoTransform', false)
|
||||
},
|
||||
// 是否开启下拉刷新状态栏占位,适用于隐藏导航栏时,下拉刷新需要避开状态栏高度的情况,默认为否
|
||||
useRefresherStatusBarPlaceholder: {
|
||||
type: Boolean,
|
||||
default: u.gc('useRefresherStatusBarPlaceholder', false)
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
R: Enum.Refresher,
|
||||
//下拉刷新状态
|
||||
refresherStatus: Enum.Refresher.Default,
|
||||
refresherTouchstartY: 0,
|
||||
lastRefresherTouchmove: null,
|
||||
refresherReachMaxAngle: true,
|
||||
refresherTransform: 'translateY(0px)',
|
||||
refresherTransition: '',
|
||||
finalRefresherDefaultStyle: 'black',
|
||||
refresherRevealStackCount: 0,
|
||||
refresherCompleteTimeout: null,
|
||||
refresherCompleteSubTimeout: null,
|
||||
refresherEndTimeout: null,
|
||||
isTouchmovingTimeout: null,
|
||||
refresherTriggered: false,
|
||||
isTouchmoving: false,
|
||||
isTouchEnded: false,
|
||||
isUserPullDown: false,
|
||||
privateRefresherEnabled: -1,
|
||||
privateShowRefresherWhenReload: false,
|
||||
customRefresherHeight: -1,
|
||||
showCustomRefresher: false,
|
||||
doRefreshAnimateAfter: false,
|
||||
isRefresherInComplete: false,
|
||||
showF2: false,
|
||||
f2Transform: '',
|
||||
pullDownTimeStamp: 0,
|
||||
moveDis: 0,
|
||||
oldMoveDis: 0,
|
||||
currentDis: 0,
|
||||
oldCurrentMoveDis: 0,
|
||||
oldRefresherTouchmoveY: 0,
|
||||
oldTouchDirection: '',
|
||||
oldEmitedTouchDirection: '',
|
||||
oldPullingDistance: -1,
|
||||
refresherThresholdUpdateTag: 0
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
refresherDefaultStyle: {
|
||||
handler(newVal) {
|
||||
if (newVal.length) {
|
||||
this.finalRefresherDefaultStyle = newVal;
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
refresherStatus(newVal) {
|
||||
newVal === Enum.Refresher.Loading && this._cleanRefresherEndTimeout();
|
||||
this.refresherVibrate && (newVal === Enum.Refresher.ReleaseToRefresh || newVal === Enum.Refresher.GoF2) && this._doVibrateShort();
|
||||
this.$emit('refresherStatusChange', newVal);
|
||||
this.$emit('update:refresherStatus', newVal);
|
||||
},
|
||||
// 监听当前下拉刷新启用/禁用状态
|
||||
refresherEnabled(newVal) {
|
||||
// 当禁用下拉刷新时,强制收回正在展示的下拉刷新view
|
||||
!newVal && this.endRefresh();
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
pullDownDisTimeStamp() {
|
||||
return 1000 / this.refresherFps;
|
||||
},
|
||||
refresherThresholdUnitConverted() {
|
||||
return u.addUnit(this.refresherThreshold, this.unit);
|
||||
},
|
||||
finalRefresherEnabled() {
|
||||
if (this.useChatRecordMode) return false;
|
||||
if (this.privateRefresherEnabled === -1) return this.refresherEnabled;
|
||||
return this.privateRefresherEnabled === 1;
|
||||
},
|
||||
finalRefresherThreshold() {
|
||||
let refresherThreshold = this.refresherThresholdUnitConverted;
|
||||
let idDefault = false;
|
||||
if (refresherThreshold === u.addUnit(80, this.unit)) {
|
||||
idDefault = true;
|
||||
if (this.showRefresherUpdateTime) {
|
||||
refresherThreshold = u.addUnit(120, this.unit);
|
||||
}
|
||||
}
|
||||
if (idDefault && this.customRefresherHeight > 0) return this.customRefresherHeight + this.finalRefresherThresholdPlaceholder;
|
||||
return u.convertToPx(refresherThreshold) + this.finalRefresherThresholdPlaceholder;
|
||||
},
|
||||
finalRefresherF2Threshold() {
|
||||
return u.convertToPx(u.addUnit(this.refresherF2Threshold, this.unit));
|
||||
},
|
||||
finalRefresherThresholdPlaceholder() {
|
||||
return this.useRefresherStatusBarPlaceholder ? this.statusBarHeight : 0;
|
||||
},
|
||||
finalRefresherFixedBacHeight() {
|
||||
return u.convertToPx(this.refresherFixedBacHeight);
|
||||
},
|
||||
finalRefresherThemeStyle() {
|
||||
return this.refresherThemeStyle.length ? this.refresherThemeStyle : this.defaultThemeStyle;
|
||||
},
|
||||
finalRefresherOutRate() {
|
||||
let rate = this.refresherOutRate;
|
||||
rate = Math.max(0,rate);
|
||||
rate = Math.min(1,rate);
|
||||
return rate;
|
||||
},
|
||||
finalRefresherPullRate() {
|
||||
let rate = this.refresherPullRate;
|
||||
rate = Math.max(0,rate);
|
||||
return rate;
|
||||
},
|
||||
finalRefresherTransform() {
|
||||
if (this.refresherNoTransform || this.refresherTransform === 'translateY(0px)') return 'none';
|
||||
return this.refresherTransform;
|
||||
},
|
||||
finalShowRefresherWhenReload() {
|
||||
return this.showRefresherWhenReload || this.privateShowRefresherWhenReload;
|
||||
},
|
||||
finalRefresherTriggered() {
|
||||
if (!(this.finalRefresherEnabled && !this.useCustomRefresher)) return false;
|
||||
return this.refresherTriggered;
|
||||
},
|
||||
showRefresher() {
|
||||
const showRefresher = this.finalRefresherEnabled || this.useCustomRefresher && !this.useChatRecordMode;
|
||||
// #ifndef APP-NVUE
|
||||
this.active && this.customRefresherHeight === -1 && showRefresher && this.updateCustomRefresherHeight();
|
||||
// #endif
|
||||
return showRefresher;
|
||||
},
|
||||
hasTouchmove() {
|
||||
// #ifdef VUE2
|
||||
// #ifdef APP-VUE || H5
|
||||
if (this.$listeners && !this.$listeners.refresherTouchmove) return false;
|
||||
// #endif
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
return this.watchRefresherTouchmove;
|
||||
// #endif
|
||||
return true;
|
||||
// #endif
|
||||
return this.watchRefresherTouchmove;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
// 终止下拉刷新状态
|
||||
endRefresh() {
|
||||
this.totalData = this.realTotalData;
|
||||
this._refresherEnd();
|
||||
this._endSystemLoadingAndRefresh();
|
||||
this._handleScrollViewBounce({ bounce: true });
|
||||
this.$nextTick(() => {
|
||||
this.refresherTriggered = false;
|
||||
})
|
||||
},
|
||||
// 手动更新自定义下拉刷新view高度
|
||||
updateCustomRefresherHeight() {
|
||||
u.delay(() => this.$nextTick(this._updateCustomRefresherHeight));
|
||||
},
|
||||
// 关闭二楼
|
||||
closeF2() {
|
||||
this._handleCloseF2();
|
||||
},
|
||||
// 自定义下拉刷新被触发
|
||||
_onRefresh(fromScrollView = false, isUserPullDown = true) {
|
||||
if (fromScrollView && !(this.finalRefresherEnabled && !this.useCustomRefresher)) return;
|
||||
this.$emit('onRefresh');
|
||||
this.$emit('Refresh');
|
||||
// #ifdef APP-NVUE
|
||||
if (this.loading) {
|
||||
u.delay(this._nRefresherEnd, 500)
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
if (this.loading || this.isRefresherInComplete) return;
|
||||
this.loadingType = Enum.LoadingType.Refresher;
|
||||
if (this.nShowRefresherReveal) return;
|
||||
this.isUserPullDown = isUserPullDown;
|
||||
this.isUserReload = !isUserPullDown;
|
||||
this._startLoading(true);
|
||||
this.refresherTriggered = true;
|
||||
if (this.reloadWhenRefresh && isUserPullDown) {
|
||||
this.useChatRecordMode ? this._onLoadingMore('click') : this._reload(false, false, isUserPullDown);
|
||||
}
|
||||
},
|
||||
// 自定义下拉刷新被复位
|
||||
_onRestore() {
|
||||
this.refresherTriggered = 'restore';
|
||||
this.$emit('onRestore');
|
||||
this.$emit('Restore');
|
||||
},
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
// touch开始
|
||||
_refresherTouchstart(e) {
|
||||
this._handleListTouchstart();
|
||||
if (this._touchDisabled()) return;
|
||||
this._handleRefresherTouchstart(u.getTouch(e));
|
||||
},
|
||||
// #endif
|
||||
// 进一步处理touch开始结果
|
||||
_handleRefresherTouchstart(touch) {
|
||||
if (!this.loading && this.isTouchEnded) {
|
||||
this.isTouchmoving = false;
|
||||
}
|
||||
this.loadingType = Enum.LoadingType.Refresher;
|
||||
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
|
||||
this.isTouchEnded = false;
|
||||
this.refresherTransition = '';
|
||||
this.refresherTouchstartY = touch.touchY;
|
||||
this.$emit('refresherTouchstart', this.refresherTouchstartY);
|
||||
this.lastRefresherTouchmove = touch;
|
||||
this._cleanRefresherCompleteTimeout();
|
||||
this._cleanRefresherEndTimeout();
|
||||
},
|
||||
|
||||
// 非app-vue或微信小程序或QQ小程序或h5平台,使用js控制下拉刷新
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
// touch中
|
||||
_refresherTouchmove(e) {
|
||||
const currentTimeStamp = u.getTime();
|
||||
let touch = null;
|
||||
let refresherTouchmoveY = 0;
|
||||
if (this.watchTouchDirectionChange) {
|
||||
// 检测下拉刷新方向改变
|
||||
touch = u.getTouch(e);
|
||||
refresherTouchmoveY = touch.touchY;
|
||||
const direction = refresherTouchmoveY > this.oldRefresherTouchmoveY ? 'top' : 'bottom';
|
||||
// 只有在方向改变的时候才emit相关事件
|
||||
if (direction === this.oldTouchDirection && direction !== this.oldEmitedTouchDirection) {
|
||||
this._handleTouchDirectionChange({ direction });
|
||||
this.oldEmitedTouchDirection = direction;
|
||||
}
|
||||
this.oldTouchDirection = direction;
|
||||
this.oldRefresherTouchmoveY = refresherTouchmoveY;
|
||||
}
|
||||
// 节流处理,在pullDownDisTimeStamp时间内的下拉刷新中事件不进行处理
|
||||
if (this.pullDownTimeStamp && currentTimeStamp - this.pullDownTimeStamp <= this.pullDownDisTimeStamp) return;
|
||||
// 如果不允许下拉,则return
|
||||
if (this._touchDisabled()) return;
|
||||
this.pullDownTimeStamp = Number(currentTimeStamp);
|
||||
touch = u.getTouch(e);
|
||||
refresherTouchmoveY = touch.touchY;
|
||||
// 获取当前touch的y - 初始touch的y,计算它们的差
|
||||
let moveDis = refresherTouchmoveY - this.refresherTouchstartY;
|
||||
if (moveDis < 0) return;
|
||||
// 对下拉刷新的角度进行限制
|
||||
if (this.refresherMaxAngle >= 0 && this.refresherMaxAngle <= 90 && this.lastRefresherTouchmove && this.lastRefresherTouchmove.touchY <= refresherTouchmoveY) {
|
||||
if (!moveDis && !this.refresherAngleEnableChangeContinued && this.moveDis < 1 && !this.refresherReachMaxAngle) return;
|
||||
const x = Math.abs(touch.touchX - this.lastRefresherTouchmove.touchX);
|
||||
const y = Math.abs(refresherTouchmoveY - this.lastRefresherTouchmove.touchY);
|
||||
const z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
|
||||
if ((x || y) && x > 1) {
|
||||
// 获取下拉刷新前后两次位移的角度
|
||||
const angle = Math.asin(y / z) / Math.PI * 180;
|
||||
// 如果角度小于配置要求,则return
|
||||
if (angle < this.refresherMaxAngle) {
|
||||
this.lastRefresherTouchmove = touch;
|
||||
this.refresherReachMaxAngle = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// 获取最终的moveDis
|
||||
moveDis = this._getFinalRefresherMoveDis(moveDis);
|
||||
// 处理下拉刷新位移
|
||||
this._handleRefresherTouchmove(moveDis, touch);
|
||||
// 下拉刷新时,禁止页面滚动以防止页面向下滚动和下拉刷新同时作用导致下拉刷新位置偏移超过预期
|
||||
if (!this.disabledBounce) {
|
||||
// #ifndef MP-LARK
|
||||
this._handleScrollViewBounce({ bounce: false });
|
||||
// #endif
|
||||
this.disabledBounce = true;
|
||||
}
|
||||
this._emitTouchmove({ pullingDistance: moveDis, dy: this.moveDis - this.oldMoveDis });
|
||||
},
|
||||
// #endif
|
||||
// 进一步处理touch中结果
|
||||
_handleRefresherTouchmove(moveDis, touch) {
|
||||
this.refresherReachMaxAngle = true;
|
||||
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
|
||||
this.isTouchmoving = true;
|
||||
this.isTouchEnded = false;
|
||||
// 更新下拉刷新状态
|
||||
// 下拉刷新距离超过阈值
|
||||
if (moveDis >= this.finalRefresherThreshold) {
|
||||
// 如果开启了下拉进入二楼并且下拉刷新距离超过进入二楼阈值,则当前下拉刷新状态为松手进入二楼,否则为松手立即刷新
|
||||
this.refresherStatus = this.refresherF2Enabled && moveDis >= this.finalRefresherF2Threshold ? Enum.Refresher.GoF2 : Enum.Refresher.ReleaseToRefresh;
|
||||
} else {
|
||||
// 下拉刷新距离未超过阈值,显示默认状态
|
||||
this.refresherStatus = Enum.Refresher.Default;
|
||||
}
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
// this.scrollEnable = false;
|
||||
// 通过transform控制下拉刷新view垂直偏移
|
||||
this.refresherTransform = `translateY(${moveDis}px)`;
|
||||
this.lastRefresherTouchmove = touch;
|
||||
// #endif
|
||||
this.moveDis = moveDis;
|
||||
},
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
// touch结束
|
||||
_refresherTouchend(e) {
|
||||
// 下拉刷新用户手离开屏幕,允许列表滚动
|
||||
this._handleScrollViewBounce({bounce: true});
|
||||
if (this._touchDisabled() || !this.isTouchmoving) return;
|
||||
const touch = u.getTouch(e);
|
||||
let refresherTouchendY = touch.touchY;
|
||||
let moveDis = refresherTouchendY - this.refresherTouchstartY;
|
||||
moveDis = this._getFinalRefresherMoveDis(moveDis);
|
||||
this._handleRefresherTouchend(moveDis);
|
||||
this.disabledBounce = false;
|
||||
},
|
||||
// #endif
|
||||
// 进一步处理touch结束结果
|
||||
_handleRefresherTouchend(moveDis) {
|
||||
// #ifndef APP-PLUS || H5 || MP-WEIXIN
|
||||
if (!this.isTouchmoving) return;
|
||||
// #endif
|
||||
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
|
||||
this.refresherReachMaxAngle = true;
|
||||
this.isTouchEnded = true;
|
||||
const refresherThreshold = this.finalRefresherThreshold;
|
||||
if (moveDis >= refresherThreshold && (this.refresherStatus === Enum.Refresher.ReleaseToRefresh || this.refresherStatus === Enum.Refresher.GoF2)) {
|
||||
// 如果是松手进入二楼状态,则触发进入二楼
|
||||
if (this.refresherStatus === Enum.Refresher.GoF2) {
|
||||
this._handleGoF2();
|
||||
this._refresherEnd();
|
||||
} else {
|
||||
// 如果是松手立即刷新状态,则触发下拉刷新
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
this.refresherTransform = `translateY(${refresherThreshold}px)`;
|
||||
this.refresherTransition = 'transform .1s linear';
|
||||
// #endif
|
||||
u.delay(() => {
|
||||
this._emitTouchmove({ pullingDistance: refresherThreshold, dy: this.moveDis - refresherThreshold });
|
||||
}, 0.1);
|
||||
this.moveDis = refresherThreshold;
|
||||
this.refresherStatus = Enum.Refresher.Loading;
|
||||
this._doRefresherLoad();
|
||||
}
|
||||
} else {
|
||||
this._refresherEnd();
|
||||
this.isTouchmovingTimeout = u.delay(() => {
|
||||
this.isTouchmoving = false;
|
||||
}, this.refresherDefaultDuration);
|
||||
}
|
||||
this.scrollEnable = true;
|
||||
this.$emit('refresherTouchend', moveDis);
|
||||
},
|
||||
// 处理列表触摸开始事件
|
||||
_handleListTouchstart() {
|
||||
if (this.useChatRecordMode && this.autoHideKeyboardWhenChat) {
|
||||
uni.hideKeyboard();
|
||||
this.$emit('hidedKeyboard');
|
||||
}
|
||||
},
|
||||
// 处理scroll-view bounce是否生效
|
||||
_handleScrollViewBounce({ bounce }) {
|
||||
if (!this.usePageScroll && !this.scrollToTopBounceEnabled) {
|
||||
if (this.wxsScrollTop <= 5) {
|
||||
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
this.refresherTransition = '';
|
||||
// #endif
|
||||
this.scrollEnable = bounce;
|
||||
} else if (bounce) {
|
||||
this.scrollEnable = bounce;
|
||||
}
|
||||
}
|
||||
},
|
||||
// wxs正在下拉状态改变处理
|
||||
_handleWxsPullingDownStatusChange(onPullingDown) {
|
||||
this.wxsOnPullingDown = onPullingDown;
|
||||
if (onPullingDown && !this.useChatRecordMode) {
|
||||
this.renderPropScrollTop = 0;
|
||||
}
|
||||
},
|
||||
// wxs正在下拉处理
|
||||
_handleWxsPullingDown({ moveDis, diffDis }){
|
||||
this._emitTouchmove({ pullingDistance: moveDis,dy: diffDis });
|
||||
},
|
||||
// wxs触摸方向改变
|
||||
_handleTouchDirectionChange({ direction }) {
|
||||
this.$emit('touchDirectionChange',direction);
|
||||
},
|
||||
// wxs通知更新其props
|
||||
_handlePropUpdate(){
|
||||
this.wxsPropType = u.getTime().toString();
|
||||
},
|
||||
// 下拉刷新结束
|
||||
_refresherEnd(shouldEndLoadingDelay = true, fromAddData = false, isUserPullDown = false, setLoading = true) {
|
||||
if (this.loadingType === Enum.LoadingType.Refresher) {
|
||||
// 计算当前下拉刷新结束需要延迟的时间
|
||||
const refresherCompleteDelay = (fromAddData && (isUserPullDown || this.showRefresherWhenReload)) ? this.refresherCompleteDelay : 0;
|
||||
// 如果延迟时间大于0,则展示刷新结束状态,否则直接展示默认状态
|
||||
const refresherStatus = refresherCompleteDelay > 0 ? Enum.Refresher.Complete : Enum.Refresher.Default;
|
||||
if (this.finalShowRefresherWhenReload) {
|
||||
const stackCount = this.refresherRevealStackCount;
|
||||
this.refresherRevealStackCount --;
|
||||
if (stackCount > 1) return;
|
||||
}
|
||||
this._cleanRefresherEndTimeout();
|
||||
this.refresherEndTimeout = u.delay(() => {
|
||||
// 更新下拉刷新状态
|
||||
this.refresherStatus = refresherStatus;
|
||||
// 如果当前下拉刷新状态不是刷新结束,则认为其不在刷新结束状态
|
||||
if (refresherStatus !== Enum.Refresher.Complete) {
|
||||
this.isRefresherInComplete = false;
|
||||
}
|
||||
}, this.refresherStatus !== Enum.Refresher.Default && refresherStatus === Enum.Refresher.Default ? this.refresherCompleteDuration : 0);
|
||||
|
||||
// #ifndef APP-NVUE
|
||||
if (refresherCompleteDelay > 0) {
|
||||
this.isRefresherInComplete = true;
|
||||
}
|
||||
// #endif
|
||||
this._cleanRefresherCompleteTimeout();
|
||||
this.refresherCompleteTimeout = u.delay(() => {
|
||||
let animateDuration = 1;
|
||||
const animateType = this.refresherEndBounceEnabled && fromAddData ? 'cubic-bezier(0.19,1.64,0.42,0.72)' : 'linear';
|
||||
if (fromAddData) {
|
||||
animateDuration = this.refresherEndBounceEnabled ? this.refresherCompleteDuration / 1000 : this.refresherCompleteDuration / 3000;
|
||||
}
|
||||
this.refresherTransition = `transform ${fromAddData ? animateDuration : this.refresherDefaultDuration / 1000}s ${animateType}`;
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
this.refresherTransform = 'translateY(0px)';
|
||||
this.currentDis = 0;
|
||||
// #endif
|
||||
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
this.wxsPropType = this.refresherTransition + 'end' + u.getTime();
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this._nRefresherEnd();
|
||||
// #endif
|
||||
this.moveDis = 0;
|
||||
// #ifndef APP-NVUE
|
||||
if (refresherStatus === Enum.Refresher.Complete) {
|
||||
if (this.refresherCompleteSubTimeout) {
|
||||
clearTimeout(this.refresherCompleteSubTimeout);
|
||||
this.refresherCompleteSubTimeout = null;
|
||||
}
|
||||
this.refresherCompleteSubTimeout = u.delay(() => {
|
||||
this.$nextTick(() => {
|
||||
this.refresherStatus = Enum.Refresher.Default;
|
||||
this.isRefresherInComplete = false;
|
||||
})
|
||||
}, animateDuration * 800);
|
||||
}
|
||||
// #endif
|
||||
this._emitTouchmove({ pullingDistance: 0, dy: this.moveDis });
|
||||
}, refresherCompleteDelay);
|
||||
}
|
||||
if (setLoading) {
|
||||
u.delay(() => this.loading = false, shouldEndLoadingDelay ? 10 : 0);
|
||||
isUserPullDown && this._onRestore();
|
||||
}
|
||||
},
|
||||
// 处理进入二楼
|
||||
_handleGoF2() {
|
||||
if (this.showF2 || !this.refresherF2Enabled) return;
|
||||
this.$emit('refresherF2Change', 'go');
|
||||
|
||||
if (!this.showRefresherF2) return;
|
||||
// #ifndef APP-NVUE
|
||||
this.f2Transform = `translateY(${-this.superContentHeight}px)`;
|
||||
this.showF2 = true;
|
||||
u.delay(() => {
|
||||
this.f2Transform = 'translateY(0px)';
|
||||
}, 100, 'f2ShowDelay')
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
this.showF2 = true;
|
||||
this.$nextTick(() => {
|
||||
weexAnimation.transition(this.$refs['zp-n-f2'], {
|
||||
styles: { transform: `translateY(${-this.superContentHeight}px)` },
|
||||
duration: 0,
|
||||
timingFunction: 'linear',
|
||||
needLayout: true,
|
||||
delay: 0
|
||||
})
|
||||
this.nF2Opacity = 1;
|
||||
})
|
||||
u.delay(() => {
|
||||
weexAnimation.transition(this.$refs['zp-n-f2'], {
|
||||
styles: { transform: 'translateY(0px)' },
|
||||
duration: this.refresherF2Duration,
|
||||
timingFunction: 'linear',
|
||||
needLayout: true,
|
||||
delay: 0
|
||||
})
|
||||
}, 10, 'f2GoDelay')
|
||||
// #endif
|
||||
},
|
||||
// 处理退出二楼
|
||||
_handleCloseF2() {
|
||||
if (!this.showF2 || !this.refresherF2Enabled) return;
|
||||
this.$emit('refresherF2Change', 'close');
|
||||
|
||||
if (!this.showRefresherF2) return;
|
||||
// #ifndef APP-NVUE
|
||||
this.f2Transform = `translateY(${-this.superContentHeight}px)`;
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
weexAnimation.transition(this.$refs['zp-n-f2'], {
|
||||
styles: { transform: `translateY(${-this.superContentHeight}px)` },
|
||||
duration: this.refresherF2Duration,
|
||||
timingFunction: 'linear',
|
||||
needLayout: true,
|
||||
delay: 0
|
||||
})
|
||||
// #endif
|
||||
|
||||
u.delay(() => {
|
||||
this.showF2 = false;
|
||||
this.nF2Opacity = 0;
|
||||
}, this.refresherF2Duration, 'f2CloseDelay')
|
||||
},
|
||||
// 模拟用户手动触发下拉刷新
|
||||
_doRefresherRefreshAnimate() {
|
||||
this._cleanRefresherCompleteTimeout();
|
||||
// 用户处理用户在短时间内多次调用reload的情况,此时下拉刷新view不需要重复显示,只需要保证最后一次reload对应的请求结束后收回下拉刷新view即可
|
||||
// #ifndef APP-NVUE
|
||||
const doRefreshAnimateAfter = !this.doRefreshAnimateAfter && (this.finalShowRefresherWhenReload) && this
|
||||
.customRefresherHeight === -1 && this.refresherThreshold === u.addUnit(80, this.unit);
|
||||
if (doRefreshAnimateAfter) {
|
||||
this.doRefreshAnimateAfter = true;
|
||||
return;
|
||||
}
|
||||
// #endif
|
||||
this.refresherRevealStackCount ++;
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
this.refresherTransform = `translateY(${this.finalRefresherThreshold}px)`;
|
||||
// #endif
|
||||
// #ifdef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
this.wxsPropType = 'begin' + u.getTime();
|
||||
// #endif
|
||||
this.moveDis = this.finalRefresherThreshold;
|
||||
this.refresherStatus = Enum.Refresher.Loading;
|
||||
this.isTouchmoving = true;
|
||||
this.isTouchmovingTimeout && clearTimeout(this.isTouchmovingTimeout);
|
||||
this._doRefresherLoad(false);
|
||||
},
|
||||
// 触发下拉刷新
|
||||
_doRefresherLoad(isUserPullDown = true) {
|
||||
this._onRefresh(false, isUserPullDown);
|
||||
this.loading = true;
|
||||
},
|
||||
// #ifndef APP-VUE || MP-WEIXIN || MP-QQ || H5
|
||||
// 获取处理后的moveDis
|
||||
_getFinalRefresherMoveDis(moveDis) {
|
||||
let diffDis = moveDis - this.oldCurrentMoveDis;
|
||||
this.oldCurrentMoveDis = moveDis;
|
||||
if (diffDis > 0) {
|
||||
// 根据配置的下拉刷新用户手势位移与实际需要的位移比率计算最终的diffDis
|
||||
diffDis = diffDis * this.finalRefresherPullRate;
|
||||
if (this.currentDis > this.finalRefresherThreshold) {
|
||||
diffDis = diffDis * (1 - this.finalRefresherOutRate);
|
||||
}
|
||||
}
|
||||
// 控制diffDis过大的情况,比如进入页面突然猛然下拉,此时diffDis不应进行太大的偏移
|
||||
diffDis = diffDis > 100 ? diffDis / 100 : diffDis;
|
||||
this.currentDis += diffDis;
|
||||
this.currentDis = Math.max(0, this.currentDis);
|
||||
return this.currentDis;
|
||||
},
|
||||
// 判断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);
|
||||
},
|
||||
// #endif
|
||||
// 更新自定义下拉刷新view高度
|
||||
_updateCustomRefresherHeight() {
|
||||
this._getNodeClientRect('.zp-custom-refresher-slot-view').then((res) => {
|
||||
this.customRefresherHeight = res ? res[0].height : 0;
|
||||
this.showCustomRefresher = this.customRefresherHeight > 0;
|
||||
if (this.doRefreshAnimateAfter) {
|
||||
this.doRefreshAnimateAfter = false;
|
||||
this._doRefresherRefreshAnimate();
|
||||
}
|
||||
});
|
||||
},
|
||||
// emit pullingDown事件
|
||||
_emitTouchmove(e) {
|
||||
// #ifndef APP-NVUE
|
||||
e.viewHeight = this.finalRefresherThreshold;
|
||||
// #endif
|
||||
e.rate = e.viewHeight > 0 ? e.pullingDistance / e.viewHeight : 0;
|
||||
this.hasTouchmove && this.oldPullingDistance !== e.pullingDistance && this.$emit('refresherTouchmove', e);
|
||||
this.oldPullingDistance = e.pullingDistance;
|
||||
},
|
||||
// 清除refresherCompleteTimeout
|
||||
_cleanRefresherCompleteTimeout() {
|
||||
this.refresherCompleteTimeout = this._cleanTimeout(this.refresherCompleteTimeout);
|
||||
// #ifdef APP-NVUE
|
||||
this._nRefresherEnd(false);
|
||||
// #endif
|
||||
},
|
||||
// 清除refresherEndTimeout
|
||||
_cleanRefresherEndTimeout() {
|
||||
this.refresherEndTimeout = this._cleanTimeout(this.refresherEndTimeout);
|
||||
},
|
||||
}
|
||||
}
|
||||
550
uni_modules/z-paging/components/z-paging/js/modules/scroller.js
Normal file
550
uni_modules/z-paging/components/z-paging/js/modules/scroller.js
Normal file
@@ -0,0 +1,550 @@
|
||||
// [z-paging]scroll相关模块
|
||||
import u from '.././z-paging-utils'
|
||||
import Enum from '.././z-paging-enum'
|
||||
|
||||
// #ifdef APP-NVUE
|
||||
const weexDom = weex.requireModule('dom');
|
||||
// #endif
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 使用页面滚动,默认为否,当设置为是时则使用页面的滚动而非此组件内部的scroll-view的滚动,使用页面滚动时z-paging无需设置确定的高度且对于长列表展示性能更高,但配置会略微繁琐
|
||||
usePageScroll: {
|
||||
type: Boolean,
|
||||
default: u.gc('usePageScroll', false)
|
||||
},
|
||||
// 是否可以滚动,使用内置scroll-view和nvue时有效,默认为是
|
||||
scrollable: {
|
||||
type: Boolean,
|
||||
default: u.gc('scrollable', true)
|
||||
},
|
||||
// 控制是否出现滚动条,默认为是
|
||||
showScrollbar: {
|
||||
type: Boolean,
|
||||
default: u.gc('showScrollbar', true)
|
||||
},
|
||||
// 是否允许横向滚动,默认为否
|
||||
scrollX: {
|
||||
type: Boolean,
|
||||
default: u.gc('scrollX', false)
|
||||
},
|
||||
// iOS设备上滚动到顶部时是否允许回弹效果,默认为否。关闭回弹效果后可使滚动到顶部与下拉刷新更连贯,但是有吸顶view时滚动到顶部时可能出现抖动。
|
||||
scrollToTopBounceEnabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('scrollToTopBounceEnabled', false)
|
||||
},
|
||||
// iOS设备上滚动到底部时是否允许回弹效果,默认为是。
|
||||
scrollToBottomBounceEnabled: {
|
||||
type: Boolean,
|
||||
default: u.gc('scrollToBottomBounceEnabled', true)
|
||||
},
|
||||
// 在设置滚动条位置时使用动画过渡,默认为否
|
||||
scrollWithAnimation: {
|
||||
type: Boolean,
|
||||
default: u.gc('scrollWithAnimation', false)
|
||||
},
|
||||
// 值应为某子元素id(id不能以数字开头)。设置哪个方向可滚动,则在哪个方向滚动到该元素
|
||||
scrollIntoView: {
|
||||
type: String,
|
||||
default: u.gc('scrollIntoView', '')
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
scrollTop: 0,
|
||||
oldScrollTop: 0,
|
||||
scrollLeft: 0,
|
||||
oldScrollLeft: 0,
|
||||
scrollViewStyle: {},
|
||||
scrollViewContainerStyle: {},
|
||||
scrollViewInStyle: {},
|
||||
pageScrollTop: -1,
|
||||
scrollEnable: true,
|
||||
privateScrollWithAnimation: -1,
|
||||
cacheScrollNodeHeight: -1,
|
||||
superContentHeight: 0,
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
oldScrollTop(newVal) {
|
||||
!this.usePageScroll && this._scrollTopChange(newVal,false);
|
||||
},
|
||||
pageScrollTop(newVal) {
|
||||
this.usePageScroll && this._scrollTopChange(newVal,true);
|
||||
},
|
||||
usePageScroll: {
|
||||
handler(newVal) {
|
||||
this.loaded && this.autoHeight && this._setAutoHeight(!newVal);
|
||||
// #ifdef H5
|
||||
if (newVal) {
|
||||
this.$nextTick(() => {
|
||||
const mainScrollRef = this.$refs['zp-scroll-view'].$refs.main;
|
||||
if (mainScrollRef) {
|
||||
mainScrollRef.style = {};
|
||||
}
|
||||
})
|
||||
}
|
||||
// #endif
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
finalScrollTop(newVal) {
|
||||
this.renderPropScrollTop = newVal < 6 ? 0 : 10;
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
finalScrollWithAnimation() {
|
||||
if (this.privateScrollWithAnimation !== -1) {
|
||||
return this.privateScrollWithAnimation === 1;
|
||||
}
|
||||
return this.scrollWithAnimation;
|
||||
},
|
||||
finalScrollViewStyle() {
|
||||
if (this.superContentZIndex != 1) {
|
||||
this.scrollViewStyle['z-index'] = this.superContentZIndex;
|
||||
this.scrollViewStyle['position'] = 'relative';
|
||||
}
|
||||
return this.scrollViewStyle;
|
||||
},
|
||||
finalScrollTop() {
|
||||
return this.usePageScroll ? this.pageScrollTop : this.oldScrollTop;
|
||||
},
|
||||
// 当前是否是旧版webview
|
||||
finalIsOldWebView() {
|
||||
return this.isOldWebView && !this.usePageScroll;
|
||||
},
|
||||
// 当前scroll-view/list-view是否允许滚动
|
||||
finalScrollable() {
|
||||
return this.scrollable && !this.usePageScroll && this.scrollEnable
|
||||
&& (this.refresherCompleteScrollable ? true : this.refresherStatus !== Enum.Refresher.Complete)
|
||||
&& (this.refresherRefreshingScrollable ? true : this.refresherStatus !== Enum.Refresher.Loading);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 滚动到顶部,animate为是否展示滚动动画,默认为是
|
||||
scrollToTop(animate, checkReverse = true) {
|
||||
// 如果是聊天记录模式并且列表倒置了,则滚动到顶部实际上是滚动到底部
|
||||
if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) {
|
||||
this.scrollToBottom(animate, false);
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this._scrollToTop(animate, false);
|
||||
// #ifdef APP-NVUE
|
||||
if (this.nvueFastScroll && animate) {
|
||||
u.delay(() => {
|
||||
this._scrollToTop(false, false);
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
// 滚动到底部,animate为是否展示滚动动画,默认为是
|
||||
scrollToBottom(animate, checkReverse = true) {
|
||||
// 如果是聊天记录模式并且列表倒置了,则滚动到底部实际上是滚动到顶部
|
||||
if (this.useChatRecordMode && checkReverse && !this.isChatRecordModeAndNotInversion) {
|
||||
this.scrollToTop(animate, false);
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
this._scrollToBottom(animate);
|
||||
// #ifdef APP-NVUE
|
||||
if (this.nvueFastScroll && animate) {
|
||||
u.delay(() => {
|
||||
this._scrollToBottom(false);
|
||||
});
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
// 滚动到指定view(vue中有效)。sel为需要滚动的view的id值,不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
|
||||
scrollIntoViewById(sel, offset, animate) {
|
||||
this._scrollIntoView(sel, offset, animate);
|
||||
},
|
||||
// 滚动到指定view(vue中有效)。nodeTop为需要滚动的view的top值(通过uni.createSelectorQuery()获取);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
|
||||
scrollIntoViewByNodeTop(nodeTop, offset, animate) {
|
||||
this.scrollTop = this.oldScrollTop;
|
||||
this.$nextTick(() => {
|
||||
this._scrollIntoViewByNodeTop(nodeTop, offset, animate);
|
||||
})
|
||||
},
|
||||
// y轴滚动到指定位置(vue中有效)。y为与顶部的距离,单位为px;offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
|
||||
scrollToY(y, offset, animate) {
|
||||
this.scrollTop = this.oldScrollTop;
|
||||
this.$nextTick(() => {
|
||||
this._scrollToY(y, offset, animate);
|
||||
})
|
||||
},
|
||||
// x轴滚动到指定位置(非页面滚动且在vue中有效)。x为与左侧的距离,单位为px;offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
|
||||
scrollToX(x, offset, animate) {
|
||||
this.scrollLeft = this.oldScrollLeft;
|
||||
this.$nextTick(() => {
|
||||
this._scrollToX(x, offset, animate);
|
||||
})
|
||||
},
|
||||
// 滚动到指定view(nvue中和虚拟列表中有效)。index为需要滚动的view的index(第几个,从0开始);offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
|
||||
scrollIntoViewByIndex(index, offset, animate) {
|
||||
if (index >= this.realTotalData.length) {
|
||||
u.consoleErr('当前滚动的index超出已渲染列表长度,请先通过refreshToPage加载到对应index页并等待渲染成功后再调用此方法!');
|
||||
return;
|
||||
}
|
||||
this.$nextTick(() => {
|
||||
// #ifdef APP-NVUE
|
||||
// 在nvue中,根据index获取对应节点信息并滚动到此节点位置
|
||||
this._scrollIntoView(index, offset, animate);
|
||||
// #endif
|
||||
// #ifndef APP-NVUE
|
||||
if (this.finalUseVirtualList) {
|
||||
const isCellFixed = this.cellHeightMode === Enum.CellHeightMode.Fixed;
|
||||
u.delay(() => {
|
||||
if (this.finalUseVirtualList) {
|
||||
// 虚拟列表 + 每个cell高度完全相同模式下,此时滚动到对应index的cell就是滚动到scrollTop = cellHeight * index的位置
|
||||
// 虚拟列表 + 高度是动态非固定的模式下,此时滚动到对应index的cell就是滚动到scrollTop = 缓存的cell高度数组中第index个的lastTotalHeight的位置
|
||||
const scrollTop = isCellFixed ? this.virtualCellHeight * index : this.virtualHeightCacheList[index].lastTotalHeight;
|
||||
this.scrollToY(scrollTop, offset, animate);
|
||||
}
|
||||
}, isCellFixed ? 0 : 100)
|
||||
}
|
||||
// #endif
|
||||
})
|
||||
},
|
||||
// 滚动到指定view(nvue中有效)。view为需要滚动的view(通过`this.$refs.xxx`获取),不包含"#";offset为偏移量,单位为px;animate为是否展示滚动动画,默认为否
|
||||
scrollIntoViewByView(view, offset, animate) {
|
||||
this._scrollIntoView(view, offset, animate);
|
||||
},
|
||||
// 当使用页面滚动并且自定义下拉刷新时,请在页面的onPageScroll中调用此方法,告知z-paging当前的pageScrollTop,否则会导致在任意位置都可以下拉刷新
|
||||
updatePageScrollTop(value) {
|
||||
this.pageScrollTop = value;
|
||||
},
|
||||
// 当使用页面滚动并且设置了slot="top"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="top"的view高度动态改变时,在其高度需要更新时调用此方法
|
||||
updatePageScrollTopHeight() {
|
||||
this._updatePageScrollTopOrBottomHeight('top');
|
||||
},
|
||||
// 当使用页面滚动并且设置了slot="bottom"时,默认初次加载会自动获取其高度,并使内部容器下移,当slot="bottom"的view高度动态改变时,在其高度需要更新时调用此方法
|
||||
updatePageScrollBottomHeight() {
|
||||
this._updatePageScrollTopOrBottomHeight('bottom');
|
||||
},
|
||||
// 更新slot="left"和slot="right"宽度,当slot="left"或slot="right"宽度动态改变时调用
|
||||
updateLeftAndRightWidth() {
|
||||
if (!this.finalIsOldWebView) return;
|
||||
this.$nextTick(() => this._updateLeftAndRightWidth(this.scrollViewContainerStyle, 'zp-page'));
|
||||
},
|
||||
// 更新z-paging内置scroll-view的scrollTop
|
||||
updateScrollViewScrollTop(scrollTop, animate = true) {
|
||||
this._updatePrivateScrollWithAnimation(animate);
|
||||
this.scrollTop = this.oldScrollTop;
|
||||
this.$nextTick(() => {
|
||||
this.scrollTop = scrollTop;
|
||||
this.oldScrollTop = this.scrollTop;
|
||||
});
|
||||
},
|
||||
|
||||
// 当滚动到顶部时
|
||||
_onScrollToUpper() {
|
||||
this._emitScrollEvent('scrolltoupper');
|
||||
this.$emit('scrollTopChange', 0);
|
||||
this.$nextTick(() => {
|
||||
this.oldScrollTop = 0;
|
||||
})
|
||||
},
|
||||
// 当滚动到底部时
|
||||
_onScrollToLower(e) {
|
||||
(!e.detail || !e.detail.direction || e.detail.direction === 'bottom')
|
||||
&& this.toBottomLoadingMoreEnabled
|
||||
&& this._onLoadingMore(this.useChatRecordMode ? 'click' : 'toBottom')
|
||||
},
|
||||
// 滚动到顶部
|
||||
_scrollToTop(animate = true, isPrivate = true) {
|
||||
// #ifdef APP-NVUE
|
||||
// 在nvue中需要通过weex.scrollToElement滚动到顶部,此时在顶部插入了一个view,使得滚动到这个view位置
|
||||
const el = this.$refs['zp-n-list-top-tag'];
|
||||
if (this.usePageScroll) {
|
||||
this._getNodeClientRect('zp-page-scroll-top', false).then(node => {
|
||||
const nodeHeight = node ? node[0].height : 0;
|
||||
weexDom.scrollToElement(el, {
|
||||
offset: -nodeHeight,
|
||||
animated: animate
|
||||
});
|
||||
});
|
||||
} else {
|
||||
if (!this.isIos && this.nvueListIs === 'scroller') {
|
||||
this._getNodeClientRect('zp-n-refresh-container', false).then(node => {
|
||||
const nodeHeight = node ? node[0].height : 0;
|
||||
weexDom.scrollToElement(el, {
|
||||
offset: -nodeHeight,
|
||||
animated: animate
|
||||
});
|
||||
});
|
||||
} else {
|
||||
weexDom.scrollToElement(el, {
|
||||
offset: 0,
|
||||
animated: animate
|
||||
});
|
||||
}
|
||||
}
|
||||
return;
|
||||
// #endif
|
||||
if (this.usePageScroll) {
|
||||
this.$nextTick(() => {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: 0,
|
||||
duration: animate ? 100 : 0,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
this._updatePrivateScrollWithAnimation(animate);
|
||||
this.scrollTop = this.oldScrollTop;
|
||||
this.$nextTick(() => {
|
||||
this.scrollTop = 0;
|
||||
this.oldScrollTop = this.scrollTop;
|
||||
});
|
||||
},
|
||||
// 滚动到底部
|
||||
async _scrollToBottom(animate = true) {
|
||||
// #ifdef APP-NVUE
|
||||
// 在nvue中需要通过weex.scrollToElement滚动到顶部,此时在底部插入了一个view,使得滚动到这个view位置
|
||||
const el = this.$refs['zp-n-list-bottom-tag'];
|
||||
if (el) {
|
||||
weexDom.scrollToElement(el, {
|
||||
offset: 0,
|
||||
animated: animate
|
||||
});
|
||||
} else {
|
||||
u.consoleErr('滚动到底部失败,因为您设置了hideNvueBottomTag为true');
|
||||
}
|
||||
return;
|
||||
// #endif
|
||||
if (this.usePageScroll) {
|
||||
this.$nextTick(() => {
|
||||
uni.pageScrollTo({
|
||||
scrollTop: Number.MAX_VALUE,
|
||||
duration: animate ? 100 : 0,
|
||||
});
|
||||
});
|
||||
return;
|
||||
}
|
||||
try {
|
||||
this._updatePrivateScrollWithAnimation(animate);
|
||||
const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container');
|
||||
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
|
||||
const pagingContainerH = pagingContainerNode ? pagingContainerNode[0].height : 0;
|
||||
const scrollViewH = scrollViewNode ? scrollViewNode[0].height : 0;
|
||||
if (pagingContainerH > scrollViewH) {
|
||||
this.scrollTop = this.oldScrollTop;
|
||||
this.$nextTick(() => {
|
||||
this.scrollTop = pagingContainerH - scrollViewH + this.virtualPlaceholderTopHeight;
|
||||
this.oldScrollTop = this.scrollTop;
|
||||
});
|
||||
}
|
||||
} catch (e) {}
|
||||
},
|
||||
// 滚动到指定view
|
||||
_scrollIntoView(sel, offset = 0, animate = false, finishCallback) {
|
||||
try {
|
||||
this.scrollTop = this.oldScrollTop;
|
||||
this.$nextTick(() => {
|
||||
// #ifdef APP-NVUE
|
||||
const refs = this.$parent.$refs;
|
||||
if (!refs) return;
|
||||
const dataType = Object.prototype.toString.call(sel);
|
||||
let el = null;
|
||||
if (dataType === '[object Number]') {
|
||||
const els = refs[`z-paging-${sel}`];
|
||||
el = els ? els[0] : null;
|
||||
} else if (dataType === '[object Array]') {
|
||||
el = sel[0];
|
||||
} else {
|
||||
el = sel;
|
||||
}
|
||||
if (el) {
|
||||
weexDom.scrollToElement(el, {
|
||||
offset: -offset,
|
||||
animated: animate
|
||||
});
|
||||
} else {
|
||||
u.consoleErr('在nvue中滚动到指定位置,cell必须设置 :ref="`z-paging-${index}`"');
|
||||
}
|
||||
return;
|
||||
// #endif
|
||||
this._getNodeClientRect('#' + sel.replace('#', ''), this.$parent).then((node) => {
|
||||
if (node) {
|
||||
let nodeTop = node[0].top;
|
||||
this._scrollIntoViewByNodeTop(nodeTop, offset, animate);
|
||||
finishCallback && finishCallback();
|
||||
}
|
||||
});
|
||||
});
|
||||
} catch (e) {}
|
||||
},
|
||||
// 通过nodeTop滚动到指定view
|
||||
_scrollIntoViewByNodeTop(nodeTop, offset = 0, animate = false) {
|
||||
// 如果是聊天记录模式并且列表倒置了,此时nodeTop需要等于scroll-view高度 - nodeTop
|
||||
if (this.isChatRecordModeAndInversion) {
|
||||
this._getNodeClientRect('.zp-scroll-view').then(sNode => {
|
||||
if (sNode) {
|
||||
this._scrollToY(sNode[0].height - nodeTop, offset, animate, true);
|
||||
}
|
||||
})
|
||||
} else {
|
||||
this._scrollToY(nodeTop, offset, animate, true);
|
||||
}
|
||||
},
|
||||
// y轴滚动到指定位置
|
||||
_scrollToY(y, offset = 0, animate = false, addScrollTop = false) {
|
||||
this._updatePrivateScrollWithAnimation(animate);
|
||||
u.delay(() => {
|
||||
if (this.usePageScroll) {
|
||||
if (addScrollTop && this.pageScrollTop !== -1) {
|
||||
y += this.pageScrollTop;
|
||||
}
|
||||
const scrollTop = y - offset;
|
||||
uni.pageScrollTo({
|
||||
scrollTop,
|
||||
duration: animate ? 100 : 0
|
||||
});
|
||||
} else {
|
||||
if (addScrollTop) {
|
||||
y += this.oldScrollTop;
|
||||
}
|
||||
this.scrollTop = y - offset;
|
||||
}
|
||||
}, 10)
|
||||
},
|
||||
// x轴滚动到指定位置
|
||||
_scrollToX(x, offset = 0, animate = false) {
|
||||
this._updatePrivateScrollWithAnimation(animate);
|
||||
u.delay(() => {
|
||||
if (!this.usePageScroll) {
|
||||
this.scrollLeft = x - offset;
|
||||
} else {
|
||||
u.consoleErr('使用页面滚动时不支持scrollToX');
|
||||
}
|
||||
}, 10)
|
||||
},
|
||||
// scroll-view滚动中
|
||||
_scroll(e) {
|
||||
this.$emit('scroll', e);
|
||||
const { scrollTop, scrollLeft } = e.detail;
|
||||
// #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);
|
||||
},
|
||||
// emit scrolltolower/scrolltoupper事件
|
||||
_emitScrollEvent(type) {
|
||||
const reversedType = type === 'scrolltolower' ? 'scrolltoupper' : 'scrolltolower';
|
||||
const eventType = this.useChatRecordMode && !this.isChatRecordModeAndNotInversion
|
||||
? reversedType
|
||||
: type;
|
||||
|
||||
this.$emit(eventType);
|
||||
},
|
||||
// 更新内置的scroll-view是否启用滚动动画
|
||||
_updatePrivateScrollWithAnimation(animate) {
|
||||
this.privateScrollWithAnimation = animate ? 1 : 0;
|
||||
u.delay(() => this.$nextTick(() => {
|
||||
// 在滚动结束后将滚动动画状态设置回初始状态
|
||||
this.privateScrollWithAnimation = -1;
|
||||
}), 100, 'updateScrollWithAnimationDelay')
|
||||
},
|
||||
// 检测scrollView是否要铺满屏幕
|
||||
_doCheckScrollViewShouldFullHeight(totalData) {
|
||||
if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) {
|
||||
// #ifndef APP-NVUE
|
||||
this.$nextTick(() => {
|
||||
this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => {
|
||||
this._preCheckShowNoMoreInside(totalData, scrollViewNode, pagingContainerNode)
|
||||
});
|
||||
})
|
||||
// #endif
|
||||
// #ifdef APP-NVUE
|
||||
this._preCheckShowNoMoreInside(totalData)
|
||||
// #endif
|
||||
} else {
|
||||
this._preCheckShowNoMoreInside(totalData)
|
||||
}
|
||||
},
|
||||
// 检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时,默认z-paging需要铺满全屏,避免数据过少时内部的empty-view无法正确展示)
|
||||
async _checkScrollViewShouldFullHeight(callback) {
|
||||
try {
|
||||
const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
|
||||
const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content');
|
||||
if (!scrollViewNode || !pagingContainerNode) return;
|
||||
const scrollViewHeight = pagingContainerNode[0].height;
|
||||
const scrollViewTop = scrollViewNode[0].top;
|
||||
if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) {
|
||||
this._setAutoHeight(true, scrollViewNode);
|
||||
callback(scrollViewNode, pagingContainerNode);
|
||||
} else {
|
||||
this._setAutoHeight(false);
|
||||
callback(null, null);
|
||||
}
|
||||
} catch (e) {
|
||||
callback(null, null);
|
||||
}
|
||||
},
|
||||
// 更新缓存中z-paging整个内容容器高度
|
||||
async _updateCachedSuperContentHeight() {
|
||||
const superContentNode = await this._getNodeClientRect('.z-paging-content');
|
||||
if (superContentNode) {
|
||||
this.superContentHeight = superContentNode[0].height;
|
||||
}
|
||||
},
|
||||
// scrollTop改变时触发
|
||||
_scrollTopChange(newVal, isPageScrollTop){
|
||||
this.$emit('scrollTopChange', newVal);
|
||||
this.$emit('update:scrollTop', newVal);
|
||||
this._checkShouldShowBackToTop(newVal);
|
||||
// 之前在安卓中scroll-view有概率滚动到顶部时scrollTop不为0导致下拉刷新判断异常,因此判断scrollTop在105之内都允许下拉刷新,但此方案会导致某些情况(例如滚动到距离顶部10px处)下拉抖动,因此改为通过获取zp-scroll-view的节点信息中的scrollTop进行验证的方案
|
||||
// const scrollTop = this.isIos ? (newVal > 5 ? 6 : 0) : (newVal > 105 ? 106 : (newVal > 5 ? 6 : 0));
|
||||
const scrollTop = newVal > 5 ? 6 : 0;
|
||||
if (isPageScrollTop && this.wxsPageScrollTop !== scrollTop) {
|
||||
this.wxsPageScrollTop = scrollTop;
|
||||
} else if (!isPageScrollTop && this.wxsScrollTop !== scrollTop) {
|
||||
this.wxsScrollTop = scrollTop;
|
||||
if (scrollTop > 6) {
|
||||
this.scrollEnable = true;
|
||||
}
|
||||
}
|
||||
},
|
||||
// 更新使用页面滚动时slot="top"或"bottom"插入view的高度
|
||||
_updatePageScrollTopOrBottomHeight(type) {
|
||||
// #ifndef APP-NVUE
|
||||
if (!this.usePageScroll) return;
|
||||
// #endif
|
||||
this._doCheckScrollViewShouldFullHeight(this.realTotalData);
|
||||
const node = `.zp-page-${type}`;
|
||||
const marginText = `margin${type.slice(0,1).toUpperCase() + type.slice(1)}`;
|
||||
let safeAreaInsetBottomAdd = this.safeAreaInsetBottom;
|
||||
this.$nextTick(() => {
|
||||
let delayTime = 0;
|
||||
// #ifdef MP-BAIDU || APP-NVUE
|
||||
delayTime = 50;
|
||||
// #endif
|
||||
u.delay(() => {
|
||||
this._getNodeClientRect(node).then((res) => {
|
||||
if (res) {
|
||||
let pageScrollNodeHeight = res[0].height;
|
||||
if (type === 'bottom') {
|
||||
if (safeAreaInsetBottomAdd) {
|
||||
pageScrollNodeHeight += this.safeAreaBottom;
|
||||
}
|
||||
} else {
|
||||
this.cacheTopHeight = pageScrollNodeHeight;
|
||||
}
|
||||
this.$set(this.scrollViewStyle, marginText, `${pageScrollNodeHeight}px`);
|
||||
} else if (safeAreaInsetBottomAdd) {
|
||||
this.$set(this.scrollViewStyle, marginText, `${this.safeAreaBottom}px`);
|
||||
}
|
||||
});
|
||||
}, delayTime)
|
||||
})
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
// [z-paging]虚拟列表模块
|
||||
import u from '.././z-paging-utils'
|
||||
import c from '.././z-paging-constant'
|
||||
import Enum from '.././z-paging-enum'
|
||||
|
||||
export default {
|
||||
props: {
|
||||
// 是否使用虚拟列表,默认为否
|
||||
useVirtualList: {
|
||||
type: Boolean,
|
||||
default: u.gc('useVirtualList', false)
|
||||
},
|
||||
// 在使用虚拟列表时,是否使用兼容模式,默认为否
|
||||
useCompatibilityMode: {
|
||||
type: Boolean,
|
||||
default: u.gc('useCompatibilityMode', false)
|
||||
},
|
||||
// 使用兼容模式时传递的附加数据
|
||||
extraData: {
|
||||
type: Object,
|
||||
default: u.gc('extraData', {})
|
||||
},
|
||||
// 是否在z-paging内部循环渲染列表(内置列表),默认为否。若use-virtual-list为true,则此项恒为true
|
||||
useInnerList: {
|
||||
type: Boolean,
|
||||
default: u.gc('useInnerList', false)
|
||||
},
|
||||
// 强制关闭inner-list,默认为false,如果为true将强制关闭innerList,适用于开启了虚拟列表后需要强制关闭inner-list的情况
|
||||
forceCloseInnerList: {
|
||||
type: Boolean,
|
||||
default: u.gc('forceCloseInnerList', false)
|
||||
},
|
||||
// 内置列表cell的key名称,仅nvue有效,在nvue中开启use-inner-list时必须填此项
|
||||
cellKeyName: {
|
||||
type: String,
|
||||
default: u.gc('cellKeyName', '')
|
||||
},
|
||||
// innerList样式
|
||||
innerListStyle: {
|
||||
type: Object,
|
||||
default: u.gc('innerListStyle', {})
|
||||
},
|
||||
// innerCell样式
|
||||
innerCellStyle: {
|
||||
type: Object,
|
||||
default: u.gc('innerCellStyle', {})
|
||||
},
|
||||
// 预加载的列表可视范围(列表高度)页数,默认为12,即预加载当前页及上下各12页的cell。此数值越大,则虚拟列表中加载的dom越多,内存消耗越大(会维持在一个稳定值),但增加预加载页面数量可缓解快速滚动短暂白屏问题
|
||||
preloadPage: {
|
||||
type: [Number, String],
|
||||
default: u.gc('preloadPage', 12),
|
||||
validator: (value) => {
|
||||
if (value <= 0) u.consoleErr('preload-page必须大于0!');
|
||||
return value > 0;
|
||||
}
|
||||
},
|
||||
// 虚拟列表cell高度模式,默认为fixed,也就是每个cell高度完全相同,将以第一个cell高度为准进行计算。可选值【dynamic】,即代表高度是动态非固定的,【dynamic】性能低于【fixed】。
|
||||
cellHeightMode: {
|
||||
type: String,
|
||||
default: u.gc('cellHeightMode', Enum.CellHeightMode.Fixed)
|
||||
},
|
||||
// 固定的cell高度,cellHeightMode=fixed才有效,若设置了值,则不计算第一个cell高度而使用设置的cell高度
|
||||
fixedCellHeight: {
|
||||
type: [Number, String],
|
||||
default: u.gc('fixedCellHeight', 0)
|
||||
},
|
||||
// 虚拟列表列数,默认为1。常用于每行有多列的情况,例如每行有2列数据,需要将此值设置为2
|
||||
virtualListCol: {
|
||||
type: [Number, String],
|
||||
default: u.gc('virtualListCol', 1)
|
||||
},
|
||||
// 虚拟列表scroll取样帧率,默认为80,过低容易出现白屏问题,过高容易出现卡顿问题
|
||||
virtualScrollFps: {
|
||||
type: [Number, String],
|
||||
default: u.gc('virtualScrollFps', 80)
|
||||
},
|
||||
// 虚拟列表cell id的前缀,适用于一个页面有多个虚拟列表的情况,用以区分不同虚拟列表cell的id,注意:请勿传数字或以数字开头的字符串。如设置为list1,则cell的id应为:list1-zp-id-${item.zp_index}
|
||||
virtualCellIdPrefix: {
|
||||
type: String,
|
||||
default: u.gc('virtualCellIdPrefix', '')
|
||||
},
|
||||
// 虚拟列表是否使用swiper-item包裹,默认为否,此属性为了解决vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度进而导致虚拟列表失败的问题
|
||||
// 仅vue3+(微信小程序或QQ小程序)+非内置列表写法虚拟列表有效,其他情况此属性设置任何值都无效,所以如果您在swiper-item内使用z-paging的非内置虚拟列表写法,将此属性设置为true即可
|
||||
virtualInSwiperSlot: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
virtualListKey: u.getInstanceId(),
|
||||
virtualPageHeight: 0,
|
||||
virtualCellHeight: 0,
|
||||
virtualScrollTimeStamp: 0,
|
||||
|
||||
virtualList: [],
|
||||
virtualPlaceholderTopHeight: 0,
|
||||
virtualPlaceholderBottomHeight: 0,
|
||||
virtualTopRangeIndex: 0,
|
||||
virtualBottomRangeIndex: 0,
|
||||
lastVirtualTopRangeIndex: 0,
|
||||
lastVirtualBottomRangeIndex: 0,
|
||||
virtualItemInsertedCount: 0,
|
||||
|
||||
virtualHeightCacheList: [],
|
||||
|
||||
getCellHeightRetryCount: {
|
||||
fixed: 0,
|
||||
dynamic: 0
|
||||
},
|
||||
pagingOrgTop: -1,
|
||||
updateVirtualListFromDataChange: false
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 监听总数据的改变,刷新虚拟列表布局
|
||||
realTotalData() {
|
||||
this.updateVirtualListRender();
|
||||
},
|
||||
// 监听虚拟列表渲染数组的改变并emit
|
||||
virtualList(newVal){
|
||||
this.$emit('update:virtualList', newVal);
|
||||
this.$emit('virtualListChange', newVal);
|
||||
},
|
||||
// 监听虚拟列表顶部占位高度改变并emit
|
||||
virtualPlaceholderTopHeight(newVal) {
|
||||
this.$emit('virtualTopHeightChange', newVal);
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
virtualCellIndexKey() {
|
||||
return c.listCellIndexKey;
|
||||
},
|
||||
finalUseVirtualList() {
|
||||
if (this.useVirtualList && this.usePageScroll){
|
||||
u.consoleErr('使用页面滚动时,开启虚拟列表无效!');
|
||||
}
|
||||
return this.useVirtualList && !this.usePageScroll;
|
||||
},
|
||||
finalUseInnerList() {
|
||||
return this.useInnerList || (this.finalUseVirtualList && !this.forceCloseInnerList);
|
||||
},
|
||||
finalCellKeyName() {
|
||||
// #ifdef APP-NVUE
|
||||
if (this.finalUseVirtualList && !this.cellKeyName.length){
|
||||
u.consoleErr('在nvue中开启use-virtual-list必须设置cell-key-name,否则将可能导致列表渲染错误!');
|
||||
}
|
||||
// #endif
|
||||
return this.cellKeyName;
|
||||
},
|
||||
finalVirtualPageHeight(){
|
||||
return this.virtualPageHeight > 0 ? this.virtualPageHeight : this.windowHeight;
|
||||
},
|
||||
finalFixedCellHeight() {
|
||||
return u.convertToPx(this.fixedCellHeight);
|
||||
},
|
||||
fianlVirtualCellIdPrefix() {
|
||||
const prefix = this.virtualCellIdPrefix ? this.virtualCellIdPrefix + '-' : '';
|
||||
return prefix + 'zp-id';
|
||||
},
|
||||
finalPlaceholderTopHeightStyle() {
|
||||
// #ifdef VUE2
|
||||
return { transform: this.virtualPlaceholderTopHeight > 0 ? `translateY(${this.virtualPlaceholderTopHeight}px)` : 'none' };
|
||||
// #endif
|
||||
return {};
|
||||
},
|
||||
virtualRangePageHeight(){
|
||||
return this.finalVirtualPageHeight * this.preloadPage;
|
||||
},
|
||||
virtualScrollDisTimeStamp() {
|
||||
return 1000 / this.virtualScrollFps;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 在使用动态高度虚拟列表时,若在列表数组中需要插入某个item,需要调用此方法;item:需要插入的item,index:插入的cell位置,若index为2,则插入的item在原list的index=1之后,index从0开始
|
||||
doInsertVirtualListItem(item, index) {
|
||||
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
|
||||
this.realTotalData.splice(index, 0, item);
|
||||
// #ifdef VUE3
|
||||
this.realTotalData = [...this.realTotalData];
|
||||
// #endif
|
||||
this.virtualItemInsertedCount ++;
|
||||
if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
|
||||
item = { item };
|
||||
}
|
||||
const cellIndexKey = this.virtualCellIndexKey;
|
||||
item[cellIndexKey] = `custom-${this.virtualItemInsertedCount}`;
|
||||
item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`;
|
||||
this.$nextTick(async () => {
|
||||
let retryCount = 0;
|
||||
while (retryCount <= 10) {
|
||||
await u.wait(c.delayTime);
|
||||
|
||||
const cellNode = await this._getVirtualCellNodeByIndex(item[cellIndexKey]);
|
||||
// 如果获取当前cell的节点信息失败,则重试(不超过10次)
|
||||
if (!cellNode) {
|
||||
retryCount ++;
|
||||
continue;
|
||||
}
|
||||
|
||||
const currentHeight = cellNode ? cellNode[0].height : 0;
|
||||
const lastHeightCache = this.virtualHeightCacheList[index - 1];
|
||||
const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0;
|
||||
// 在缓存的cell高度数组中,插入此cell高度信息
|
||||
this.virtualHeightCacheList.splice(index, 0, {
|
||||
height: currentHeight,
|
||||
lastTotalHeight,
|
||||
totalHeight: lastTotalHeight + currentHeight
|
||||
});
|
||||
|
||||
// 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell的高度
|
||||
for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
|
||||
const thisNode = this.virtualHeightCacheList[i];
|
||||
thisNode.lastTotalHeight += currentHeight;
|
||||
thisNode.totalHeight += currentHeight;
|
||||
}
|
||||
|
||||
this._updateVirtualScroll(this.oldScrollTop);
|
||||
break;
|
||||
}
|
||||
})
|
||||
},
|
||||
// 在使用动态高度虚拟列表时,手动更新指定cell的缓存高度(当cell高度在初始化之后再次改变后调用);index:需要更新的cell在列表中的位置,从0开始
|
||||
didUpdateVirtualListCell(index) {
|
||||
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
|
||||
const currentNode = this.virtualHeightCacheList[index];
|
||||
this.$nextTick(() => {
|
||||
this._getVirtualCellNodeByIndex(index).then(cellNode => {
|
||||
// 更新当前cell的高度
|
||||
const cellNodeHeight = cellNode ? cellNode[0].height : 0;
|
||||
const heightDis = cellNodeHeight - currentNode.height;
|
||||
currentNode.height = cellNodeHeight;
|
||||
currentNode.totalHeight = currentNode.lastTotalHeight + cellNodeHeight;
|
||||
|
||||
// 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要加上当前cell变化的高度
|
||||
for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
|
||||
const thisNode = this.virtualHeightCacheList[i];
|
||||
thisNode.totalHeight += heightDis;
|
||||
thisNode.lastTotalHeight += heightDis;
|
||||
}
|
||||
});
|
||||
})
|
||||
},
|
||||
// 在使用动态高度虚拟列表时,若删除了列表数组中的某个item,需要调用此方法以更新高度缓存数组;index:删除的cell在列表中的位置,从0开始
|
||||
didDeleteVirtualListCell(index) {
|
||||
if (this.cellHeightMode !== Enum.CellHeightMode.Dynamic) return;
|
||||
const currentNode = this.virtualHeightCacheList[index];
|
||||
// 从当前index起后续的cell缓存高度的lastTotalHeight和totalHeight需要减去当前cell的高度
|
||||
for (let i = index + 1; i < this.virtualHeightCacheList.length; i++) {
|
||||
const thisNode = this.virtualHeightCacheList[i];
|
||||
thisNode.totalHeight -= currentNode.height;
|
||||
thisNode.lastTotalHeight -= currentNode.height;
|
||||
}
|
||||
// 将当前cell的高度信息从高度缓存数组中删除
|
||||
this.virtualHeightCacheList.splice(index, 1);
|
||||
},
|
||||
// 手动触发虚拟列表渲染更新,可用于解决例如修改了虚拟列表数组中元素,但展示未更新的情况
|
||||
updateVirtualListRender() {
|
||||
// #ifndef APP-NVUE
|
||||
if (this.finalUseVirtualList) {
|
||||
this.updateVirtualListFromDataChange = true;
|
||||
this.$nextTick(() => {
|
||||
this.getCellHeightRetryCount.fixed = 0;
|
||||
if (this.realTotalData.length) {
|
||||
this.cellHeightMode === Enum.CellHeightMode.Fixed && this.isFirstPage && this._updateFixedCellHeight()
|
||||
} else {
|
||||
this._resetDynamicListState(!this.isUserPullDown);
|
||||
}
|
||||
this._updateVirtualScroll(this.oldScrollTop);
|
||||
})
|
||||
}
|
||||
// #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) {
|
||||
this.$nextTick(() => {
|
||||
u.delay(() => {
|
||||
this._getVirtualCellNodeByIndex(0).then(cellNode => {
|
||||
if (!cellNode) {
|
||||
if (this.getCellHeightRetryCount.fixed > 10) return;
|
||||
this.getCellHeightRetryCount.fixed ++;
|
||||
// 如果获取第一个cell的节点信息失败,则重试(不超过10次)
|
||||
this._updateFixedCellHeight();
|
||||
} else {
|
||||
this.virtualCellHeight = cellNode[0].height;
|
||||
this._updateVirtualScroll(this.oldScrollTop);
|
||||
}
|
||||
});
|
||||
}, c.delayTime, 'updateFixedCellHeightDelay');
|
||||
})
|
||||
} else {
|
||||
this.virtualCellHeight = this.finalFixedCellHeight;
|
||||
}
|
||||
},
|
||||
// cellHeightMode为dynamic时获取每个cell高度
|
||||
_updateDynamicCellHeight(list, dataFrom = 'bottom') {
|
||||
const dataFromTop = dataFrom === 'top';
|
||||
const heightCacheList = this.virtualHeightCacheList;
|
||||
const currentCacheList = dataFromTop ? [] : heightCacheList;
|
||||
let listTotalHeight = 0;
|
||||
this.$nextTick(() => {
|
||||
u.delay(async () => {
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
const cellNode = await this._getVirtualCellNodeByIndex(list[i][this.virtualCellIndexKey]);
|
||||
const currentHeight = cellNode ? cellNode[0].height : 0;
|
||||
if (!cellNode) {
|
||||
if (this.getCellHeightRetryCount.dynamic <= 10) {
|
||||
heightCacheList.splice(heightCacheList.length - i, i);
|
||||
this.getCellHeightRetryCount.dynamic ++;
|
||||
// 如果获取当前cell的节点信息失败,则重试(不超过10次)
|
||||
this._updateDynamicCellHeight(list, dataFrom);
|
||||
}
|
||||
return;
|
||||
}
|
||||
const lastHeightCache = currentCacheList.length ? currentCacheList.slice(-1)[0] : null;
|
||||
const lastTotalHeight = lastHeightCache ? lastHeightCache.totalHeight : 0;
|
||||
// 缓存当前cell的高度信息:height-当前cell高度;lastTotalHeight-前面所有cell的高度总和;totalHeight-包含当前cell的所有高度总和
|
||||
currentCacheList.push({
|
||||
height: currentHeight,
|
||||
lastTotalHeight,
|
||||
totalHeight: lastTotalHeight + currentHeight
|
||||
});
|
||||
if (dataFromTop) {
|
||||
listTotalHeight += currentHeight;
|
||||
}
|
||||
}
|
||||
// 如果数据是从顶部拼接的
|
||||
if (dataFromTop && list.length) {
|
||||
for (let i = 0; i < heightCacheList.length; i++) {
|
||||
// 更新之前所有项的缓存高度,需要加上此次插入的所有cell高度之和(因为是从顶部插入的cell)
|
||||
const heightCacheItem = heightCacheList[i];
|
||||
heightCacheItem.lastTotalHeight += listTotalHeight;
|
||||
heightCacheItem.totalHeight += listTotalHeight;
|
||||
}
|
||||
this.virtualHeightCacheList = currentCacheList.concat(heightCacheList);
|
||||
}
|
||||
this._updateVirtualScroll(this.oldScrollTop);
|
||||
}, c.delayTime, 'updateDynamicCellHeightDelay')
|
||||
})
|
||||
},
|
||||
// 设置cellItem的index
|
||||
_setCellIndex(list, dataFrom = 'bottom') {
|
||||
let currentItemIndex = 0;
|
||||
const cellIndexKey = this.virtualCellIndexKey;
|
||||
dataFrom === 'bottom' && ([Enum.QueryFrom.Refresh, Enum.QueryFrom.Reload].indexOf(this.queryFrom) >= 0) && this._resetDynamicListState();
|
||||
if (this.totalData.length && this.queryFrom !== Enum.QueryFrom.Refresh) {
|
||||
if (dataFrom === 'bottom') {
|
||||
currentItemIndex = this.realTotalData.length;
|
||||
const lastItem = this.realTotalData.length ? this.realTotalData.slice(-1)[0] : null;
|
||||
if (lastItem && lastItem[cellIndexKey] !== undefined) {
|
||||
currentItemIndex = lastItem[cellIndexKey] + 1;
|
||||
}
|
||||
} else if (dataFrom === 'top') {
|
||||
const firstItem = this.realTotalData.length ? this.realTotalData[0] : null;
|
||||
if (firstItem && firstItem[cellIndexKey] !== undefined) {
|
||||
currentItemIndex = firstItem[cellIndexKey] - list.length;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._resetDynamicListState();
|
||||
}
|
||||
for (let i = 0; i < list.length; i++) {
|
||||
let item = list[i];
|
||||
if (!item || Object.prototype.toString.call(item) !== '[object Object]') {
|
||||
item = { item };
|
||||
}
|
||||
if (item[c.listCellIndexUniqueKey]) {
|
||||
item = u.deepCopy(item);
|
||||
}
|
||||
item[cellIndexKey] = currentItemIndex + i;
|
||||
item[c.listCellIndexUniqueKey] = `${this.virtualListKey}-${item[cellIndexKey]}`;
|
||||
list[i] = item;
|
||||
}
|
||||
this.getCellHeightRetryCount.dynamic = 0;
|
||||
this.cellHeightMode === Enum.CellHeightMode.Dynamic && this._updateDynamicCellHeight(list, dataFrom);
|
||||
},
|
||||
// 更新scroll滚动(虚拟列表滚动时触发)
|
||||
_updateVirtualScroll(scrollTop, scrollDiff = 0) {
|
||||
const currentTimeStamp = u.getTime();
|
||||
scrollTop === 0 && this._resetTopRange();
|
||||
if (scrollTop !== 0 && this.virtualScrollTimeStamp && currentTimeStamp - this.virtualScrollTimeStamp <= this.virtualScrollDisTimeStamp) {
|
||||
return;
|
||||
}
|
||||
this.virtualScrollTimeStamp = currentTimeStamp;
|
||||
|
||||
let scrollIndex = 0;
|
||||
const cellHeightMode = this.cellHeightMode;
|
||||
if (cellHeightMode === Enum.CellHeightMode.Fixed) {
|
||||
// 如果是固定高度的虚拟列表
|
||||
// 计算当前滚动到的cell的index = scrollTop / 虚拟列表cell的固定高度
|
||||
scrollIndex = parseInt(scrollTop / this.virtualCellHeight) || 0;
|
||||
// 更新顶部和底部占位view的高度(为兼容考虑,顶部采用transformY的方式占位)
|
||||
this._updateFixedTopRangeIndex(scrollIndex);
|
||||
this._updateFixedBottomRangeIndex(scrollIndex);
|
||||
} else if(cellHeightMode === Enum.CellHeightMode.Dynamic) {
|
||||
// 如果是不固定高度的虚拟列表
|
||||
// 当前滚动的方向
|
||||
const scrollDirection = scrollDiff > 0 ? 'top' : 'bottom';
|
||||
// 视图区域的高度
|
||||
const rangePageHeight = this.virtualRangePageHeight;
|
||||
// 顶部视图区域外的高度(顶部不需要渲染而是需要占位部分的高度)
|
||||
const topRangePageOffset = scrollTop - rangePageHeight;
|
||||
// 底部视图区域外的高度(底部不需要渲染而是需要占位部分的高度)
|
||||
const bottomRangePageOffset = scrollTop + this.finalVirtualPageHeight + rangePageHeight;
|
||||
|
||||
let virtualBottomRangeIndex = 0;
|
||||
let virtualPlaceholderBottomHeight = 0;
|
||||
let reachedLimitBottom = false;
|
||||
const heightCacheList = this.virtualHeightCacheList;
|
||||
const lastHeightCache = !!heightCacheList ? heightCacheList.slice(-1)[0] : null;
|
||||
|
||||
let startTopRangeIndex = this.virtualTopRangeIndex;
|
||||
// 如果是向底部滚动(顶部占位的高度不断增大,顶部的实际渲染cell数量不断减少)
|
||||
if (scrollDirection === 'bottom') {
|
||||
// 从顶部视图边缘的cell的位置开始向后查找
|
||||
for (let i = startTopRangeIndex; i < heightCacheList.length; i++){
|
||||
const heightCacheItem = heightCacheList[i];
|
||||
// 如果查找到某个cell对应的totalHeight大于顶部视图区域外的高度,则此cell为顶部视图边缘的cell
|
||||
if (heightCacheItem && heightCacheItem.totalHeight > topRangePageOffset) {
|
||||
// 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找
|
||||
this.virtualTopRangeIndex = i;
|
||||
this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 如果是向顶部滚动(顶部占位的高度不断减少,顶部的实际渲染cell数量不断增加)
|
||||
let topRangeMatched = false;
|
||||
// 从顶部视图边缘的cell的位置开始向前查找
|
||||
for (let i = startTopRangeIndex; i >= 0; i--){
|
||||
const heightCacheItem = heightCacheList[i];
|
||||
// 如果查找到某个cell对应的totalHeight小于顶部视图区域外的高度,则此cell为顶部视图边缘的cell
|
||||
if (heightCacheItem && heightCacheItem.totalHeight < topRangePageOffset) {
|
||||
// 记录顶部视图边缘cell的index并更新顶部占位区域的高度并停止继续查找
|
||||
this.virtualTopRangeIndex = i;
|
||||
this.virtualPlaceholderTopHeight = heightCacheItem.lastTotalHeight;
|
||||
topRangeMatched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 如果查找不到,则认为顶部占位高度为0了,顶部cell不需要继续复用,重置topRangeIndex和placeholderTopHeight
|
||||
!topRangeMatched && this._resetTopRange();
|
||||
}
|
||||
// 从顶部视图边缘的cell的位置开始向后查找
|
||||
for (let i = this.virtualTopRangeIndex; i < heightCacheList.length; i++){
|
||||
const heightCacheItem = heightCacheList[i];
|
||||
// 如果查找到某个cell对应的totalHeight大于底部视图区域外的高度,则此cell为底部视图边缘的cell
|
||||
if (heightCacheItem && heightCacheItem.totalHeight > bottomRangePageOffset) {
|
||||
// 记录底部视图边缘cell的index并更新底部占位区域的高度并停止继续查找
|
||||
virtualBottomRangeIndex = i;
|
||||
virtualPlaceholderBottomHeight = lastHeightCache.totalHeight - heightCacheItem.totalHeight;
|
||||
reachedLimitBottom = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!reachedLimitBottom || this.virtualBottomRangeIndex === 0) {
|
||||
this.virtualBottomRangeIndex = this.realTotalData.length ? this.realTotalData.length - 1 : this.pageSize;
|
||||
this.virtualPlaceholderBottomHeight = 0;
|
||||
} else {
|
||||
this.virtualBottomRangeIndex = virtualBottomRangeIndex;
|
||||
this.virtualPlaceholderBottomHeight = virtualPlaceholderBottomHeight;
|
||||
}
|
||||
this._updateVirtualList();
|
||||
}
|
||||
},
|
||||
// 更新fixedCell模式下topRangeIndex&placeholderTopHeight
|
||||
_updateFixedTopRangeIndex(scrollIndex) {
|
||||
let virtualTopRangeIndex = this.virtualCellHeight === 0 ? 0 : scrollIndex - (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * this.preloadPage;
|
||||
virtualTopRangeIndex *= this.virtualListCol;
|
||||
virtualTopRangeIndex = Math.max(0, virtualTopRangeIndex);
|
||||
this.virtualTopRangeIndex = virtualTopRangeIndex;
|
||||
this.virtualPlaceholderTopHeight = (virtualTopRangeIndex / this.virtualListCol) * this.virtualCellHeight;
|
||||
},
|
||||
// 更新fixedCell模式下bottomRangeIndex&placeholderBottomHeight
|
||||
_updateFixedBottomRangeIndex(scrollIndex) {
|
||||
let virtualBottomRangeIndex = this.virtualCellHeight === 0 ? this.pageSize : scrollIndex + (parseInt(this.finalVirtualPageHeight / this.virtualCellHeight) || 1) * (this.preloadPage + 1);
|
||||
virtualBottomRangeIndex *= this.virtualListCol;
|
||||
virtualBottomRangeIndex = Math.min(this.realTotalData.length, virtualBottomRangeIndex);
|
||||
this.virtualBottomRangeIndex = virtualBottomRangeIndex;
|
||||
this.virtualPlaceholderBottomHeight = (this.realTotalData.length - virtualBottomRangeIndex) * this.virtualCellHeight / this.virtualListCol;
|
||||
this._updateVirtualList();
|
||||
},
|
||||
// 更新virtualList
|
||||
_updateVirtualList() {
|
||||
const shouldUpdateList = this.updateVirtualListFromDataChange || (this.lastVirtualTopRangeIndex !== this.virtualTopRangeIndex || this.lastVirtualBottomRangeIndex !== this.virtualBottomRangeIndex);
|
||||
if (shouldUpdateList) {
|
||||
this.updateVirtualListFromDataChange = false;
|
||||
this.lastVirtualTopRangeIndex = this.virtualTopRangeIndex;
|
||||
this.lastVirtualBottomRangeIndex = this.virtualBottomRangeIndex;
|
||||
this.virtualList = this.realTotalData.slice(this.virtualTopRangeIndex, this.virtualBottomRangeIndex + 1);
|
||||
}
|
||||
},
|
||||
// 重置动态cell模式下的高度缓存数据、虚拟列表和滚动状态
|
||||
_resetDynamicListState(resetVirtualList = false) {
|
||||
this.virtualHeightCacheList = [];
|
||||
if (resetVirtualList) {
|
||||
this.virtualList = [];
|
||||
}
|
||||
this.virtualTopRangeIndex = 0;
|
||||
this.virtualPlaceholderTopHeight = 0;
|
||||
},
|
||||
// 重置topRangeIndex和placeholderTopHeight
|
||||
_resetTopRange() {
|
||||
this.virtualTopRangeIndex = 0;
|
||||
this.virtualPlaceholderTopHeight = 0;
|
||||
this._updateVirtualList();
|
||||
},
|
||||
// 检测虚拟列表当前滚动位置,如发现滚动位置不正确则重新计算虚拟列表相关参数(为解决在App中可能出现的长时间进入后台后打开App白屏的问题)
|
||||
_checkVirtualListScroll() {
|
||||
if (this.finalUseVirtualList) {
|
||||
this.$nextTick(() => {
|
||||
this._getNodeClientRect('.zp-paging-touch-view').then(node => {
|
||||
const currentTop = node ? node[0].top : 0;
|
||||
if (!node || (currentTop === this.pagingOrgTop && this.virtualPlaceholderTopHeight !== 0)) {
|
||||
this._updateVirtualScroll(0);
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
},
|
||||
// 获取对应index的虚拟列表cell节点信息
|
||||
_getVirtualCellNodeByIndex(index) {
|
||||
let inDom = this.finalUseInnerList;
|
||||
// 在vue3+(微信小程序或QQ小程序)中,使用非内置列表写法时,若z-paging在swiper-item内存在无法获取slot插入的cell高度的问题
|
||||
// 通过uni.createSelectorQuery().in(this.$parent)来解决此问题
|
||||
// #ifdef VUE3
|
||||
// #ifdef MP-WEIXIN || MP-QQ
|
||||
if (this.forceCloseInnerList && this.virtualInSwiperSlot) {
|
||||
inDom = this.$parent;
|
||||
}
|
||||
// #endif
|
||||
// #endif
|
||||
return this._getNodeClientRect(`#${this.fianlVirtualCellIdPrefix}-${index}`, inDom);
|
||||
},
|
||||
// 处理使用内置列表时点击了cell事件
|
||||
_innerCellClick(item, index) {
|
||||
this.$emit('innerCellClick', item, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user