提交 e8391332 编写于 作者: richard_1015's avatar richard_1015

Merge branch 'next' of https://github.com/jdf2e/nutui into next

......@@ -160,7 +160,7 @@
"version": "3.0.0",
"name": "Notify",
"type": "component",
"cName": "methods",
"cName": "消息通知",
"desc": "在页面顶部展示消息提示,支持函数调用和组件调用两种方式",
"sort": 4,
"show": true,
......
......@@ -4,10 +4,10 @@
<nut-cell>
<ul class="infiniteUl" id="scroll">
<nut-infiniteloading
containerId="scroll"
:useWindow="false"
:hasMore="hasMore"
@loadMore="loadMore"
container-id="scroll"
:use-window="false"
:has-more="hasMore"
@load-more="loadMore"
>
<li
class="infiniteLi"
......@@ -23,11 +23,12 @@
<nut-cell>
<ul class="infiniteUl" id="refreshScroll">
<nut-infiniteloading
containerId="refreshScroll"
:useWindow="false"
:isOpenRefresh="true"
:hasMore="refreshHasMore"
@loadMore="refreshLoadMore"
pull-icon="JD"
container-id="refreshScroll"
:use-window="false"
:is-open-refresh="true"
:has-more="refreshHasMore"
@load-more="refreshLoadMore"
@refresh="refresh"
>
<li
......@@ -44,10 +45,12 @@
<nut-cell>
<ul class="infiniteUl" id="customScroll">
<nut-infiniteloading
containerId="customScroll"
:useWindow="false"
:hasMore="customHasMore"
@loadMore="customLoadMore"
load-txt="loading"
load-more-txt="没有啦~"
container-id="customScroll"
:use-window="false"
:has-more="customHasMore"
@load-more="customLoadMore"
>
<li
class="infiniteLi"
......@@ -55,15 +58,6 @@
: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>
......
......@@ -7,11 +7,11 @@
### 安装
```javascript
import { createApp } from 'vue';
import { InfiniteLoading } from '@nutui/nutui';
import { createApp } from 'vue';
import { InfiniteLoading } from '@nutui/nutui';
const app = createApp();
app.use(InfiniteLoading);
const app = createApp();
app.use(InfiniteLoading);
```
## 代码演示
......@@ -22,9 +22,9 @@
<ul class="infiniteUl" id="scroll">
<nut-infiniteloading
containerId = 'scroll'
:useWindow='false'
:hasMore="hasMore"
@loadMore="loadMore"
:use-window='false'
:has-more="hasMore"
@load-more="loadMore"
>
<li class="infiniteLi" v-for="(item, index) in defultList" :key="index">{{item}}</li>
</nut-infiniteloading>
......@@ -62,11 +62,12 @@ setup() {
```html
<ul class="infiniteUl" id="refreshScroll">
<nut-infiniteloading
containerId="refreshScroll"
:useWindow="false"
:isOpenRefresh="true"
:hasMore="refreshHasMore"
@loadMore="refreshLoadMore"
pull-icon="JD"
container-id="refreshScroll"
:use-window="false"
:is-open-refresh="true"
:has-more="refreshHasMore"
@load-more="refreshLoadMore"
@refresh="refresh"
>
<li
......@@ -119,10 +120,10 @@ setup() {
```html
<ul class="infiniteUl" id="customScroll">
<nut-infiniteloading
containerId = 'customScroll'
:useWindow='false'
:hasMore="customHasMore"
@loadMore="customLoadMore"
container-id = 'customScroll'
:use-window='false'
:has-more="customHasMore"
@load-more="customLoadMore"
>
<li class="infiniteLi" v-for="(item, index) in customList" :key="index">{{item}}</li>
<template v-slot:loading>
......@@ -170,27 +171,23 @@ setup() {
| 参数 | 说明 | 类型 | 默认值 |
|--------------|----------------------------------|--------|------------------|
| hasMore | 是否还有更多数据 | Boolean | true |
| threshold | 距离底部多远加载 | Number | 200 |
| useWindow | 将滚动侦听器添加到 window 否则侦听组件的父节点 | Boolean | true |
| useCapture | 是否使用捕获模式 true 捕获 false 冒泡 | Boolean | false |
| containerId | 在 useWindow 属性为 false 的时候,自定义设置节点ID | String | '' |
| unloadMoreTxt | “没有更多数”据展示文案 | String | '哎呀,这里是底部了啦' |
| isOpenRefresh | 是否开启下拉刷新 | Boolean | false |
### Slot
| name | 说明 |
|--------|----------------|
| loading | 自定义“加载中”的展示形式 |
| unloadMore | 自定义“没有更多数据”的展示形式 |
| refreshLoading | 自定义下拉刷新中“加载中”的展示形式 |
| has-more | 是否还有更多数据 | Boolean | `true` |
| threshold | 距离底部多远加载 | Number | `200` |
| use-window | 将滚动侦听器添加到 window 否则侦听组件的父节点 | Boolean | `true` |
| use-capture | 是否使用捕获模式 true 捕获 false 冒泡 | Boolean | `false` |
| container-id | 在 useWindow 属性为 false 的时候,自定义设置节点ID | String | `''` |
| load-more-txt | “没有更多数”据展示文案 | String | `'哎呀,这里是底部了啦'` |
| is-open-refresh | 是否开启下拉刷新 | Boolean | `false` |
| pull-icon | 下拉刷新[图标名称](#/icon) | String | `https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png` |
| pull-txt | 下拉刷新提示文案 | String | `松手刷新` |
| load-icon | 上拉加载[图标名称](#/icon) | Boolean | `https://img10.360buyimg.com/imagetools/jfs/t1/169863/6/4565/6306/60125948E7e92774e/40b3a0cf42852bcb.png` |
| load-txt | 上拉加载提示文案 | String | `加载中...` |
### Events
| 事件名 | 说明 | 回调参数 |
|--------|----------------|--------------|
| loadMore | 继续加载的回调函数 | done 函数,用于关闭加载中状态 |
| scrollChange | 实时监听滚动高度 | 滚动高度 |
| load-more | 继续加载的回调函数 | done 函数,用于关闭加载中状态 |
| scroll-change | 实时监听滚动高度 | 滚动高度 |
| refresh | 下拉刷新事件回调 | done 函数,用于关闭加载中状态 |
\ No newline at end of file
......@@ -20,7 +20,7 @@
}
.top-text {
font-size: 10px;
color: rgba(128, 128, 128, 1);
color: $text-color;
}
}
}
......@@ -28,8 +28,8 @@
display: block;
width: 100%;
padding-top: 16px;
font-size: 12px;
color: rgba(200, 200, 200, 1);
font-size: $font-size-small;
color: $infinite-bottom-color;
text-align: center;
.bottom-box {
......@@ -40,7 +40,7 @@
}
.bottom-text {
font-size: 10px;
color: rgba(128, 128, 128, 1);
color: $text-color;
}
}
}
......
<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
};
}
});
......
......@@ -215,6 +215,10 @@ $notify-warning-background-color: linear-gradient(
$tabbar-active-color: $primary-color;
//infiniteloading
$infinite-bottom-color: #c8c8c8;
view-block {
display: block;
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册