|
|
@@ -1,49 +1,37 @@
|
|
|
<template>
|
|
|
<view-block
|
|
|
- class="nut-infiniteloading"
|
|
|
+ :class="classes"
|
|
|
ref="scroller"
|
|
|
@touchstart="touchStart"
|
|
|
@touchmove="touchMove"
|
|
|
@touchend="touchEnd"
|
|
|
>
|
|
|
<view-block class="nut-infinite-top" ref="refreshTop" :style="getStyle">
|
|
|
- <view-block 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-block class="top-text">松开刷新</view-block>
|
|
|
+ <view-block class="top-box">
|
|
|
+ <nut-icon class="top-img" :name="pullIcon"></nut-icon>
|
|
|
+ <view-block class="top-text">{{ pullTxt }}</view-block>
|
|
|
</view-block>
|
|
|
-
|
|
|
- <slot name="refreshLoading" v-else></slot>
|
|
|
</view-block>
|
|
|
|
|
|
- <view-block class="nut-infinite-container"><slot></slot></view-block>
|
|
|
+ <view-block class="nut-infinite-container">
|
|
|
+ <slot></slot>
|
|
|
+ </view-block>
|
|
|
|
|
|
<view-block class="nut-infinite-bottom">
|
|
|
<template v-if="isInfiniting">
|
|
|
- <view-block 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-block class="bottom-text">加载中···</view-block>
|
|
|
+ <view-block class="bottom-box">
|
|
|
+ <nut-icon class="bottom-img" :name="loadIcon"></nut-icon>
|
|
|
+ <view-block class="bottom-text">{{ loadTxt }}</view-block>
|
|
|
</view-block>
|
|
|
-
|
|
|
- <slot name="loading" v-else></slot>
|
|
|
</template>
|
|
|
<template v-else-if="!hasMore">
|
|
|
- <view-block class="tips" v-if="!slotUnloadMore">{{
|
|
|
- unloadMoreTxt
|
|
|
- }}</view-block>
|
|
|
- <slot name="unloadMore" v-else></slot>
|
|
|
+ <view-block class="tips">{{ loadMoreTxt }}</view-block>
|
|
|
</template>
|
|
|
</view-block>
|
|
|
</view-block>
|
|
|
</template>
|
|
|
<script lang="ts">
|
|
|
import {
|
|
|
- ref,
|
|
|
toRefs,
|
|
|
onMounted,
|
|
|
onUnmounted,
|
|
|
@@ -63,7 +51,25 @@ export default create({
|
|
|
type: Number,
|
|
|
default: 200
|
|
|
},
|
|
|
- unloadMoreTxt: {
|
|
|
+ pullIcon: {
|
|
|
+ type: String,
|
|
|
+ default:
|
|
|
+ 'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
|
|
|
+ },
|
|
|
+ pullTxt: {
|
|
|
+ type: String,
|
|
|
+ default: '松开刷新'
|
|
|
+ },
|
|
|
+ loadIcon: {
|
|
|
+ type: String,
|
|
|
+ default:
|
|
|
+ 'https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png'
|
|
|
+ },
|
|
|
+ loadTxt: {
|
|
|
+ type: String,
|
|
|
+ default: '加载中···'
|
|
|
+ },
|
|
|
+ loadMoreTxt: {
|
|
|
type: String,
|
|
|
default: '哎呀,这里是底部了啦'
|
|
|
},
|
|
|
@@ -84,63 +90,43 @@ export default create({
|
|
|
default: false
|
|
|
}
|
|
|
},
|
|
|
- emits: ['scrollChange', 'loadMore', 'refresh'],
|
|
|
+ emits: ['scroll-change', 'load-more', 'refresh'],
|
|
|
|
|
|
setup(props, { emit, slots }) {
|
|
|
- console.log('componentName', componentName);
|
|
|
-
|
|
|
- const {
|
|
|
- hasMore,
|
|
|
- threshold,
|
|
|
- containerId,
|
|
|
- useWindow,
|
|
|
- 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 isTouching = ref(false);
|
|
|
- const isInfiniting = ref(false);
|
|
|
- const refreshMaxH = ref(0);
|
|
|
-
|
|
|
- const slot = reactive({
|
|
|
- slotLoading: false,
|
|
|
- slotUnloadMore: false,
|
|
|
- slotRefreshLoading: false
|
|
|
- });
|
|
|
-
|
|
|
- const pageStart = reactive({
|
|
|
+ const state = reactive({
|
|
|
+ scrollEl: window as Window | HTMLElement | (Node & ParentNode),
|
|
|
+ scroller: null as null | HTMLElement,
|
|
|
+ refreshTop: null as null | HTMLElement,
|
|
|
+ beforeScrollTop: 0,
|
|
|
+ isTouching: false,
|
|
|
+ isInfiniting: false,
|
|
|
+ refreshMaxH: 0,
|
|
|
y: 0,
|
|
|
x: 0,
|
|
|
distance: 0
|
|
|
});
|
|
|
|
|
|
+ const classes = computed(() => {
|
|
|
+ const prefixCls = componentName;
|
|
|
+ return {
|
|
|
+ [prefixCls]: true
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
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;
|
|
|
+ return {
|
|
|
+ height: state.distance < 0 ? `0px` : `${state.distance}px`,
|
|
|
+ transition: state.isTouching
|
|
|
+ ? `height 0s cubic-bezier(0.25,0.1,0.25,1)`
|
|
|
+ : `height 0.2s cubic-bezier(0.25,0.1,0.25,1)`
|
|
|
+ };
|
|
|
});
|
|
|
|
|
|
- /** 获取监听自定义滚动节点 */
|
|
|
- const getParentElement = el => {
|
|
|
- if (containerId.value != '') {
|
|
|
- return document.querySelector(`#${containerId.value}`);
|
|
|
- }
|
|
|
- return el && el.parentNode;
|
|
|
+ const getParentElement = (el: HTMLElement) => {
|
|
|
+ return !!props.containerId
|
|
|
+ ? document.querySelector(`#${props.containerId}`)
|
|
|
+ : el && el.parentNode;
|
|
|
};
|
|
|
|
|
|
const requestAniFrame = () => {
|
|
|
@@ -152,7 +138,7 @@ export default create({
|
|
|
}
|
|
|
);
|
|
|
};
|
|
|
- /** 获取滚动条高度 */
|
|
|
+
|
|
|
const getWindowScrollTop = () => {
|
|
|
return window.pageYOffset !== undefined
|
|
|
? window.pageYOffset
|
|
|
@@ -163,24 +149,22 @@ export default create({
|
|
|
).scrollTop;
|
|
|
};
|
|
|
|
|
|
- const calculateTopPosition = el => {
|
|
|
- if (!el) {
|
|
|
- return 0;
|
|
|
- }
|
|
|
- return el.offsetTop + calculateTopPosition(el.offsetParent);
|
|
|
+ const calculateTopPosition = (el: HTMLElement): number => {
|
|
|
+ return !el
|
|
|
+ ? 0
|
|
|
+ : el.offsetTop + calculateTopPosition(el.offsetParent as HTMLElement);
|
|
|
};
|
|
|
|
|
|
- /** 判断是否滚动到底部 */
|
|
|
const isScrollAtBottom = () => {
|
|
|
let offsetDistance = 0;
|
|
|
let resScrollTop = 0;
|
|
|
- let direction = 'down'; // 滚动的方向
|
|
|
+ let direction = 'down';
|
|
|
const windowScrollTop = getWindowScrollTop();
|
|
|
- if (useWindow.value) {
|
|
|
- if (scroller.value) {
|
|
|
+ if (props.useWindow) {
|
|
|
+ if (state.scroller) {
|
|
|
offsetDistance =
|
|
|
- calculateTopPosition(scroller.value) +
|
|
|
- scroller.value.offsetHeight -
|
|
|
+ calculateTopPosition(state.scroller) +
|
|
|
+ state.scroller.offsetHeight -
|
|
|
windowScrollTop -
|
|
|
window.innerHeight;
|
|
|
}
|
|
|
@@ -190,119 +174,109 @@ export default create({
|
|
|
scrollHeight,
|
|
|
clientHeight,
|
|
|
scrollTop
|
|
|
- } = scrollEl as HTMLElement;
|
|
|
+ } = state.scrollEl as HTMLElement;
|
|
|
|
|
|
offsetDistance = scrollHeight - clientHeight - scrollTop;
|
|
|
resScrollTop = scrollTop;
|
|
|
}
|
|
|
|
|
|
- if (beforeScrollTop.value > resScrollTop) {
|
|
|
+ if (state.beforeScrollTop > resScrollTop) {
|
|
|
direction = 'up';
|
|
|
} else {
|
|
|
direction = 'down';
|
|
|
}
|
|
|
|
|
|
- beforeScrollTop.value = resScrollTop;
|
|
|
+ state.beforeScrollTop = resScrollTop;
|
|
|
|
|
|
- emit('scrollChange', resScrollTop);
|
|
|
+ emit('scroll-change', resScrollTop);
|
|
|
|
|
|
- return offsetDistance <= threshold.value && direction == 'down';
|
|
|
+ return offsetDistance <= props.threshold && direction == 'down';
|
|
|
};
|
|
|
|
|
|
const infiniteDone = () => {
|
|
|
- isInfiniting.value = false;
|
|
|
+ state.isInfiniting = false;
|
|
|
};
|
|
|
- /** 滚动函数 */
|
|
|
+
|
|
|
const handleScroll = () => {
|
|
|
requestAniFrame()(() => {
|
|
|
- if (!isScrollAtBottom() || !hasMore.value || isInfiniting.value) {
|
|
|
+ if (!isScrollAtBottom() || !props.hasMore || state.isInfiniting) {
|
|
|
return false;
|
|
|
} else {
|
|
|
- console.log('无限加载更多');
|
|
|
- isInfiniting.value = true;
|
|
|
- emit('loadMore', infiniteDone);
|
|
|
+ state.isInfiniting = true;
|
|
|
+ emit('load-more', infiniteDone);
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
|
|
|
- /** 滚动监听 */
|
|
|
const scrollListener = () => {
|
|
|
- scrollEl.addEventListener('scroll', handleScroll, useCapture.value);
|
|
|
+ state.scrollEl.addEventListener('scroll', handleScroll, props.useCapture);
|
|
|
};
|
|
|
|
|
|
- /** 下拉加载完成回到初始状态 */
|
|
|
const refreshDone = () => {
|
|
|
- pageStart.distance = 0;
|
|
|
- isTouching.value = false;
|
|
|
+ state.distance = 0;
|
|
|
+ state.isTouching = false;
|
|
|
};
|
|
|
|
|
|
- const touchStart = event => {
|
|
|
+ const touchStart = (event: TouchEvent) => {
|
|
|
if (
|
|
|
- beforeScrollTop.value == 0 &&
|
|
|
- !isTouching.value &&
|
|
|
- isOpenRefresh.value
|
|
|
+ state.beforeScrollTop == 0 &&
|
|
|
+ !state.isTouching &&
|
|
|
+ props.isOpenRefresh
|
|
|
) {
|
|
|
- pageStart.y = event.touches[0].pageY;
|
|
|
- isTouching.value = true;
|
|
|
+ state.y = event.touches[0].pageY;
|
|
|
+ state.isTouching = true;
|
|
|
|
|
|
- const childHeight = ((refreshTop.value as HTMLElement)
|
|
|
+ const childHeight = ((state.refreshTop as HTMLElement)
|
|
|
.firstElementChild as HTMLElement).offsetHeight;
|
|
|
- refreshMaxH.value = Math.floor(childHeight * 1 + 10);
|
|
|
+ state.refreshMaxH = Math.floor(childHeight * 1 + 10);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- const touchMove = event => {
|
|
|
- pageStart.distance = event.touches[0].pageY - pageStart.y;
|
|
|
+ const touchMove = (event: TouchEvent) => {
|
|
|
+ state.distance = event.touches[0].pageY - state.y;
|
|
|
|
|
|
- if (pageStart.distance > 0 && isTouching.value) {
|
|
|
+ if (state.distance > 0 && state.isTouching) {
|
|
|
event.preventDefault();
|
|
|
- if (pageStart.distance >= refreshMaxH.value)
|
|
|
- pageStart.distance = refreshMaxH.value;
|
|
|
+ if (state.distance >= state.refreshMaxH)
|
|
|
+ state.distance = state.refreshMaxH;
|
|
|
} else {
|
|
|
- pageStart.distance = 0;
|
|
|
- isTouching.value = false;
|
|
|
+ state.distance = 0;
|
|
|
+ state.isTouching = false;
|
|
|
}
|
|
|
};
|
|
|
+
|
|
|
const touchEnd = () => {
|
|
|
- if (pageStart.distance < refreshMaxH.value) {
|
|
|
- pageStart.distance = 0;
|
|
|
+ if (state.distance < state.refreshMaxH) {
|
|
|
+ state.distance = 0;
|
|
|
} else {
|
|
|
emit('refresh', refreshDone);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- /** 生命周期 首次加载 */
|
|
|
onMounted(() => {
|
|
|
- const parentElement = getParentElement(scroller);
|
|
|
-
|
|
|
- let scrollElCopy = window;
|
|
|
- if (useWindow.value === false) {
|
|
|
- scrollElCopy = parentElement;
|
|
|
- }
|
|
|
-
|
|
|
- scrollEl = scrollElCopy;
|
|
|
+ const parentElement = getParentElement(
|
|
|
+ state.scroller as HTMLElement
|
|
|
+ ) as Node & ParentNode;
|
|
|
+ state.scrollEl = props.useWindow ? window : parentElement;
|
|
|
|
|
|
scrollListener();
|
|
|
-
|
|
|
- slot.slotUnloadMore = slots.unloadMore ? true : false;
|
|
|
- slot.slotLoading = slots.loading ? true : false;
|
|
|
- slot.slotRefreshLoading = slots.refreshLoading ? true : false;
|
|
|
});
|
|
|
|
|
|
- /** 移除监听 */
|
|
|
onUnmounted(() => {
|
|
|
- scrollEl.removeEventListener('scroll', handleScroll, useCapture.value);
|
|
|
+ state.scrollEl.removeEventListener(
|
|
|
+ 'scroll',
|
|
|
+ handleScroll,
|
|
|
+ props.useCapture
|
|
|
+ );
|
|
|
});
|
|
|
|
|
|
return {
|
|
|
- scroller,
|
|
|
- refreshTop,
|
|
|
+ classes,
|
|
|
+ ...toRefs(state),
|
|
|
touchStart,
|
|
|
touchMove,
|
|
|
touchEnd,
|
|
|
- getStyle,
|
|
|
- isInfiniting,
|
|
|
- ...toRefs(slot)
|
|
|
+ getStyle
|
|
|
};
|
|
|
}
|
|
|
});
|