k-scroll-view.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513
  1. <template>
  2. <view class="k-scroll-view" ref="k-scroll-view" :style="[scrollContainerStyle]" @touchstart="touchstart" @touchmove="touchmove" @touchend="touchend" @touchcancel="touchcancel">
  3. <view v-if="refreshType === 'native' && showPullDown" class="native-refresh-icon" :style="[{ top: `${moveY}px` }]" :class="[{ xuanzhun: doRefreshing }]">
  4. <text class="my-icons-custom icon-jiantou-refresh" style="color: #52A63A;font-size: 14px;"></text>
  5. </view>
  6. <view class="scroll-load-refresh" v-if="refreshType === 'custom' && showPullDown">
  7. <text class="my-icons-custom icon-jiazai xuanzhun" v-if="refreshText === loadingTip" style="color: #52A63A;font-size: 60rpx;"></text>
  8. {{ refreshText }}
  9. </view>
  10. <view class="go-to-top-icon" v-if="old.scrollTop > 20" @tap="goTop"><text class="my-icons-custom icon-jiantou-up" style="color: #4cd964;font-size: 40rpx;"></text></view>
  11. <view class="scroll-load-more" v-if="showPullUp">
  12. <view class="translate-line" v-if="onPullUpText === loadingTip">
  13. <view class="line"></view>
  14. <view class="line"></view>
  15. <view class="line"></view>
  16. <view class="line"></view>
  17. <view class="line"></view>
  18. <view class="line"></view>
  19. </view>
  20. {{ onPullUpText }}
  21. </view>
  22. <scroll-view
  23. class="scroll-Y"
  24. scroll-y="true"
  25. :style="[scrollContentStyle]"
  26. :scroll-top="scrollTop"
  27. :lower-threshold="bottom"
  28. @scrolltoupper="upper"
  29. @scrolltolower="lower"
  30. @scroll="scroll"
  31. >
  32. <view class="scroll-content">
  33. <slot></slot>
  34. <view class="empty-tips" v-if="showEmpty">{{ emptyTip }}</view>
  35. </view>
  36. </scroll-view>
  37. </view>
  38. </template>
  39. <script>
  40. /**
  41. * 封装 下拉刷新,上拉加载
  42. */
  43. export default {
  44. name: 'k-scroll-view',
  45. components: {},
  46. props: {
  47. refreshType: {
  48. type: String,
  49. default: 'custom'
  50. },
  51. refreshTip: {
  52. type: String,
  53. default: '正在下拉'
  54. },
  55. loadTip: {
  56. type: String,
  57. default: '获取更多数据'
  58. },
  59. loadingTip: {
  60. type: String,
  61. default: '正在加载中...'
  62. },
  63. emptyTip: {
  64. type: String,
  65. default: '--我是有底线的--'
  66. },
  67. touchHeight: {
  68. type: Number,
  69. default: 50
  70. },
  71. height: Number,
  72. bottom: {
  73. // 和底部距离多远,执行触底方法(用来预加载)
  74. type: Number,
  75. default: 50
  76. },
  77. autoPullUp: {
  78. // 是否自动上拉
  79. type: [String, Boolean],
  80. default: true
  81. },
  82. stopPullDown: {
  83. // 禁用下拉
  84. type: [String, Boolean],
  85. default: true
  86. }
  87. },
  88. data() {
  89. return {
  90. scrollTop: 0,
  91. old: {
  92. scrollTop: 0
  93. },
  94. touch_start: {
  95. x: '',
  96. y: ''
  97. }, // 手指起始位置
  98. touch_end: {
  99. x: '',
  100. y: ''
  101. }, // 手指位置
  102. touch_direction: '', // 手指移动方向
  103. isInTop: true, // 是否在顶部
  104. isInBottom: false, // 是否在底部
  105. showPullDown: false, // 是否显示下拉刷新
  106. showPullUp: false, // 是否显示上拉加载
  107. showEmpty: false, // 显示无数据
  108. refreshText: '下拉刷新', // 下拉刷新提示文字
  109. onPullUpText: '上拉加载', // 上拉加载 提示文字
  110. pullDownCanDo: false, // 是否可以调用刷新
  111. pullUpCanDo: false, // 是否可以调用加载
  112. moveY: 0,
  113. doRefreshing: false, // 开始刷新
  114. doLoadmore: false, // 开始上拉加载
  115. isCloseTip: true, // 是否关闭了提示
  116. hasMove: false
  117. };
  118. },
  119. computed: {
  120. scrollContainerStyle() {
  121. return {
  122. height: `${this.height || this.height_}rpx`
  123. };
  124. },
  125. scrollContentStyle() {
  126. return {
  127. // height: `${this.height || this.height_}rpx`
  128. };
  129. }
  130. },
  131. watch: {},
  132. created() {
  133. const that = this;
  134. uni.getSystemInfo({
  135. success: function(e) {
  136. that.height_ = (750 / e.windowWidth) * e.windowHeight;
  137. }
  138. });
  139. },
  140. mounted() {},
  141. destroyed() {},
  142. methods: {
  143. scroll: function(e) {
  144. this.touch_direction = '';
  145. this.pullDownCanDo = false;
  146. this.pullUpCanDo = false;
  147. this.hasMove = true;
  148. this.old.scrollTop = e.detail.scrollTop;
  149. this.$emit('@onScroll', this.old.scrollTop);
  150. if (this.old.scrollTop === 0) {
  151. this.isInTop = true;
  152. } else {
  153. this.isInTop = false;
  154. }
  155. },
  156. upper: function(e) {
  157. // console.log("到达顶部")
  158. this.isInBottom = false;
  159. this.isInTop = true;
  160. },
  161. lower: function(e) {
  162. // console.log("到达底部")
  163. this.isInBottom = true;
  164. this.isInTop = false;
  165. // 开启自动上拉
  166. if (this.autoPullUp && this.autoPullUp !== 'false') {
  167. this.onPullUp();
  168. }
  169. },
  170. goTop: function(e) {
  171. // 回到顶部
  172. this.scrollTop = this.old.scrollTop;
  173. this.$nextTick(function() {
  174. this.scrollTop = 0;
  175. });
  176. },
  177. touchstart: function(event) {
  178. // 手指按下
  179. // console.log('start');
  180. this.touch_start.x = event.changedTouches[0].pageX;
  181. this.touch_start.y = event.changedTouches[0].pageY;
  182. this.hasMove = false;
  183. },
  184. touchmove: function(event) {
  185. // console.log('move');
  186. const end_move_page_x = event.changedTouches[0].pageX;
  187. const end_move_page_y = event.changedTouches[0].pageY;
  188. const moveX = this.touch_start.x - end_move_page_x;
  189. const moveY = this.touch_start.y - end_move_page_y;
  190. if (moveX === 0) {
  191. // console.log('回到原位置');
  192. this.stopTips();
  193. }
  194. if (moveX > 0) {
  195. // console.log('向左滑动');
  196. if (Math.abs(moveX) > Math.abs(moveY)) {
  197. this.touch_direction = 'left';
  198. }
  199. }
  200. if (moveX < 0) {
  201. // console.log('向右滑动');
  202. if (Math.abs(moveX) > Math.abs(moveY)) {
  203. this.touch_direction = 'right';
  204. }
  205. }
  206. if (moveY === 0) {
  207. // console.log('回到原位置');
  208. this.stopTips();
  209. }
  210. if (moveY > 0) {
  211. // console.log('向上滑动');
  212. if (Math.abs(moveY) > Math.abs(moveX)) {
  213. this.touch_direction = 'top';
  214. }
  215. }
  216. if (moveY < 0) {
  217. // console.log('向下滑动');
  218. if (Math.abs(moveY) > Math.abs(moveX)) {
  219. this.touch_direction = 'bottom';
  220. }
  221. }
  222. if (this.old.scrollTop === 0) {
  223. this.isInBottom = true;
  224. this.isInTop = true;
  225. }
  226. this.hasMove = true;
  227. this.checkTouchY(moveY);
  228. },
  229. checkTouchY: function(moveY) {
  230. // console.log('move_check');
  231. const hk = this;
  232. hk.pullDownCanDo = false;
  233. hk.pullUpCanDo = false;
  234. hk.showPullUp = false;
  235. hk.showPullDown = false;
  236. // hk.showEmpty = false;
  237. // hk.isCloseTip = false;
  238. hk.moveY = Math.abs(moveY);
  239. // 上拉 并且 在底部
  240. if (hk.touch_direction === 'top' && hk.isInBottom) {
  241. // console.log("我在底部上拉")
  242. hk.showPullUp = true; // 显示上拉加载
  243. hk.onPullUpText = hk.loadTip;
  244. if (hk.moveY > hk.touchHeight) {
  245. hk.onPullUpText = '释放加载';
  246. hk.pullUpCanDo = true; // 执行加载
  247. }
  248. }
  249. // 下拉 并且在 顶部
  250. else if (hk.touch_direction === 'bottom' && hk.isInTop && hk.stopPullDown) {
  251. // console.log("我在顶部下拉")
  252. hk.showPullDown = true; // 显示下拉刷新
  253. hk.refreshText = hk.refreshTip;
  254. if (hk.moveY > hk.touchHeight) {
  255. hk.refreshText = '释放刷新';
  256. hk.pullDownCanDo = true; // 执行刷新
  257. hk.moveY = Math.abs(hk.touchHeight);
  258. }
  259. }
  260. },
  261. touchend: function(event) {
  262. // console.log("end")
  263. const hk = this;
  264. hk.doRefreshing = false;
  265. hk.doLoadmore = false;
  266. this.touch_end.x = event.changedTouches[0].pageX;
  267. this.touch_end.y = event.changedTouches[0].pageY;
  268. if (!hk.hasMove) {
  269. const moveX = this.touch_start.x - this.touch_end.x;
  270. const moveY = this.touch_start.y - this.touch_end.y;
  271. hk.touch_direction = 'top';
  272. hk.checkTouchY(hk.touchHeight + 10);
  273. }
  274. // console.log("hk.touch_direction", hk.touch_direction)
  275. // console.log("hk.isInBottom", hk.isInBottom)
  276. // console.log("hk.isInTop", hk.isInTop)
  277. // console.log("hk.pullUpCanDo", hk.pullUpCanDo)
  278. // console.log("hk.pullDownCanDo", hk.pullDownCanDo)
  279. if (hk.pullUpCanDo) {
  280. hk.onPullUpText = hk.loadingTip; // 正在加载
  281. // 调用加载更多
  282. hk.doLoadmore = true;
  283. hk.onPullUp();
  284. } else if (hk.pullDownCanDo) {
  285. // 执行刷新
  286. hk.refreshText = hk.loadingTip; // 正在刷新
  287. // 调用刷新
  288. hk.doRefreshing = true;
  289. hk.onPullDown();
  290. } else {
  291. hk.stopTips();
  292. }
  293. },
  294. touchcancel: function(event) {
  295. // console.log('cancel');
  296. },
  297. onPullDown: function() {
  298. // 刷新
  299. const hk = this;
  300. this.goTop(); // 回到顶部
  301. // console.log('下拉刷新');
  302. hk.$emit('onPullDown', hk.stopTips);
  303. // 隐藏刷新文字提示
  304. hk.clearTimer = setTimeout(function() {
  305. hk.stopTips();
  306. }, 4000);
  307. },
  308. onPullUp: function() {
  309. // 加载更多
  310. const hk = this;
  311. // console.log('上拉加载');
  312. hk.$emit('onPullUp', hk.stopTips);
  313. // 隐藏上拉提示
  314. hk.clearTimer = setTimeout(function() {
  315. hk.stopTips();
  316. }, 4000);
  317. },
  318. stopTips(config) {
  319. config = config || {};
  320. const hk = this;
  321. hk.isCloseTip = false;
  322. // 如果提示没关闭,则关闭
  323. if (!hk.isCloseTip) {
  324. if (hk.clearTimer) {
  325. clearTimeout(hk.clearTimer);
  326. }
  327. hk.clearTimer = setTimeout(function() {
  328. hk.showPullDown = false;
  329. hk.showPullUp = false;
  330. hk.pullUpCanDo = false;
  331. hk.pullDownCanDo = false;
  332. hk.doRefreshing = false;
  333. if (config.isEnd && config.isEnd !== 'false') {
  334. hk.showEmpty = true;
  335. }
  336. hk.isCloseTip = true;
  337. if (hk.clearTimer) {
  338. clearTimeout(hk.clearTimer);
  339. }
  340. }, 1000);
  341. }
  342. }
  343. }
  344. };
  345. </script>
  346. <style>
  347. @font-face {
  348. font-family: 'my-icons-custom';
  349. src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAQ8AAsAAAAACLQAAAPwAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDHAqEaIN5ATYCJAMQCwoABCAFhG0HTxtyB8gOJRHBwMBgYGNAPNSP/e/svfvNHKg+ncR0lY5HErREJUEpntTym2rTNkeVUHWaM1XIiUWWiA6hdVIRi5xYXX4n74qTE7NX7X+OmS6fz4b51zaX7MAB7Y2TB1R5VXjvQA40APEW2DkEatWJ5wm0m7YE79S+I2eAkxTsC8R1hUwEnFoGOYMLrermkaspngJ7reXxrF4APMn/Pr6CVeFE0VQFLzp9ca8U7PiAfESRhzOJLtZskHg4G7SbqNgEJHF51HEWUiY2IdqdXDtHAK3hTwofkA/UR3RmhkS3omglha3/eIVoFig6EXsoVPkQC2olhWyCj6hBYXRub0U3ngG+m06TggNF6BhH/J0xOHpn/M2ZURsx9lrIcUJ0dLSBHyfHn97/NSE86xIFy3Lqk3yp8mxvaWlibY6fzK4svX89L6bMdK+qzlpfeaa0JruOx3tUn1MbQJb2qyx+qyuqa83eNVfJtvoJKyrNJlxWXpdU78evrE2smQPLLNZqH6LMXHPP90K2rrLe6kuWV9WYhNbNOmA6kDZQiIhK6RlE1Eklmc1UW7lLwFpXRbAOreJvMn/jG9XZW3viGzfV76xZLuhY2OYz2OXU7f7vv1v3Dlfr4LDVzer8q2FBuaAi2Tl/R8GmIJJMMnJTaxcB7IW3tylAEJh16BDTmYhTWYGCD4QGv8M8gjxcwO57B9Y4wGHsA9uzq25kOBz5NBhOgF/ueR/v57PD7KcHrNGm5D5NaD8eXfphfNLI3er5RYQTv2JBen9w9PdHzeSnjylHbCzGcf6OF2/m2R5ojBcIwRm+755X9FD2r9w8biAvf1SwX60f7+ZFLp/PfcjmWcy2+4doNbgNaEw17tQZJLuMParnkDV5KjFBffAHeJ3Zl45eV9y2/G/lJ3hFp/UibxtfxkPhtygUC79uWR50iHTFgSHTNjamcqG8RXqEz/tW2a4dDfT0e3EI1ZBTSmg1VEPRYgqqVovIxG5Ckw6HoFmro9Buw/6bOwygISLbsC4NIPRqhqLbc6h69SET+xyaDPsOzXoDgXZnwuueHZZDge6moYiBUlwcgyskOi0VmRm0KD8FZRFqEd0U5wQS0gZFGF+5bEU3vR9qIX2IE4ZI2SqGoXCK1mnwffAyqFbrcD2tU0IJs0zOMPpNy5dTY1+0TKLTANRJNEiEAUnhxGLgFCR0tKh0NAPt/f4pkEwENRF6pq/DSYJoBorFcSsts0KA3K/QivpeymqDSDKrMBgU76LQdDRw+9AGqcOEDk4/vkkJkmAsI98Q09tkOQ2ipIplp1dp3uExaBfcM6NEjYwmPVcVIi2ji1hKQypyG+Ro2xahd6jisSIFAA==')
  350. format('woff2');
  351. }
  352. .my-icons-custom {
  353. font-family: 'my-icons-custom' !important;
  354. font-size: 16px;
  355. font-style: normal;
  356. -webkit-font-smoothing: antialiased;
  357. -moz-osx-font-smoothing: grayscale;
  358. }
  359. .icon-jiantou-refresh:before {
  360. content: '\e666';
  361. }
  362. .icon-jiantou-up:before {
  363. content: '\e70a';
  364. }
  365. .icon-jiazai:before {
  366. content: '\e603';
  367. }
  368. </style>
  369. <style lang="scss" scoped>
  370. @import './animate.scss';
  371. .image {
  372. width: 100%;
  373. height: 100%;
  374. }
  375. .k-scroll-view {
  376. width: 100%;
  377. position: relative;
  378. overflow-y: hidden;
  379. .scroll-Y {
  380. width: 100%;
  381. height: 100%;
  382. }
  383. .scroll-load-refresh,
  384. .scroll-load-more {
  385. text-align: center;
  386. color: #000;
  387. font-size: 25rpx;
  388. position: absolute;
  389. line-height: 60rpx;
  390. z-index: 1;
  391. background-color: rgba($color: #ffffff, $alpha: 0.7);
  392. border-bottom: 1px solid #eee;
  393. width: 100%;
  394. }
  395. .scroll-load-refresh {
  396. top: 0;
  397. display: flex;
  398. justify-content: center;
  399. align-items: center;
  400. }
  401. .scroll-load-more {
  402. bottom: 0;
  403. display: flex;
  404. justify-content: center;
  405. align-items: center;
  406. /deep/ .translate-line {
  407. height: 0;
  408. margin-right: 5px;
  409. }
  410. }
  411. .go-to-top-icon {
  412. position: absolute;
  413. right: 20rpx;
  414. bottom: calc(20rpx + 60rpx + 10rpx);
  415. width: 60rpx;
  416. height: 60rpx;
  417. background-color: #eee;
  418. border-radius: 50%;
  419. display: flex;
  420. justify-content: center;
  421. align-items: center;
  422. }
  423. .native-refresh-icon {
  424. position: fixed;
  425. top: 0;
  426. left: calc(50% - 30rpx);
  427. width: 60rpx;
  428. height: 60rpx;
  429. z-index: 999;
  430. display: flex;
  431. justify-content: center;
  432. align-items: center;
  433. }
  434. .empty-tips {
  435. width: 100%;
  436. line-height: 50rpx;
  437. text-align: center;
  438. color: #657575;
  439. font-size: 30rpx;
  440. }
  441. }
  442. </style>