Browse Source

feat: infiniteloading 开发完成

yangxiaolu3 5 years ago
parent
commit
b53e028fc1

+ 127 - 4
src/packages/infiniteloading/demo.vue

@@ -2,24 +2,147 @@
   <div class="demo">
   <div class="demo">
     <h2>基础用法</h2>
     <h2>基础用法</h2>
     <nut-cell>
     <nut-cell>
-      <nut-temp name="wifi"></nut-temp>
-      <nut-temp name="mail" txt="test txt"></nut-temp>
+      <ul class="infiniteUl" id="scroll">
+        <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
+          >
+        </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"
+        >
+          <li
+            class="infiniteLi"
+            v-for="(item, index) in customList"
+            :key="index"
+            >{{ item }}</li
+          >
+
+          <template v-slot:loading>
+            <div class="loading">
+              <span>加载中...</span>
+            </div>
+          </template>
+          <template v-slot:unloadMore>
+            <div class="unload-more">没有数据啦 ~~</div>
+          </template>
+        </nut-infiniteloading>
+      </ul>
     </nut-cell>
     </nut-cell>
   </div>
   </div>
 </template>
 </template>
 
 
 <script lang="ts">
 <script lang="ts">
+import { onMounted, ref, reactive, toRefs } from 'vue';
 import { createComponent } from '@/utils/create';
 import { createComponent } from '@/utils/create';
 const { createDemo } = createComponent('infiniteloading');
 const { createDemo } = createComponent('infiniteloading');
 export default createDemo({
 export default createDemo({
   props: {},
   props: {},
   setup() {
   setup() {
-    return {};
+    const isLoading = ref(false);
+    const hasMore = ref(true);
+
+    const customIsLoading = ref(false);
+    const customHasMore = ref(true);
+
+    const data = reactive({
+      defultList: [''],
+      customList: ['']
+    });
+    const scrollChange = dis => {
+      console.log('滚动的距离', dis);
+    };
+
+    const loadMore = () => {
+      isLoading.value = true;
+
+      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;
+      }, 500);
+    };
+
+    const customLoadMore = () => {
+      customIsLoading.value = true;
+
+      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;
+      }, 500);
+    };
+
+    const init = () => {
+      for (let i = 0; i < 10; i++) {
+        data.defultList.push(
+          `${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`
+        );
+        data.customList.push(
+          `${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`
+        );
+      }
+    };
+    onMounted(() => {
+      init();
+    });
+
+    return {
+      scrollChange,
+      loadMore,
+      isLoading,
+      hasMore,
+      customIsLoading,
+      customHasMore,
+      customLoadMore,
+      ...toRefs(data)
+    };
   }
   }
 });
 });
 </script>
 </script>
 
 
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-.nut-temp {
+.infiniteUl {
+  height: 300px;
+  width: 100%;
+  overflow-y: auto;
+  overflow-x: hidden;
+}
+.infiniteLi {
+  margin-top: 10px;
+  font-size: 14px;
+  color: rgba(100, 100, 100, 1);
 }
 }
 </style>
 </style>

+ 141 - 29
src/packages/infiniteloading/doc.md

@@ -1,34 +1,146 @@
 #  infiniteloading组件
 #  infiniteloading组件
 
 
-    ### 介绍
-    
-    基于 xxxxxxx
-    
-    ### 安装
-    
-    
-    
-    ## 代码演示
-    
-    ### 基础用法1
-    
+### 介绍
 
 
+列表滚动到底部自动加载更多数据。
+
+### 安装
+
+```javascript
+    import { createApp } from 'vue';
+    import { InfiniteLoading } from '@nutui/nutui';
+
+    const app = createApp();
+    app.use(InfiniteLoading);
+```
+
+## 代码演示
     
     
-    ## API
-    
-    ### Props
-    
-    | 参数         | 说明                             | 类型   | 默认值           |
-    |--------------|----------------------------------|--------|------------------|
-    | name         | 图标名称或图片链接               | String | -                |
-    | color        | 图标颜色                         | String | -                |
-    | size         | 图标大小,如 '20px' '2em' '2rem' | String | -                |
-    | class-prefix | 类名前缀,用于使用自定义图标     | String | 'nutui-iconfont' |
-    | tag          | HTML 标签                        | String | 'i'              |
-    
-    ### Events
-    
-    | 事件名 | 说明           | 回调参数     |
-    |--------|----------------|--------------|
-    | click  | 点击图标时触发 | event: Event |
+### 基础用法
+
+```html
+<ul class="infiniteUl" id="scroll">
+    <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>
+    </nut-infiniteloading>
+</ul>
+```
+```javascript
+setup() {
+    const isLoading = ref(false);
+    const hasMore = ref(true);
+    const data = reactive({
+      defultList: []
+    });
+    const scrollChange = (dis) => {};
+    const loadMore = () => {  
+      isLoading.value = true;
+      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;
+      }, 500);
+    };
+    const init = () => {
+      for (let i = 0; i < 10; i++) {
+        data.defultList.push(`${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`);
+      }
+    }
+    onMounted(() => {
+      init()
+    });
+    return { scrollChange, loadMore, isLoading, hasMore, ...toRefs(data) };
+}
+```
+
+### 自定义加载文案
+
+```html
+<ul class="infiniteUl" id="customScroll">
+    <nut-infiniteloading
+        containerId = 'customScroll'
+        :useWindow='false'
+        :isLoading="customIsLoading"
+        :hasMore="customHasMore"
+        @loadMore="customLoadMore"
+    >
+        <li class="infiniteLi" v-for="(item, index) in customList" :key="index">{{item}}</li>
+        <template v-slot:loading>
+            <div class="loading">
+                <span>加载中...</span>
+            </div>
+        </template>
+        <template v-slot:unloadMore>
+            <div class="unload-more">没有数据啦 ~~</div>
+        </template>
+    </nut-infiniteloading>
+</ul>
+```
+```javascript
+setup() {
+    const customIsLoading = ref(false);
+    const customHasMore = ref(true);
+    const data = reactive({
+      customList: ['']
+    });
+    const customLoadMore = () => {
+      customIsLoading.value = true;
+      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;
+      }, 500);
+    };
+    const init = () => {
+      for (let i = 0; i < 10; i++) {
+        data.customList.push(`${i} -- 塑像本来就在石头里,我只是把不要的部分去掉`);
+      }
+    }
+    onMounted(() => {
+      init()
+    });
+    return { customIsLoading, customHasMore, customLoadMore,...toRefs(data) };
+}
+```
+
+## API
+
+### Props
+
+| 参数         | 说明                             | 类型   | 默认值           |
+|--------------|----------------------------------|--------|------------------|
+| 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 | '哎呀,这里是底部了啦'            |
+
+### Slot
+
+| name | 说明           | 
+|--------|----------------|
+| loading  | 自定义“加载中”的展示形式 | 
+| unloadMore  | 自定义“没有更多数据”的展示形式 | 
+
+### Events
+
+| 事件名 | 说明           | 回调参数     |
+|--------|----------------|--------------|
+| loadMore  | 继续加载的回调函数 | - |
+| scrollChange  | 实时监听滚动高度 | 滚动高度 |
     
     

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

@@ -1,2 +1,12 @@
 .nut-infiniteloading {
 .nut-infiniteloading {
+  display: block;
+  width: 100%;
+  .nut-infinite-bottom {
+    display: block;
+    width: 100%;
+    padding-top: 16px;
+    font-size: 12px;
+    color: rgba(200, 200, 200, 1);
+    text-align: center;
+  }
 }
 }

+ 166 - 13
src/packages/infiniteloading/index.vue

@@ -1,38 +1,191 @@
 <template>
 <template>
-  <view :class="classes" @click="handleClick">
-    <view>{{ name }}</view>
-    <view>{{ txt }}</view>
+  <view class="nut-infiniteloading" ref="scroller">
+    <view class="nut-infinite-top"></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>
+        <slot name="loading" v-else></slot>
+      </template>
+      <template v-else-if="!hasMore">
+        <view class="tips" v-if="!slotUnloadMore">{{ unloadMoreTxt }}</view>
+        <slot name="unloadMore" v-else></slot>
+      </template>
+    </view>
   </view>
   </view>
 </template>
 </template>
 <script lang="ts">
 <script lang="ts">
-import { toRefs } from 'vue';
+import { ref, toRefs, onMounted, onUnmounted } from 'vue';
 import { createComponent } from '@/utils/create';
 import { createComponent } from '@/utils/create';
 const { componentName, create } = createComponent('infiniteloading');
 const { componentName, create } = createComponent('infiniteloading');
 
 
 export default create({
 export default create({
   props: {
   props: {
-    name: {
+    hasMore: {
+      type: Boolean,
+      default: true
+    },
+    isLoading: {
+      type: Boolean,
+      default: false
+    },
+    threshold: {
+      type: Number,
+      default: 200
+    },
+    unloadMoreTxt: {
       type: String,
       type: String,
-      default: ''
+      default: '哎呀,这里是底部了啦'
     },
     },
-    txt: {
+    useWindow: {
+      type: Boolean,
+      default: true
+    },
+    containerId: {
       type: String,
       type: String,
       default: ''
       default: ''
+    },
+    useCapture: {
+      type: Boolean,
+      default: false
     }
     }
   },
   },
   components: {},
   components: {},
-  emits: ['click'],
+  emits: ['scrollChange', 'loadMore'],
 
 
-  setup(props, { emit }) {
+  setup(props, { emit, slots }) {
     console.log('componentName', componentName);
     console.log('componentName', componentName);
 
 
-    const { name, txt } = toRefs(props);
+    const {
+      hasMore,
+      isLoading,
+      threshold,
+      containerId,
+      useWindow,
+      useCapture
+    } = toRefs(props);
 
 
-    const handleClick = (event: Event) => {
-      emit('click', event);
+    let scrollEl: Window | HTMLElement = window;
+    const scroller = ref<null | HTMLElement>(null);
+    const beforeScrollTop = ref(0);
+    const slotLoading = ref(false);
+    const slotUnloadMore = ref(false);
+
+    /** 获取监听自定义滚动节点 */
+    const getParentElement = el => {
+      if (containerId.value != '') {
+        return document.querySelector(`#${containerId.value}`);
+      }
+      return el && el.parentNode;
+    };
+
+    const requestAniFrame = () => {
+      return (
+        window.requestAnimationFrame ||
+        window.webkitRequestAnimationFrame ||
+        function(callback) {
+          window.setTimeout(callback, 1000 / 60);
+        }
+      );
+    };
+    /** 获取滚动条高度 */
+    const getWindowScrollTop = () => {
+      return window.pageYOffset !== undefined
+        ? window.pageYOffset
+        : (
+            document.documentElement ||
+            document.body.parentNode ||
+            document.body
+          ).scrollTop;
     };
     };
 
 
-    return { name, txt, handleClick };
+    const calculateTopPosition = el => {
+      if (!el) {
+        return 0;
+      }
+      return el.offsetTop + calculateTopPosition(el.offsetParent);
+    };
+
+    /** 判断是否滚动到底部 */
+    const isScrollAtBottom = () => {
+      let offsetDistance = 0;
+      let resScrollTop = 0;
+      let direction = 'down'; // 滚动的方向
+      const windowScrollTop = getWindowScrollTop();
+      if (useWindow.value) {
+        if (scroller.value) {
+          offsetDistance =
+            calculateTopPosition(scroller.value) +
+            scroller.value.offsetHeight -
+            windowScrollTop -
+            window.innerHeight;
+        }
+        resScrollTop = windowScrollTop;
+      } else {
+        const {
+          scrollHeight,
+          clientHeight,
+          scrollTop
+        } = scrollEl as HTMLElement;
+
+        offsetDistance = scrollHeight - clientHeight - scrollTop;
+        resScrollTop = scrollTop;
+      }
+
+      if (beforeScrollTop.value > resScrollTop) {
+        direction = 'up';
+      } else {
+        direction = 'down';
+      }
+
+      beforeScrollTop.value = resScrollTop;
+
+      emit('scrollChange', resScrollTop);
+
+      return offsetDistance <= threshold.value && direction == 'down';
+    };
+
+    /** 滚动函数 */
+    const handleScroll = () => {
+      requestAniFrame()(() => {
+        if (!isScrollAtBottom() || !hasMore.value || isLoading.value) {
+          return false;
+        } else {
+          emit('loadMore');
+        }
+      });
+    };
+
+    /** 滚动监听 */
+    const scrollListener = () => {
+      scrollEl.addEventListener('scroll', handleScroll, useCapture.value);
+    };
+
+    /** 生命周期 首次加载 */
+    onMounted(() => {
+      const parentElement = getParentElement(scroller);
+
+      let scrollElCopy = window;
+      if (useWindow.value === false) {
+        scrollElCopy = parentElement;
+      }
+
+      scrollEl = scrollElCopy;
+
+      scrollListener();
+
+      slotUnloadMore.value = slots.unloadMore ? true : false;
+      slotLoading.value = slots.loading ? true : false;
+    });
+
+    /** 移除监听 */
+    onUnmounted(() => {
+      scrollEl.removeEventListener('scroll', handleScroll, useCapture.value);
+    });
+
+    return { scroller, slotLoading, slotUnloadMore };
   }
   }
 });
 });
 </script>
 </script>