diff --git a/apps/hand-factory/pages/todo/index.vue b/apps/hand-factory/pages/todo/index.vue index 59d93b1..991ebf3 100644 --- a/apps/hand-factory/pages/todo/index.vue +++ b/apps/hand-factory/pages/todo/index.vue @@ -30,13 +30,15 @@ /> - @@ -67,12 +69,17 @@ - - - + + + + ↑ 向上滑动加载更多 + + + + @@ -101,6 +108,7 @@ export default { data() { return { statusBarHeight: 0, + scrollViewHeight: 0, // scroll-view 计算高度 tabs: [ { key: 'label', label: '待贴标签', badge: 0 }, { key: 'inspect', label: '检验任务', badge: 0 }, @@ -111,6 +119,10 @@ export default { list: [], loading: false, refreshing: false, + loadMoreCooldown: false, // 加载更多冷却期标志 + isAtBottom: false, // 是否滑动到底部 + canLoadMore: false, // 是否可以加载更多(需要再次向上滑动) + lastScrollTop: 0, // 上次滚动位置 query: { pageNum: 1, pageSize: 10, @@ -129,10 +141,18 @@ export default { // 获取状态栏高度 const systemInfo = uni.getSystemInfoSync() this.statusBarHeight = systemInfo.statusBarHeight || 0 + // 计算 scroll-view 高度 + this.calcScrollViewHeight() + }, + onReady() { + // 页面就绪后再次计算高度(确保布局完成) + this.$nextTick(() => { + this.calcScrollViewHeight() + }) }, computed: { loadMoreStatus() { - if (this.loading) return 'loading' + if (this.loading || this.loadMoreCooldown) return 'loading' if (this.list.length >= this.total) return 'noMore' return 'more' } @@ -141,6 +161,36 @@ export default { this.fetchList() }, 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 handleTabChange(key) { this.activeTab = key @@ -212,6 +262,11 @@ export default { } finally { this.loading = false this.refreshing = false + // 延迟解除冷却期,防止立即再次触发加载 + setTimeout(() => { + this.loadMoreCooldown = false + console.log('加载冷却期结束') + }, 500) } }, @@ -248,21 +303,97 @@ export default { this.fetchList() }, - // 加载更多 - onLoadMore() { - console.log('触发加载更多', { + // 滚动事件处理 + onScroll(e) { + 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, total: this.total, loading: this.loading, + loadMoreCooldown: this.loadMoreCooldown, 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 } - 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 { - flex: 1; padding: 20rpx; box-sizing: border-box; + overflow-y: auto; + -webkit-overflow-scrolling: touch; .coil-list { padding-bottom: 40rpx; @@ -410,12 +542,37 @@ export default { display: flex; justify-content: center; align-items: center; - + &:active { opacity: 0.7; } } - + + .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 { display: flex; flex-direction: column;