ソースを参照

feat: infiniteLoading 添加下拉刷新功能

yangxiaolu3 5 年 前
コミット
18cfcb0c8b

+ 62 - 21
src/packages/infiniteloading/demo.vue

@@ -6,9 +6,7 @@
         <nut-infiniteloading
           containerId="scroll"
           :useWindow="false"
-          :isLoading="isLoading"
           :hasMore="hasMore"
-          @scrollChange="scrollChange"
           @loadMore="loadMore"
         >
           <li
@@ -21,13 +19,33 @@
       </ul>
     </nut-cell>
 
+    <h2>下拉刷新</h2>
+    <nut-cell>
+      <ul class="infiniteUl" id="refreshScroll">
+        <nut-infiniteloading
+          containerId="refreshScroll"
+          :useWindow="false"
+          :isOpenRefresh="true"
+          :hasMore="refreshHasMore"
+          @loadMore="refreshLoadMore"
+          @refresh="refresh"
+        >
+          <li
+            class="infiniteLi"
+            v-for="(item, index) in refreshList"
+            :key="index"
+            >{{ item }}</li
+          >
+        </nut-infiniteloading>
+      </ul>
+    </nut-cell>
+
     <h2>自定义加载文案</h2>
     <nut-cell>
       <ul class="infiniteUl" id="customScroll">
         <nut-infiniteloading
           containerId="customScroll"
           :useWindow="false"
-          :isLoading="customIsLoading"
           :hasMore="customHasMore"
           @loadMore="customLoadMore"
         >
@@ -56,26 +74,21 @@
 import { onMounted, ref, reactive, toRefs } from 'vue';
 import { createComponent } from '@/utils/create';
 const { createDemo } = createComponent('infiniteloading');
+import { Toast } from '../toast/toast';
 export default createDemo({
   props: {},
   setup() {
-    const isLoading = ref(false);
     const hasMore = ref(true);
-
-    const customIsLoading = ref(false);
     const customHasMore = ref(true);
+    const refreshHasMore = ref(true);
 
     const data = reactive({
       defultList: [''],
-      customList: ['']
+      customList: [''],
+      refreshList: ['']
     });
-    const scrollChange = dis => {
-      console.log('滚动的距离', dis);
-    };
-
-    const loadMore = () => {
-      isLoading.value = true;
 
+    const loadMore = done => {
       setTimeout(() => {
         const curLen = data.defultList.length;
 
@@ -85,14 +98,13 @@ export default createDemo({
           );
         }
 
-        isLoading.value = false;
         if (data.defultList.length > 30) hasMore.value = false;
+
+        done();
       }, 500);
     };
 
-    const customLoadMore = () => {
-      customIsLoading.value = true;
-
+    const customLoadMore = done => {
       setTimeout(() => {
         const curLen = data.customList.length;
         for (let i = curLen; i < curLen + 10; i++) {
@@ -100,11 +112,31 @@ export default createDemo({
             `${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`
           );
         }
-        customIsLoading.value = false;
         if (data.customList.length > 30) customHasMore.value = false;
+        done();
+      }, 500);
+    };
+
+    const refreshLoadMore = done => {
+      setTimeout(() => {
+        const curLen = data.refreshList.length;
+        for (let i = curLen; i < curLen + 10; i++) {
+          data.refreshList.push(
+            `${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`
+          );
+        }
+        if (data.refreshList.length > 30) refreshHasMore.value = false;
+        done();
       }, 500);
     };
 
+    const refresh = done => {
+      setTimeout(() => {
+        Toast.success('刷新成功');
+        done();
+      }, 1000);
+    };
+
     const init = () => {
       for (let i = 0; i < 10; i++) {
         data.defultList.push(
@@ -113,6 +145,9 @@ export default createDemo({
         data.customList.push(
           `${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`
         );
+        data.refreshList.push(
+          `${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`
+        );
       }
     };
     onMounted(() => {
@@ -120,13 +155,13 @@ export default createDemo({
     });
 
     return {
-      scrollChange,
       loadMore,
-      isLoading,
       hasMore,
-      customIsLoading,
       customHasMore,
       customLoadMore,
+      refreshHasMore,
+      refreshLoadMore,
+      refresh,
       ...toRefs(data)
     };
   }
@@ -145,4 +180,10 @@ export default createDemo({
   font-size: 14px;
   color: rgba(100, 100, 100, 1);
 }
+
+.loading {
+  display: block;
+  width: 100%;
+  text-align: center;
+}
 </style>

+ 66 - 16
src/packages/infiniteloading/doc.md

@@ -23,9 +23,7 @@
     <nut-infiniteloading
         containerId = 'scroll'
         :useWindow='false'
-        :isLoading="isLoading"
         :hasMore="hasMore"
-        @scrollChange="scrollChange"
         @loadMore="loadMore"
     >
         <li class="infiniteLi" v-for="(item, index) in defultList" :key="index">{{item}}</li>
@@ -34,21 +32,18 @@
 ```
 ```javascript
 setup() {
-    const isLoading = ref(false);
     const hasMore = ref(true);
     const data = reactive({
       defultList: []
     });
-    const scrollChange = (dis) => {};
-    const loadMore = () => {  
-      isLoading.value = true;
+    const loadMore = done => {  
       setTimeout(() => {
         const curLen = data.defultList.length;
         for (let i = curLen; i < curLen + 10; i++) {
           data.defultList.push(`${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`);
         }
-        isLoading.value = false;
         if (data.defultList.length > 30) hasMore.value = false;
+        done()
       }, 500);
     };
     const init = () => {
@@ -59,10 +54,66 @@ setup() {
     onMounted(() => {
       init()
     });
-    return { scrollChange, loadMore, isLoading, hasMore, ...toRefs(data) };
+    return { loadMore, hasMore, ...toRefs(data) };
 }
 ```
+### 下拉刷新
 
+```html
+<ul class="infiniteUl" id="refreshScroll">
+  <nut-infiniteloading
+    containerId="refreshScroll"
+    :useWindow="false"
+    :isOpenRefresh="true"
+    :hasMore="refreshHasMore"
+    @loadMore="refreshLoadMore"
+    @refresh="refresh"
+  >
+    <li
+      class="infiniteLi"
+      v-for="(item, index) in refreshList"
+      :key="index"
+      >{{ item }}</li
+    >
+  </nut-infiniteloading>
+</ul>
+```
+```javascript
+setup() {
+    const refreshHasMore = ref(true);
+    const data = reactive({
+      refreshList: []
+    });
+    const refreshLoadMore = done => {
+      setTimeout(() => {
+        const curLen = data.refreshList.length;
+        for (let i = curLen; i < curLen + 10; i++) {
+          data.refreshList.push(
+            `${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`
+          );
+        }
+        if (data.refreshList.length > 30) refreshHasMore.value = false;
+        done()
+      }, 500);
+    };
+
+    const refresh = (done) => {
+      setTimeout(()=>{
+        Toast.success('刷新成功');
+        done()
+      },1000)
+    }
+    const init = () => {
+      for (let i = 0; i < 10; i++) {
+        data.refreshList.push(`${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`);
+      }
+    }
+    onMounted(() => {
+      init()
+    });
+    return { refreshLoadMore, refreshHasMore, refresh, ...toRefs(data) };
+}
+```
 ### 自定义加载文案
 
 ```html
@@ -70,7 +121,6 @@ setup() {
     <nut-infiniteloading
         containerId = 'customScroll'
         :useWindow='false'
-        :isLoading="customIsLoading"
         :hasMore="customHasMore"
         @loadMore="customLoadMore"
     >
@@ -88,20 +138,18 @@ setup() {
 ```
 ```javascript
 setup() {
-    const customIsLoading = ref(false);
     const customHasMore = ref(true);
     const data = reactive({
       customList: ['']
     });
-    const customLoadMore = () => {
-      customIsLoading.value = true;
+    const customLoadMore = done => {
       setTimeout(() => {
         const curLen = data.customList.length;
         for (let i = curLen; i < curLen + 10; i++) {
           data.customList.push(`${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`);
         }
-        customIsLoading.value = false;
         if (data.customList.length > 30) customHasMore.value = false;
+        done()
       }, 500);
     };
     const init = () => {
@@ -112,7 +160,7 @@ setup() {
     onMounted(() => {
       init()
     });
-    return { customIsLoading, customHasMore, customLoadMore,...toRefs(data) };
+    return { customHasMore, customLoadMore,...toRefs(data) };
 }
 ```
 
@@ -123,12 +171,12 @@ setup() {
 | 参数         | 说明                             | 类型   | 默认值           |
 |--------------|----------------------------------|--------|------------------|
 | hasMore         | 是否还有更多数据               | Boolean | true                |
-| isLoading        | 是否加载中                         | Boolean | false                |
 | threshold         | 距离底部多远加载 | Number | 200               |
 | useWindow | 将滚动侦听器添加到 window 否则侦听组件的父节点     | Boolean | true |
 | useCapture          | 是否使用捕获模式 true 捕获 false 冒泡                        | Boolean | false            |
 | containerId          | 在 useWindow 属性为 false 的时候,自定义设置节点ID                        | String | ''            |
 | unloadMoreTxt          | “没有更多数”据展示文案                        | String | '哎呀,这里是底部了啦'            |
+| isOpenRefresh        | 是否开启下拉刷新                         | Boolean | false                |
 
 ### Slot
 
@@ -136,11 +184,13 @@ setup() {
 |--------|----------------|
 | loading  | 自定义“加载中”的展示形式 | 
 | unloadMore  | 自定义“没有更多数据”的展示形式 | 
+| refreshLoading  | 自定义下拉刷新中“加载中”的展示形式 | 
 
 ### Events
 
 | 事件名 | 说明           | 回调参数     |
 |--------|----------------|--------------|
-| loadMore  | 继续加载的回调函数 | - |
+| loadMore  | 继续加载的回调函数 | done 函数,用于关闭加载中状态 |
 | scrollChange  | 实时监听滚动高度 | 滚动高度 |
+| refresh  | 下拉刷新事件回调 | done 函数,用于关闭加载中状态 |
     

+ 35 - 0
src/packages/infiniteloading/index.scss

@@ -1,6 +1,29 @@
 .nut-infiniteloading {
   display: block;
   width: 100%;
+  .nut-infinite-top {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    width: 100%;
+    overflow: hidden;
+    .top-box {
+      width: 100%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+
+      .top-img {
+        width: 28px;
+        height: 24px;
+      }
+      .top-text {
+        font-size: 10px;
+        color: rgba(128, 128, 128, 1);
+      }
+    }
+  }
   .nut-infinite-bottom {
     display: block;
     width: 100%;
@@ -8,5 +31,17 @@
     font-size: 12px;
     color: rgba(200, 200, 200, 1);
     text-align: center;
+
+    .bottom-box {
+      .bottom-img {
+        margin-right: 5px;
+        width: 28px;
+        height: 24px;
+      }
+      .bottom-text {
+        font-size: 10px;
+        color: rgba(128, 128, 128, 1);
+      }
+    }
   }
 }

+ 137 - 19
src/packages/infiniteloading/index.vue

@@ -1,12 +1,35 @@
 <template>
-  <view class="nut-infiniteloading" ref="scroller">
-    <view class="nut-infinite-top"></view>
+  <view
+    class="nut-infiniteloading"
+    ref="scroller"
+    @touchstart="touchStart"
+    @touchmove="touchMove"
+    @touchend="touchEnd"
+  >
+    <view class="nut-infinite-top" ref="refreshTop" :style="getStyle">
+      <view class="top-box" v-if="!slotRefreshLoading">
+        <nut-icon
+          class="top-img"
+          name="https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png"
+        ></nut-icon>
+        <view class="top-text">松开刷新</view>
+      </view>
+
+      <slot name="refreshLoading" v-else></slot>
+    </view>
 
     <view class="nut-infinite-container"><slot></slot></view>
 
     <view class="nut-infinite-bottom">
-      <template v-if="isLoading">
-        <nut-icon name="refresh" v-if="!slotLoading"></nut-icon>
+      <template v-if="isInfiniting">
+        <view v-if="!slotLoading" class="bottom-box">
+          <nut-icon
+            class="bottom-img"
+            name="https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png"
+          ></nut-icon>
+          <view class="bottom-text">加载中···</view>
+        </view>
+
         <slot name="loading" v-else></slot>
       </template>
       <template v-else-if="!hasMore">
@@ -17,7 +40,15 @@
   </view>
 </template>
 <script lang="ts">
-import { ref, toRefs, onMounted, onUnmounted } from 'vue';
+import {
+  ref,
+  toRefs,
+  onMounted,
+  onUnmounted,
+  reactive,
+  computed,
+  CSSProperties
+} from 'vue';
 import { createComponent } from '@/utils/create';
 const { componentName, create } = createComponent('infiniteloading');
 
@@ -27,10 +58,6 @@ export default create({
       type: Boolean,
       default: true
     },
-    isLoading: {
-      type: Boolean,
-      default: false
-    },
     threshold: {
       type: Number,
       default: 200
@@ -50,28 +77,63 @@ export default create({
     useCapture: {
       type: Boolean,
       default: false
+    },
+    isOpenRefresh: {
+      type: Boolean,
+      default: false
     }
   },
   components: {},
-  emits: ['scrollChange', 'loadMore'],
+  emits: ['scrollChange', 'loadMore', 'refresh'],
 
   setup(props, { emit, slots }) {
     console.log('componentName', componentName);
 
     const {
       hasMore,
-      isLoading,
       threshold,
       containerId,
       useWindow,
-      useCapture
+      useCapture,
+      isOpenRefresh
     } = toRefs(props);
 
     let scrollEl: Window | HTMLElement = window;
     const scroller = ref<null | HTMLElement>(null);
+    const refreshTop = ref<null | HTMLElement>(null);
     const beforeScrollTop = ref(0);
-    const slotLoading = ref(false);
-    const slotUnloadMore = ref(false);
+    const isTouching = ref(false);
+    const isInfiniting = ref(false);
+    const refreshMaxH = ref(0);
+
+    const slot = reactive({
+      slotLoading: false,
+      slotUnloadMore: false,
+      slotRefreshLoading: false
+    });
+
+    const pageStart = reactive({
+      y: 0,
+      x: 0,
+      distance: 0
+    });
+
+    const getStyle = computed(() => {
+      const style: CSSProperties = {};
+
+      if (pageStart.distance < 0) {
+        style.height = 0 + 'px';
+      } else {
+        style.height = pageStart.distance + 'px';
+      }
+
+      if (isTouching.value) {
+        style.transition = `height 0s cubic-bezier(0.25,0.1,0.25,1)`;
+      } else {
+        style.transition = `height 0.2s cubic-bezier(0.25,0.1,0.25,1)`;
+      }
+      return style;
+    });
 
     /** 获取监听自定义滚动节点 */
     const getParentElement = el => {
@@ -147,13 +209,18 @@ export default create({
       return offsetDistance <= threshold.value && direction == 'down';
     };
 
+    const infiniteDone = () => {
+      isInfiniting.value = false;
+    };
     /** 滚动函数 */
     const handleScroll = () => {
       requestAniFrame()(() => {
-        if (!isScrollAtBottom() || !hasMore.value || isLoading.value) {
+        if (!isScrollAtBottom() || !hasMore.value || isInfiniting.value) {
           return false;
         } else {
-          emit('loadMore');
+          console.log('无限加载更多');
+          isInfiniting.value = true;
+          emit('loadMore', infiniteDone);
         }
       });
     };
@@ -163,6 +230,47 @@ export default create({
       scrollEl.addEventListener('scroll', handleScroll, useCapture.value);
     };
 
+    /** 下拉加载完成回到初始状态 */
+    const refreshDone = () => {
+      pageStart.distance = 0;
+      isTouching.value = false;
+    };
+
+    const touchStart = event => {
+      if (
+        beforeScrollTop.value == 0 &&
+        !isTouching.value &&
+        isOpenRefresh.value
+      ) {
+        pageStart.y = event.touches[0].pageY;
+        isTouching.value = true;
+
+        const childHeight = ((refreshTop.value as HTMLElement)
+          .firstElementChild as HTMLElement).offsetHeight;
+        refreshMaxH.value = Math.floor(childHeight * 1 + 10);
+      }
+    };
+
+    const touchMove = event => {
+      pageStart.distance = event.touches[0].pageY - pageStart.y;
+
+      if (pageStart.distance > 0 && isTouching.value) {
+        event.preventDefault();
+        if (pageStart.distance >= refreshMaxH.value)
+          pageStart.distance = refreshMaxH.value;
+      } else {
+        pageStart.distance = 0;
+        isTouching.value = false;
+      }
+    };
+    const touchEnd = () => {
+      if (pageStart.distance < refreshMaxH.value) {
+        pageStart.distance = 0;
+      } else {
+        emit('refresh', refreshDone);
+      }
+    };
+
     /** 生命周期 首次加载 */
     onMounted(() => {
       const parentElement = getParentElement(scroller);
@@ -176,8 +284,9 @@ export default create({
 
       scrollListener();
 
-      slotUnloadMore.value = slots.unloadMore ? true : false;
-      slotLoading.value = slots.loading ? true : false;
+      slot.slotUnloadMore = slots.unloadMore ? true : false;
+      slot.slotLoading = slots.loading ? true : false;
+      slot.slotRefreshLoading = slots.refreshLoading ? true : false;
     });
 
     /** 移除监听 */
@@ -185,7 +294,16 @@ export default create({
       scrollEl.removeEventListener('scroll', handleScroll, useCapture.value);
     });
 
-    return { scroller, slotLoading, slotUnloadMore };
+    return {
+      scroller,
+      refreshTop,
+      touchStart,
+      touchMove,
+      touchEnd,
+      getStyle,
+      isInfiniting,
+      ...toRefs(slot)
+    };
   }
 });
 </script>