|
|
@@ -1,12 +1,5 @@
|
|
|
<template>
|
|
|
- <view :class="classes" ref="scroller" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd">
|
|
|
- <view class="nut-infinite-top" ref="refreshTop" :style="getStyle">
|
|
|
- <view class="top-box">
|
|
|
- <nut-icon class="top-img" v-bind="$attrs" :name="pullIcon"></nut-icon>
|
|
|
- <view class="top-text">{{ pullTxt || translate('pullTxt') }}</view>
|
|
|
- </view>
|
|
|
- </view>
|
|
|
-
|
|
|
+ <view :class="classes" ref="scroller">
|
|
|
<view class="nut-infinite-container">
|
|
|
<slot></slot>
|
|
|
</view>
|
|
|
@@ -14,16 +7,16 @@
|
|
|
<view class="nut-infinite-bottom">
|
|
|
<template v-if="isInfiniting">
|
|
|
<view class="bottom-box">
|
|
|
- <template v-if="!slots.loading">
|
|
|
- <nut-icon class="bottom-img" v-bind="$attrs" :name="loadIcon"></nut-icon>
|
|
|
+ <slot name="loading">
|
|
|
+ <nut-icon v-if="loadIcon" class="bottom-img" v-bind="$attrs" :name="loadIcon"></nut-icon>
|
|
|
<view class="bottom-text">{{ loadTxt || translate('loading') }}</view>
|
|
|
- </template>
|
|
|
- <slot name="loading" v-else></slot>
|
|
|
+ </slot>
|
|
|
</view>
|
|
|
</template>
|
|
|
<template v-else-if="!hasMore">
|
|
|
- <view class="tips" v-if="!slots.finished">{{ loadMoreTxt || translate('loadMoreTxt') }}</view>
|
|
|
- <slot name="finished" v-else></slot>
|
|
|
+ <slot name="finished">
|
|
|
+ <view class="tips">{{ loadMoreTxt || translate('loadMoreTxt') }}</view>
|
|
|
+ </slot>
|
|
|
</template>
|
|
|
</view>
|
|
|
</view>
|
|
|
@@ -35,17 +28,24 @@ import {
|
|
|
onUnmounted,
|
|
|
reactive,
|
|
|
computed,
|
|
|
- CSSProperties,
|
|
|
onActivated,
|
|
|
onDeactivated,
|
|
|
- ref
|
|
|
+ ref,
|
|
|
+ watch,
|
|
|
+ nextTick
|
|
|
} from 'vue';
|
|
|
import { createComponent } from '@/packages/utils/create';
|
|
|
const { componentName, create, translate } = createComponent('infiniteloading');
|
|
|
import { useTouch } from '@/packages/utils/useTouch';
|
|
|
+import requestAniFrame from '@/packages/utils/raf';
|
|
|
+import { getScrollTopRoot } from '@/packages/utils/util';
|
|
|
|
|
|
export default create({
|
|
|
props: {
|
|
|
+ modelValue: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
hasMore: {
|
|
|
type: Boolean,
|
|
|
default: true
|
|
|
@@ -54,17 +54,9 @@ export default create({
|
|
|
type: Number,
|
|
|
default: 200
|
|
|
},
|
|
|
- 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'
|
|
|
+ default: ''
|
|
|
},
|
|
|
loadTxt: {
|
|
|
type: String,
|
|
|
@@ -85,24 +77,17 @@ export default create({
|
|
|
useCapture: {
|
|
|
type: Boolean,
|
|
|
default: false
|
|
|
- },
|
|
|
- isOpenRefresh: {
|
|
|
- type: Boolean,
|
|
|
- default: false
|
|
|
}
|
|
|
},
|
|
|
- emits: ['scroll-change', 'load-more', 'refresh'],
|
|
|
+ emits: ['scroll-change', 'load-more', 'update:modelValue'],
|
|
|
|
|
|
setup(props, { emit, slots }) {
|
|
|
const touch: any = useTouch();
|
|
|
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
|
|
|
@@ -115,32 +100,6 @@ export default create({
|
|
|
};
|
|
|
});
|
|
|
|
|
|
- const getStyle = computed(() => {
|
|
|
- const style: CSSProperties = {};
|
|
|
- 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 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;
|
|
|
- };
|
|
|
-
|
|
|
const calculateTopPosition = (el: HTMLElement): number => {
|
|
|
return !el ? 0 : el.offsetTop + calculateTopPosition(el.offsetParent as HTMLElement);
|
|
|
};
|
|
|
@@ -149,8 +108,10 @@ export default create({
|
|
|
let offsetDistance = 0;
|
|
|
let resScrollTop = 0;
|
|
|
let direction = 'down';
|
|
|
- const windowScrollTop = getWindowScrollTop();
|
|
|
+
|
|
|
if (props.useWindow) {
|
|
|
+ const windowScrollTop = getScrollTopRoot();
|
|
|
+
|
|
|
if (state.scroller) {
|
|
|
offsetDistance =
|
|
|
calculateTopPosition(state.scroller) + state.scroller.offsetHeight - windowScrollTop - window.innerHeight;
|
|
|
@@ -176,17 +137,15 @@ export default create({
|
|
|
return offsetDistance <= props.threshold && direction == 'down';
|
|
|
};
|
|
|
|
|
|
- const infiniteDone = () => {
|
|
|
- state.isInfiniting = false;
|
|
|
- };
|
|
|
-
|
|
|
const handleScroll = () => {
|
|
|
- requestAniFrame()(() => {
|
|
|
+ requestAniFrame(() => {
|
|
|
if (!isScrollAtBottom() || !props.hasMore || state.isInfiniting) {
|
|
|
return false;
|
|
|
} else {
|
|
|
state.isInfiniting = true;
|
|
|
- emit('load-more', infiniteDone);
|
|
|
+
|
|
|
+ emit('update:modelValue', true);
|
|
|
+ nextTick(() => emit('load-more'));
|
|
|
}
|
|
|
});
|
|
|
};
|
|
|
@@ -195,67 +154,24 @@ export default create({
|
|
|
state.scrollEl.addEventListener('scroll', handleScroll, props.useCapture);
|
|
|
};
|
|
|
|
|
|
- const refreshDone = () => {
|
|
|
- state.distance = 0;
|
|
|
- state.isTouching = false;
|
|
|
- };
|
|
|
-
|
|
|
- const touchStart = (event: TouchEvent) => {
|
|
|
- touch.start(event);
|
|
|
-
|
|
|
- if (state.beforeScrollTop == 0 && !state.isTouching && props.isOpenRefresh) {
|
|
|
- state.y = event.touches[0].pageY;
|
|
|
- state.isTouching = true;
|
|
|
-
|
|
|
- const childHeight = ((state.refreshTop as HTMLElement).firstElementChild as HTMLElement).offsetHeight;
|
|
|
- state.refreshMaxH = Math.floor(childHeight * 1 + 10);
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const touchMove = (event: TouchEvent) => {
|
|
|
- touch.move(event);
|
|
|
-
|
|
|
- state.distance = event.touches[0].pageY - state.y;
|
|
|
-
|
|
|
- if ((touch as any).isVertical() && state.distance > 0 && state.isTouching) {
|
|
|
- event.preventDefault();
|
|
|
- if (state.distance >= state.refreshMaxH) state.distance = state.refreshMaxH;
|
|
|
- } else {
|
|
|
- state.distance = 0;
|
|
|
- state.isTouching = false;
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- const touchEnd = () => {
|
|
|
- if (state.distance) {
|
|
|
- if (state.distance < state.refreshMaxH) {
|
|
|
- state.distance = 0;
|
|
|
- } else {
|
|
|
- emit('refresh', refreshDone);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- setTimeout(() => {
|
|
|
- touch.reset();
|
|
|
- }, 0);
|
|
|
- };
|
|
|
-
|
|
|
// 滚动监听对象
|
|
|
const getParentElement = (el: HTMLElement) => {
|
|
|
return !!props.containerId ? document.querySelector(`#${props.containerId}`) : el && el.parentNode;
|
|
|
};
|
|
|
|
|
|
+ const removeScrollListener = () => {
|
|
|
+ state.scrollEl.removeEventListener('scroll', handleScroll, props.useCapture);
|
|
|
+ };
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
const parentElement = getParentElement(state.scroller as HTMLElement) as Node & ParentNode;
|
|
|
state.scrollEl = props.useWindow ? window : parentElement;
|
|
|
|
|
|
scrollListener();
|
|
|
-
|
|
|
- console.log(slots);
|
|
|
});
|
|
|
|
|
|
onUnmounted(() => {
|
|
|
- state.scrollEl.removeEventListener('scroll', handleScroll, props.useCapture);
|
|
|
+ removeScrollListener();
|
|
|
});
|
|
|
|
|
|
const isKeepAlive = ref(false);
|
|
|
@@ -269,16 +185,23 @@ export default create({
|
|
|
|
|
|
onDeactivated(() => {
|
|
|
isKeepAlive.value = true;
|
|
|
- state.scrollEl.removeEventListener('scroll', handleScroll, props.useCapture);
|
|
|
+ removeScrollListener();
|
|
|
});
|
|
|
|
|
|
+ watch(
|
|
|
+ () => props.modelValue,
|
|
|
+ (val) => {
|
|
|
+ console.log('监听', val);
|
|
|
+ if (val) {
|
|
|
+ } else {
|
|
|
+ state.isInfiniting = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
return {
|
|
|
classes,
|
|
|
...toRefs(state),
|
|
|
- touchStart,
|
|
|
- touchMove,
|
|
|
- touchEnd,
|
|
|
- getStyle,
|
|
|
translate,
|
|
|
slots
|
|
|
};
|