fix: 滚动bug修复
This commit is contained in:
parent
1cbb373a4f
commit
4df395b2ad
@ -56,7 +56,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, watch, onMounted, onUnmounted } from 'vue'
|
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
|
||||||
import { getInspirationFlowApi, getBatchOssPresignedUrlsApi } from '@/utils/api.js'
|
import { getInspirationFlowApi, getBatchOssPresignedUrlsApi } from '@/utils/api.js'
|
||||||
import { doubleTapLike } from '@/utils/likeHelper.js'
|
import { doubleTapLike } from '@/utils/likeHelper.js'
|
||||||
import { USE_MOCK_DATA, getMockDataByCategory, generateMockItems, calcSpan } from '../config/mockData.js'
|
import { USE_MOCK_DATA, getMockDataByCategory, generateMockItems, calcSpan } from '../config/mockData.js'
|
||||||
@ -77,7 +77,7 @@ const GAP = rpx2px(16)
|
|||||||
const BORDER_W = rpx2px(2)
|
const BORDER_W = rpx2px(2)
|
||||||
const SCALE = 0.9
|
const SCALE = 0.9
|
||||||
const ROWS = 4
|
const ROWS = 4
|
||||||
const AUTO_SCROLL_SPEED = 1.2
|
const AUTO_SCROLL_SPEED = 0.5
|
||||||
const AUTO_RESUME_DELAY = 2500
|
const AUTO_RESUME_DELAY = 2500
|
||||||
const PRELOAD_THRESHOLD = rpx2px(300)
|
const PRELOAD_THRESHOLD = rpx2px(300)
|
||||||
|
|
||||||
@ -112,24 +112,17 @@ let appendTimer = null // 防抖定时器
|
|||||||
|
|
||||||
const startAutoScroll = () => {
|
const startAutoScroll = () => {
|
||||||
if (!isComponentMounted) {
|
if (!isComponentMounted) {
|
||||||
console.log('[WaterfallGrid] startAutoScroll blocked: not mounted')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (rafId) cancelAnimationFrame(rafId)
|
if (rafId) cancelAnimationFrame(rafId)
|
||||||
console.log('[WaterfallGrid] startAutoScroll started')
|
|
||||||
|
|
||||||
// 启动时先追加一次数据(使用模拟数据时直接追加)
|
|
||||||
if (!isLoadingMore && props.useMockData) {
|
|
||||||
console.log('[WaterfallGrid] startAutoScroll calling appendMore')
|
|
||||||
appendMore()
|
|
||||||
}
|
|
||||||
|
|
||||||
let lastScrollLeft = 0 // 用于检测手动滚动
|
let lastScrollLeft = 0 // 用于检测手动滚动
|
||||||
|
|
||||||
const step = () => {
|
const step = () => {
|
||||||
if (!isComponentMounted) {
|
if (!isComponentMounted) {
|
||||||
rafId = null
|
rafId = null
|
||||||
console.log('[WaterfallGrid] RAF stopped: not mounted')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,7 +158,6 @@ const startAutoScroll = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const stopAutoScroll = () => {
|
const stopAutoScroll = () => {
|
||||||
console.log('[WaterfallGrid] stopAutoScroll called')
|
|
||||||
if (rafId) {
|
if (rafId) {
|
||||||
cafFn(rafId)
|
cafFn(rafId)
|
||||||
rafId = null
|
rafId = null
|
||||||
@ -174,7 +166,8 @@ const stopAutoScroll = () => {
|
|||||||
appendTimer = null
|
appendTimer = null
|
||||||
clearTimeout(resumeTimer)
|
clearTimeout(resumeTimer)
|
||||||
resumeTimer = null
|
resumeTimer = null
|
||||||
userInteracting = false // 重置用户交互状态
|
userInteracting = false
|
||||||
|
isLoadingMore = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const pauseForUser = () => {
|
const pauseForUser = () => {
|
||||||
@ -217,7 +210,7 @@ const scrollStyle = computed(() => ({
|
|||||||
width: props.screenWidth + 'px',
|
width: props.screenWidth + 'px',
|
||||||
height: (props.screenHeight - props.bannerBottom) + 'px',
|
height: (props.screenHeight - props.bannerBottom) + 'px',
|
||||||
zIndex: 2,
|
zIndex: 2,
|
||||||
// overflow: 'hidden',
|
// overflow: 'visible',
|
||||||
}))
|
}))
|
||||||
|
|
||||||
// ========== 布局引擎 ==========
|
// ========== 布局引擎 ==========
|
||||||
@ -454,7 +447,6 @@ const batchGetPresignedUrls = async (urls) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const loadUsers = async () => {
|
const loadUsers = async () => {
|
||||||
console.log('[WaterfallGrid] loadUsers called, isComponentMounted:', isComponentMounted, 'useMockData:', props.useMockData, 'category:', props.category)
|
|
||||||
if (!isComponentMounted) return
|
if (!isComponentMounted) return
|
||||||
|
|
||||||
// 切换分类时重置偏移量
|
// 切换分类时重置偏移量
|
||||||
@ -465,16 +457,16 @@ const loadUsers = async () => {
|
|||||||
|
|
||||||
if (props.useMockData) {
|
if (props.useMockData) {
|
||||||
// 使用分类对应的模拟数据
|
// 使用分类对应的模拟数据
|
||||||
console.log('[WaterfallGrid] 使用模拟数据, category:', props.category)
|
|
||||||
const mockData = getMockDataByCategory(props.category)
|
const mockData = getMockDataByCategory(props.category)
|
||||||
items = mockData.items
|
items = mockData.items
|
||||||
// 更新偏移量
|
// 更新偏移量
|
||||||
mockDataOffset = items.length
|
mockDataOffset = items.length
|
||||||
console.log('[WaterfallGrid] loadUsers 模拟数据加载完成, items:', items.length, 'offset:', mockDataOffset)
|
|
||||||
} else {
|
} else {
|
||||||
// 使用真实API
|
// 使用真实API
|
||||||
const res = await getInspirationFlowApi({ limit: 40, type: props.category })
|
const res = await getInspirationFlowApi({ limit: 40, type: props.category })
|
||||||
console.log('[WaterfallGrid] loadUsers got response, isComponentMounted:', isComponentMounted)
|
|
||||||
if (!isComponentMounted) return
|
if (!isComponentMounted) return
|
||||||
if (res.code === 200 && res.data?.items) {
|
if (res.code === 200 && res.data?.items) {
|
||||||
items = res.data.items
|
items = res.data.items
|
||||||
@ -492,35 +484,29 @@ const loadUsers = async () => {
|
|||||||
span: item.span ?? null,
|
span: item.span ?? null,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log('[WaterfallGrid] loadUsers withData:', withData.length, 'sample likes:', withData.slice(0, 3).map(d => d.likes))
|
|
||||||
allUsers.value = withData
|
allUsers.value = withData
|
||||||
cards.value = layout.compute(withData)
|
cards.value = layout.compute(withData)
|
||||||
totalWidth.value = layout.getTotalWidth()
|
totalWidth.value = layout.getTotalWidth()
|
||||||
}
|
}
|
||||||
|
isLoadingMore = false
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[WaterfallGrid] 加载用户失败', e?.message ?? e)
|
console.error('[WaterfallGrid] 加载用户失败', e?.message ?? e)
|
||||||
|
isLoadingMore = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const appendMore = async () => {
|
const appendMore = async () => {
|
||||||
console.log('[WaterfallGrid] appendMore called', {
|
|
||||||
isLoadingMore,
|
|
||||||
isComponentMounted,
|
|
||||||
useMockData: props.useMockData,
|
|
||||||
category: props.category,
|
|
||||||
cardsLength: cards.value.length,
|
|
||||||
totalWidth: totalWidth.value
|
|
||||||
})
|
|
||||||
if (!isComponentMounted) {
|
if (!isComponentMounted) {
|
||||||
console.log('[WaterfallGrid] appendMore blocked: not mounted')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (isLoadingMore) {
|
if (isLoadingMore) {
|
||||||
console.log('[WaterfallGrid] appendMore blocked: already loading, waiting...')
|
|
||||||
// 等待一下再试
|
// 等待一下再试
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (isComponentMounted && !isLoadingMore) {
|
if (isComponentMounted && !isLoadingMore) {
|
||||||
console.log('[WaterfallGrid] appendMore retry after wait')
|
|
||||||
appendMore()
|
appendMore()
|
||||||
}
|
}
|
||||||
}, 500)
|
}, 500)
|
||||||
@ -536,7 +522,7 @@ const appendMore = async () => {
|
|||||||
const allItems = mockData.items
|
const allItems = mockData.items
|
||||||
const batchSize = 20
|
const batchSize = 20
|
||||||
|
|
||||||
console.log('[WaterfallGrid] 模拟数据追加, offset:', mockDataOffset, 'items:', allItems.length)
|
|
||||||
|
|
||||||
// 从 mockDataOffset 位置开始取 batchSize 个
|
// 从 mockDataOffset 位置开始取 batchSize 个
|
||||||
const itemsToAdd = []
|
const itemsToAdd = []
|
||||||
@ -556,7 +542,7 @@ const appendMore = async () => {
|
|||||||
mockDataOffset = mockDataOffset + batchSize
|
mockDataOffset = mockDataOffset + batchSize
|
||||||
|
|
||||||
items = itemsToAdd
|
items = itemsToAdd
|
||||||
console.log('[WaterfallGrid] 模拟数据追加完成, items:', items.length, 'next offset:', mockDataOffset)
|
|
||||||
} else {
|
} else {
|
||||||
// 使用真实API
|
// 使用真实API
|
||||||
const res = await getInspirationFlowApi({ limit: 20, type: props.category })
|
const res = await getInspirationFlowApi({ limit: 20, type: props.category })
|
||||||
@ -567,7 +553,7 @@ const appendMore = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (items && items.length > 0) {
|
if (items && items.length > 0) {
|
||||||
console.log('[WaterfallGrid] appendMore before:', 'cards.length:', cards.value.length, 'totalWidth:', totalWidth.value)
|
|
||||||
const withData = items.map((item) => {
|
const withData = items.map((item) => {
|
||||||
return {
|
return {
|
||||||
id: item.asset_id, // 直接使用 asset_id(已经在上面保证唯一)
|
id: item.asset_id, // 直接使用 asset_id(已经在上面保证唯一)
|
||||||
@ -578,17 +564,17 @@ const appendMore = async () => {
|
|||||||
span: item.span ?? null,
|
span: item.span ?? null,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
console.log('[WaterfallGrid] appendMore withData:', withData.length)
|
|
||||||
const placed = layout.addCards(withData)
|
const placed = layout.addCards(withData)
|
||||||
console.log('[WaterfallGrid] appendMore placed:', placed.length, 'layout.curX:', layout.curX)
|
|
||||||
cards.value = [...cards.value, ...placed]
|
cards.value = [...cards.value, ...placed]
|
||||||
totalWidth.value = layout.getTotalWidth()
|
totalWidth.value = layout.getTotalWidth()
|
||||||
console.log('[WaterfallGrid] appendMore after:', 'cards.length:', cards.value.length, 'totalWidth:', totalWidth.value)
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('[WaterfallGrid] 追加用户失败', e?.message ?? e)
|
console.error('[WaterfallGrid] 追加用户失败', e?.message ?? e)
|
||||||
} finally {
|
} finally {
|
||||||
console.log('[WaterfallGrid] appendMore finally, resetting isLoadingMore')
|
|
||||||
isLoadingMore = false
|
isLoadingMore = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -598,11 +584,11 @@ const cardTapTimers = {};
|
|||||||
|
|
||||||
const handleCardClick = (card) => {
|
const handleCardClick = (card) => {
|
||||||
|
|
||||||
// if (cardTapTimers[card.id]) {
|
if (cardTapTimers[card.id]) {
|
||||||
// 第二次点击,双击点赞
|
// 第二次点击,双击点赞
|
||||||
clearTimeout(cardTapTimers[card.id]);
|
clearTimeout(cardTapTimers[card.id]);
|
||||||
delete cardTapTimers[card.id];
|
delete cardTapTimers[card.id];
|
||||||
console.log('双击,触发动画');
|
|
||||||
|
|
||||||
// 触发动画
|
// 触发动画
|
||||||
likingMap.value = { ...likingMap.value, [card.id]: true };
|
likingMap.value = { ...likingMap.value, [card.id]: true };
|
||||||
@ -616,14 +602,14 @@ const handleCardClick = (card) => {
|
|||||||
uni.showToast({ title: '点赞成功', icon: 'success' });
|
uni.showToast({ title: '点赞成功', icon: 'success' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
// } else {
|
} else {
|
||||||
// // 第一次点击,单击跳转
|
// 第一次点击,单击跳转
|
||||||
// console.log('单击,等待跳转');
|
|
||||||
// cardTapTimers[card.id] = setTimeout(() => {
|
cardTapTimers[card.id] = setTimeout(() => {
|
||||||
// delete cardTapTimers[card.id];
|
delete cardTapTimers[card.id];
|
||||||
// uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
|
uni.navigateTo({ url: `/pages/asset-detail/asset-detail?asset_id=${card.id}` });
|
||||||
// }, 300);
|
}, 300);
|
||||||
// }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========== 初始化 ==========
|
// ========== 初始化 ==========
|
||||||
@ -631,12 +617,14 @@ onMounted(() => {
|
|||||||
isComponentMounted = true
|
isComponentMounted = true
|
||||||
const containerH = props.screenHeight - props.bannerBottom
|
const containerH = props.screenHeight - props.bannerBottom
|
||||||
layout = new WaterfallLayout(containerH, props.category)
|
layout = new WaterfallLayout(containerH, props.category)
|
||||||
console.log('[WaterfallGrid] onMounted, starting...')
|
currentScrollLeft = 0
|
||||||
loadUsers().then(() => startAutoScroll())
|
scrollLeft.value = 0
|
||||||
|
loadUsers().then(() => {
|
||||||
|
nextTick(() => startAutoScroll())
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
console.log('[WaterfallGrid] onUnmounted called!')
|
|
||||||
isComponentMounted = false
|
isComponentMounted = false
|
||||||
stopAutoScroll()
|
stopAutoScroll()
|
||||||
clearTimeout(resumeTimer)
|
clearTimeout(resumeTimer)
|
||||||
@ -654,9 +642,7 @@ watch(() => [props.screenHeight, props.bannerBottom], () => {
|
|||||||
|
|
||||||
// 监听分类变化,重新加载数据
|
// 监听分类变化,重新加载数据
|
||||||
watch(() => props.category, (newCategory) => {
|
watch(() => props.category, (newCategory) => {
|
||||||
console.log('[WaterfallGrid] category changed to:', newCategory)
|
|
||||||
if (isComponentMounted) {
|
if (isComponentMounted) {
|
||||||
// 先停止当前的自动滚动
|
|
||||||
stopAutoScroll()
|
stopAutoScroll()
|
||||||
|
|
||||||
// 取消待执行的追加
|
// 取消待执行的追加
|
||||||
@ -664,7 +650,10 @@ watch(() => props.category, (newCategory) => {
|
|||||||
clearTimeout(appendTimer)
|
clearTimeout(appendTimer)
|
||||||
appendTimer = null
|
appendTimer = null
|
||||||
}
|
}
|
||||||
isLoadingMore = false // 重置加载状态
|
|
||||||
|
// 重置滚动位置
|
||||||
|
currentScrollLeft = 0
|
||||||
|
scrollLeft.value = 0
|
||||||
|
|
||||||
// 重新创建布局(使用新的 span 阈值)
|
// 重新创建布局(使用新的 span 阈值)
|
||||||
const containerH = props.screenHeight - props.bannerBottom
|
const containerH = props.screenHeight - props.bannerBottom
|
||||||
@ -672,9 +661,18 @@ watch(() => props.category, (newCategory) => {
|
|||||||
cards.value = []
|
cards.value = []
|
||||||
allUsers.value = []
|
allUsers.value = []
|
||||||
totalWidth.value = 0
|
totalWidth.value = 0
|
||||||
|
mockDataOffset = 0
|
||||||
|
|
||||||
|
// 先停止 RAF,确保在 loadUsers 完成前不会有滚动逻辑
|
||||||
|
if (rafId) {
|
||||||
|
cafFn(rafId)
|
||||||
|
rafId = null
|
||||||
|
}
|
||||||
|
|
||||||
loadUsers().then(() => {
|
loadUsers().then(() => {
|
||||||
// 加载完成后启动自动滚动
|
nextTick(() => {
|
||||||
startAutoScroll()
|
startAutoScroll()
|
||||||
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -689,6 +687,7 @@ watch(() => props.category, (newCategory) => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
top: 32rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wf-card {
|
.wf-card {
|
||||||
@ -766,14 +765,14 @@ watch(() => props.category, (newCategory) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wf-like-wave-outer {
|
.wf-like-wave-outer {
|
||||||
top: -20rpx;
|
top: -12rpx;
|
||||||
left: -20rpx;
|
left: -12rpx;
|
||||||
border-width: 12rpx;
|
border-width: 12rpx;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wf-like-wave-inner {
|
.wf-like-wave-inner {
|
||||||
top: -4rpx;
|
top: 4rpx;
|
||||||
left: -4rpx;
|
left: 4rpx;
|
||||||
border-width: 6rpx;
|
border-width: 6rpx;
|
||||||
animation-delay: 0.2s;
|
animation-delay: 0.2s;
|
||||||
}
|
}
|
||||||
@ -788,7 +787,7 @@ watch(() => props.category, (newCategory) => {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
transform: scale(1.25);
|
transform: scale(1.2);
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -93,7 +93,7 @@ const {
|
|||||||
} = useBanner()
|
} = useBanner()
|
||||||
|
|
||||||
// banner(216+360rpx) + tab栏(16+80rpx) + 间距(8rpx) ≈ 680rpx
|
// banner(216+360rpx) + tab栏(16+80rpx) + 间距(8rpx) ≈ 680rpx
|
||||||
const bannerBottomPx = computed(() => Math.round(screenWidth.value / 750 * 716))
|
const bannerBottomPx = computed(() => Math.round(screenWidth.value / 750 * 715))
|
||||||
|
|
||||||
// ========== Handlers ==========
|
// ========== Handlers ==========
|
||||||
const handleCardClick = (card) => {
|
const handleCardClick = (card) => {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user