| <template> | 
|   <view class="tn-scroll-view-class tn-scroll-view"> | 
|     <scroll-view | 
|       class="scroll-view" | 
|       :style="[scrollViewStyle]" | 
|       scroll-y | 
|       scroll-anchoring | 
|       enable-back-to-top | 
|       :throttle="false" | 
|       :scroll-top="scrollTop" | 
|       :lower-threshold="lowerThreshold" | 
|       @scroll="handleScroll" | 
|       @touchend="handleTouchEnd" | 
|       @touchmove.prevent.stop="handleTouchMove" | 
|       @touchstart="handleTouchStart" | 
|       @scrolltolower="handleScrollTolower" | 
|     > | 
|       <view class="scroll__content" :style="[scrollContentStyle]"> | 
|         <view class="scroll__pull-down"> | 
|           <slot name="pulldown"> | 
|             <view class="scroll__refresh" :style="[refreshStyle]"> | 
|               <view><tn-loading :animation="refreshing"></tn-loading></view> | 
|               <view class="scroll__refresh--text" :style="[refreshTextStyle]">{{ refreshStateText }}</view> | 
|             </view> | 
|           </slot> | 
|         </view> | 
|         <view :id="elScrollDataId" class="scroll__data"> | 
|           <slot></slot> | 
|         </view> | 
|       </view> | 
|     </scroll-view> | 
|   </view> | 
| </template> | 
|   | 
| <script> | 
|   import componentsColor from '../../libs/mixin/components_color.js' | 
|   export default { | 
|     name: 'tn-scroll-view', | 
|     mixins: [ componentsColor ], | 
|     props: { | 
|       // H5顶部导航栏的高度 | 
|       h5NavHeight: { | 
|         type: Number, | 
|         default: 45 | 
|       }, | 
|       // 自定义顶部导航栏高度 | 
|       customNavHeight: { | 
|         type: Number, | 
|         default: 0 | 
|       }, | 
|       // 可滚动区域顶部偏移高度 | 
|       offsetTop: { | 
|         type: Number, | 
|         default: 0 | 
|       }, | 
|       // 可滚动区域底部偏移高度 | 
|       offsetBottom: { | 
|         type: Number, | 
|         default: 0 | 
|       }, | 
|       // 容器高度 (不设置则自动计算) | 
|       height: { | 
|         type: Number, | 
|         default: null | 
|       }, | 
|       // 是否禁用 | 
|       disabled: { | 
|         type: Boolean, | 
|         default: false | 
|       }, | 
|       // 禁用下拉刷新 | 
|       pullDownDisabled: { | 
|         type: Boolean, | 
|         default: false | 
|       }, | 
|       // 下拉速率 | 
|       pullDownSpeed: { | 
|         type: Number, | 
|         default: 0.5 | 
|       }, | 
|       // 刷新延迟 | 
|       refreshDelayed: { | 
|         type: Number, | 
|         default: 800 | 
|       }, | 
|       // 刷新完成后延迟 | 
|       refreshFinishDelayed: { | 
|         type: Number, | 
|         default: 800 | 
|       }, | 
|       // 下拉刷新距离 | 
|       refresherThreshold: { | 
|         type: Number, | 
|         default: 70 | 
|       }, | 
|       // 上拉加载距离 | 
|       lowerThreshold: { | 
|         type: Number, | 
|         default: 40 | 
|       }, | 
|       // 刷新状态 | 
|       refreshState: { | 
|         type: Boolean, | 
|         default: false | 
|       }, | 
|       // 正在刷新文字 | 
|       refreshingText: { | 
|         type: String, | 
|         default: '正在刷新' | 
|       }, | 
|       // 刷新成功文字 | 
|       refreshSuccessText: { | 
|         type: String, | 
|         default: '刷新成功' | 
|       }, | 
|       // 下拉中的文字 | 
|       pulldownText: { | 
|         type: String, | 
|         default: '下拉刷新' | 
|       }, | 
|       // 下拉完成的文字 | 
|       pulldownFinishText: { | 
|         type: String, | 
|         default: '松开刷新' | 
|       } | 
|     }, | 
|     data() { | 
|       return { | 
|         // 滚动容器内容id | 
|         elScrollDataId: '', | 
|         // 系统信息 | 
|         systemInfo: { | 
|           height: 0, | 
|           statusBarHeight: 0 | 
|         }, | 
|         // 距离顶部滚动高度 | 
|         scrollTop: 0, | 
|         // 滚动内容视图顶部位置 | 
|         scrollDataTop: -1, | 
|         // 滚动内容视图顶部位置偏移 | 
|         scrollDataOffsetTop: -1, | 
|         // 滚动区域的高度 | 
|         scrollViewHeight: 0, | 
|         // 当前滚动高度 | 
|         currentScrollTop: 0, | 
|         // 当前触摸点Y轴开始坐标 | 
|         currentTouchStartY: 0, | 
|         // 刷新状态文字 | 
|         refreshStateText: '下拉刷新', | 
|         // 是否刷新中 | 
|         refreshing: false, | 
|         // 是否刷新完成 | 
|         refreshFinish: false, | 
|         // 是否正在下拉 | 
|         pulldowning: false, | 
|         // 下拉高度 | 
|         pullDownHeight: 0, | 
|         // 是否显示下拉加载 | 
|         showPullDown: false | 
|       } | 
|     }, | 
|     computed: { | 
|       scrollViewStyle() { | 
|         let style = {} | 
|         style.height = this.scrollViewHeight + 'px' | 
|         if (!this.backgroundColorClass) { | 
|           style.backgroundColor = this.backgroundColorStyle | 
|         } | 
|         return style | 
|       }, | 
|       scrollContentStyle() { | 
|         let style = {} | 
|         style.transform = this.showPullDown ? `translateY(${this.pullDownHeight}px)` : `translateY(0px)` | 
|         style.transition = this.pulldowning ? `transform 100ms ease-out` : `transform 500ms cubic-bezier(0.19,1.64,0.42,0.72)` | 
|         return style | 
|       }, | 
|       refreshStyle() { | 
|         let style = {} | 
|         style.opacity = this.showPullDown ? 1 : 0 | 
|         return style | 
|       }, | 
|       refreshTextStyle() { | 
|         let style = {} | 
|         if (!this.fontColorClass) { | 
|           style.color = this.fontColorStyle | 
|         } | 
|         return style | 
|       }, | 
|       loadTextStyle() { | 
|         let style = {} | 
|         if (!this.fontColorClass) { | 
|           style.color = this.fontColorStyle | 
|         } | 
|         return style | 
|       } | 
|     }, | 
|     watch: { | 
|       refreshState(nVal, oVal) { | 
|         if (!nVal) { | 
|           if (this.showPullDown) { | 
|             // 关闭正在下拉 | 
|             this.pulldowning = false | 
|             // 隐藏下拉刷新 | 
|             this.showPullDown = false | 
|             // 关闭正在刷新 | 
|             this.refreshing = false | 
|           } | 
|         } | 
|       } | 
|     }, | 
|     created() { | 
|       this.elScrollDataId = this.$t.uuid() | 
|       this.getSystemInfo() | 
|     }, | 
|     mounted() { | 
|       this.$nextTick(() => { | 
|         this.init() | 
|       }) | 
|     }, | 
|     methods: { | 
|       // 组件初始化 | 
|       init() { | 
|         this.refreshStateText = this.pulldownText | 
|         // 初始化scrollView信息 | 
|         this.updateScrollViewInfo() | 
|       }, | 
|       // 获取系统信息 | 
|       getSystemInfo() { | 
|         const systemInfo = uni.getSystemInfoSync() | 
|         this.systemInfo.height = systemInfo.safeArea.height | 
|         this.systemInfo.statusBarHeight = systemInfo.statusBarHeight | 
|       }, | 
|       // 更新scrollView信息 | 
|       updateScrollViewInfo() { | 
|         if (this.height) { | 
|           this.scrollViewHeight = this.height | 
|         } else { | 
|           // 设置scrollView的高度和组件顶部位置 | 
|           // console.log(this.systemInfo, this.offsetTop, this.customNavHeight); | 
|           // #ifdef H5 | 
|           this.scrollViewHeight = this.systemInfo.height - ( | 
|             this.offsetTop +  | 
|             (this.customNavHeight ? this.customNavHeight : this.h5NavHeight) +  | 
|             this.offsetBottom) | 
|           this.scrollDataOffsetTop = this.offsetTop + (this.customNavHeight ? this.customNavHeight : this.h5NavHeight) | 
|           // #endif | 
|           // #ifndef H5 | 
|           this.scrollViewHeight = this.systemInfo.height - ( | 
|             this.offsetTop +  | 
|             this.systemInfo.statusBarHeight + | 
|             this.offsetBottom) | 
|           this.scrollDataOffsetTop = this.offsetTop + this.systemInfo.statusBarHeight | 
|           // #endif | 
|         } | 
|       }, | 
|       // 获取scrollView内容信息 | 
|       async getScrollDataInfo() { | 
|         const scrollInfo = await this._tGetRect(`#${this.elScrollDataId}`) | 
|         this.scrollDataTop = scrollInfo.top | 
|       }, | 
|       // 上拉触底事件 | 
|       handleScrollTolower(e) { | 
|         if (this.pullUpDisabled) return | 
|         this.$emit('scrolltolower', e) | 
|       }, | 
|       // 滚动事件 | 
|       handleScroll(e) { | 
|         this.currentScrollTop = e.detail.scrollTop | 
|         this.$emit('scroll', e.detail) | 
|       }, | 
|       // 触摸按下事件 | 
|       handleTouchStart(e) { | 
|         if (this.disabled) return | 
|         this.currentTouchStartY = e.touches[0].clientY | 
|         this.getScrollDataInfo() | 
|         this.$emit('touchStart', e) | 
|       }, | 
|       // 触摸下滑事件 | 
|       handleTouchMove(e) { | 
|         if (this.disabled) return | 
|         if (this.currentScrollTop == 0 && e.touches[0].clientY >= this.currentTouchStartY) { | 
|           // 容器滑动的偏移 | 
|           const moveOffset = this.scrollDataTop > 0 ? | 
|             (this.scrollDataOffsetTop - this.scrollDataTop) : | 
|             (Math.abs(this.scrollDataTop) + this.scrollDataOffsetTop) | 
|            | 
|           this.pulldowning = true | 
|           this.showPullDown = true | 
|           let pullDownDistance = ((e.touches[0].clientY - this.currentTouchStartY)  - moveOffset) * this.pullDownSpeed | 
|           this.pullDownHeight = pullDownDistance | 
|           // this.pullDownHeight = pullDownDistance > this.refresherThreshold ? this.refresherThreshold : pullDownDistance | 
|           this.refreshStateText = this.pullDownHeight >= this.refresherThreshold ? this.pulldownFinishText : this.pulldownText | 
|           if (pullDownDistance > this.refresherThreshold) { | 
|             this.$emit('refreshReady') | 
|           } | 
|         } | 
|         this.$emit('touchMove', e) | 
|       }, | 
|       // 触摸松开处理 | 
|       handleTouchEnd(e) { | 
|         if (this.disabled) return | 
|         // 处理下拉刷新 | 
|         if (this.showPullDown) { | 
|           // 当下拉高度小于下拉阈值 | 
|           if (this.pullDownHeight < this.refresherThreshold) { | 
|             // 关闭正在下拉 | 
|             this.pulldowning = false | 
|             // 重置下拉高度 | 
|             this.pullDownHeight = 0 | 
|             // 隐藏下拉刷新 | 
|             this.showPullDown = false | 
|             // 触发下拉中断事件 | 
|             this.$emit('refreshStop') | 
|           } else { | 
|             this.pullDownHeight = this.pullDownHeight > this.refresherThreshold ? this.refresherThreshold : this.pullDownHeight | 
|             this.refresh() | 
|           } | 
|         } | 
|          | 
|         // 触发下拉触摸松开事件 | 
|         this.$emit('touchEnd', e) | 
|       }, | 
|        | 
|       // 刷新数据 | 
|       refresh() { | 
|         // 设置刷新未完成 | 
|         this.refreshFinish = false | 
|         // 开启正在刷新 | 
|         this.refreshing = true | 
|         // 设置正在刷新状态文字 | 
|         this.refreshStateText = this.refreshingText | 
|         // 触发refresh事件 | 
|         setTimeout(() => { | 
|           this.$emit('refresh') | 
|         }, this.refreshDelayed) | 
|       }, | 
|     } | 
|   } | 
| </script> | 
|   | 
| <style lang="scss" scoped> | 
|   .tn-scroll-view { | 
|      | 
|     .scroll-view { | 
|       position: relative; | 
|       touch-action: none; | 
|        | 
|       .scroll__content { | 
|         display: flex; | 
|         will-change: transform; | 
|         flex-direction: column; | 
|          | 
|         .scroll { | 
|           &__pull-down { | 
|             position: absolute; | 
|             left: 0; | 
|             width: 100%; | 
|             display: flex; | 
|             padding: 30rpx 0; | 
|             align-items: center; | 
|             justify-content: center; | 
|             transform: translateY(-100%); | 
|           } | 
|            | 
|           &__refresh { | 
|             display: flex; | 
|             align-items: center; | 
|             justify-content: center; | 
|             &--text { | 
|               margin-left: 10rpx; | 
|             } | 
|           } | 
|            | 
|           &__data { | 
|              | 
|           } | 
|            | 
|           &__pull-up { | 
|             position: absolute; | 
|             left: 0; | 
|             bottom: 0; | 
|             width: 100%; | 
|             display: flex; | 
|             align-items: center; | 
|             justify-content: center; | 
|             transform: translateY(100%); | 
|           } | 
|           &__load { | 
|             padding: 20rpx 0; | 
|             display: flex; | 
|             align-items: center; | 
|             justify-content: center; | 
|             &--text { | 
|               margin-left: 10rpx; | 
|             } | 
|           } | 
|         } | 
|       } | 
|     } | 
|   } | 
| </style> |