四零语境前端代码仓库
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

357 lines
9.9 KiB

  1. <template>
  2. <view class="yingbing-scroller"
  3. :refreshState="refreshState" :change:refreshState="pulldownwxs.refreshStateWatcher"
  4. :pulldownable="pulldownable" :change:pulldownable="pulldownwxs.pulldownableWatcher"
  5. :pullupable="pullupable" :change:pullupable="pulldownwxs.pullupableWatcher"
  6. @touchstart="pulldownwxs.touchstart"
  7. @touchmove="pulldownwxs.touchmove"
  8. @touchend="pulldownwxs.touchend"
  9. @touchcancel="pulldownwxs.touchcancel">
  10. <view class="yingbing-scroller-wrapper">
  11. <view class="yingbing-scroller-refresh">
  12. <refresh-loading size="30" :visible="pulldownStatus == 'end' ? false : Boolean(pulldownStatus)" :color="loadingColor"></refresh-loading>
  13. <text class="yingbing-scroller-refresh-text" :style="{color: loadingColor}">{{ pulldownText }}</text>
  14. </view>
  15. <view class="yingbing-scroll">
  16. <scroll-view
  17. scroll-anchoing
  18. class="yingbing-scroll-view"
  19. scroll-y
  20. :refresher-enabled="false"
  21. :scroll-into-view="scrollIntoViewId"
  22. :scroll-with-animation="scrollWithAnimation"
  23. :show-scrollbar="false"
  24. :scroll-top="scrollTop"
  25. :lower-threshold="100"
  26. @scroll="handleScroll" @scrolltolower="handleScrolltolower"
  27. @scrolltoupper="handleScrolltoupper">
  28. <view :id="'yingbing-scroll-item_' + index" v-for="(item, index) in data" :key="item.index + '_' + item.current">
  29. <!-- #ifdef MP -->
  30. <slot :name="'wx:' + index"></slot>
  31. <!-- #endif -->
  32. <!-- #ifndef MP -->
  33. <slot :item="item" :index="index"></slot>
  34. <!-- #endif -->
  35. </view>
  36. <view class="yingbing-scroller-refresh" v-if="loadmoreable" @tap="handleScrolltolower">
  37. <refresh-loading size="30" :visible="loadmoreStatus == 'end' ? false : Boolean(loadmoreStatus)" :color="loadingColor"></refresh-loading>
  38. <text class="yingbing-scroller-refresh-text">{{loadmoreText}}</text>
  39. </view>
  40. </scroll-view>
  41. </view>
  42. <view class="yingbing-scroller-refresh">
  43. <refresh-loading size="30" :visible="pullupStatus == 'end' ? false : Boolean(pullupStatus)" :color="loadingColor"></refresh-loading>
  44. <text class="yingbing-scroller-refresh-text" :style="{color: loadingColor}">{{ pullupText }}</text>
  45. </view>
  46. </view>
  47. </view>
  48. </template>
  49. <script>
  50. import RefreshLoading from '../loading/loading.vue'
  51. const readyHeight = 80
  52. export default {
  53. options: {
  54. addGlobalClass: true,
  55. virtualHost: true,//将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等,而是希望自定义组件内部的第一层节点能够响应 flex 布局或者样式由自定义组件本身完全决定
  56. },
  57. components: {
  58. RefreshLoading
  59. },
  60. inject: ['getPrevChapterDefaultText', 'getNextChapterDefaultText', 'getChapterReadyText', 'getChapterLoadingText', 'getChapterSuccessText', 'getChapterFailText', 'getPrevChapterEndText', 'getNextChapterEndText'],
  61. props: {
  62. data: {
  63. type: Array,
  64. default () {
  65. return new Array
  66. }
  67. },
  68. loadingColor: {
  69. type: String,
  70. default: '#333'
  71. },
  72. autoplay: {
  73. type: Boolean,
  74. default: false
  75. },
  76. pulldownable: {
  77. type: Boolean,
  78. default: false
  79. },
  80. pullupable: {
  81. type: Boolean,
  82. default: false
  83. },
  84. loadmoreable: {
  85. type: Boolean,
  86. default: false
  87. }
  88. },
  89. computed: {
  90. chapterReadyText () {
  91. return this.getChapterReadyText()
  92. },
  93. chapterLoadingText () {
  94. return this.getChapterLoadingText()
  95. },
  96. chapterSuccessText () {
  97. return this.getChapterSuccessText()
  98. },
  99. chapterFailText () {
  100. return this.getChapterFailText()
  101. },
  102. prevChapterDefaultText () {
  103. return this.getPrevChapterDefaultText()
  104. },
  105. prevChapterEndText() {
  106. return this.getPrevChapterEndText()
  107. },
  108. nextChapterDefaultText() {
  109. return this.getNextChapterDefaultText()
  110. },
  111. nextChapterEndText() {
  112. return this.getNextChapterEndText()
  113. },
  114. pulldownText () {
  115. return this.pulldownStatus == 'ready' ? this.chapterReadyText : this.pulldownStatus == 'success' ? this.chapterSuccessText : this.pulldownStatus == 'fail' ? this.chapterFailText : this.pulldownStatus == 'end' ? this.prevChapterEndText : this.prevChapterDefaultText
  116. },
  117. pullupText () {
  118. return this.pullupStatus == 'ready' ? this.chapterReadyText : this.pullupStatus == 'success' ? this.chapterSuccessText : this.pullupStatus == 'fail' ? this.chapterFailText : this.pullupStatus == 'end' ? this.nextChapterEndText : this.nextChapterDefaultText
  119. },
  120. loadmoreText () {
  121. return this.loadmoreStatus == 'ready' ? this.chapterLoadingText : this.loadmoreStatus == 'success' ? this.chapterSuccessText : this.loadmoreStatus == 'fail' ? this.chapterFailText : this.loadmoreStatus == 'end' ? this.nextChapterEndText : this.nextChapterDefaultText
  122. }
  123. },
  124. data () {
  125. return {
  126. refreshState: '',
  127. pulldownStatus: '',
  128. pullupStatus: '',
  129. loadmoreStatus: '',
  130. isLoadmore: false,
  131. scrollIntoViewId: '',
  132. scrollWithAnimation: false,
  133. scrollTop: 0,
  134. scrollTopRecord: 0
  135. }
  136. },
  137. created() {
  138. this.startAutoplay()
  139. },
  140. beforeDestroy() {
  141. this.stopAutoplay()
  142. },
  143. methods: {
  144. handleScroll (e) {
  145. this.scrollTopRecord = e.detail.scrollTop
  146. this.$emit('scroll', e)
  147. },
  148. handleScrolltolower (e) {
  149. if ( !this.loadmoreable || this.isLoadmore || this.loadmoreStatus == 'end' ) return
  150. this.isLoadmore = true
  151. this.loadmoreStatus = 'ready'
  152. this.$emit('loadmore', (state) => {
  153. this.loadmoreStatus = state
  154. this.isLoadmore = false
  155. })
  156. },
  157. handleScrolltoupper (e) {
  158. this.$emit('scrolltoupper', e)
  159. },
  160. pulldown () {
  161. if ( this.pulldownStatus == 'end' ) {
  162. this.stopRefresh()
  163. return
  164. }
  165. this.$emit('pulldown', (state) => {
  166. this.pulldownStatus = state
  167. this.stopRefresh()
  168. })
  169. },
  170. pullup () {
  171. if ( this.pullupStatus == 'end' ) {
  172. this.stopRefresh()
  173. return
  174. }
  175. this.$emit('pullup', (state) => {
  176. this.pullupStatus = state
  177. this.stopRefresh()
  178. })
  179. },
  180. startPulldown () {
  181. this.pulldownStatus = 'ready'
  182. this.refreshState = ''
  183. this.$nextTick(function () {
  184. this.refreshState = 'pulldown'
  185. })
  186. },
  187. startPullup () {
  188. this.pullupStatus = 'ready'
  189. this.refreshState = ''
  190. this.$nextTick(function () {
  191. this.refreshState = 'pullup'
  192. })
  193. },
  194. stopRefresh () {
  195. this.refreshState = ''
  196. this.$nextTick(function () {
  197. this.refreshState = 'stop'
  198. })
  199. },
  200. resetRefresh () {
  201. this.pulldownStatus = ''
  202. this.pullupStatus = ''
  203. this.refreshState = ''
  204. this.loadmoreState = ''
  205. this.isLoadmore = false
  206. },
  207. scrollToIndex (index, animated = false) {
  208. this.stopAutoplay()
  209. this.scrollIntoViewId = ''
  210. this.scrollWithAnimation = animated
  211. this.$nextTick(function () {
  212. this.scrollIntoViewId = 'yingbing-scroll-item_' + index
  213. this.$nextTick(function () {
  214. this.scrollWithAnimation = false
  215. setTimeout(() => {
  216. this.startAutoplay()
  217. }, 400)
  218. })
  219. })
  220. },
  221. getItemRect (index) {
  222. return new Promise(resolve => {
  223. uni.createSelectorQuery().in(this).select('#yingbing-scroll-item_' + index).boundingClientRect(data => {
  224. resolve(data)
  225. }).exec();
  226. })
  227. },
  228. pullingdown (threshold) {
  229. if ( this.pulldownStatus != 'end' ) {
  230. // #ifndef APP-NVUE
  231. if ( threshold >= readyHeight ) {
  232. this.pulldownStatus = 'ready'
  233. } else {
  234. this.pulldownStatus = 'pull'
  235. }
  236. // #endif
  237. // #ifdef APP-NVUE
  238. if ( threshold >= 195 ) {
  239. this.pulldownStatus = 'ready'
  240. } else {
  241. this.pulldownStatus = 'pull'
  242. }
  243. // #endif
  244. }
  245. },
  246. pullingup (threshold) {
  247. if ( this.pullupStatus != 'end' ) {
  248. // #ifndef APP-NVUE
  249. if ( threshold >= readyHeight ) {
  250. this.pullupStatus = 'ready'
  251. } else {
  252. this.pullupStatus = 'pull'
  253. }
  254. // #endif
  255. // #ifdef APP-NVUE
  256. if ( threshold >= 195 ) {
  257. this.pullupStatus = 'ready'
  258. } else {
  259. this.pullupStatus = 'pull'
  260. }
  261. // #endif
  262. }
  263. },
  264. setScrollTop (top) {
  265. this.scrollTop = top
  266. this.scrollTopRecord = top
  267. },
  268. startAutoplay () {
  269. this.stopAutoplay()
  270. this.scrollTop = this.scrollTopRecord
  271. if ( this.autoplay && this.data.length > 0 ) {
  272. this.autoplayTimer = setInterval(() => {
  273. if ( this.scrollTop - this.scrollTopRecord > 2 ) this.scrollTop = this.scrollTopRecord//如果scrollTop比滚动距离大2像素,说明发生过等待章节加载,需要重新赋值scrollTop真实的滚动距离
  274. this.scrollTop += 1
  275. }, 20)
  276. }
  277. },
  278. stopAutoplay () {
  279. if ( this.autoplayTimer ) {
  280. clearInterval(this.autoplayTimer)
  281. this.autoplayTimer = null
  282. }
  283. }
  284. },
  285. watch: {
  286. autoplay () {
  287. this.startAutoplay()
  288. }
  289. }
  290. }
  291. </script>
  292. <!-- #ifdef APP-VUE || H5 || MP-WEIXIN || MP-QQ -->
  293. <script module="pulldownwxs" lang="wxs" src="./pulldown.wxs"></script>
  294. <!-- #endif -->
  295. <style scoped>
  296. /* #ifdef APP-VUE || H5 */
  297. /deep/ .yingbing-scroll-view .uni-scroll-view::-webkit-scrollbar {
  298. display: none;
  299. width: 0 !important;
  300. height: 0 !important;
  301. -webkit-appearance: none;
  302. background: transparent;
  303. }
  304. /* #endif */
  305. /* #ifdef MP */
  306. /deep/ ::-webkit-scrollbar {
  307. display: none;
  308. width: 0 !important;
  309. height: 0 !important;
  310. -webkit-appearance: none;
  311. background: transparent;
  312. }
  313. /* #endif */
  314. .yingbing-scroller {
  315. /* #ifndef APP-NVUE */
  316. height: 100%;
  317. /* #endif */
  318. /* #ifdef APP-NVUE */
  319. flex: 1;
  320. /* #endif */
  321. position: relative;
  322. overflow: hidden;
  323. }
  324. .yingbing-scroller-wrapper {
  325. height: calc(100% + 160px);
  326. position: absolute;
  327. top: -80px;
  328. left: 0;
  329. right: 0;
  330. display: flex;
  331. flex-direction: column;
  332. }
  333. .yingbing-scroller-refresh {
  334. height: 80px;
  335. padding: 20px 0;
  336. box-sizing: border-box;
  337. display: flex;
  338. align-items: center;
  339. justify-content: center;
  340. }
  341. .yingbing-scroller-refresh-text {
  342. font-size: 13px;
  343. margin-left: 10px;
  344. }
  345. .yingbing-scroll {
  346. flex: 1;
  347. position: relative;
  348. }
  349. .yingbing-scroll-view {
  350. position: absolute;
  351. top: 0;
  352. left: 0;
  353. right: 0;
  354. bottom: 0;
  355. }
  356. </style>