customScrollList.vue 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693
  1. <template>
  2. <view class="scroll-list-wrap" :style="[scrollListWrapStyle]">
  3. <scroll-view
  4. class="scroll-view"
  5. :class="[elClass]"
  6. :style="[listWrapStyle]"
  7. scroll-y
  8. scroll-anchoring
  9. enable-back-to-top
  10. :scroll-top="scrollTop"
  11. :lower-threshold="defaultOption.lowerThreshold"
  12. @scroll="handleScroll"
  13. @touchend="handleTouchEnd"
  14. @touchmove.prevent.stop="handleTouchMove"
  15. @touchstart="handleTouchStart"
  16. @scrolltolower="handleScrolltolower"
  17. >
  18. <view class="scroll-content" :style="[scrollContentStyle]">
  19. <view class="pull-down-wrap">
  20. <slot name="pulldown" v-if="$slots.pulldown"></slot>
  21. <view class="refresh-view" :style="[refreshViewStyle]" v-else>
  22. <view class="pull-down-animation" :class="{ refreshing: refreshing }" :style="[pullDownAnimationStyle]"></view>
  23. <text class="pull-down-text" :style="[pullDownTextStyle]">{{ refreshStateText }}</text>
  24. </view>
  25. </view>
  26. <view class="empty-wrap-me" v-if="showEmpty">
  27. <slot name="empty" v-if="$slots.empty"></slot>
  28. <view class="empty-view-me" v-else>
  29. <image class="empty-image" :src="defaultOption.emptyImage || images.empty" mode="aspectFit"></image>
  30. <text class="empty-text" :style="[emptyTextStyle]">{{ emptyText }}</text>
  31. </view>
  32. </view>
  33. <view class="list-content"><slot></slot></view>
  34. <view class="pull-up-wrap" v-if="showPullUp">
  35. <slot name="pullup" v-if="$slots.pullup"></slot>
  36. <view class="load-view" v-else>
  37. <view class="pull-up-animation" v-if="loading" :style="[pullUpAnimationStyle]"></view>
  38. <text class="pull-up-text" :style="[pullUpTextStyle]">{{ loadStateText }}</text>
  39. </view>
  40. </view>
  41. </view>
  42. </scroll-view>
  43. </view>
  44. </template>
  45. <script>
  46. import images from './images.js';
  47. export default {
  48. name: 'scroll-list',
  49. props: {
  50. // 配置信息
  51. option: {
  52. type: Object,
  53. default: () => ({})
  54. }
  55. },
  56. data() {
  57. return {
  58. defaultOption: {
  59. page: 1, // 分页
  60. size: 10, // 分页大小
  61. auto: true, // 自动加载
  62. height: null, // 组件高度
  63. disabled: false, // 禁用
  64. background: '', // 背景颜色属性
  65. emptyImage: '', // 空数据提示图片
  66. offsetBottom: 0, // 底部高度补偿
  67. pullDownSpeed: 0.5, // 下拉速率
  68. lowerThreshold: 40, // 距离底部上拉加载距离
  69. refresherThreshold: 80, // 距离顶部下拉刷新距离
  70. refreshDelayed: 800, // 刷新延迟
  71. refreshFinishDelayed: 800, // 刷新完成后的延迟
  72. safeArea: false, // 是否开启安全区域适配
  73. emptyTextColor: '#82848a', // 空提示文字颜色
  74. loadTextColor: '#82848a', // 上拉加载文字颜色
  75. loadIconColor: '#82848a', // 上拉加载图标颜色
  76. refresherTextColor: '#82848a', // 下拉刷新文字颜色
  77. refresherIconColor: '#82848a', // 下拉刷新图标颜色
  78. emptyText: '暂无列表~', // 空数据提示文字
  79. loadingText: '正在加载中~', // 加载中文字
  80. loadFailText: '加载失败啦~', // 加载失败文字
  81. noMoreText: '没有更多啦~', // 没有更多文字
  82. refreshingText: '正在刷新~', // 正在刷新文字
  83. refreshFailText: '刷新失败~', // 刷新失败文字
  84. refreshSuccessText: '刷新成功~', // 刷新成功文字
  85. pulldownText: '下拉刷新~', // 下拉中的文字
  86. pulldownFinishText: '松开刷新~' // 下拉完成的文字
  87. },
  88. images, // 内置图片
  89. elClass: '', // 组件动态class
  90. windowInfo: {}, // 窗口信息
  91. scrollTop: 0, // 距离顶部滚动高度
  92. scrollViewTop: -1, // 滚动视图顶部位置
  93. scrollViewHeight: 0, // 滚动视图高度
  94. currentPage: 1, // 当前分页页码
  95. currentSize: 15, // 当前分页大小
  96. currentScrollTop: 0, // 当前滚动高度
  97. emptyText: '暂无列表~',
  98. loadStateText: '正在加载中~', // 加载状态文字
  99. refreshStateText: '下拉刷新~', // 刷新状态文字
  100. loadDisabled: false, // 是否禁用上拉加载
  101. loading: false, // 是否加载中
  102. refreshing: false, // 是否刷新中
  103. refreshFinish: false, // 是否刷新完成
  104. pulldowning: false, // 是否正在下拉
  105. pullDownHeight: 0, // 下拉高度
  106. showEmpty: false, // 是否显示空数据提示
  107. showPullUp: false, // 是否显示上拉加载
  108. showPullDown: false // 是否显示下拉刷新
  109. };
  110. },
  111. methods: {
  112. // 组件初始化
  113. handleInit() {
  114. // 合并配置
  115. this.defaultOption = Object.assign(this.defaultOption, this.option);
  116. this.showEmpty = !this.defaultOption.auto;
  117. this.currentPage = this.defaultOption.page;
  118. this.currentSize = this.defaultOption.size;
  119. this.emptyText = this.defaultOption.emptyText;
  120. this.loadStateText = this.defaultOption.loadingText;
  121. this.refreshStateText = this.defaultOption.pulldownText;
  122. // 计算高度
  123. this.queryRect('.' + this.elClass).then(rect => {
  124. // 设置组件顶部位置
  125. this.scrollViewTop = rect.top;
  126. // 判断是否自动加载
  127. if (this.defaultOption.auto) this.load();
  128. });
  129. },
  130. /**
  131. * 加载数据
  132. *
  133. */
  134. load() {
  135. if (this.defaultOption.disabled || this.loading || this.loadDisabled) return;
  136. // 开启正在加载
  137. this.loading = true;
  138. // 设置正在加载状态文字
  139. this.loadStateText = this.defaultOption.loadingText;
  140. // 显示上拉加载
  141. this.showPullUp = true;
  142. // 分页参数
  143. let paging = { page: this.currentPage, size: this.currentSize };
  144. // 触发load事件
  145. this.$emit('load', paging);
  146. },
  147. // 加载成功
  148. loadSuccess(data = {}) {
  149. // 解构数据
  150. const { list, total } = data;
  151. // 判断列表是否是数组
  152. if (Array.isArray(list)) {
  153. // 判断列表长度
  154. if (list.length) {
  155. // 判断列表长度和列表总数是否相同
  156. if (list.length >= total) {
  157. // 设置禁用上拉加载
  158. this.loadDisabled = true;
  159. // 加载状态文字
  160. this.loadStateText = this.defaultOption.noMoreText;
  161. } else {
  162. // 关闭禁用上拉加载
  163. this.loadDisabled = false;
  164. // 设置分页参数
  165. this.currentPage++;
  166. // 加载状态为加载中
  167. this.loadStateText = this.defaultOption.loadingText;
  168. // 加载计算
  169. this.loadCompute();
  170. }
  171. // 显示上拉加载
  172. this.showPullUp = true;
  173. // 隐藏空数据提示
  174. this.showEmpty = false;
  175. } else {
  176. // 设置禁用上拉加载
  177. this.loadDisabled = true;
  178. // 隐藏上拉加载
  179. this.showPullUp = false;
  180. // 隐藏上拉加载
  181. this.showPullUp = false;
  182. // 显示空数据提示
  183. this.showEmpty = true;
  184. }
  185. // 关闭正在加载
  186. this.loading = false;
  187. // 触发加载成功事件
  188. this.$emit('loadSuccess', list);
  189. } else {
  190. // 不是数组类型当作加载失败处理
  191. this.loadFail();
  192. console.error('the list must be a array');
  193. }
  194. },
  195. // 加载失败
  196. loadFail() {
  197. // 关闭正在加载
  198. this.loading = false;
  199. // 关闭空数据提示
  200. this.showEmpty = false;
  201. // 显示上拉加载
  202. this.showPullUp = true;
  203. // 加载状态为加载失败
  204. this.loadStateText = this.defaultOption.loadFailText;
  205. // 触发加载失败事件
  206. this.$emit('loadFail');
  207. },
  208. // 刷新数据
  209. refresh() {
  210. // 如果是下拉刷新
  211. if (this.pullDownHeight == this.defaultOption.refresherThreshold) {
  212. // 关闭正在加载
  213. this.loading = false;
  214. // 隐藏上拉加载
  215. this.showPullUp = false;
  216. } else {
  217. // 开启正在加载
  218. this.loading = true;
  219. // 隐藏空数据提示
  220. this.showEmpty = false;
  221. // 显示上拉加载
  222. this.showPullUp = true;
  223. // 设置正在刷新状态文字
  224. this.loadStateText = this.defaultOption.refreshingText;
  225. }
  226. // 设置刷新未完成
  227. this.refreshFinish = false;
  228. // 开启正在刷新
  229. this.refreshing = true;
  230. // 设置正在刷新状态文字
  231. this.refreshStateText = this.defaultOption.refreshingText;
  232. // 设置分页参数
  233. this.currentPage = 1;
  234. this.currentSize = this.defaultOption.size;
  235. let paging = { page: this.currentPage, size: this.currentSize };
  236. // 触发refresh事件
  237. setTimeout(() => {
  238. this.$emit('refresh', paging);
  239. }, this.defaultOption.refreshDelayed);
  240. },
  241. // 刷新成功
  242. refreshSuccess(data) {
  243. // 解构数据
  244. const { list, total } = data;
  245. // 判断列表是否是数组
  246. if (Array.isArray(list)) {
  247. // 判断列表长度
  248. if (list.length) {
  249. // 判断列表长度和列表总数是否相同
  250. if (list.length >= total) {
  251. // 设置禁用上拉加载
  252. this.loadDisabled = true;
  253. // 设置没有更多状态文字
  254. this.loadStateText = this.defaultOption.noMoreText;
  255. } else {
  256. // 设置分页参数
  257. this.currentPage++;
  258. // 关闭禁用上拉加载
  259. this.loadDisabled = false;
  260. // 设置加载中状态文字
  261. this.loadStateText = this.defaultOption.loadingText;
  262. // 开启自动加载
  263. this.defaultOption.auto = true;
  264. // 加载计算
  265. this.loadCompute();
  266. }
  267. // 关闭空数据提示
  268. this.showEmpty = false;
  269. // 显示上拉加载
  270. this.showPullUp = true;
  271. } else {
  272. // 设置禁用上拉加载
  273. this.loadDisabled = true;
  274. // 隐藏上拉加载
  275. this.showPullUp = false;
  276. // 显示空数据提示
  277. this.showEmpty = true;
  278. // 设置没有更多状态文字
  279. this.loadStateText = this.defaultOption.noMoreText;
  280. }
  281. // 关闭正在加载
  282. this.loading = false;
  283. // 设置刷新成功状态文字
  284. this.refreshStateText = this.defaultOption.refreshSuccessText;
  285. // 关闭正在刷新
  286. this.refreshing = false;
  287. // 关闭正在下拉
  288. this.pulldowning = false;
  289. // 触发刷新成功事件
  290. this.$emit('refreshSuccess', list);
  291. setTimeout(() => {
  292. // 设置刷新完成
  293. this.refreshFinish = true;
  294. // 重置下拉高度
  295. this.pullDownHeight = 0;
  296. // 隐藏下拉刷新
  297. this.showPullDown = false;
  298. this.$emit('refreshSuccess');
  299. }, this.defaultOption.refreshFinishDelayed);
  300. } else {
  301. // 不是数组类型当作刷新失败处理
  302. this.refreshFail();
  303. console.error('the list must be a array');
  304. }
  305. },
  306. // 刷新失败
  307. refreshFail() {
  308. // 设置加载失败状态文字
  309. this.loadStateText = this.defaultOption.refreshFailText;
  310. // 设置刷新失败状态文字
  311. this.refreshStateText = this.defaultOption.refreshFailText;
  312. // 关闭正在加载
  313. this.loading = false;
  314. // 显示下拉加载
  315. this.showPullUp = true;
  316. // 关闭正在刷新
  317. this.refreshing = false;
  318. // 关闭正在下拉
  319. this.pulldowning = false;
  320. // 延迟执行刷新完成后状态
  321. setTimeout(() => {
  322. // 设置刷新完成
  323. this.refreshFinish = true;
  324. // 重置下拉高度
  325. this.pullDownHeight = 0;
  326. // 隐藏下拉刷新
  327. this.showPullDown = false;
  328. // 触发刷新失败事件
  329. this.$emit('refreshError');
  330. }, this.defaultOption.refreshFinishDelayed);
  331. },
  332. // 加载计算
  333. loadCompute() {
  334. // 判断是否自动加载
  335. if (this.defaultOption.auto) {
  336. // 延迟执行下否者可能会高度计算错误
  337. setTimeout(() => {
  338. this.$nextTick(() => {
  339. this.queryRect('.list-content').then(rect => {
  340. if (rect.height <= this.scrollViewHeight) {
  341. this.load();
  342. }
  343. });
  344. });
  345. }, 100);
  346. }
  347. },
  348. /**
  349. *
  350. * 上拉触底事件 滚动触底
  351. * @param {Object} e
  352. */
  353. handleScrolltolower(e) {
  354. if (this.loadDisabled) return;
  355. this.$emit('scrolltolower', e);
  356. //
  357. this.load();
  358. },
  359. /**
  360. * 滚动事件
  361. * @param {Object} event
  362. *
  363. */
  364. handleScroll(event) {
  365. this.currentScrollTop = event.detail.scrollTop;
  366. this.$emit('scroll', event.detail);
  367. },
  368. /**
  369. * 当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。
  370. * 触摸按下处理
  371. * @param {Object} event
  372. */
  373. handleTouchStart(event) {
  374. if (this.defaultOption.disabled) return;
  375. this.currentTouchStartY = event.touches[0].clientY;
  376. this.$emit('touchStart', event);
  377. },
  378. /**
  379. * 当手指在屏幕上滑动时连续地触发。在这个事件发生期间,调用preventDefault() 可以阻止滚动。
  380. * 触摸按下滑动处理
  381. * @param {Object} event
  382. */
  383. handleTouchMove(event) {
  384. if (this.defaultOption.disabled || this.currentScrollTop) return;
  385. if (event.touches[0].clientY >= this.currentTouchStartY) {
  386. this.pulldowning = true;
  387. this.showPullDown = true;
  388. let pullDownDistance = (event.touches[0].clientY - this.currentTouchStartY) * this.defaultOption.pullDownSpeed;
  389. this.pullDownHeight = pullDownDistance > this.defaultOption.refresherThreshold ? this.defaultOption.refresherThreshold : pullDownDistance;
  390. this.refreshStateText =
  391. this.pullDownHeight >= this.defaultOption.refresherThreshold ? this.defaultOption.pulldownFinishText : this.defaultOption.pulldownText;
  392. this.$emit('touchMove', event);
  393. }
  394. },
  395. /**
  396. * 移动端
  397. * 当手指从屏幕上移开时触发
  398. * 触摸松开处理
  399. * @param {Object} event
  400. */
  401. handleTouchEnd(event) {
  402. if (this.defaultOption.disabled) return;
  403. // 当下拉高度小于下拉阈值
  404. if (this.pullDownHeight < this.defaultOption.refresherThreshold) {
  405. // 关闭正在下拉
  406. this.pulldowning = false;
  407. // 重置下拉高度
  408. this.pullDownHeight = 0;
  409. // 隐藏下拉刷新
  410. this.showPullDown = false;
  411. // 触发下拉中断事件
  412. this.$emit('refreshStop');
  413. } else {
  414. this.refresh();
  415. }
  416. // 触发下拉触摸松开事件
  417. this.$emit('touchEnd', event);
  418. },
  419. // 更新组件
  420. updateScrollView() {
  421. if (this.defaultOption.height) {
  422. this.scrollViewHeight = uni.upx2px(this.defaultOption.height);
  423. } else {
  424. this.scrollViewHeight = this.windowInfo.windowHeight - this.scrollViewTop;
  425. }
  426. this.scrollViewObserve();
  427. },
  428. // 监听列表高度变化
  429. listContentObserve() {
  430. this.disconnectObserve('_listContentObserve');
  431. const listContentObserve = this.createIntersectionObserver({
  432. thresholds: [0, 0.5, 1]
  433. });
  434. listContentObserve.relativeToViewport({
  435. // #ifdef H5
  436. top: -(this.windowInfo.windowTop + rect.top),
  437. // #endif
  438. // #ifndef H5
  439. top: -rect.top
  440. // #endif
  441. });
  442. },
  443. // 监听组件位置变化
  444. scrollViewObserve() {
  445. this.disconnectObserve('_scrollViewObserve');
  446. this.$nextTick(() => {
  447. this.queryRect('.' + this.elClass).then(rect => {
  448. const scrollViewObserve = this.createIntersectionObserver({
  449. thresholds: [0, 0.5, 1]
  450. });
  451. scrollViewObserve.relativeToViewport({
  452. // #ifdef H5
  453. top: -(this.windowInfo.windowTop + rect.top),
  454. // #endif
  455. // #ifndef H5
  456. top: -rect.top
  457. // #endif
  458. });
  459. scrollViewObserve.observe('.' + this.elClass, position => {
  460. // #ifdef H5
  461. this.scrollViewTop = position.boundingClientRect.top - this.windowInfo.windowTop;
  462. // #endif
  463. // #ifndef H5
  464. this.scrollViewTop = position.boundingClientRect.top;
  465. // #endif
  466. });
  467. this._scrollViewObserve = scrollViewObserve;
  468. });
  469. });
  470. },
  471. // 断开监听组件
  472. disconnectObserve(observerName) {
  473. const observer = this[observerName];
  474. observer && observer.disconnect();
  475. },
  476. // 查询dom节点信息
  477. queryRect(selector, all) {
  478. return new Promise(resolve => {
  479. uni.createSelectorQuery()
  480. .in(this)
  481. [all ? 'selectAll' : 'select'](selector)
  482. .boundingClientRect(rect => {
  483. if (all && Array.isArray(rect) && rect.length) {
  484. resolve(rect);
  485. }
  486. if (!all && rect) {
  487. resolve(rect);
  488. }
  489. })
  490. .exec();
  491. });
  492. },
  493. // 16进制转RGB
  494. hexToRgb(hex) {
  495. const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
  496. hex = hex.replace(shorthandRegex, (m, r, g, b) => {
  497. return r + r + g + g + b + b;
  498. });
  499. const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
  500. return result
  501. ? {
  502. r: parseInt(result[1], 16),
  503. g: parseInt(result[2], 16),
  504. b: parseInt(result[3], 16)
  505. }
  506. : null;
  507. }
  508. },
  509. computed: {
  510. scrollListWrapStyle(){
  511. let style = {};
  512. style.background = this.defaultOption.background;
  513. return style;
  514. },
  515. // 组件容器样式
  516. listWrapStyle() {
  517. let style = {};
  518. const { offsetBottom } = this.defaultOption;
  519. style.height = this.scrollViewHeight - uni.upx2px(offsetBottom) + 'px';
  520. if (this.defaultOption.safeArea) style.paddingBottom = 'env(safe-area-inset-bottom) !important';
  521. return style;
  522. },
  523. // 滚动内容样式
  524. scrollContentStyle() {
  525. const style = {};
  526. const { pullDownHeight, pulldowning, showPullDown } = this;
  527. style.transform = showPullDown ? `translateY(${pullDownHeight}px)` : `translateY(0px)`;
  528. style.transition = pulldowning ? `transform 100ms ease-out` : `transform 200ms cubic-bezier(0.19,1.64,0.42,0.72)`;
  529. return style;
  530. },
  531. // 下拉刷新样式
  532. refreshViewStyle() {
  533. const style = {};
  534. const { showPullDown } = this;
  535. style.opacity = showPullDown ? 1 : 0;
  536. return style;
  537. },
  538. // 下拉中动画样式
  539. pullDownAnimationStyle() {
  540. const style = {};
  541. const { refresherIconColor, refresherThreshold } = this.defaultOption;
  542. const { refreshing, pullDownHeight } = this;
  543. const { r, g, b } = this.hexToRgb(refresherIconColor);
  544. const rate = pullDownHeight / refresherThreshold;
  545. style.borderColor = `rgba(${r},${g},${b},0.2)`;
  546. style.borderTopColor = refresherIconColor;
  547. if (!refreshing) {
  548. style.transform = `rotate(${360 * rate}deg)`;
  549. style.transition = 'transform 100ms linear';
  550. }
  551. return style;
  552. },
  553. pullDownTextStyle() {
  554. const style = {};
  555. const { refresherTextColor } = this.defaultOption;
  556. style.color = refresherTextColor;
  557. return style;
  558. },
  559. // 上拉中动画样式
  560. pullUpAnimationStyle() {
  561. const style = {};
  562. const { loadIconColor } = this.defaultOption;
  563. const { r, g, b } = this.hexToRgb(loadIconColor);
  564. style.borderColor = `rgba(${r},${g},${b},0.2)`;
  565. style.borderTopColor = loadIconColor;
  566. return style;
  567. },
  568. // 上拉中文字样式
  569. pullUpTextStyle() {
  570. const style = {};
  571. const { loadTextColor } = this.defaultOption;
  572. style.color = loadTextColor;
  573. return style;
  574. },
  575. // 空数据提示文字样式
  576. emptyTextStyle() {
  577. const style = {};
  578. const { emptyTextColor } = this.defaultOption;
  579. style.color = emptyTextColor;
  580. return style;
  581. }
  582. },
  583. watch: {
  584. scrollViewTop(val) {
  585. this.updateScrollView();
  586. }
  587. },
  588. created() {
  589. this.elClass = 'scroll-view-' + this._uid;
  590. this.windowInfo = uni.getSystemInfoSync();
  591. },
  592. mounted() {
  593. this.handleInit();
  594. }
  595. };
  596. </script>
  597. <style lang="scss" scoped>
  598. .scroll-list-wrap {
  599. box-sizing: border-box;
  600. .scroll-view {
  601. position: relative;
  602. .scroll-content {
  603. height: 100%;
  604. display: flex;
  605. will-change: transform;
  606. flex-direction: column;
  607. .pull-down-wrap {
  608. left: 0;
  609. width: 100%;
  610. display: flex;
  611. padding: 30rpx 0;
  612. position: absolute;
  613. align-items: flex-end;
  614. justify-content: center;
  615. transform: translateY(-100%);
  616. .refresh-view {
  617. display: flex;
  618. justify-content: center;
  619. .pull-down-animation {
  620. width: 32rpx;
  621. height: 32rpx;
  622. border-width: 4rpx;
  623. border-style: solid;
  624. border-radius: 50%;
  625. &.refreshing {
  626. animation: spin 0.5s linear infinite;
  627. }
  628. @keyframes spin {
  629. to {
  630. transform: rotate(360deg);
  631. }
  632. }
  633. }
  634. .pull-down-text {
  635. margin-left: 10rpx;
  636. }
  637. }
  638. }
  639. .empty-wrap-me {
  640. top: 0;
  641. left: 0;
  642. width: 100%;
  643. height: 100%;
  644. display: flex;
  645. position: absolute;
  646. align-items: center;
  647. justify-content: center;
  648. flex-direction: column;
  649. .empty-view-me {
  650. margin: auto;
  651. display: flex !important;
  652. align-items: center;
  653. flex-direction: column;
  654. .empty-image {
  655. width: 200rpx;
  656. height: 200rpx;
  657. }
  658. .empty-text {
  659. color: #606266;
  660. margin-top: 20rpx;
  661. }
  662. }
  663. }
  664. .list-content {
  665. }
  666. .pull-up-wrap {
  667. display: flex;
  668. align-items: center;
  669. justify-content: center;
  670. .load-view {
  671. padding: 20rpx 0;
  672. display: flex;
  673. align-items: center;
  674. justify-content: center;
  675. .pull-up-animation {
  676. width: 32rpx;
  677. height: 32rpx;
  678. border-width: 4rpx;
  679. border-style: solid;
  680. border-radius: 50%;
  681. animation: spin 0.5s linear infinite;
  682. }
  683. .pull-up-text {
  684. margin-left: 10rpx;
  685. }
  686. }
  687. }
  688. }
  689. }
  690. }
  691. </style>