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:
parent
f861f82675
commit
ccea3a99e5
@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
@ -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);
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user