z-paging-main.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
  1. // [z-paging]核心js
  2. import zStatic from './z-paging-static'
  3. import c from './z-paging-constant'
  4. import u from './z-paging-utils'
  5. import zPagingRefresh from '../components/z-paging-refresh'
  6. import zPagingLoadMore from '../components/z-paging-load-more'
  7. import zPagingEmptyView from '../../z-paging-empty-view/z-paging-empty-view'
  8. // modules
  9. import dataHandleModule from './modules/data-handle'
  10. import i18nModule from './modules/i18n'
  11. import nvueModule from './modules/nvue'
  12. import emptyModule from './modules/empty'
  13. import refresherModule from './modules/refresher'
  14. import loadMoreModule from './modules/load-more'
  15. import loadingModule from './modules/loading'
  16. import scrollerModule from './modules/scroller'
  17. import backToTopModule from './modules/back-to-top'
  18. import virtualListModule from './modules/virtual-list'
  19. import Enum from './z-paging-enum'
  20. const systemInfo = uni.getSystemInfoSync();
  21. // #ifdef APP-NVUE
  22. const weexDom = weex.requireModule('dom');
  23. // #endif
  24. export default {
  25. name: "z-paging",
  26. components: {
  27. zPagingRefresh,
  28. zPagingLoadMore,
  29. zPagingEmptyView
  30. },
  31. mixins: [
  32. dataHandleModule,
  33. i18nModule,
  34. nvueModule,
  35. emptyModule,
  36. refresherModule,
  37. loadMoreModule,
  38. loadingModule,
  39. scrollerModule,
  40. backToTopModule,
  41. virtualListModule
  42. ],
  43. data() {
  44. return {
  45. //--------------静态资源---------------
  46. base64Arrow: zStatic.base64Arrow,
  47. base64Flower: zStatic.base64Flower,
  48. base64BackToTop: zStatic.base64BackToTop,
  49. //-------------全局数据相关--------------
  50. //当前加载类型
  51. loadingType: Enum.LoadingType.Refresher,
  52. requestTimeStamp: 0,
  53. chatRecordLoadingMoreText: '',
  54. wxsPropType: '',
  55. renderPropScrollTop: -1,
  56. checkScrolledToBottomTimeOut: null,
  57. systemInfo: null,
  58. cssSafeAreaInsetBottom: -1,
  59. cacheTopHeight: -1,
  60. //--------------状态&判断---------------
  61. insideOfPaging: -1,
  62. isLoadFailed: false,
  63. isIos: systemInfo.platform === 'ios',
  64. disabledBounce: false,
  65. fromCompleteEmit: false,
  66. disabledCompleteEmit: false,
  67. //---------------wxs相关---------------
  68. wxsIsScrollTopInTopRange: true,
  69. wxsScrollTop: 0,
  70. wxsPageScrollTop: 0,
  71. wxsOnPullingDown: false,
  72. };
  73. },
  74. props: {
  75. //调用complete后延迟处理的时间,单位为毫秒,默认0毫秒,优先级高于minDelay
  76. delay: {
  77. type: [Number, String],
  78. default: u.gc('delay', 0),
  79. },
  80. //触发@query后最小延迟处理的时间,单位为毫秒,默认0毫秒,优先级低于delay(假设设置为300毫秒,若分页请求时间小于300毫秒,则在调用complete后延迟[300毫秒-请求时长];若请求时长大于300毫秒,则不延迟),当show-refresher-when-reload为true或reload(true)时,其最小值为400
  81. minDelay: {
  82. type: [Number, String],
  83. default: u.gc('minDelay', 0),
  84. },
  85. //设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替
  86. pagingStyle: {
  87. type: Object,
  88. default: function() {
  89. return u.gc('pagingStyle', {});
  90. },
  91. },
  92. //z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100%
  93. height: {
  94. type: String,
  95. default: u.gc('height', '')
  96. },
  97. //z-paging的宽度,优先级低于pagingStyle中设置的width;传字符串,如100px、100rpx、100%
  98. width: {
  99. type: String,
  100. default: u.gc('width', '')
  101. },
  102. //z-paging的背景色,优先级低于pagingStyle中设置的background。传字符串,如"#ffffff"
  103. bgColor: {
  104. type: String,
  105. default: u.gc('bgColor', '')
  106. },
  107. //设置z-paging的容器(插槽的父view)的style
  108. pagingContentStyle: {
  109. type: Object,
  110. default: function() {
  111. return u.gc('pagingContentStyle', {});
  112. },
  113. },
  114. //z-paging是否自动高度,若自动高度则会自动铺满屏幕
  115. autoHeight: {
  116. type: Boolean,
  117. default: u.gc('autoHeight', false)
  118. },
  119. //z-paging是否自动高度时,附加的高度,注意添加单位px或rpx,若需要减少高度,则传负数
  120. autoHeightAddition: {
  121. type: [Number, String],
  122. default: u.gc('autoHeightAddition', '0px')
  123. },
  124. //loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认black
  125. defaultThemeStyle: {
  126. type: String,
  127. default: u.gc('defaultThemeStyle', 'black')
  128. },
  129. //z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效)
  130. fixed: {
  131. type: Boolean,
  132. default: u.gc('fixed', true)
  133. },
  134. //是否开启底部安全区域适配
  135. safeAreaInsetBottom: {
  136. type: Boolean,
  137. default: u.gc('safeAreaInsetBottom', false)
  138. },
  139. //开启底部安全区域适配后,是否使用placeholder形式实现,默认为否。为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域
  140. useSafeAreaPlaceholder: {
  141. type: Boolean,
  142. default: u.gc('useSafeAreaPlaceholder', false)
  143. },
  144. //slot="top"的view的z-index,默认为99,仅使用页面滚动时有效
  145. topZIndex: {
  146. type: Number,
  147. default: u.gc('topZIndex', 99)
  148. },
  149. //z-paging内容容器父view的z-index,默认为1
  150. superContentZIndex: {
  151. type: Number,
  152. default: u.gc('superContentZIndex', 1)
  153. },
  154. //z-paging内容容器部分的z-index,默认为10
  155. contentZIndex: {
  156. type: Number,
  157. default: u.gc('contentZIndex', 10)
  158. },
  159. //使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是
  160. autoFullHeight: {
  161. type: Boolean,
  162. default: u.gc('autoFullHeight', true)
  163. },
  164. //是否监听列表触摸方向改变,默认为否
  165. watchTouchDirectionChange: {
  166. type: Boolean,
  167. default: u.gc('watchTouchDirectionChange', false)
  168. },
  169. },
  170. created(){
  171. if (this.createdReload && !this.refresherOnly && this.auto) {
  172. this._startLoading();
  173. this._preReload();
  174. }
  175. },
  176. mounted() {
  177. this.wxsPropType = u.getTime().toString();
  178. this.renderJsIgnore;
  179. if (!this.createdReload && !this.refresherOnly && this.auto) {
  180. this.$nextTick(() => {
  181. this._preReload();
  182. })
  183. }
  184. this.finalUseCache && this._setListByLocalCache();
  185. let delay = 0;
  186. // #ifdef H5 || MP
  187. delay = 100;
  188. // #endif
  189. this.$nextTick(() => {
  190. this.systemInfo = uni.getSystemInfoSync();
  191. !this.usePageScroll && this.autoHeight && this._setAutoHeight();
  192. this.loaded = true;
  193. })
  194. this.updatePageScrollTopHeight();
  195. this.updatePageScrollBottomHeight();
  196. this._updateLeftAndRightWidth();
  197. if (this.finalRefresherEnabled && this.useCustomRefresher) {
  198. this.$nextTick(() => {
  199. this.isTouchmoving = true;
  200. })
  201. }
  202. this._onEmit();
  203. // #ifdef APP-NVUE
  204. if (!this.isIos && !this.useChatRecordMode) {
  205. this.nLoadingMoreFixedHeight = true;
  206. }
  207. this._nUpdateRefresherWidth();
  208. // #endif
  209. // #ifndef APP-NVUE
  210. this.finalUseVirtualList && this._virtualListInit();
  211. // #endif
  212. // #ifndef APP-PLUS
  213. this.$nextTick(() => {
  214. setTimeout(() => {
  215. this._getCssSafeAreaInsetBottom();
  216. },delay)
  217. })
  218. // #endif
  219. },
  220. destroyed() {
  221. this._offEmit();
  222. },
  223. // #ifdef VUE3
  224. unmounted() {
  225. this._offEmit();
  226. },
  227. // #endif
  228. watch: {
  229. defaultThemeStyle: {
  230. handler(newVal) {
  231. if (newVal.length) {
  232. this.finalRefresherDefaultStyle = newVal;
  233. }
  234. },
  235. immediate: true
  236. },
  237. autoHeight(newVal) {
  238. this.loaded && !this.usePageScroll && this._setAutoHeight(newVal);
  239. },
  240. autoHeightAddition(newVal) {
  241. this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal);
  242. },
  243. },
  244. computed: {
  245. finalPagingStyle() {
  246. const pagingStyle = this.pagingStyle;
  247. if (!this.systemInfo) return pagingStyle;
  248. const windowTop = this.windowTop;
  249. const windowBottom = this.windowBottom;
  250. if (!this.usePageScroll && this.fixed) {
  251. if (windowTop && !pagingStyle.top) {
  252. pagingStyle.top = windowTop + 'px';
  253. }
  254. if (windowBottom && !pagingStyle.bottom) {
  255. pagingStyle.bottom = windowBottom + 'px';
  256. }
  257. }
  258. if (this.bgColor.length && !pagingStyle['background']) {
  259. pagingStyle['background'] = this.bgColor;
  260. }
  261. if (this.height.length && !pagingStyle['height']) {
  262. pagingStyle['height'] = this.height;
  263. }
  264. if (this.width.length && !pagingStyle['width']) {
  265. pagingStyle['width'] = this.width;
  266. }
  267. return pagingStyle;
  268. },
  269. finalLowerThreshold() {
  270. return u.convertTextToPx(this.lowerThreshold);
  271. },
  272. finalPagingContentStyle() {
  273. if (this.contentZIndex != 1) {
  274. this.pagingContentStyle['z-index'] = this.contentZIndex;
  275. this.pagingContentStyle['position'] = 'relative';
  276. }
  277. return this.pagingContentStyle;
  278. },
  279. safeAreaBottom() {
  280. if (!this.systemInfo) return 0;
  281. let safeAreaBottom = 0;
  282. // #ifdef APP-PLUS
  283. safeAreaBottom = this.systemInfo.safeAreaInsets.bottom || 0;
  284. // #endif
  285. // #ifndef APP-PLUS
  286. safeAreaBottom = this.cssSafeAreaInsetBottom === -1 ? 0 : this.cssSafeAreaInsetBottom;
  287. // #endif
  288. return safeAreaBottom;
  289. },
  290. renderJsIgnore() {
  291. if ((this.usePageScroll && this.useChatRecordMode) || !this.refresherEnabled || !this.useCustomRefresher) {
  292. this.$nextTick(() => {
  293. this.renderPropScrollTop = 10;
  294. })
  295. }
  296. return 0;
  297. },
  298. windowHeight() {
  299. return !this.systemInfo ? 0 : this.systemInfo.windowHeight || 0;
  300. },
  301. windowTop() {
  302. //暂时修复vue3中隐藏系统导航栏后windowTop获取不正确的问题,具体bug详见https://ask.dcloud.net.cn/question/141634
  303. //感谢litangyu!!https://github.com/SmileZXLee/uni-z-paging/issues/25
  304. // #ifdef VUE3 && H5
  305. const pageHeadNode = document.getElementsByTagName("uni-page-head");
  306. if (!pageHeadNode.length) return 0;
  307. // #endif
  308. return !this.systemInfo ? 0 : this.systemInfo.windowTop || 0;
  309. },
  310. windowBottom() {
  311. if (!this.systemInfo) return 0;
  312. let windowBottom = this.systemInfo.windowBottom || 0;
  313. if (this.safeAreaInsetBottom && !this.useSafeAreaPlaceholder) {
  314. windowBottom += this.safeAreaBottom;
  315. }
  316. return windowBottom;
  317. },
  318. isOldWebView() {
  319. // #ifndef APP-NVUE || MP-KUAISHOU
  320. try {
  321. const systemInfos = systemInfo.system.split(' ');
  322. const deviceType = systemInfos[0];
  323. const version = parseInt(systemInfos[1].slice(0,1));
  324. if ((deviceType === 'iOS' && version <= 10) || (deviceType === 'Android' && version <= 6)) {
  325. return true;
  326. }
  327. } catch(e){
  328. return false;
  329. }
  330. // #endif
  331. return false;
  332. },
  333. isIosAndH5(){
  334. // #ifndef H5
  335. return false;
  336. // #endif
  337. return this.isIos;
  338. }
  339. },
  340. methods: {
  341. //当前版本号
  342. getVersion() {
  343. return `z-paging v${zConstant.version}`;
  344. },
  345. //设置nvue List的specialEffects
  346. setSpecialEffects(args) {
  347. this.setListSpecialEffects(args);
  348. },
  349. //与setSpecialEffects等效,兼容旧版本
  350. setListSpecialEffects(args) {
  351. this.nFixFreezing = args && Object.keys(args).length;
  352. if (this.isIos) {
  353. this.privateRefresherEnabled = 0;
  354. }
  355. if (!this.usePageScroll) {
  356. this.$refs['zp-n-list'].setSpecialEffects(args);
  357. }
  358. },
  359. //使手机发生较短时间的振动(15ms)
  360. _doVibrateShort() {
  361. // #ifdef APP-PLUS
  362. if (this.isIos) {
  363. const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator');
  364. const feedbackGenerator = new UISelectionFeedbackGenerator();
  365. feedbackGenerator.init();
  366. setTimeout(() => {
  367. feedbackGenerator.selectionChanged();
  368. },0)
  369. } else {
  370. plus.device.vibrate(15);
  371. }
  372. // #endif
  373. // #ifndef APP-PLUS
  374. uni.vibrateShort();
  375. // #endif
  376. },
  377. //检测scrollView是否要铺满屏幕
  378. _doCheckScrollViewShouldFullHeight(totalData) {
  379. if (this.autoFullHeight && this.usePageScroll && this.isTotalChangeFromAddData) {
  380. // #ifndef APP-NVUE
  381. this.$nextTick(() => {
  382. this._checkScrollViewShouldFullHeight((scrollViewNode, pagingContainerNode) => {
  383. this._preCheckShowNoMoreInside(totalData, scrollViewNode, pagingContainerNode)
  384. });
  385. })
  386. // #endif
  387. // #ifdef APP-NVUE
  388. this._preCheckShowNoMoreInside(totalData)
  389. // #endif
  390. } else {
  391. this._preCheckShowNoMoreInside(totalData)
  392. }
  393. },
  394. //检测z-paging是否要全屏覆盖(当使用页面滚动并且不满全屏时,默认z-paging需要铺满全屏,避免数据过少时内部的empty-view无法正确展示)
  395. async _checkScrollViewShouldFullHeight(callback) {
  396. try {
  397. const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
  398. const pagingContainerNode = await this._getNodeClientRect('.zp-paging-container-content');
  399. if (!scrollViewNode || !pagingContainerNode) return;
  400. const scrollViewHeight = pagingContainerNode[0].height;
  401. const scrollViewTop = scrollViewNode[0].top;
  402. if (this.isAddedData && scrollViewHeight + scrollViewTop <= this.windowHeight) {
  403. this._setAutoHeight(true, scrollViewNode);
  404. callback(scrollViewNode, pagingContainerNode);
  405. } else {
  406. this._setAutoHeight(false);
  407. callback(null, null);
  408. }
  409. } catch (e) {
  410. callback(null, null);
  411. }
  412. },
  413. //设置z-paging高度
  414. async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) {
  415. let heightKey = 'min-height';
  416. // #ifndef APP-NVUE
  417. heightKey = 'min-height';
  418. // #endif
  419. try {
  420. if (shouldFullHeight) {
  421. let finalScrollViewNode = scrollViewNode ? scrollViewNode : await this._getNodeClientRect('.zp-scroll-view');
  422. let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom');
  423. if (finalScrollViewNode) {
  424. const scrollViewTop = finalScrollViewNode[0].top;
  425. let scrollViewHeight = this.windowHeight - scrollViewTop;
  426. if(finalScrollBottomNode){
  427. scrollViewHeight -= finalScrollBottomNode[0].height;
  428. }
  429. const additionHeight = u.convertTextToPx(this.autoHeightAddition);
  430. const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px !important';
  431. this.$set(this.scrollViewStyle, heightKey, finalHeight);
  432. this.$set(this.scrollViewInStyle, heightKey, finalHeight);
  433. }
  434. } else {
  435. this.$delete(this.scrollViewStyle, heightKey);
  436. this.$delete(this.scrollViewInStyle, heightKey);
  437. }
  438. } catch (e) {}
  439. },
  440. //通过获取css设置的底部安全区域占位view高度设置bottom距离
  441. _getCssSafeAreaInsetBottom(){
  442. this._getNodeClientRect('.zp-safe-area-inset-bottom').then((res) => {
  443. if (res) {
  444. this.cssSafeAreaInsetBottom = res[0].height;
  445. if (this.safeAreaInsetBottom) {
  446. this.updatePageScrollBottomHeight();
  447. }
  448. }
  449. });
  450. },
  451. //触发更新是否超出页面状态
  452. _updateInsideOfPaging() {
  453. if (this.insideMore && this.insideOfPaging === true) {
  454. setTimeout(() => {
  455. this.doLoadMore();
  456. }, 200)
  457. }
  458. },
  459. //获取节点尺寸
  460. _getNodeClientRect(select, inThis = true, scrollOffset = false) {
  461. // #ifdef APP-NVUE
  462. select = select.replace('.', '').replace('#', '');
  463. const ref = this.$refs[select];
  464. return new Promise((resolve, reject) => {
  465. if (ref) {
  466. weexDom.getComponentRect(ref, option => {
  467. resolve(option && option.result ? [option.size] : false);
  468. })
  469. } else {
  470. resolve(false);
  471. }
  472. });
  473. return;
  474. // #endif
  475. //#ifdef MP-ALIPAY
  476. inThis = false;
  477. //#endif
  478. let res = inThis ? uni.createSelectorQuery().in(this) : uni.createSelectorQuery();
  479. scrollOffset ? res.select(select).scrollOffset() : res.select(select).boundingClientRect();
  480. return new Promise((resolve, reject) => {
  481. res.exec(data => {
  482. resolve((data && data != '' && data != undefined && data.length) ? data : false);
  483. });
  484. });
  485. },
  486. //清除timeout
  487. _cleanTimeout(timeout) {
  488. if (timeout) {
  489. clearTimeout(timeout);
  490. timeout = null;
  491. }
  492. return timeout;
  493. },
  494. //添加全局emit监听
  495. _onEmit() {
  496. uni.$on(c.errorUpdateKey, () => {
  497. if (this.loading) {
  498. this.complete(false);
  499. }
  500. })
  501. uni.$on(c.completeUpdateKey, (data) => {
  502. setTimeout(() => {
  503. if (this.loading) {
  504. if (!this.disabledCompleteEmit) {
  505. const type = data.type || 'normal';
  506. const list = data.list || data;
  507. const rule = data.rule;
  508. this.fromCompleteEmit = true;
  509. switch (type){
  510. case 'normal':
  511. this.complete(list);
  512. break;
  513. case 'total':
  514. this.completeByTotal(list, rule);
  515. break;
  516. case 'nomore':
  517. this.completeByNoMore(list, rule);
  518. break;
  519. case 'key':
  520. this.completeByKey(list, rule);
  521. break;
  522. default:
  523. break;
  524. }
  525. } else {
  526. this.disabledCompleteEmit = false;
  527. }
  528. }
  529. }, 1);
  530. })
  531. },
  532. //销毁全局emit和listener监听
  533. _offEmit(){
  534. uni.$off(c.errorUpdateKey);
  535. uni.$off(c.completeUpdateKey);
  536. }
  537. },
  538. };