// [z-paging]核心js import zStatic from './z-paging-static' import c from './z-paging-constant' import u from './z-paging-utils' import zPagingRefresh from '../components/z-paging-refresh' import zPagingLoadMore from '../components/z-paging-load-more' import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view' // modules import dataHandleModule from './modules/data-handle' import i18nModule from './modules/i18n' import nvueModule from './modules/nvue' import emptyModule from './modules/empty' import refresherModule from './modules/refresher' import loadMoreModule from './modules/load-more' import loadingModule from './modules/loading' import scrollerModule from './modules/scroller' import backToTopModule from './modules/back-to-top' import virtualListModule from './modules/virtual-list' import Enum from './z-paging-enum' const systemInfo = uni.getSystemInfoSync(); // #ifdef APP-NVUE const weexDom = weex.requireModule('dom'); // #endif export default { name: "z-paging", components: { zPagingRefresh, zPagingLoadMore, zPagingEmptyView }, mixins: [ dataHandleModule, i18nModule, nvueModule, emptyModule, refresherModule, loadMoreModule, loadingModule, scrollerModule, backToTopModule, virtualListModule ], data() { return { //--------------静态资源--------------- base64Arrow: zStatic.base64Arrow, base64Flower: zStatic.base64Flower, base64BackToTop: zStatic.base64BackToTop, //-------------全局数据相关-------------- //当前加载类型 loadingType: Enum.LoadingType.Refresher, requestTimeStamp: 0, chatRecordLoadingMoreText: '', wxsPropType: '', renderPropScrollTop: -1, checkScrolledToBottomTimeOut: null, systemInfo: null, cssSafeAreaInsetBottom: -1, cacheTopHeight: -1, //--------------状态&判断--------------- insideOfPaging: -1, isLoadFailed: false, isIos: systemInfo.platform === 'ios', disabledBounce: false, fromCompleteEmit: false, disabledCompleteEmit: false, //---------------wxs相关--------------- wxsIsScrollTopInTopRange: true, wxsScrollTop: 0, wxsPageScrollTop: 0, wxsOnPullingDown: false, }; }, props: { //调用complete后延迟处理的时间,单位为毫秒,默认0毫秒,优先级高于minDelay delay: { type: [Number, String], default: u.gc('delay', 0), }, //触发@query后最小延迟处理的时间,单位为毫秒,默认0毫秒,优先级低于delay(假设设置为300毫秒,若分页请求时间小于300毫秒,则在调用complete后延迟[300毫秒-请求时长];若请求时长大于300毫秒,则不延迟),当show-refresher-when-reload为true或reload(true)时,其最小值为400 minDelay: { type: [Number, String], default: u.gc('minDelay', 0), }, //设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替 pagingStyle: { type: Object, default: function() { return u.gc('pagingStyle', {}); }, }, //z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100% height: { type: String, default: u.gc('height', '') }, //z-paging的宽度,优先级低于pagingStyle中设置的width;传字符串,如100px、100rpx、100% width: { type: String, default: u.gc('width', '') }, //z-paging的背景色,优先级低于pagingStyle中设置的background。传字符串,如"#ffffff" bgColor: { type: String, default: u.gc('bgColor', '') }, //设置z-paging的容器(插槽的父view)的style pagingContentStyle: { type: Object, default: function() { return u.gc('pagingContentStyle', {}); }, }, //z-paging是否自动高度,若自动高度则会自动铺满屏幕 autoHeight: { type: Boolean, default: u.gc('autoHeight', false) }, //z-paging是否自动高度时,附加的高度,注意添加单位px或rpx,若需要减少高度,则传负数 autoHeightAddition: { type: [Number, String], default: u.gc('autoHeightAddition', '0px') }, //loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认black defaultThemeStyle: { type: String, default: u.gc('defaultThemeStyle', 'black') }, //z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效) fixed: { type: Boolean, default: u.gc('fixed', true) }, //是否开启底部安全区域适配 safeAreaInsetBottom: { type: Boolean, default: u.gc('safeAreaInsetBottom', false) }, //开启底部安全区域适配后,是否使用placeholder形式实现,默认为否。为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域 useSafeAreaPlaceholder: { type: Boolean, default: u.gc('useSafeAreaPlaceholder', false) }, //slot="top"的view的z-index,默认为99,仅使用页面滚动时有效 topZIndex: { type: Number, default: u.gc('topZIndex', 99) }, //z-paging内容容器父view的z-index,默认为1 superContentZIndex: { type: Number, default: u.gc('superContentZIndex', 1) }, //z-paging内容容器部分的z-index,默认为10 contentZIndex: { type: Number, default: u.gc('contentZIndex', 10) }, //使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是 autoFullHeight: { type: Boolean, default: u.gc('autoFullHeight', true) }, //是否监听列表触摸方向改变,默认为否 watchTouchDirectionChange: { type: Boolean, default: u.gc('watchTouchDirectionChange', false) }, }, created(){ if (this.createdReload && !this.refresherOnly && this.auto) { this._startLoading(); this._preReload(); } }, mounted() { this.wxsPropType = u.getTime().toString(); this.renderJsIgnore; if (!this.createdReload && !this.refresherOnly && this.auto) { this.$nextTick(() => { this._preReload(); }) } this.finalUseCache && this._setListByLocalCache(); let delay = 0; // #ifdef H5 || MP delay = 100; // #endif this.$nextTick(() => { this.systemInfo = uni.getSystemInfoSync(); !this.usePageScroll && this.autoHeight && this._setAutoHeight(); this.loaded = true; }) this.updatePageScrollTopHeight(); this.updatePageScrollBottomHeight(); this._updateLeftAndRightWidth(); if (this.finalRefresherEnabled && this.useCustomRefresher) { this.$nextTick(() => { this.isTouchmoving = true; }) } this._onEmit(); // #ifdef APP-NVUE if (!this.isIos && !this.useChatRecordMode) { this.nLoadingMoreFixedHeight = true; } this._nUpdateRefresherWidth(); // #endif // #ifndef APP-NVUE this.finalUseVirtualList && this._virtualListInit(); // #endif // #ifndef APP-PLUS this.$nextTick(() => { setTimeout(() => { this._getCssSafeAreaInsetBottom(); },delay) }) // #endif }, destroyed() { this._offEmit(); }, // #ifdef VUE3 unmounted() { this._offEmit(); }, // #endif watch: { defaultThemeStyle: { handler(newVal) { if (newVal.length) { this.finalRefresherDefaultStyle = newVal; } }, immediate: true }, autoHeight(newVal) { this.loaded && !this.usePageScroll && this._setAutoHeight(newVal); }, autoHeightAddition(newVal) { this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal); }, }, computed: { finalPagingStyle() { const pagingStyle = this.pagingStyle; if (!this.systemInfo) return pagingStyle; const windowTop = this.windowTop; const windowBottom = this.windowBottom; if (!this.usePageScroll && this.fixed) { if (windowTop && !pagingStyle.top) { pagingStyle.top = windowTop + 'px'; } if (windowBottom && !pagingStyle.bottom) { pagingStyle.bottom = windowBottom + 'px'; } } if (this.bgColor.length && !pagingStyle['background']) { pagingStyle['background'] = this.bgColor; } if (this.height.length && !pagingStyle['height']) { pagingStyle['height'] = this.height; } if (this.width.length && !pagingStyle['width']) { pagingStyle['width'] = this.width; } return pagingStyle; }, finalLowerThreshold() { return u.convertTextToPx(this.lowerThreshold); }, finalPagingContentStyle() { if (this.contentZIndex != 1) { this.pagingContentStyle['z-index'] = this.contentZIndex; this.pagingContentStyle['position'] = 'relative'; } return this.pagingContentStyle; }, safeAreaBottom() { if (!this.systemInfo) return 0; let safeAreaBottom = 0; // #ifdef APP-PLUS safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0; // #endif // #ifndef APP-PLUS safeAreaBottom = this.cssSafeAreaInsetBottom === -1 ? 0 : this.cssSafeAreaInsetBottom; // #endif return safeAreaBottom; }, renderJsIgnore() { if ((this.usePageScroll && this.useChatRecordMode) || !this.refresherEnabled || !this.useCustomRefresher) { this.$nextTick(() => { this.renderPropScrollTop = 10; }) } return 0; }, windowHeight() { return !this.systemInfo ? 0 : this.systemInfo.windowHeight || 0; }, windowTop() { //暂时修复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 ? 0 : this.systemInfo.windowTop || 0; }, windowBottom() { if (!this.systemInfo) return 0; let windowBottom = this.systemInfo.windowBottom || 0; if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder) { windowBottom += this.safeAreaBottom; } return windowBottom; }, isOldWebView() { // #ifndef APP-NVUE || MP-KUAISHOU try { const systemInfos = systemInfo.system.split(' '); const deviceType = systemInfos[0]; const version = parseInt(systemInfos[1].slice(0,1)); if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) { return true; } } catch(e){ return false; } // #endif return false; }, isIosAndH5(){ // #ifndef H5 return false; // #endif return this.isIos; } }, methods: { //当前版本号 getVersion() { return `z-paging v${zConstant.version}`; }, //设置nvue List的specialEffects setSpecialEffects(args) { this.setListSpecialEffects(args); }, //与setSpecialEffects等效,兼容旧版本 setListSpecialEffects(args) { this.nFixFreezing = args && Object.keys(args).length; if (this.isIos) { this.privateRefresherEnabled = 0; } if (!this.usePageScroll) { this.$refs['zp-n-list'].setSpecialEffects(args); } }, //使手机发生较短时间的振动(15ms) _doVibrateShort() { // #ifdef APP-PLUS if (this.isIos) { const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator'); const feedbackGenerator = new UISelectionFeedbackGenerator(); feedbackGenerator.init(); setTimeout(() => { feedbackGenerator.selectionChanged(); },0) } else { plus.device.vibrate(15); } // #endif // #ifndef APP-PLUS uni.vibrateShort(); // #endif }, //检测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 _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) { let heightKey = 'min-height'; // #ifndef APP-NVUE heightKey = 'min-height'; // #endif try { if (shouldFullHeight) { let finalScrollViewNode = scrollViewNode ? scrollViewNode : await this._getNodeClientRect('.zp-scroll-view'); let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom'); if (finalScrollViewNode) { const scrollViewTop = finalScrollViewNode[0].top; let scrollViewHeight = this.windowHeight - scrollViewTop; if(finalScrollBottomNode){ scrollViewHeight -= finalScrollBottomNode[0].height; } const additionHeight = u.convertTextToPx(this.autoHeightAddition); const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px !important'; this.$set(this.scrollViewStyle, heightKey, finalHeight); this.$set(this.scrollViewInStyle, heightKey, finalHeight); } } else { this.$delete(this.scrollViewStyle, heightKey); this.$delete(this.scrollViewInStyle, heightKey); } } catch (e) {} }, //通过获取css设置的底部安全区域占位view高度设置bottom距离 _getCssSafeAreaInsetBottom(){ this._getNodeClientRect('.zp-safe-area-inset-bottom').then((res) => { if (res) { this.cssSafeAreaInsetBottom = res[0].height; if (this.safeAreaInsetBottom) { this.updatePageScrollBottomHeight(); } } }); }, //触发更新是否超出页面状态 _updateInsideOfPaging() { if (this.insideMore && this.insideOfPaging === true) { setTimeout(() => { this.doLoadMore(); }, 200) } }, //获取节点尺寸 _getNodeClientRect(select, inThis = true, scrollOffset = false) { // #ifdef APP-NVUE select = select.replace('.', '').replace('#', ''); 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 //#ifdef MP-ALIPAY inThis = false; //#endif let res = inThis ? uni.createSelectorQuery().in(this) : 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); }); }); }, //清除timeout _cleanTimeout(timeout) { if (timeout) { clearTimeout(timeout); timeout = null; } return timeout; }, //添加全局emit监听 _onEmit() { uni.$on(c.errorUpdateKey, () => { if (this.loading) { this.complete(false); } }) uni.$on(c.completeUpdateKey, (data) => { setTimeout(() => { if (this.loading) { if (!this.disabledCompleteEmit) { const type = data.type || 'normal'; const list = data.list || data; const rule = data.rule; this.fromCompleteEmit = true; switch (type){ case 'normal': this.complete(list); break; case 'total': this.completeByTotal(list, rule); break; case 'nomore': this.completeByNoMore(list, rule); break; case 'key': this.completeByKey(list, rule); break; default: break; } } else { this.disabledCompleteEmit = false; } } }, 1); }) }, //销毁全局emit和listener监听 _offEmit(){ uni.$off(c.errorUpdateKey); uni.$off(c.completeUpdateKey); } }, };