From ccea3a99e5e583822689bf416fd58f8a168510af Mon Sep 17 00:00:00 2001 From: Ethanfly Date: Tue, 10 Feb 2026 10:50:41 +0800 Subject: [PATCH] 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. --- admin/src/views/display/LadderSummary.vue | 35 ++++++++++++++++--- .../src/views/display/LadderSummaryOrange.vue | 35 ++++++++++++++++--- admin/src/views/display/RankingBoard.vue | 22 ++++++++++-- .../src/views/display/RankingBoardOrange.vue | 19 ++++++++-- server/src/controllers/userController.js | 31 ++++++++++++++-- 5 files changed, 127 insertions(+), 15 deletions(-) diff --git a/admin/src/views/display/LadderSummary.vue b/admin/src/views/display/LadderSummary.vue index 132d20c4..632704f9 100644 --- a/admin/src/views/display/LadderSummary.vue +++ b/admin/src/views/display/LadderSummary.vue @@ -243,6 +243,7 @@ const boardsScrollY = ref(0); const matchesScrollY = ref(0); const REFRESH_COOLDOWN_MS = 3000; +const PAUSE_AT_END_MS = 2800; // 到底后停留,让最后一项完整露出后再刷新 let lastRefreshAt = 0; const onScrollEndRefresh = () => { @@ -360,6 +361,8 @@ const startBoardsScroll = () => { if (!boardsScrollEl.value || !boardsGroup1.value) return; if (loadingBoards.value) return; + let boardsPauseScheduled = false; + const tick = () => { if (!boardsScrollEl.value || !boardsGroup1.value) { boardsRaf = requestAnimationFrame(tick); @@ -369,6 +372,10 @@ const startBoardsScroll = () => { boardsRaf = requestAnimationFrame(tick); return; } + if (boardsPauseScheduled) { + boardsRaf = requestAnimationFrame(tick); + return; + } const h = boardsGroup1.value.offsetHeight; const containerH = boardsScrollEl.value.offsetHeight; @@ -380,8 +387,15 @@ const startBoardsScroll = () => { boardsScrollY.value -= 0.35; if (Math.abs(boardsScrollY.value) >= h) { - boardsScrollY.value = 0; - onScrollEndRefresh(); + boardsScrollY.value = -h; + if (!boardsPauseScheduled) { + boardsPauseScheduled = true; + setTimeout(() => { + onScrollEndRefresh(); + boardsScrollY.value = 0; + boardsPauseScheduled = false; + }, PAUSE_AT_END_MS); + } } boardsRaf = requestAnimationFrame(tick); }; @@ -394,6 +408,8 @@ const startMatchesScroll = () => { if (!matchesScrollEl.value || !matchesGroup1.value) return; if (loadingMatches.value) return; + let matchesPauseScheduled = false; + const tick = () => { if (!matchesScrollEl.value || !matchesGroup1.value) { matchesRaf = requestAnimationFrame(tick); @@ -403,6 +419,10 @@ const startMatchesScroll = () => { matchesRaf = requestAnimationFrame(tick); return; } + if (matchesPauseScheduled) { + matchesRaf = requestAnimationFrame(tick); + return; + } const h = matchesGroup1.value.offsetHeight; const containerH = matchesScrollEl.value.offsetHeight; @@ -414,8 +434,15 @@ const startMatchesScroll = () => { matchesScrollY.value -= 0.4; if (Math.abs(matchesScrollY.value) >= h) { - matchesScrollY.value = 0; - onScrollEndRefresh(); + matchesScrollY.value = -h; + if (!matchesPauseScheduled) { + matchesPauseScheduled = true; + setTimeout(() => { + onScrollEndRefresh(); + matchesScrollY.value = 0; + matchesPauseScheduled = false; + }, PAUSE_AT_END_MS); + } } matchesRaf = requestAnimationFrame(tick); }; diff --git a/admin/src/views/display/LadderSummaryOrange.vue b/admin/src/views/display/LadderSummaryOrange.vue index 720068d6..5c11a34c 100644 --- a/admin/src/views/display/LadderSummaryOrange.vue +++ b/admin/src/views/display/LadderSummaryOrange.vue @@ -243,6 +243,7 @@ const boardsScrollY = ref(0); const matchesScrollY = ref(0); const REFRESH_COOLDOWN_MS = 3000; +const PAUSE_AT_END_MS = 2800; // 到底后停留,让最后一项完整露出后再刷新 let lastRefreshAt = 0; const onScrollEndRefresh = () => { @@ -360,6 +361,8 @@ const startBoardsScroll = () => { if (!boardsScrollEl.value || !boardsGroup1.value) return; if (loadingBoards.value) return; + let boardsPauseScheduled = false; + const tick = () => { if (!boardsScrollEl.value || !boardsGroup1.value) { boardsRaf = requestAnimationFrame(tick); @@ -369,6 +372,10 @@ const startBoardsScroll = () => { boardsRaf = requestAnimationFrame(tick); return; } + if (boardsPauseScheduled) { + boardsRaf = requestAnimationFrame(tick); + return; + } const h = boardsGroup1.value.offsetHeight; const containerH = boardsScrollEl.value.offsetHeight; @@ -380,8 +387,15 @@ const startBoardsScroll = () => { boardsScrollY.value -= 0.35; if (Math.abs(boardsScrollY.value) >= h) { - boardsScrollY.value = 0; - onScrollEndRefresh(); + boardsScrollY.value = -h; + if (!boardsPauseScheduled) { + boardsPauseScheduled = true; + setTimeout(() => { + onScrollEndRefresh(); + boardsScrollY.value = 0; + boardsPauseScheduled = false; + }, PAUSE_AT_END_MS); + } } boardsRaf = requestAnimationFrame(tick); }; @@ -394,6 +408,8 @@ const startMatchesScroll = () => { if (!matchesScrollEl.value || !matchesGroup1.value) return; if (loadingMatches.value) return; + let matchesPauseScheduled = false; + const tick = () => { if (!matchesScrollEl.value || !matchesGroup1.value) { matchesRaf = requestAnimationFrame(tick); @@ -403,6 +419,10 @@ const startMatchesScroll = () => { matchesRaf = requestAnimationFrame(tick); return; } + if (matchesPauseScheduled) { + matchesRaf = requestAnimationFrame(tick); + return; + } const h = matchesGroup1.value.offsetHeight; const containerH = matchesScrollEl.value.offsetHeight; @@ -414,8 +434,15 @@ const startMatchesScroll = () => { matchesScrollY.value -= 0.4; if (Math.abs(matchesScrollY.value) >= h) { - matchesScrollY.value = 0; - onScrollEndRefresh(); + matchesScrollY.value = -h; + if (!matchesPauseScheduled) { + matchesPauseScheduled = true; + setTimeout(() => { + onScrollEndRefresh(); + matchesScrollY.value = 0; + matchesPauseScheduled = false; + }, PAUSE_AT_END_MS); + } } matchesRaf = requestAnimationFrame(tick); }; diff --git a/admin/src/views/display/RankingBoard.vue b/admin/src/views/display/RankingBoard.vue index 131c9da8..de6fb4da 100644 --- a/admin/src/views/display/RankingBoard.vue +++ b/admin/src/views/display/RankingBoard.vue @@ -500,6 +500,7 @@ const scrollYMale = ref(0); const scrollYFemale = ref(0); const REFRESH_COOLDOWN_MS = 3000; + const PAUSE_AT_END_MS = 2800; // 到底后停留时长,让最后一名完整露出后再刷新 let lastRefreshAt = 0; // 更新时间 @@ -518,22 +519,37 @@ const startContinuousScroll = (listRef, scrollYRef, reqIdRef) => { if (reqIdRef.value) cancelAnimationFrame(reqIdRef.value); + let pauseScheduled = false; + const scroll = () => { if (isRefreshing.value || listRef.value.length === 0) { reqIdRef.value = requestAnimationFrame(scroll); return; } + // 已到底并处于“停留展示最后一名”的暂停期,本帧不滚动 + if (pauseScheduled) { + reqIdRef.value = requestAnimationFrame(scroll); + return; + } + // 每一帧移动的像素 (降低速度,使其更易阅读) scrollYRef.value -= 0.6; // 计算单份数据的高度:100条 * (90px行高 + 12px间距) const singleSetHeight = listRef.value.length * 102; - // 如果滚完了一整套数据,瞬间重置回 0,并触发一次刷新 + // 滚完一整屏:先停在底部一段时间让最后一名露出,再刷新并重置 if (Math.abs(scrollYRef.value) >= singleSetHeight) { - scrollYRef.value = 0; - onScrollEndRefresh(); + scrollYRef.value = -singleSetHeight; + if (!pauseScheduled) { + pauseScheduled = true; + setTimeout(() => { + onScrollEndRefresh(); + scrollYRef.value = 0; + pauseScheduled = false; + }, PAUSE_AT_END_MS); + } } reqIdRef.value = requestAnimationFrame(scroll); diff --git a/admin/src/views/display/RankingBoardOrange.vue b/admin/src/views/display/RankingBoardOrange.vue index b989c2c3..fb8994bb 100644 --- a/admin/src/views/display/RankingBoardOrange.vue +++ b/admin/src/views/display/RankingBoardOrange.vue @@ -549,6 +549,7 @@ const scrollYMale = ref(0); const scrollYFemale = ref(0); const REFRESH_COOLDOWN_MS = 3000; + const PAUSE_AT_END_MS = 2800; // 到底后停留时长,让最后一名完整露出后再刷新 let lastRefreshAt = 0; const updateTime = () => { @@ -564,19 +565,33 @@ const startContinuousScroll = (listRef, scrollYRef, reqIdRef, groupRef) => { if (reqIdRef.value) cancelAnimationFrame(reqIdRef.value); + let pauseScheduled = false; + const scroll = () => { if (isRefreshing.value || listRef.value.length <= 3 || !groupRef.value) { reqIdRef.value = requestAnimationFrame(scroll); return; } + if (pauseScheduled) { + reqIdRef.value = requestAnimationFrame(scroll); + return; + } + scrollYRef.value -= 0.6; const singleSetHeight = groupRef.value.offsetHeight + 12; if (Math.abs(scrollYRef.value) >= singleSetHeight) { - scrollYRef.value = 0; - onScrollEndRefresh(); + scrollYRef.value = -singleSetHeight; + if (!pauseScheduled) { + pauseScheduled = true; + setTimeout(() => { + onScrollEndRefresh(); + scrollYRef.value = 0; + pauseScheduled = false; + }, PAUSE_AT_END_MS); + } } reqIdRef.value = requestAnimationFrame(scroll); diff --git a/server/src/controllers/userController.js b/server/src/controllers/userController.js index a7b1857e..f9315052 100644 --- a/server/src/controllers/userController.js +++ b/server/src/controllers/userController.js @@ -226,6 +226,12 @@ class UserController { updateData.gender = normalizedGender; await existingUser.update(updateData); + if (nickname) { + await LadderUser.update( + { real_name: nickname }, + { where: { user_id: existingUser.id } }, + ); + } user = existingUser; } else { // 新用户注册 @@ -260,7 +266,12 @@ class UserController { updateData.gender = normalizedGender; await user.update(updateData); - + if (nickname) { + await LadderUser.update( + { real_name: nickname }, + { where: { user_id: user.id } }, + ); + } // 如果手机号变化,重新关联天梯用户 await this.linkLadderUsers(user.id, phone); } @@ -371,6 +382,14 @@ class UserController { await user.update(updateData); + // 用户修改昵称时,同步更新该用户下所有天梯用户的 real_name,保持两边一致 + if (nickname) { + await LadderUser.update( + { real_name: nickname }, + { where: { user_id: user.id } }, + ); + } + res.json( success( { @@ -447,13 +466,21 @@ class UserController { const { nickname, avatar, phone, gender } = req.body; const user = req.user; + const newNickname = nickname || user.nickname; await user.update({ - nickname: nickname || user.nickname, + nickname: newNickname, avatar: avatar || user.avatar, phone: phone || user.phone, gender: gender !== undefined ? gender : user.gender, }); + if (nickname) { + await LadderUser.update( + { real_name: nickname }, + { where: { user_id: user.id } }, + ); + } + res.json(success(null, "更新成功")); } catch (err) { console.error("更新用户信息失败:", err);