new-ruoyi-geek/ruoyi-geek-app/uni_modules/uview-plus/components/u-pull-refresh/u-pull-refresh.vue

334 lines
7.6 KiB
Vue
Raw Normal View History

2025-11-17 15:20:25 +00:00
<template>
<view
class="u-pull-refresh"
@touchstart="onTouchStart"
@touchmove="onTouchMove"
@touchend="onTouchEnd"
@touchcancel="onTouchEnd"
>
<!-- 下拉刷新区域 -->
<view
class="refresh-area"
:style="{ height: refreshDistance + 'px' }"
:class="{ refreshing: isRefreshing }"
>
<!-- 不同状态的插槽 -->
<slot
v-if="refreshStatus === 'pull'"
name="pull"
:distance="refreshDistance"
:threshold="threshold"
>
<!-- 默认下拉状态 -->
<view class="refresh-content">
<view class="refresh-indicator">
<up-icon name="arrow-downward" size="26px"></up-icon>
</view>
<text class="refresh-text">{{ t("up.pullRefresh.pull") }}</text>
</view>
</slot>
<slot
v-else-if="refreshStatus === 'release'"
name="release"
:distance="refreshDistance"
:threshold="threshold"
>
<!-- 默认释放状态 -->
<view class="refresh-content">
<view class="refresh-indicator">
<up-icon name="arrow-upward" size="26px"></up-icon>
</view>
<text class="refresh-text">{{ t("up.pullRefresh.release") }}</text>
</view>
</slot>
<slot
v-else-if="refreshStatus === 'refreshing'"
name="refreshing"
>
<!-- 默认刷新中状态 -->
<view class="refresh-content">
<view class="refresh-indicator">
<view class="spinner"></view>
</view>
<text class="refresh-text">{{ t("up.pullRefresh.refreshing") }}...</text>
</view>
</slot>
</view>
<!-- 内容区域 -->
<view
class="refresh-content-wrapper"
:style="{ transform: `translateY(${contentTranslateY}px)` }"
>
<scroll-view
v-if="useScrollView"
class="scroll-wrapper"
:scroll-y="true"
:enable-back-to-top="enableBackToTop"
:scroll-top="scrollTop"
:lower-threshold="lowerThreshold"
@scroll="handleScroll"
@scrolltolower="handleScrollToLower"
>
<slot></slot>
<!-- 使用 u-loadmore 组件实现上拉加载更多 -->
<u-loadmore
v-if="showLoadmore"
v-bind="loadmoreProps"
/>
</scroll-view>
<view v-else>
<slot></slot>
<!-- 使用 u-loadmore 组件实现上拉加载更多 -->
<u-loadmore
v-if="showLoadmore"
v-bind="loadmoreProps"
/>
</view>
</view>
</view>
</template>
<script>
import { t } from '../../libs/i18n'
export default {
name: 'u-pull-refresh',
props: {
// 是否正在刷新
refreshing: {
type: Boolean,
default: false
},
// 下拉刷新阈值
threshold: {
type: Number,
default: 80
},
// 阻尼系数
damping: {
type: Number,
default: 0.4
},
// 最大下拉距离
maxDistance: {
type: Number,
default: 120
},
// 是否显示加载更多
showLoadmore: {
type: Boolean,
default: false
},
// u-loadmore 组件的 props 配置
loadmoreProps: {
type: Object,
default: () => ({
status: 'loadmore',
// loadmoreText: '加载更多',
// loadingText: '正在加载...',
// nomoreText: '没有更多了'
})
},
// 是否使用 scroll-view 包装内容
useScrollView: {
type: Boolean,
default: true
},
// scroll-view 相关属性
enableBackToTop: {
type: Boolean,
default: false
},
lowerThreshold: {
type: [Number, String],
default: 50
},
scrollTop: {
type: [Number, String],
default: 0
}
},
data() {
return {
// 下拉刷新相关
isRefreshing: false,
refreshStatus: 'pull', // pull, release, refreshing
refreshDistance: 0,
startY: 0,
currentY: 0,
touching: false,
// 动画相关
contentTranslateY: 0
}
},
emits: ['refresh', 'loadmore', 'scroll'],
watch: {
refreshing: {
handler(newVal) {
if (!newVal) {
this.finishRefresh()
} else {
this.startRefresh()
}
}
}
},
methods: {
t,
// 触摸开始
onTouchStart(e) {
if (this.isRefreshing) return
this.touching = true
this.startY = e.touches[0].pageY
this.currentY = this.startY
this.refreshStatus = 'pull'
},
// 触摸移动
onTouchMove(e) {
if (!this.touching || this.isRefreshing) return
this.currentY = e.touches[0].pageY
const diff = this.currentY - this.startY
// 只有在顶部且下拉时才触发下拉刷新
if (diff > 0 && this.isScrollViewAtTop()) {
this.refreshDistance = Math.min(diff * this.damping, this.maxDistance)
this.contentTranslateY = this.refreshDistance
// 更新状态
if (this.refreshDistance >= this.threshold) {
this.refreshStatus = 'release'
} else {
this.refreshStatus = 'pull'
}
// 阻止默认滚动行为,防止触发页面级滚动
e.preventDefault()
e.stopPropagation()
}
},
// 触摸结束
onTouchEnd() {
if (!this.touching) return
this.touching = false
if (this.refreshDistance >= this.threshold && !this.isRefreshing) {
// 触发刷新
this.startRefresh()
this.$emit('refresh')
} else {
// 回弹
this.resetRefresh()
}
},
// 开始刷新
startRefresh() {
this.isRefreshing = true
this.refreshStatus = 'refreshing'
this.refreshDistance = this.threshold
this.contentTranslateY = this.threshold
},
// 完成刷新
finishRefresh() {
this.isRefreshing = false
this.refreshStatus = 'pull'
this.resetRefresh()
},
// 重置刷新状态
resetRefresh() {
this.refreshDistance = 0
this.contentTranslateY = 0
},
// 检查 scroll-view 是否在顶部
isScrollViewAtTop() {
// 这里可以更精确地判断,但简单起见直接返回 true
// 实际项目中可能需要通过 scroll 事件获取 scrollTop 判断
return true
},
// 处理滚动事件
handleScroll(e) {
this.$emit('scroll', e)
},
// 处理滚动到底部事件
handleScrollToLower(e) {
// 只有当 loadmore 状态为 loadmore 时才触发
if (this.showLoadmore && this.loadmoreProps.status === 'loadmore') {
this.$emit('loadmore')
}
}
}
}
</script>
<style scoped lang="scss">
.u-pull-refresh {
position: relative;
height: 100%;
overflow: hidden;
}
.refresh-area {
position: absolute;
left: 0;
right: 0;
top: 0;
display: flex;
align-items: flex-end;
justify-content: center;
overflow: hidden;
transition: height 0.2s ease-out;
}
.refresh-content-wrapper {
height: 100%;
transition: transform 0.2s ease-out;
}
.scroll-wrapper {
height: 100%;
}
.refresh-content {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
padding-bottom: 10px;
}
.spinner {
width: 16px;
height: 16px;
border: 2px solid #f3f3f3;
border-top: 2px solid #666;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.refresh-text {
font-size: 14px;
color: #666;
}
</style>