feat: Enhance scrolling behavior with pause before refresh

- Introduced a pause mechanism at the end of scrolling for both Ladder and Ranking boards, allowing the last item to be fully visible before triggering a data refresh.
- Updated scrolling logic to prevent immediate resets, improving user experience during data loading.
- Added a consistent pause duration across components to standardize behavior during scrolling interactions.
This commit is contained in:
Ethanfly 2026-02-10 10:50:41 +08:00
parent f861f82675
commit ccea3a99e5
5 changed files with 127 additions and 15 deletions

View File

@ -243,6 +243,7 @@ const boardsScrollY = ref(0);
const matchesScrollY = ref(0); const matchesScrollY = ref(0);
const REFRESH_COOLDOWN_MS = 3000; const REFRESH_COOLDOWN_MS = 3000;
const PAUSE_AT_END_MS = 2800; //
let lastRefreshAt = 0; let lastRefreshAt = 0;
const onScrollEndRefresh = () => { const onScrollEndRefresh = () => {
@ -360,6 +361,8 @@ const startBoardsScroll = () => {
if (!boardsScrollEl.value || !boardsGroup1.value) return; if (!boardsScrollEl.value || !boardsGroup1.value) return;
if (loadingBoards.value) return; if (loadingBoards.value) return;
let boardsPauseScheduled = false;
const tick = () => { const tick = () => {
if (!boardsScrollEl.value || !boardsGroup1.value) { if (!boardsScrollEl.value || !boardsGroup1.value) {
boardsRaf = requestAnimationFrame(tick); boardsRaf = requestAnimationFrame(tick);
@ -369,6 +372,10 @@ const startBoardsScroll = () => {
boardsRaf = requestAnimationFrame(tick); boardsRaf = requestAnimationFrame(tick);
return; return;
} }
if (boardsPauseScheduled) {
boardsRaf = requestAnimationFrame(tick);
return;
}
const h = boardsGroup1.value.offsetHeight; const h = boardsGroup1.value.offsetHeight;
const containerH = boardsScrollEl.value.offsetHeight; const containerH = boardsScrollEl.value.offsetHeight;
@ -380,8 +387,15 @@ const startBoardsScroll = () => {
boardsScrollY.value -= 0.35; boardsScrollY.value -= 0.35;
if (Math.abs(boardsScrollY.value) >= h) { if (Math.abs(boardsScrollY.value) >= h) {
boardsScrollY.value = 0; boardsScrollY.value = -h;
if (!boardsPauseScheduled) {
boardsPauseScheduled = true;
setTimeout(() => {
onScrollEndRefresh(); onScrollEndRefresh();
boardsScrollY.value = 0;
boardsPauseScheduled = false;
}, PAUSE_AT_END_MS);
}
} }
boardsRaf = requestAnimationFrame(tick); boardsRaf = requestAnimationFrame(tick);
}; };
@ -394,6 +408,8 @@ const startMatchesScroll = () => {
if (!matchesScrollEl.value || !matchesGroup1.value) return; if (!matchesScrollEl.value || !matchesGroup1.value) return;
if (loadingMatches.value) return; if (loadingMatches.value) return;
let matchesPauseScheduled = false;
const tick = () => { const tick = () => {
if (!matchesScrollEl.value || !matchesGroup1.value) { if (!matchesScrollEl.value || !matchesGroup1.value) {
matchesRaf = requestAnimationFrame(tick); matchesRaf = requestAnimationFrame(tick);
@ -403,6 +419,10 @@ const startMatchesScroll = () => {
matchesRaf = requestAnimationFrame(tick); matchesRaf = requestAnimationFrame(tick);
return; return;
} }
if (matchesPauseScheduled) {
matchesRaf = requestAnimationFrame(tick);
return;
}
const h = matchesGroup1.value.offsetHeight; const h = matchesGroup1.value.offsetHeight;
const containerH = matchesScrollEl.value.offsetHeight; const containerH = matchesScrollEl.value.offsetHeight;
@ -414,8 +434,15 @@ const startMatchesScroll = () => {
matchesScrollY.value -= 0.4; matchesScrollY.value -= 0.4;
if (Math.abs(matchesScrollY.value) >= h) { if (Math.abs(matchesScrollY.value) >= h) {
matchesScrollY.value = 0; matchesScrollY.value = -h;
if (!matchesPauseScheduled) {
matchesPauseScheduled = true;
setTimeout(() => {
onScrollEndRefresh(); onScrollEndRefresh();
matchesScrollY.value = 0;
matchesPauseScheduled = false;
}, PAUSE_AT_END_MS);
}
} }
matchesRaf = requestAnimationFrame(tick); matchesRaf = requestAnimationFrame(tick);
}; };

View File

@ -243,6 +243,7 @@ const boardsScrollY = ref(0);
const matchesScrollY = ref(0); const matchesScrollY = ref(0);
const REFRESH_COOLDOWN_MS = 3000; const REFRESH_COOLDOWN_MS = 3000;
const PAUSE_AT_END_MS = 2800; //
let lastRefreshAt = 0; let lastRefreshAt = 0;
const onScrollEndRefresh = () => { const onScrollEndRefresh = () => {
@ -360,6 +361,8 @@ const startBoardsScroll = () => {
if (!boardsScrollEl.value || !boardsGroup1.value) return; if (!boardsScrollEl.value || !boardsGroup1.value) return;
if (loadingBoards.value) return; if (loadingBoards.value) return;
let boardsPauseScheduled = false;
const tick = () => { const tick = () => {
if (!boardsScrollEl.value || !boardsGroup1.value) { if (!boardsScrollEl.value || !boardsGroup1.value) {
boardsRaf = requestAnimationFrame(tick); boardsRaf = requestAnimationFrame(tick);
@ -369,6 +372,10 @@ const startBoardsScroll = () => {
boardsRaf = requestAnimationFrame(tick); boardsRaf = requestAnimationFrame(tick);
return; return;
} }
if (boardsPauseScheduled) {
boardsRaf = requestAnimationFrame(tick);
return;
}
const h = boardsGroup1.value.offsetHeight; const h = boardsGroup1.value.offsetHeight;
const containerH = boardsScrollEl.value.offsetHeight; const containerH = boardsScrollEl.value.offsetHeight;
@ -380,8 +387,15 @@ const startBoardsScroll = () => {
boardsScrollY.value -= 0.35; boardsScrollY.value -= 0.35;
if (Math.abs(boardsScrollY.value) >= h) { if (Math.abs(boardsScrollY.value) >= h) {
boardsScrollY.value = 0; boardsScrollY.value = -h;
if (!boardsPauseScheduled) {
boardsPauseScheduled = true;
setTimeout(() => {
onScrollEndRefresh(); onScrollEndRefresh();
boardsScrollY.value = 0;
boardsPauseScheduled = false;
}, PAUSE_AT_END_MS);
}
} }
boardsRaf = requestAnimationFrame(tick); boardsRaf = requestAnimationFrame(tick);
}; };
@ -394,6 +408,8 @@ const startMatchesScroll = () => {
if (!matchesScrollEl.value || !matchesGroup1.value) return; if (!matchesScrollEl.value || !matchesGroup1.value) return;
if (loadingMatches.value) return; if (loadingMatches.value) return;
let matchesPauseScheduled = false;
const tick = () => { const tick = () => {
if (!matchesScrollEl.value || !matchesGroup1.value) { if (!matchesScrollEl.value || !matchesGroup1.value) {
matchesRaf = requestAnimationFrame(tick); matchesRaf = requestAnimationFrame(tick);
@ -403,6 +419,10 @@ const startMatchesScroll = () => {
matchesRaf = requestAnimationFrame(tick); matchesRaf = requestAnimationFrame(tick);
return; return;
} }
if (matchesPauseScheduled) {
matchesRaf = requestAnimationFrame(tick);
return;
}
const h = matchesGroup1.value.offsetHeight; const h = matchesGroup1.value.offsetHeight;
const containerH = matchesScrollEl.value.offsetHeight; const containerH = matchesScrollEl.value.offsetHeight;
@ -414,8 +434,15 @@ const startMatchesScroll = () => {
matchesScrollY.value -= 0.4; matchesScrollY.value -= 0.4;
if (Math.abs(matchesScrollY.value) >= h) { if (Math.abs(matchesScrollY.value) >= h) {
matchesScrollY.value = 0; matchesScrollY.value = -h;
if (!matchesPauseScheduled) {
matchesPauseScheduled = true;
setTimeout(() => {
onScrollEndRefresh(); onScrollEndRefresh();
matchesScrollY.value = 0;
matchesPauseScheduled = false;
}, PAUSE_AT_END_MS);
}
} }
matchesRaf = requestAnimationFrame(tick); matchesRaf = requestAnimationFrame(tick);
}; };

View File

@ -500,6 +500,7 @@
const scrollYMale = ref(0); const scrollYMale = ref(0);
const scrollYFemale = ref(0); const scrollYFemale = ref(0);
const REFRESH_COOLDOWN_MS = 3000; const REFRESH_COOLDOWN_MS = 3000;
const PAUSE_AT_END_MS = 2800; //
let lastRefreshAt = 0; let lastRefreshAt = 0;
// //
@ -518,22 +519,37 @@
const startContinuousScroll = (listRef, scrollYRef, reqIdRef) => { const startContinuousScroll = (listRef, scrollYRef, reqIdRef) => {
if (reqIdRef.value) cancelAnimationFrame(reqIdRef.value); if (reqIdRef.value) cancelAnimationFrame(reqIdRef.value);
let pauseScheduled = false;
const scroll = () => { const scroll = () => {
if (isRefreshing.value || listRef.value.length === 0) { if (isRefreshing.value || listRef.value.length === 0) {
reqIdRef.value = requestAnimationFrame(scroll); reqIdRef.value = requestAnimationFrame(scroll);
return; return;
} }
//
if (pauseScheduled) {
reqIdRef.value = requestAnimationFrame(scroll);
return;
}
// (使) // (使)
scrollYRef.value -= 0.6; scrollYRef.value -= 0.6;
// 100 * (90px + 12px) // 100 * (90px + 12px)
const singleSetHeight = listRef.value.length * 102; const singleSetHeight = listRef.value.length * 102;
// 0 //
if (Math.abs(scrollYRef.value) >= singleSetHeight) { if (Math.abs(scrollYRef.value) >= singleSetHeight) {
scrollYRef.value = 0; scrollYRef.value = -singleSetHeight;
if (!pauseScheduled) {
pauseScheduled = true;
setTimeout(() => {
onScrollEndRefresh(); onScrollEndRefresh();
scrollYRef.value = 0;
pauseScheduled = false;
}, PAUSE_AT_END_MS);
}
} }
reqIdRef.value = requestAnimationFrame(scroll); reqIdRef.value = requestAnimationFrame(scroll);

View File

@ -549,6 +549,7 @@
const scrollYMale = ref(0); const scrollYMale = ref(0);
const scrollYFemale = ref(0); const scrollYFemale = ref(0);
const REFRESH_COOLDOWN_MS = 3000; const REFRESH_COOLDOWN_MS = 3000;
const PAUSE_AT_END_MS = 2800; //
let lastRefreshAt = 0; let lastRefreshAt = 0;
const updateTime = () => { const updateTime = () => {
@ -564,19 +565,33 @@
const startContinuousScroll = (listRef, scrollYRef, reqIdRef, groupRef) => { const startContinuousScroll = (listRef, scrollYRef, reqIdRef, groupRef) => {
if (reqIdRef.value) cancelAnimationFrame(reqIdRef.value); if (reqIdRef.value) cancelAnimationFrame(reqIdRef.value);
let pauseScheduled = false;
const scroll = () => { const scroll = () => {
if (isRefreshing.value || listRef.value.length <= 3 || !groupRef.value) { if (isRefreshing.value || listRef.value.length <= 3 || !groupRef.value) {
reqIdRef.value = requestAnimationFrame(scroll); reqIdRef.value = requestAnimationFrame(scroll);
return; return;
} }
if (pauseScheduled) {
reqIdRef.value = requestAnimationFrame(scroll);
return;
}
scrollYRef.value -= 0.6; scrollYRef.value -= 0.6;
const singleSetHeight = groupRef.value.offsetHeight + 12; const singleSetHeight = groupRef.value.offsetHeight + 12;
if (Math.abs(scrollYRef.value) >= singleSetHeight) { if (Math.abs(scrollYRef.value) >= singleSetHeight) {
scrollYRef.value = 0; scrollYRef.value = -singleSetHeight;
if (!pauseScheduled) {
pauseScheduled = true;
setTimeout(() => {
onScrollEndRefresh(); onScrollEndRefresh();
scrollYRef.value = 0;
pauseScheduled = false;
}, PAUSE_AT_END_MS);
}
} }
reqIdRef.value = requestAnimationFrame(scroll); reqIdRef.value = requestAnimationFrame(scroll);

View File

@ -226,6 +226,12 @@ class UserController {
updateData.gender = normalizedGender; updateData.gender = normalizedGender;
await existingUser.update(updateData); await existingUser.update(updateData);
if (nickname) {
await LadderUser.update(
{ real_name: nickname },
{ where: { user_id: existingUser.id } },
);
}
user = existingUser; user = existingUser;
} else { } else {
// 新用户注册 // 新用户注册
@ -260,7 +266,12 @@ class UserController {
updateData.gender = normalizedGender; updateData.gender = normalizedGender;
await user.update(updateData); await user.update(updateData);
if (nickname) {
await LadderUser.update(
{ real_name: nickname },
{ where: { user_id: user.id } },
);
}
// 如果手机号变化,重新关联天梯用户 // 如果手机号变化,重新关联天梯用户
await this.linkLadderUsers(user.id, phone); await this.linkLadderUsers(user.id, phone);
} }
@ -371,6 +382,14 @@ class UserController {
await user.update(updateData); await user.update(updateData);
// 用户修改昵称时,同步更新该用户下所有天梯用户的 real_name保持两边一致
if (nickname) {
await LadderUser.update(
{ real_name: nickname },
{ where: { user_id: user.id } },
);
}
res.json( res.json(
success( success(
{ {
@ -447,13 +466,21 @@ class UserController {
const { nickname, avatar, phone, gender } = req.body; const { nickname, avatar, phone, gender } = req.body;
const user = req.user; const user = req.user;
const newNickname = nickname || user.nickname;
await user.update({ await user.update({
nickname: nickname || user.nickname, nickname: newNickname,
avatar: avatar || user.avatar, avatar: avatar || user.avatar,
phone: phone || user.phone, phone: phone || user.phone,
gender: gender !== undefined ? gender : user.gender, gender: gender !== undefined ? gender : user.gender,
}); });
if (nickname) {
await LadderUser.update(
{ real_name: nickname },
{ where: { user_id: user.id } },
);
}
res.json(success(null, "更新成功")); res.json(success(null, "更新成功"));
} catch (err) { } catch (err) {
console.error("更新用户信息失败:", err); console.error("更新用户信息失败:", err);