z-paging-main.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538
  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 commonLayoutModule from './modules/common-layout'
  10. import dataHandleModule from './modules/data-handle'
  11. import i18nModule from './modules/i18n'
  12. import nvueModule from './modules/nvue'
  13. import emptyModule from './modules/empty'
  14. import refresherModule from './modules/refresher'
  15. import loadMoreModule from './modules/load-more'
  16. import loadingModule from './modules/loading'
  17. import chatRecordModerModule from './modules/chat-record-mode'
  18. import scrollerModule from './modules/scroller'
  19. import backToTopModule from './modules/back-to-top'
  20. import virtualListModule from './modules/virtual-list'
  21. import Enum from './z-paging-enum'
  22. const systemInfo = u.getSystemInfoSync();
  23. export default {
  24. name: "z-paging",
  25. components: {
  26. zPagingRefresh,
  27. zPagingLoadMore,
  28. zPagingEmptyView
  29. },
  30. mixins: [
  31. commonLayoutModule,
  32. dataHandleModule,
  33. i18nModule,
  34. nvueModule,
  35. emptyModule,
  36. refresherModule,
  37. loadMoreModule,
  38. loadingModule,
  39. chatRecordModerModule,
  40. scrollerModule,
  41. backToTopModule,
  42. virtualListModule
  43. ],
  44. data() {
  45. return {
  46. // --------------静态资源---------------
  47. base64BackToTop: zStatic.base64BackToTop,
  48. // -------------全局数据相关--------------
  49. // 当前加载类型
  50. loadingType: Enum.LoadingType.Refresher,
  51. requestTimeStamp: 0,
  52. wxsPropType: '',
  53. renderPropScrollTop: -1,
  54. checkScrolledToBottomTimeOut: null,
  55. cacheTopHeight: -1,
  56. statusBarHeight: systemInfo.statusBarHeight,
  57. scrollViewHeight: 0,
  58. pagingOrgTop: -1,
  59. // --------------状态&判断---------------
  60. insideOfPaging: -1,
  61. isLoadFailed: false,
  62. isIos: systemInfo.platform === 'ios',
  63. disabledBounce: false,
  64. fromCompleteEmit: false,
  65. disabledCompleteEmit: false,
  66. pageLaunched: false,
  67. active: false,
  68. // ---------------wxs相关---------------
  69. wxsIsScrollTopInTopRange: true,
  70. wxsScrollTop: 0,
  71. wxsPageScrollTop: 0,
  72. wxsOnPullingDown: false,
  73. };
  74. },
  75. props: {
  76. // 调用complete后延迟处理的时间,单位为毫秒,默认0毫秒,优先级高于minDelay
  77. delay: {
  78. type: [Number, String],
  79. default: u.gc('delay', 0),
  80. },
  81. // 触发@query后最小延迟处理的时间,单位为毫秒,默认0毫秒,优先级低于delay(假设设置为300毫秒,若分页请求时间小于300毫秒,则在调用complete后延迟[300毫秒-请求时长];若请求时长大于300毫秒,则不延迟),当show-refresher-when-reload为true或reload(true)时,其最小值为400
  82. minDelay: {
  83. type: [Number, String],
  84. default: u.gc('minDelay', 0),
  85. },
  86. // 设置z-paging的style,部分平台(如微信小程序)无法直接修改组件的style,可使用此属性代替
  87. pagingStyle: {
  88. type: Object,
  89. default: u.gc('pagingStyle', {}),
  90. },
  91. // 设置z-paging的class,优先级低于pagingStyle和height、width、maxWidth、bgColor
  92. pagingClass: {
  93. type: [String, Array, Object],
  94. default: u.gc('pagingClass', ''),
  95. },
  96. // z-paging的高度,优先级低于pagingStyle中设置的height;传字符串,如100px、100rpx、100%
  97. height: {
  98. type: String,
  99. default: u.gc('height', '')
  100. },
  101. // z-paging的宽度,优先级低于pagingStyle中设置的width;传字符串,如100px、100rpx、100%
  102. width: {
  103. type: String,
  104. default: u.gc('width', '')
  105. },
  106. // z-paging的最大宽度,优先级低于pagingStyle中设置的max-width;传字符串,如100px、100rpx、100%。默认为空,也就是铺满窗口宽度,若设置了特定值则会自动添加margin: 0 auto
  107. maxWidth: {
  108. type: String,
  109. default: u.gc('maxWidth', '')
  110. },
  111. // z-paging的背景色,优先级低于pagingStyle中设置的background。传字符串,如"#ffffff"
  112. bgColor: {
  113. type: String,
  114. default: u.gc('bgColor', '')
  115. },
  116. // 设置z-paging的容器(插槽的父view)的style
  117. pagingContentStyle: {
  118. type: Object,
  119. default: u.gc('pagingContentStyle', {}),
  120. },
  121. // z-paging是否自动高度,若自动高度则会自动铺满屏幕
  122. autoHeight: {
  123. type: Boolean,
  124. default: u.gc('autoHeight', false)
  125. },
  126. // z-paging是否自动高度时,附加的高度,注意添加单位px或rpx,若需要减少高度,则传负数
  127. autoHeightAddition: {
  128. type: [Number, String],
  129. default: u.gc('autoHeightAddition', '0px')
  130. },
  131. // loading(下拉刷新、上拉加载更多)的主题样式,支持black,white,默认black
  132. defaultThemeStyle: {
  133. type: String,
  134. default: u.gc('defaultThemeStyle', 'black')
  135. },
  136. // z-paging是否使用fixed布局,若使用fixed布局,则z-paging的父view无需固定高度,z-paging高度默认为100%,默认为是(当使用内置scroll-view滚动时有效)
  137. fixed: {
  138. type: Boolean,
  139. default: u.gc('fixed', true)
  140. },
  141. // 是否开启底部安全区域适配
  142. safeAreaInsetBottom: {
  143. type: Boolean,
  144. default: u.gc('safeAreaInsetBottom', false)
  145. },
  146. // 开启底部安全区域适配后,是否使用placeholder形式实现,默认为否。为否时滚动区域会自动避开底部安全区域,也就是所有滚动内容都不会挡住底部安全区域,若设置为是,则滚动时滚动内容会挡住底部安全区域,但是当滚动到底部时才会避开底部安全区域
  147. useSafeAreaPlaceholder: {
  148. type: Boolean,
  149. default: u.gc('useSafeAreaPlaceholder', false)
  150. },
  151. // z-paging bottom的背景色,默认透明,传字符串,如"#ffffff"
  152. bottomBgColor: {
  153. type: String,
  154. default: u.gc('bottomBgColor', '')
  155. },
  156. // slot="top"的view的z-index,默认为99,仅使用页面滚动时有效
  157. topZIndex: {
  158. type: Number,
  159. default: u.gc('topZIndex', 99)
  160. },
  161. // z-paging内容容器父view的z-index,默认为1
  162. superContentZIndex: {
  163. type: Number,
  164. default: u.gc('superContentZIndex', 1)
  165. },
  166. // z-paging内容容器部分的z-index,默认为1
  167. contentZIndex: {
  168. type: Number,
  169. default: u.gc('contentZIndex', 1)
  170. },
  171. // z-paging二楼的z-index,默认为100
  172. f2ZIndex: {
  173. type: Number,
  174. default: u.gc('f2ZIndex', 100)
  175. },
  176. // 使用页面滚动时,是否在不满屏时自动填充满屏幕,默认为是
  177. autoFullHeight: {
  178. type: Boolean,
  179. default: u.gc('autoFullHeight', true)
  180. },
  181. // 是否监听列表触摸方向改变,默认为否
  182. watchTouchDirectionChange: {
  183. type: Boolean,
  184. default: u.gc('watchTouchDirectionChange', false)
  185. },
  186. // 是否监听列表滚动方向改变,默认为否
  187. watchScrollDirectionChange: {
  188. type: Boolean,
  189. default: u.gc('watchScrollDirectionChange', false)
  190. },
  191. // 是否只使用基础布局,设置为true后将关闭mounted自动请求数据、关闭下拉刷新和滚动到底部加载更多,强制隐藏空数据图。默认为否
  192. layoutOnly: {
  193. type: Boolean,
  194. default: u.gc('layoutOnly', false)
  195. },
  196. // z-paging中布局的单位,默认为rpx
  197. unit: {
  198. type: String,
  199. default: u.gc('unit', 'rpx')
  200. }
  201. },
  202. created() {
  203. // 组件创建时,检测是否开始加载状态
  204. if (this.createdReload && !this.isOnly && this.auto) {
  205. this._startLoading();
  206. this.$nextTick(this._preReload);
  207. }
  208. },
  209. mounted() {
  210. this.active = true;
  211. this.wxsPropType = u.getTime().toString();
  212. this.renderJsIgnore;
  213. if (!this.createdReload && !this.isOnly && this.auto) {
  214. // 开始预加载
  215. u.delay(() => this.$nextTick(this._preReload), 0);
  216. }
  217. // 如果开启了列表缓存,在初始化的时候通过缓存数据填充列表数据
  218. this.finalUseCache && this._setListByLocalCache();
  219. let delay = 0;
  220. // #ifdef H5 || MP
  221. delay = c.delayTime;
  222. // #endif
  223. this.systemInfo = u.getSystemInfoSync();
  224. this.$nextTick(() => {
  225. // 初始化systemInfo
  226. this.systemInfo = u.getSystemInfoSync();
  227. // 初始化z-paging高度
  228. !this.usePageScroll && this.autoHeight && this._setAutoHeight();
  229. this.loaded = true;
  230. u.delay(() => {
  231. // 更新fixed模式下z-paging的布局,主要是更新windowTop、windowBottom
  232. this.updateFixedLayout();
  233. // 更新缓存中z-paging整个内容容器高度
  234. this._updateCachedSuperContentHeight();
  235. // 更新z-paging中scroll-view高度
  236. this._updateScrollViewHeight();
  237. });
  238. })
  239. // 初始化页面滚动模式下slot="top"、slot="bottom"高度
  240. this.updatePageScrollTopHeight();
  241. this.updatePageScrollBottomHeight();
  242. // 初始化slot="left"、slot="right"宽度
  243. this.updateLeftAndRightWidth();
  244. if (this.finalRefresherEnabled && this.useCustomRefresher) {
  245. this.$nextTick(() => {
  246. this.isTouchmoving = true;
  247. })
  248. }
  249. if (!this.layoutOnly) {
  250. // 监听uni.$emit中全局emit的complete error等事件
  251. this._onEmit();
  252. }
  253. // #ifdef APP-NVUE
  254. if (!this.isIos && !this.useChatRecordMode) {
  255. this.nLoadingMoreFixedHeight = true;
  256. }
  257. // 在nvue中更新nvue下拉刷新view容器的宽度,而不是写死默认的750rpx,需要考虑列表宽度不是铺满屏幕的情况
  258. this._nUpdateRefresherWidth();
  259. // #endif
  260. // #ifndef APP-PLUS
  261. this.$nextTick(() => {
  262. // 非app平台中,在通过获取css设置的底部安全区域占位view高度设置bottom距离后,更新页面滚动底部高度
  263. setTimeout(() => {
  264. this._getCssSafeAreaInsetBottom(() => this.safeAreaInsetBottom && this.updatePageScrollBottomHeight());
  265. }, delay)
  266. })
  267. // #endif
  268. },
  269. destroyed() {
  270. this._handleUnmounted();
  271. },
  272. // #ifdef VUE3
  273. unmounted() {
  274. this._handleUnmounted();
  275. },
  276. // #endif
  277. watch: {
  278. defaultThemeStyle: {
  279. handler(newVal) {
  280. if (newVal.length) {
  281. this.finalRefresherDefaultStyle = newVal;
  282. }
  283. },
  284. immediate: true
  285. },
  286. autoHeight(newVal) {
  287. this.loaded && !this.usePageScroll && this._setAutoHeight(newVal);
  288. },
  289. autoHeightAddition(newVal) {
  290. this.loaded && !this.usePageScroll && this.autoHeight && this._setAutoHeight(newVal);
  291. },
  292. },
  293. computed: {
  294. // 当前z-paging的内置样式
  295. finalPagingStyle() {
  296. const pagingStyle = { ...this.pagingStyle };
  297. if (!this.systemInfo) return pagingStyle;
  298. const { windowTop, windowBottom } = this;
  299. if (!this.usePageScroll && this.fixed) {
  300. if (windowTop && !pagingStyle.top) {
  301. pagingStyle.top = windowTop + 'px';
  302. }
  303. if (windowBottom && !pagingStyle.bottom) {
  304. pagingStyle.bottom = windowBottom + 'px';
  305. }
  306. }
  307. if (this.bgColor.length && !pagingStyle['background']) {
  308. pagingStyle['background'] = this.bgColor;
  309. }
  310. if (this.height.length && !pagingStyle['height']) {
  311. pagingStyle['height'] = this.height;
  312. }
  313. if (this.width.length && !pagingStyle['width']) {
  314. pagingStyle['width'] = this.width;
  315. }
  316. if (this.maxWidth.length && !pagingStyle['max-width']) {
  317. pagingStyle['max-width'] = this.maxWidth;
  318. pagingStyle['margin'] = '0 auto';
  319. }
  320. return pagingStyle;
  321. },
  322. // 当前z-paging内容的样式
  323. finalPagingContentStyle() {
  324. if (this.contentZIndex != 1) {
  325. this.pagingContentStyle['z-index'] = this.contentZIndex;
  326. this.pagingContentStyle['position'] = 'relative';
  327. }
  328. return this.pagingContentStyle;
  329. },
  330. // 最终的当前开启安全区域适配后,是否使用placeholder形式实现。如果slot=bottom存在,则应当交由固定在底部的view处理,因此需排除此情况
  331. finalUseSafeAreaPlaceholder() {
  332. return this.useSafeAreaPlaceholder && !this.zSlots.bottom;
  333. },
  334. renderJsIgnore() {
  335. if ((this.usePageScroll && this.useChatRecordMode) || (!this.refresherEnabled && this.scrollable) || !this.useCustomRefresher) {
  336. this.$nextTick(() => {
  337. this.renderPropScrollTop = 10;
  338. })
  339. }
  340. return 0;
  341. },
  342. windowHeight() {
  343. if (!this.systemInfo) return 0;
  344. return this.systemInfo.windowHeight || 0;
  345. },
  346. windowBottom() {
  347. if (!this.systemInfo) return 0;
  348. return this.systemInfo.windowBottom || 0;
  349. },
  350. // 是否是ios+h5
  351. isIosAndH5() {
  352. // #ifndef H5
  353. return false;
  354. // #endif
  355. return this.isIos;
  356. },
  357. // 是否是只使用基础布局或者只使用下拉刷新
  358. isOnly() {
  359. return this.layoutOnly || this.refresherOnly;
  360. },
  361. },
  362. methods: {
  363. // 当前版本号
  364. getVersion() {
  365. return `z-paging v${c.version}`;
  366. },
  367. // 设置nvue List的specialEffects
  368. setSpecialEffects(args) {
  369. this.setListSpecialEffects(args);
  370. },
  371. // 与setSpecialEffects等效,兼容旧版本
  372. setListSpecialEffects(args) {
  373. this.nFixFreezing = args && Object.keys(args).length;
  374. if (this.isIos) {
  375. this.privateRefresherEnabled = 0;
  376. }
  377. !this.usePageScroll && this.$refs['zp-n-list'].setSpecialEffects(args);
  378. },
  379. // #ifdef APP-VUE
  380. // 当app长时间进入后台后进入前台,因系统内存管理导致app重新加载时,进行一些适配处理
  381. _handlePageLaunch() {
  382. // 首次触发不进行处理,只有进入后台后打开app重新加载时才处理
  383. if (this.pageLaunched) {
  384. // 解决在vue3+ios中,app ReLaunch时顶部下拉刷新展示位置向下偏移的问题
  385. // #ifdef VUE3
  386. this.refresherThresholdUpdateTag = 1;
  387. this.$nextTick(() => {
  388. this.refresherThresholdUpdateTag = 0;
  389. })
  390. // #endif
  391. // 解决使用虚拟列表时,app ReLaunch时白屏问题
  392. this._checkVirtualListScroll();
  393. }
  394. this.pageLaunched = true;
  395. },
  396. // #endif
  397. // 使手机发生较短时间的振动(15ms)
  398. _doVibrateShort() {
  399. // #ifndef H5
  400. // #ifdef APP-PLUS
  401. if (this.isIos) {
  402. const UISelectionFeedbackGenerator = plus.ios.importClass('UISelectionFeedbackGenerator');
  403. const feedbackGenerator = new UISelectionFeedbackGenerator();
  404. feedbackGenerator.init();
  405. setTimeout(() => {
  406. feedbackGenerator.selectionChanged();
  407. }, 0)
  408. } else {
  409. plus.device.vibrate(15);
  410. }
  411. // #endif
  412. // #ifndef APP-PLUS
  413. uni.vibrateShort();
  414. // #endif
  415. // #endif
  416. },
  417. // 设置z-paging高度
  418. async _setAutoHeight(shouldFullHeight = true, scrollViewNode = null) {
  419. const heightKey = 'min-height';
  420. try {
  421. if (shouldFullHeight) {
  422. // 如果需要铺满全屏,则计算当前全屏可是区域的高度
  423. let finalScrollViewNode = scrollViewNode || await this._getNodeClientRect('.zp-scroll-view');
  424. let finalScrollBottomNode = await this._getNodeClientRect('.zp-page-bottom');
  425. if (finalScrollViewNode) {
  426. const scrollViewTop = finalScrollViewNode[0].top;
  427. let scrollViewHeight = this.windowHeight - scrollViewTop;
  428. scrollViewHeight -= finalScrollBottomNode ? finalScrollBottomNode[0].height : 0;
  429. const additionHeight = u.convertToPx(this.autoHeightAddition);
  430. // 在支付宝小程序中,添加!important会导致min-height失效,因此在支付宝小程序中需要去掉
  431. let importantSuffix = ' !important';
  432. // #ifdef MP-ALIPAY
  433. importantSuffix = '';
  434. // #endif
  435. const finalHeight = scrollViewHeight + additionHeight - (this.insideMore ? 1 : 0) + 'px' + importantSuffix;
  436. this.$set(this.scrollViewStyle, heightKey, finalHeight);
  437. this.$set(this.scrollViewInStyle, heightKey, finalHeight);
  438. }
  439. } else {
  440. this.$delete(this.scrollViewStyle, heightKey);
  441. this.$delete(this.scrollViewInStyle, heightKey);
  442. }
  443. } catch (e) {}
  444. },
  445. // 更新scroll-view高度
  446. async _updateScrollViewHeight() {
  447. const scrollViewNode = await this._getNodeClientRect('.zp-scroll-view');
  448. if (scrollViewNode) {
  449. const scrollViewNodeHeight = scrollViewNode[0].height;
  450. this.scrollViewHeight = scrollViewNodeHeight;
  451. this.pagingOrgTop = scrollViewNode[0].top;
  452. // 设置scroll-view内容器的最小高度等于scroll-view的高度(为了解决在快手小程序中内容较少时scroll-view内容器高度无法铺满scroll-view的问题)
  453. // #ifdef MP-KUAISHOU
  454. this.$set(this.scrollViewInStyle, 'min-height', scrollViewNodeHeight + 'px');
  455. // #endif
  456. }
  457. },
  458. // 组件销毁后续处理
  459. _handleUnmounted() {
  460. this.active = false;
  461. if (!this.layoutOnly) {
  462. this._offEmit();
  463. }
  464. // 取消监听键盘高度变化事件(H5、百度小程序、抖音小程序、飞书小程序、QQ小程序、快手小程序不支持)
  465. // #ifndef H5 || MP-BAIDU || MP-TOUTIAO || MP-QQ || MP-KUAISHOU
  466. this.useChatRecordMode && uni.offKeyboardHeightChange(this._handleKeyboardHeightChange);
  467. // #endif
  468. },
  469. // 触发更新是否超出页面状态
  470. _updateInsideOfPaging() {
  471. this.insideMore && this.insideOfPaging === true && setTimeout(this.doLoadMore, 200)
  472. },
  473. // 清除timeout
  474. _cleanTimeout(timeout) {
  475. if (timeout) {
  476. clearTimeout(timeout);
  477. timeout = null;
  478. }
  479. return timeout;
  480. },
  481. // 添加全局emit监听
  482. _onEmit() {
  483. uni.$on(c.errorUpdateKey, (errorMsg) => {
  484. if (this.loading) {
  485. if (!!errorMsg) {
  486. this.customerEmptyViewErrorText = errorMsg;
  487. }
  488. this.complete(false).catch(() => {});
  489. }
  490. })
  491. uni.$on(c.completeUpdateKey, (data) => {
  492. setTimeout(() => {
  493. if (this.loading) {
  494. if (!this.disabledCompleteEmit) {
  495. const type = data.type || 'normal';
  496. const list = data.list || data;
  497. const rule = data.rule;
  498. this.fromCompleteEmit = true;
  499. switch (type){
  500. case 'normal':
  501. this.complete(list);
  502. break;
  503. case 'total':
  504. this.completeByTotal(list, rule);
  505. break;
  506. case 'nomore':
  507. this.completeByNoMore(list, rule);
  508. break;
  509. case 'key':
  510. this.completeByKey(list, rule);
  511. break;
  512. default:
  513. break;
  514. }
  515. } else {
  516. this.disabledCompleteEmit = false;
  517. }
  518. }
  519. }, 1);
  520. })
  521. },
  522. // 销毁全局emit和listener监听
  523. _offEmit(){
  524. uni.$off(c.errorUpdateKey);
  525. uni.$off(c.completeUpdateKey);
  526. },
  527. },
  528. };