chat-app/src/uni_modules/tmui/components/tm-scrolly/scrolly-vue.vue
scout b54bfe63ad
Some checks are pending
Check / lint (push) Waiting to run
Check / typecheck (push) Waiting to run
Check / build (build, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build, 18.x, windows-latest) (push) Waiting to run
Check / build (build:app, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:app, 18.x, windows-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, ubuntu-latest) (push) Waiting to run
Check / build (build:mp-weixin, 18.x, windows-latest) (push) Waiting to run
init
2024-11-11 14:46:14 +08:00

300 lines
7.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<scroll-view
class="scroyy"
:scroll-top="scrollTop"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
@scroll="onScroll"
@scrolltoupper="onScrollToTop"
@scrolltolower="onScrollToBottom"
scroll-y
enable-back-to-top
enhanced
scroll-with-animation
:bounces="false"
:style="{ height: props.height + 'rpx' }"
>
<view
:class="['scroyy__track', 'scroyy__track--' + (loosing ? 'loosing' : '')]"
:style="{ transform: `translate3d(0, ${_barHeight}rpx, 0)` }"
>
<view class="scroyy__tips" :class="['scroyy__track--' + (loosing ? 'loosing' : '')]" :style="{ height: _barHeight + 'rpx' }">
<slot name="pull" :status="{ refreshStatus }">
<view v-if="refreshStatus === 2" class="flex flex-row flex-row-center-center">
<tm-icon :font-size="24" color="primary" name="tmicon-shuaxin" spin></tm-icon>
<tm-text color="grey" _class="pl-16" :label="loadingTexts[refreshStatus]"></tm-text>
</view>
<view
v-if="refreshStatus != -1 && refreshStatus != 2"
class="flex flex-row flex-row-center-center srrryration"
:style="{
opacity: `${refreshStatus == 0 ? _barHeight / props.loadBarHeight : 1}`
}"
>
<view :class="refreshStatus == 0 ? 'srrryration srrryrationOn' : 'srrryration srrryrationOf'">
<tm-icon :font-size="24" color="primary" name="tmicon-long-arrow-down"></tm-icon>
</view>
<tm-text color="grey" _class="pl-16" :label="loadingTexts[refreshStatus]"></tm-text>
</view>
</slot>
</view>
<slot></slot>
<view :class="['scroyy__track--loosing flex ']" :style="{ height: (isBootRefresh ? props.loadBarHeight : 0) + 'rpx' }">
<slot name="bottom" :status="{ isBootRefresh }">
<view
v-if="isBootRefresh"
class="flex flex-row flex-row-center-center"
:style="{ height: (isBootRefresh ? props.loadBarHeight : 0) + 'rpx' }"
>
<tm-icon :font-size="24" color="primary" name="tmicon-shuaxin" spin></tm-icon>
<tm-text color="grey" _class="pl-16" label="数据加载中"></tm-text>
</view>
</slot>
</view>
</view>
</scroll-view>
</template>
<script lang="ts" setup>
import tmIcon from '../tm-icon/tm-icon.vue'
import tmText from '../tm-text/tm-text.vue'
import { getCurrentInstance, nextTick, onMounted, ref, Ref, watch } from 'vue'
import { propsdetail } from './propsdetail'
const proxy = getCurrentInstance()?.proxy ?? null
const emits = defineEmits(['bottom', 'change', 'refresh', 'timeout', 'update:modelValue', 'update:bottomValue'])
const props = defineProps({
...propsdetail
})
// 下拉开始的起点,主要用于计算下拉高度
const startPoint: Ref<{
pageX: number
pageY: number
} | null> = ref(null)
const isPulling = ref(false) // 是否下拉中
const _maxBarHeight = ref(props.maxBarHeight) // 最大下拉高度,单位 rpx
// 触发刷新的下拉高度单位rpx
// 松开时下拉高度大于这个值即会触发刷新,触发刷新后松开,会恢复到这个高度并保持,直到刷新结束
const _barHeight = ref(0)
/** 开始刷新 - 刷新成功/失败 最大间隔时间setTimeout句柄 */
let maxRefreshAnimateTimeFlag: number | null = 0
/** 关闭动画耗时setTimeout句柄 */
let closingAnimateTimeFlag: number | null = 0
//加载框的高度
const refreshStatus = ref(-1)
const loosing = ref(false)
const enableToRefresh = ref(true)
const scrollTop = ref(0)
/** 触底下拉刷新参数。 */
const isBootRefresh = ref(props.bottomValue)
watch(
() => props.modelValue,
() => {
if (!props.modelValue) {
if (maxRefreshAnimateTimeFlag != null) {
clearTimeout(maxRefreshAnimateTimeFlag)
}
refreshStatus.value = 3
close()
}
}
)
watch(
() => props.bottomValue,
() => {
isBootRefresh.value = props.bottomValue
}
)
onMounted(() => {
clearTimeout(maxRefreshAnimateTimeFlag)
clearTimeout(closingAnimateTimeFlag)
nextTick(() => setDefault())
})
function setDefault() {
if (props.defaultValue) {
setRefreshBarHeight(props.loadBarHeight)
refreshStatus.value = 2
loosing.value = true
isPulling.value = true
enableToRefresh.value = false
startPoint.value = null
}
}
function onScrollToBottom() {
if (isBootRefresh.value) return
emits('update:bottomValue')
emits('bottom')
}
function onScrollToTop() {
enableToRefresh.value = true
}
function onScroll(e: any) {
enableToRefresh.value = e.detail?.scrollTop === 0
}
function setScrollTop(tp: number) {
scrollTop.value = tp
}
function scrollToTop() {
setScrollTop(0)
}
function onTouchStart(e: WechatMiniprogram.Component.TrivialInstance) {
if (isPulling.value || !enableToRefresh.value) return
const { touches } = e
if (touches.length !== 1) return
const { pageX, pageY } = touches[0]
loosing.value = false
startPoint.value = {
pageX,
pageY
}
isPulling.value = true
}
function onTouchMove(e: WechatMiniprogram.Component.TrivialInstance) {
if (!startPoint.value) return
const { touches } = e
if (touches.length !== 1) return
const { pageY } = touches[0]
const offset = pageY - startPoint.value.pageY
const barsHeight = uni.$tm.u.torpx(offset)
if (barsHeight > 0) {
if (barsHeight > _maxBarHeight.value) {
// 限高
setRefreshBarHeight(_maxBarHeight.value)
// this.startPoint.pageY = pageY - this.toPx(this.maxBarHeight); // 限高的同时修正起点,避免触摸点上移时无效果
} else {
setRefreshBarHeight(barsHeight)
}
}
}
function onTouchEnd(e: WechatMiniprogram.Component.TrivialInstance) {
if (!startPoint.value) return
const { changedTouches } = e
if (changedTouches.length !== 1) return
const { pageY } = changedTouches[0]
const barsHeight = uni.$tm.u.torpx(pageY - startPoint.value.pageY)
startPoint.value = null // 清掉起点之后将忽略touchMove、touchEnd事件
loosing.value = true
isBootRefresh.value = false
// 松开时高度超过阈值则触发刷新
if (barsHeight > props.loadBarHeight) {
_barHeight.value = props.loadBarHeight
refreshStatus.value = 2
emits('change', true)
emits('update:modelValue', true)
emits('refresh')
maxRefreshAnimateTimeFlag = setTimeout(() => {
maxRefreshAnimateTimeFlag = null
if (refreshStatus.value === 2) {
// 超时回调
emits('timeout')
close() // 超时仍未被回调,则直接结束下拉
}
}, props.refreshTimeout as any) as any as number
} else {
close()
}
}
function setRefreshBarHeight(barsHeight: number) {
if (barsHeight >= props.loadBarHeight) {
refreshStatus.value = 1
} else {
refreshStatus.value = 0
}
return new Promise((resolve) => {
_barHeight.value = barsHeight
nextTick(() => {
resolve(barsHeight)
})
})
}
function close() {
const animationDuration = 350
_barHeight.value = 0
emits('change', false)
emits('update:modelValue', false)
closingAnimateTimeFlag = setTimeout(() => {
closingAnimateTimeFlag = null
refreshStatus.value = -1
isPulling.value = false // 退出下拉状态
loosing.value = false
enableToRefresh.value = true
}, animationDuration) as any as number
}
</script>
<style>
.srrryration {
transition-property: transform, height, opacity;
transition-timing-function: ease;
transition-duration: 0.25s;
}
.srrryrationOn {
transform: rotate(0deg);
}
.srrryrationOf {
transform: rotate(180deg);
}
</style>
<style scoped>
.scroyy {
overflow: hidden;
/* #ifndef APP-NVUE */
max-height: 100vh;
/* #endif */
}
.scroyy__track {
position: relative;
}
.scroyy__track--loosing {
transition-property: transform, height, opacity;
transition-timing-function: ease;
transition-duration: 0.35s;
}
.scroyy__tips {
position: absolute;
color: #bbb;
font-size: 24rpx;
top: 0;
width: 100%;
transform: translateY(-100%);
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
overflow: hidden;
}
.scroyy__text {
margin: 16rpx 0 0;
}
.scroyy__wrap {
position: relative;
}
</style>