feat(todo页面): 优化待办列表滚动加载体验
1. 新增scroll-view高度动态计算适配不同设备 2. 将原scrolltolower触发改为滑动手势触发加载更多 3. 添加加载冷却期防止重复触发 4. 新增底部滑动提示动画 5. 重构加载更多逻辑并添加调试日志
This commit is contained in:
@@ -33,10 +33,12 @@
|
|||||||
<scroll-view
|
<scroll-view
|
||||||
scroll-y
|
scroll-y
|
||||||
class="list-container"
|
class="list-container"
|
||||||
|
:style="{ height: scrollViewHeight + 'px' }"
|
||||||
:refresher-enabled="true"
|
:refresher-enabled="true"
|
||||||
:refresher-triggered="refreshing"
|
:refresher-triggered="refreshing"
|
||||||
@refresherrefresh="onRefresh"
|
@refresherrefresh="onRefresh"
|
||||||
@scrolltolower="onLoadMore"
|
@scroll="onScroll"
|
||||||
|
lower-threshold="100"
|
||||||
>
|
>
|
||||||
<!-- 待贴标签列表 -->
|
<!-- 待贴标签列表 -->
|
||||||
<view v-if="activeTab === 'label'" class="coil-list">
|
<view v-if="activeTab === 'label'" class="coil-list">
|
||||||
@@ -67,12 +69,17 @@
|
|||||||
<uni-load-more status="loading" />
|
<uni-load-more status="loading" />
|
||||||
</view>
|
</view>
|
||||||
|
|
||||||
<!-- 加载更多 -->
|
<!-- 加载更多提示 -->
|
||||||
<view v-if="activeTab === 'label' && list.length > 0" class="load-more-wrapper" @click="onLoadMore">
|
<view v-if="activeTab === 'label' && list.length > 0 && list.length < total" class="load-more-wrapper">
|
||||||
<uni-load-more
|
<view v-if="isAtBottom && canLoadMore" class="bottom-hint">
|
||||||
:status="loadMoreStatus"
|
<text class="hint-text">↑ 向上滑动加载更多</text>
|
||||||
:content-text="{ contentdown: '点击加载更多', contentrefresh: '加载中...', contentnomore: '没有更多了' }"
|
</view>
|
||||||
/>
|
<view v-else @click="onLoadMore">
|
||||||
|
<uni-load-more
|
||||||
|
:status="loadMoreStatus"
|
||||||
|
:content-text="{ contentdown: '点击加载更多', contentrefresh: '加载中...', contentnomore: '没有更多了' }"
|
||||||
|
/>
|
||||||
|
</view>
|
||||||
</view>
|
</view>
|
||||||
</scroll-view>
|
</scroll-view>
|
||||||
|
|
||||||
@@ -101,6 +108,7 @@ export default {
|
|||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
statusBarHeight: 0,
|
statusBarHeight: 0,
|
||||||
|
scrollViewHeight: 0, // scroll-view 计算高度
|
||||||
tabs: [
|
tabs: [
|
||||||
{ key: 'label', label: '待贴标签', badge: 0 },
|
{ key: 'label', label: '待贴标签', badge: 0 },
|
||||||
{ key: 'inspect', label: '检验任务', badge: 0 },
|
{ key: 'inspect', label: '检验任务', badge: 0 },
|
||||||
@@ -111,6 +119,10 @@ export default {
|
|||||||
list: [],
|
list: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
refreshing: false,
|
refreshing: false,
|
||||||
|
loadMoreCooldown: false, // 加载更多冷却期标志
|
||||||
|
isAtBottom: false, // 是否滑动到底部
|
||||||
|
canLoadMore: false, // 是否可以加载更多(需要再次向上滑动)
|
||||||
|
lastScrollTop: 0, // 上次滚动位置
|
||||||
query: {
|
query: {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -129,10 +141,18 @@ export default {
|
|||||||
// 获取状态栏高度
|
// 获取状态栏高度
|
||||||
const systemInfo = uni.getSystemInfoSync()
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
this.statusBarHeight = systemInfo.statusBarHeight || 0
|
this.statusBarHeight = systemInfo.statusBarHeight || 0
|
||||||
|
// 计算 scroll-view 高度
|
||||||
|
this.calcScrollViewHeight()
|
||||||
|
},
|
||||||
|
onReady() {
|
||||||
|
// 页面就绪后再次计算高度(确保布局完成)
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.calcScrollViewHeight()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
loadMoreStatus() {
|
loadMoreStatus() {
|
||||||
if (this.loading) return 'loading'
|
if (this.loading || this.loadMoreCooldown) return 'loading'
|
||||||
if (this.list.length >= this.total) return 'noMore'
|
if (this.list.length >= this.total) return 'noMore'
|
||||||
return 'more'
|
return 'more'
|
||||||
}
|
}
|
||||||
@@ -141,6 +161,36 @@ export default {
|
|||||||
this.fetchList()
|
this.fetchList()
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
// 计算 scroll-view 高度
|
||||||
|
calcScrollViewHeight() {
|
||||||
|
const systemInfo = uni.getSystemInfoSync()
|
||||||
|
const windowHeight = systemInfo.windowHeight
|
||||||
|
const statusBarHeight = systemInfo.statusBarHeight || 0
|
||||||
|
|
||||||
|
// 各区域高度(单位:px)
|
||||||
|
const navBarHeight = 44 // 导航栏高度
|
||||||
|
const tabBarHeight = 50 // Tab栏高度
|
||||||
|
const filterBarHeight = 60 // 筛选栏高度(留有余量)
|
||||||
|
const bottomSafeArea = systemInfo.safeAreaInsets?.bottom || 0 // 底部安全区域
|
||||||
|
const tabBarOffset = 50 // 底部TabBar高度
|
||||||
|
|
||||||
|
// 计算 scroll-view 可用高度(确保最小高度为200px)
|
||||||
|
let calcHeight = windowHeight - statusBarHeight - navBarHeight - tabBarHeight - filterBarHeight - bottomSafeArea - tabBarOffset
|
||||||
|
this.scrollViewHeight = Math.max(calcHeight, 200)
|
||||||
|
|
||||||
|
console.log('scroll-view 高度计算:', {
|
||||||
|
windowHeight,
|
||||||
|
statusBarHeight,
|
||||||
|
navBarHeight,
|
||||||
|
tabBarHeight,
|
||||||
|
filterBarHeight,
|
||||||
|
bottomSafeArea,
|
||||||
|
tabBarOffset,
|
||||||
|
calcHeight,
|
||||||
|
scrollViewHeight: this.scrollViewHeight
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 切换Tab
|
// 切换Tab
|
||||||
handleTabChange(key) {
|
handleTabChange(key) {
|
||||||
this.activeTab = key
|
this.activeTab = key
|
||||||
@@ -212,6 +262,11 @@ export default {
|
|||||||
} finally {
|
} finally {
|
||||||
this.loading = false
|
this.loading = false
|
||||||
this.refreshing = false
|
this.refreshing = false
|
||||||
|
// 延迟解除冷却期,防止立即再次触发加载
|
||||||
|
setTimeout(() => {
|
||||||
|
this.loadMoreCooldown = false
|
||||||
|
console.log('加载冷却期结束')
|
||||||
|
}, 500)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -248,21 +303,97 @@ export default {
|
|||||||
this.fetchList()
|
this.fetchList()
|
||||||
},
|
},
|
||||||
|
|
||||||
// 加载更多
|
// 滚动事件处理
|
||||||
onLoadMore() {
|
onScroll(e) {
|
||||||
console.log('触发加载更多', {
|
const scrollTop = e.detail.scrollTop
|
||||||
|
const scrollHeight = e.detail.scrollHeight
|
||||||
|
const clientHeight = this.scrollViewHeight
|
||||||
|
|
||||||
|
// 防止无效值
|
||||||
|
if (!scrollHeight || !clientHeight) return
|
||||||
|
|
||||||
|
// 判断是否滑动到底部(距离底部小于 150px)
|
||||||
|
const distanceToBottom = scrollHeight - scrollTop - clientHeight
|
||||||
|
const isNearBottom = distanceToBottom < 150
|
||||||
|
|
||||||
|
// 判断滑动方向(增加最小变化阈值,防止微小抖动)
|
||||||
|
const scrollDelta = scrollTop - this.lastScrollTop
|
||||||
|
const isScrollingDown = scrollDelta > 3
|
||||||
|
const isScrollingUp = scrollDelta < -3
|
||||||
|
|
||||||
|
console.log('滚动事件:', {
|
||||||
|
scrollTop,
|
||||||
|
scrollHeight,
|
||||||
|
clientHeight,
|
||||||
|
distanceToBottom,
|
||||||
|
isNearBottom,
|
||||||
|
isScrollingDown,
|
||||||
|
isScrollingUp,
|
||||||
|
isAtBottom: this.isAtBottom,
|
||||||
|
canLoadMore: this.canLoadMore
|
||||||
|
})
|
||||||
|
|
||||||
|
// 如果滑动到底部,标记状态但不加载
|
||||||
|
if (isNearBottom && !this.isAtBottom) {
|
||||||
|
console.log('滑动到底部,等待再次向上滑动触发加载')
|
||||||
|
this.isAtBottom = true
|
||||||
|
this.canLoadMore = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果已经到底部,并且用户开始向上滑动,触发加载
|
||||||
|
if (this.isAtBottom && this.canLoadMore && isScrollingUp) {
|
||||||
|
console.log('底部后向上滑动,触发加载更多')
|
||||||
|
this.canLoadMore = false
|
||||||
|
this.doLoadMore()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果离开底部区域,重置状态
|
||||||
|
if (!isNearBottom && this.isAtBottom) {
|
||||||
|
console.log('离开底部区域')
|
||||||
|
this.isAtBottom = false
|
||||||
|
this.canLoadMore = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只有滑动方向明确时才更新 lastScrollTop
|
||||||
|
if (Math.abs(scrollDelta) > 3) {
|
||||||
|
this.lastScrollTop = scrollTop
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 实际执行加载更多
|
||||||
|
doLoadMore() {
|
||||||
|
console.log('执行加载更多', {
|
||||||
listLength: this.list.length,
|
listLength: this.list.length,
|
||||||
total: this.total,
|
total: this.total,
|
||||||
loading: this.loading,
|
loading: this.loading,
|
||||||
|
loadMoreCooldown: this.loadMoreCooldown,
|
||||||
pageNum: this.query.pageNum
|
pageNum: this.query.pageNum
|
||||||
})
|
})
|
||||||
if (this.list.length >= this.total || this.loading) {
|
|
||||||
console.log('加载更多被阻止:已到最后一页或正在加载中')
|
// 防抖保护:如果正在加载、冷却期或已到最后一页,直接返回
|
||||||
|
if (this.list.length >= this.total || this.loading || this.loadMoreCooldown) {
|
||||||
|
console.log('加载更多被阻止:已到最后一页、正在加载中或冷却期内')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.query.pageNum++
|
|
||||||
console.log('加载第', this.query.pageNum, '页')
|
// 设置冷却期,防止连续触发
|
||||||
this.fetchList(true)
|
this.loadMoreCooldown = true
|
||||||
|
|
||||||
|
// 增加短暂延迟,防止 scrolltolower 立即重复触发
|
||||||
|
if (this._loadMoreTimer) {
|
||||||
|
clearTimeout(this._loadMoreTimer)
|
||||||
|
}
|
||||||
|
|
||||||
|
this._loadMoreTimer = setTimeout(() => {
|
||||||
|
this.query.pageNum++
|
||||||
|
console.log('加载第', this.query.pageNum, '页')
|
||||||
|
this.fetchList(true)
|
||||||
|
}, 150)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 加载更多(点击按钮时调用)
|
||||||
|
onLoadMore() {
|
||||||
|
this.doLoadMore()
|
||||||
},
|
},
|
||||||
|
|
||||||
// 重贴标签
|
// 重贴标签
|
||||||
@@ -397,9 +528,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.list-container {
|
.list-container {
|
||||||
flex: 1;
|
|
||||||
padding: 20rpx;
|
padding: 20rpx;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
|
overflow-y: auto;
|
||||||
|
-webkit-overflow-scrolling: touch;
|
||||||
|
|
||||||
.coil-list {
|
.coil-list {
|
||||||
padding-bottom: 40rpx;
|
padding-bottom: 40rpx;
|
||||||
@@ -416,6 +548,31 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bottom-hint {
|
||||||
|
padding: 30rpx 0 50rpx;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.hint-text {
|
||||||
|
font-size: 28rpx;
|
||||||
|
color: #1a73e8;
|
||||||
|
font-weight: 500;
|
||||||
|
animation: pulse 1.5s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse {
|
||||||
|
0%, 100% {
|
||||||
|
opacity: 0.5;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-3rpx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.placeholder-page {
|
.placeholder-page {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
|||||||
Reference in New Issue
Block a user