mirror of
https://gitee.com/vant-contrib/vant.git
synced 2025-04-24 10:20:19 +08:00
refactor(PullRefresh): refactor with composition api
This commit is contained in:
parent
38740b6c1c
commit
9f632f151e
@ -1,10 +1,13 @@
|
|||||||
|
import { ref, watch, reactive, nextTick } from 'vue';
|
||||||
|
|
||||||
// Utils
|
// Utils
|
||||||
import { createNamespace } from '../utils';
|
import { createNamespace } from '../utils';
|
||||||
|
import { getScrollTop } from '../utils/dom/scroll';
|
||||||
import { preventDefault } from '../utils/dom/event';
|
import { preventDefault } from '../utils/dom/event';
|
||||||
import { getScrollTop, getScroller } from '../utils/dom/scroll';
|
|
||||||
|
|
||||||
// Mixins
|
// Composition
|
||||||
import { TouchMixin } from '../mixins/touch';
|
import { useTouch } from '../composition/use-touch';
|
||||||
|
import { useScroller } from '../composition/use-scroller';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import Loading from '../loading';
|
import Loading from '../loading';
|
||||||
@ -15,8 +18,6 @@ const DEFAULT_HEAD_HEIGHT = 50;
|
|||||||
const TEXT_STATUS = ['pulling', 'loosing', 'success'];
|
const TEXT_STATUS = ['pulling', 'loosing', 'success'];
|
||||||
|
|
||||||
export default createComponent({
|
export default createComponent({
|
||||||
mixins: [TouchMixin],
|
|
||||||
|
|
||||||
props: {
|
props: {
|
||||||
disabled: Boolean,
|
disabled: Boolean,
|
||||||
successText: String,
|
successText: String,
|
||||||
@ -43,104 +44,36 @@ export default createComponent({
|
|||||||
|
|
||||||
emits: ['refresh', 'update:modelValue'],
|
emits: ['refresh', 'update:modelValue'],
|
||||||
|
|
||||||
data() {
|
setup(props, { emit, slots }) {
|
||||||
return {
|
let reachTop;
|
||||||
|
|
||||||
|
const rootRef = ref();
|
||||||
|
const scroller = useScroller(rootRef);
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
status: 'normal',
|
status: 'normal',
|
||||||
distance: 0,
|
distance: 0,
|
||||||
duration: 0,
|
duration: 0,
|
||||||
};
|
|
||||||
},
|
|
||||||
|
|
||||||
computed: {
|
|
||||||
touchable() {
|
|
||||||
return (
|
|
||||||
this.status !== 'loading' && this.status !== 'success' && !this.disabled
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
headStyle() {
|
|
||||||
if (this.headHeight !== DEFAULT_HEAD_HEIGHT) {
|
|
||||||
return {
|
|
||||||
height: `${this.headHeight}px`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
watch: {
|
|
||||||
modelValue(loading) {
|
|
||||||
this.duration = this.animationDuration;
|
|
||||||
|
|
||||||
if (loading) {
|
|
||||||
this.setStatus(+this.headHeight, true);
|
|
||||||
} else if (
|
|
||||||
this.$slots.success ? this.$slots.success() : this.successText
|
|
||||||
) {
|
|
||||||
this.showSuccessTip();
|
|
||||||
} else {
|
|
||||||
this.setStatus(0, false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
mounted() {
|
|
||||||
this.bindTouchEvent(this.$refs.track);
|
|
||||||
this.scrollEl = getScroller(this.$el);
|
|
||||||
},
|
|
||||||
|
|
||||||
methods: {
|
|
||||||
checkPullStart(event) {
|
|
||||||
this.ceiling = getScrollTop(this.scrollEl) === 0;
|
|
||||||
|
|
||||||
if (this.ceiling) {
|
|
||||||
this.duration = 0;
|
|
||||||
this.touchStart(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onTouchStart(event) {
|
|
||||||
if (this.touchable) {
|
|
||||||
this.checkPullStart(event);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onTouchMove(event) {
|
|
||||||
if (!this.touchable) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!this.ceiling) {
|
|
||||||
this.checkPullStart(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.touchMove(event);
|
|
||||||
|
|
||||||
if (this.ceiling && this.deltaY >= 0 && this.direction === 'vertical') {
|
|
||||||
preventDefault(event);
|
|
||||||
this.setStatus(this.ease(this.deltaY));
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onTouchEnd() {
|
|
||||||
if (this.touchable && this.ceiling && this.deltaY) {
|
|
||||||
this.duration = this.animationDuration;
|
|
||||||
|
|
||||||
if (this.status === 'loosing') {
|
|
||||||
this.setStatus(+this.headHeight, true);
|
|
||||||
this.$emit('update:modelValue', true);
|
|
||||||
|
|
||||||
// ensure value change can be watched
|
|
||||||
this.$nextTick(() => {
|
|
||||||
this.$emit('refresh');
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
this.setStatus(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ease(distance) {
|
const touch = useTouch();
|
||||||
const headHeight = +this.headHeight;
|
const { deltaY, direction } = touch;
|
||||||
|
|
||||||
|
const getHeadStyle = () => {
|
||||||
|
if (props.headHeight !== DEFAULT_HEAD_HEIGHT) {
|
||||||
|
return {
|
||||||
|
height: `${props.headHeight}px`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isTouchable = () =>
|
||||||
|
state.status !== 'loading' &&
|
||||||
|
state.status !== 'success' &&
|
||||||
|
!props.disabled;
|
||||||
|
|
||||||
|
const ease = (distance) => {
|
||||||
|
const headHeight = +props.headHeight;
|
||||||
|
|
||||||
if (distance > headHeight) {
|
if (distance > headHeight) {
|
||||||
if (distance < headHeight * 2) {
|
if (distance < headHeight * 2) {
|
||||||
@ -151,34 +84,31 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Math.round(distance);
|
return Math.round(distance);
|
||||||
},
|
};
|
||||||
|
|
||||||
|
const setStatus = (distance, isLoading) => {
|
||||||
|
state.distance = distance;
|
||||||
|
|
||||||
setStatus(distance, isLoading) {
|
|
||||||
let status;
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
status = 'loading';
|
state.status = 'loading';
|
||||||
} else if (distance === 0) {
|
} else if (distance === 0) {
|
||||||
status = 'normal';
|
state.status = 'normal';
|
||||||
|
} else if (distance < props.headHeight) {
|
||||||
|
state.status = 'pulling';
|
||||||
} else {
|
} else {
|
||||||
status = distance < this.headHeight ? 'pulling' : 'loosing';
|
state.status = 'loosing';
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
this.distance = distance;
|
const renderStatus = () => {
|
||||||
|
const { status, distance } = state;
|
||||||
|
|
||||||
if (status !== this.status) {
|
if (slots[status]) {
|
||||||
this.status = status;
|
return slots[status]({ distance });
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
genStatus() {
|
|
||||||
const { status, distance } = this;
|
|
||||||
|
|
||||||
if (this.$slots[status]) {
|
|
||||||
return this.$slots[status]({ distance });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const nodes = [];
|
const nodes = [];
|
||||||
const text = (status !== 'normal' && this[`${status}Text`]) || t(status);
|
const text = (status !== 'normal' && props[`${status}Text`]) || t(status);
|
||||||
|
|
||||||
if (TEXT_STATUS.indexOf(status) !== -1) {
|
if (TEXT_STATUS.indexOf(status) !== -1) {
|
||||||
nodes.push(<div class={bem('text')}>{text}</div>);
|
nodes.push(<div class={bem('text')}>{text}</div>);
|
||||||
@ -189,32 +119,104 @@ export default createComponent({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return nodes;
|
return nodes;
|
||||||
},
|
};
|
||||||
|
|
||||||
showSuccessTip() {
|
const showSuccessTip = () => {
|
||||||
this.status = 'success';
|
state.status = 'success';
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
this.setStatus(0);
|
setStatus(0);
|
||||||
}, this.successDuration);
|
}, props.successDuration);
|
||||||
},
|
};
|
||||||
},
|
|
||||||
|
|
||||||
render() {
|
const checkPosition = (event) => {
|
||||||
|
reachTop = getScrollTop(scroller.value) === 0;
|
||||||
|
|
||||||
|
if (reachTop) {
|
||||||
|
state.duration = 0;
|
||||||
|
touch.start(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchStart = (event) => {
|
||||||
|
if (isTouchable()) {
|
||||||
|
checkPosition(event);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchMove = (event) => {
|
||||||
|
if (isTouchable()) {
|
||||||
|
if (!reachTop) {
|
||||||
|
checkPosition(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
touch.move(event);
|
||||||
|
|
||||||
|
if (reachTop && deltaY.value >= 0 && direction.value === 'vertical') {
|
||||||
|
preventDefault(event);
|
||||||
|
setStatus(ease(deltaY.value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTouchEnd = () => {
|
||||||
|
if (reachTop && deltaY.value && isTouchable()) {
|
||||||
|
state.duration = props.animationDuration;
|
||||||
|
|
||||||
|
if (state.status === 'loosing') {
|
||||||
|
setStatus(+props.headHeight, true);
|
||||||
|
emit('update:modelValue', true);
|
||||||
|
|
||||||
|
// ensure value change can be watched
|
||||||
|
nextTick(() => {
|
||||||
|
emit('refresh');
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setStatus(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(value) => {
|
||||||
|
state.duration = props.animationDuration;
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
setStatus(+props.headHeight, true);
|
||||||
|
} else if (slots.success || props.successText) {
|
||||||
|
showSuccessTip();
|
||||||
|
} else {
|
||||||
|
setStatus(0, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
const trackStyle = {
|
const trackStyle = {
|
||||||
transitionDuration: `${this.duration}ms`,
|
transitionDuration: `${state.duration}ms`,
|
||||||
transform: this.distance ? `translate3d(0,${this.distance}px, 0)` : '',
|
transform: state.distance
|
||||||
|
? `translate3d(0,${state.distance}px, 0)`
|
||||||
|
: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class={bem()}>
|
<div ref={rootRef} class={bem()}>
|
||||||
<div ref="track" class={bem('track')} style={trackStyle}>
|
<div
|
||||||
<div class={bem('head')} style={this.headStyle}>
|
class={bem('track')}
|
||||||
{this.genStatus()}
|
style={trackStyle}
|
||||||
|
onTouchstart={onTouchStart}
|
||||||
|
onTouchmove={onTouchMove}
|
||||||
|
onTouchend={onTouchEnd}
|
||||||
|
onTouchcancel={onTouchEnd}
|
||||||
|
>
|
||||||
|
<div class={bem('head')} style={getHeadStyle()}>
|
||||||
|
{renderStatus()}
|
||||||
</div>
|
</div>
|
||||||
{this.$slots.default?.()}
|
{slots.default?.()}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user