提交 18cfcb0c 编写于 作者: Y yangxiaolu3

feat: infiniteLoading 添加下拉刷新功能

上级 16e486f8
......@@ -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>
......@@ -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 函数,用于关闭加载中状态 |
\ No newline at end of file
.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);
}
}
}
}
<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>
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册