index.js 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. import { use } from '../utils';
  2. import Loading from '../loading';
  3. import { on, off } from '../utils/event';
  4. import {
  5. getScrollTop,
  6. getElementTop,
  7. getVisibleHeight,
  8. getScrollEventTarget
  9. } from '../utils/scroll';
  10. const [sfc, bem, t] = use('list');
  11. export default sfc({
  12. model: {
  13. prop: 'loading'
  14. },
  15. props: {
  16. error: Boolean,
  17. loading: Boolean,
  18. finished: Boolean,
  19. errorText: String,
  20. loadingText: String,
  21. finishedText: String,
  22. immediateCheck: {
  23. type: Boolean,
  24. default: true
  25. },
  26. offset: {
  27. type: Number,
  28. default: 300
  29. },
  30. direction: {
  31. type: String,
  32. default: 'down'
  33. }
  34. },
  35. mounted() {
  36. this.scroller = getScrollEventTarget(this.$el);
  37. this.handler(true);
  38. if (this.immediateCheck) {
  39. this.$nextTick(this.check);
  40. }
  41. },
  42. destroyed() {
  43. this.handler(false);
  44. },
  45. activated() {
  46. this.handler(true);
  47. },
  48. deactivated() {
  49. this.handler(false);
  50. },
  51. watch: {
  52. loading() {
  53. this.$nextTick(this.check);
  54. },
  55. finished() {
  56. this.$nextTick(this.check);
  57. }
  58. },
  59. methods: {
  60. check() {
  61. if (this.loading || this.finished || this.error) {
  62. return;
  63. }
  64. const el = this.$el;
  65. const { scroller } = this;
  66. const scrollerHeight = getVisibleHeight(scroller);
  67. /* istanbul ignore next */
  68. if (
  69. !scrollerHeight ||
  70. window.getComputedStyle(el).display === 'none' ||
  71. el.offsetParent === null
  72. ) {
  73. return;
  74. }
  75. const { offset, direction } = this;
  76. function isReachEdge() {
  77. if (el === scroller) {
  78. const scrollTop = getScrollTop(el);
  79. if (direction === 'up') {
  80. return scrollTop <= offset;
  81. }
  82. const targetBottom = scrollTop + scrollerHeight;
  83. return scroller.scrollHeight - targetBottom <= offset;
  84. }
  85. if (direction === 'up') {
  86. return getScrollTop(scroller) - getElementTop(el) <= offset;
  87. }
  88. const elBottom = getElementTop(el) + getVisibleHeight(el) - getElementTop(scroller);
  89. return elBottom - scrollerHeight <= offset;
  90. }
  91. if (isReachEdge()) {
  92. this.$emit('input', true);
  93. this.$emit('load');
  94. }
  95. },
  96. clickErrorText() {
  97. this.$emit('update:error', false);
  98. this.$nextTick(this.check);
  99. },
  100. handler(bind) {
  101. /* istanbul ignore else */
  102. if (this.binded !== bind) {
  103. this.binded = bind;
  104. (bind ? on : off)(this.scroller, 'scroll', this.check);
  105. }
  106. }
  107. },
  108. render(h) {
  109. return (
  110. <div class={bem()}>
  111. {this.direction === 'down' && this.slots()}
  112. {this.loading && (
  113. <div class={bem('loading')} key="loading">
  114. {this.slots('loading') || [
  115. <Loading class={bem('loading-icon')} />,
  116. <span class={bem('loading-text')}>{this.loadingText || t('loading')}</span>
  117. ]}
  118. </div>
  119. )}
  120. {this.finished && this.finishedText && (
  121. <div class={bem('finished-text')}>{this.finishedText}</div>
  122. )}
  123. {this.error && this.errorText && (
  124. <div onClick={this.clickErrorText} class={bem('error-text')}>
  125. {this.errorText}
  126. </div>
  127. )}
  128. {this.direction === 'up' && this.slots()}
  129. </div>
  130. );
  131. }
  132. });