feat/info-comment-jump

This commit is contained in:
zzc
2026-06-25 07:35:36 +08:00
parent 8d2337e5b7
commit 4fb8a05f8d
4 changed files with 149 additions and 6 deletions

View File

@@ -1,5 +1,9 @@
<template>
<view class="comment-item">
<view
class="comment-item"
:id="`comment-${localComment.id}`"
:class="{ 'comment-item--anchored': localComment.id === props.activeAnchorId }"
>
<image class="c-avatar" :src="localComment.avatar" mode="aspectFill" />
<view class="c-content-wrap">
<view class="c-header">
@@ -45,6 +49,8 @@
v-for="reply in localComment.replies"
:key="reply.id"
class="reply-item"
:id="`reply-${reply.id}`"
:class="{ 'reply-item--anchored': reply.id === props.activeAnchorId }"
>
<image class="reply-avatar" :src="reply.avatar" mode="aspectFill" />
<view class="reply-main">
@@ -104,6 +110,10 @@ const props = defineProps({
pageName: {
type: String,
default: 'rating_detail'
},
activeAnchorId: {
type: String,
default: ''
}
});
@@ -256,13 +266,21 @@ const handleLike = async (target = null) => {
display: flex;
padding: 32rpx 0;
position: relative;
transition: background-color 0.2s;
transition: background-color 0.2s, transform 0.2s;
}
.comment-item:active {
background-color: #fafbfe;
}
.comment-item--anchored {
margin: 0 -16rpx;
padding: 32rpx 16rpx;
border-radius: 24rpx;
background: linear-gradient(135deg, rgba(92, 67, 245, 0.1), rgba(92, 67, 245, 0.04));
transform: translateY(-2rpx);
}
.comment-item::after {
content: '';
position: absolute;
@@ -427,6 +445,13 @@ const handleLike = async (target = null) => {
display: flex;
gap: 16rpx;
padding-bottom: 18rpx;
border-radius: 20rpx;
transition: background-color 0.2s, padding 0.2s;
}
.reply-item--anchored {
padding: 16rpx 14rpx 18rpx;
background: rgba(92, 67, 245, 0.08);
}
.reply-avatar {

View File

@@ -79,6 +79,7 @@
:comment="comment"
:like-handler="likeHandler"
:page-name="pageName"
:active-anchor-id="activeAnchorId"
@need-login="emitNeedLogin"
@like-change="handleCommentLikeChange"
@reply="openReplyPopup"
@@ -331,8 +332,10 @@ const replyPopupVisible = ref(false);
const replyInputFocus = ref(false);
const replyContent = ref("");
const replyKeyboardHeight = ref(0);
const activeAnchorId = ref("");
let keyboardHeightListener = null;
let anchorHighlightTimer = null;
const isLoggedIn = computed(() => !!userStore.token || !!userStore.userInfo?.nickName);
const replyPlaceholder = computed(() => {
@@ -433,6 +436,98 @@ const loadMoreComments = async () => {
await loadComments();
};
const getCommentById = (commentId) => {
return comments.value.find((item) => item.id === commentId) || null;
};
const getLoadedAnchorTarget = (targetCommentId) => {
for (const comment of comments.value) {
if (String(comment.id) === String(targetCommentId)) {
return { type: "comment", id: comment.id };
}
const reply = (comment.replies || []).find((item) => String(item.id) === String(targetCommentId));
if (reply) {
return { type: "reply", id: reply.id, parentId: comment.id };
}
}
return null;
};
const markAnchorActive = (targetCommentId) => {
if (!targetCommentId) return;
activeAnchorId.value = String(targetCommentId);
if (anchorHighlightTimer) {
clearTimeout(anchorHighlightTimer);
}
anchorHighlightTimer = setTimeout(() => {
activeAnchorId.value = "";
anchorHighlightTimer = null;
}, 2200);
};
const scrollToAnchorNode = async (target) => {
if (!target?.id) return false;
markAnchorActive(target.id);
await nextTick();
return await new Promise((resolve) => {
uni.pageScrollTo({
selector: `#${target.type === "reply" ? "reply" : "comment"}-${target.id}`,
offsetTop: 108,
duration: 300,
success: () => resolve(true),
fail: () => resolve(false),
});
});
};
const ensureRepliesContainTarget = async (commentId, targetCommentId) => {
let currentComment = getCommentById(commentId);
if (!currentComment?.replyCount) return null;
if (!currentComment.repliesLoaded) {
await loadReplies(currentComment, 1);
currentComment = getCommentById(commentId);
const loadedTarget = getLoadedAnchorTarget(targetCommentId);
if (loadedTarget) return loadedTarget;
}
while (currentComment?.repliesHasMore) {
await loadReplies(currentComment, Number(currentComment.repliesPage || 1) + 1);
currentComment = getCommentById(commentId);
const loadedTarget = getLoadedAnchorTarget(targetCommentId);
if (loadedTarget) return loadedTarget;
}
return getLoadedAnchorTarget(targetCommentId);
};
const scrollToComment = async (targetCommentId) => {
if (!targetCommentId || !props.requestKey || !props.visible) return false;
let matchedTarget = getLoadedAnchorTarget(targetCommentId);
if (matchedTarget) {
return await scrollToAnchorNode(matchedTarget);
}
while (hasMore.value) {
await loadMoreComments();
matchedTarget = getLoadedAnchorTarget(targetCommentId);
if (matchedTarget) {
return await scrollToAnchorNode(matchedTarget);
}
}
for (const comment of comments.value) {
matchedTarget = await ensureRepliesContainTarget(comment.id, targetCommentId);
if (matchedTarget) {
return await scrollToAnchorNode(matchedTarget);
}
}
return false;
};
const switchTab = (tab) => {
if (currentTab.value === tab) return;
currentTab.value = tab;
@@ -627,11 +722,15 @@ onUnmounted(() => {
if (uni.offKeyboardHeightChange && keyboardHeightListener) {
uni.offKeyboardHeightChange(keyboardHeightListener);
}
if (anchorHighlightTimer) {
clearTimeout(anchorHighlightTimer);
}
});
defineExpose({
refreshComments,
loadMoreComments,
scrollToComment,
});
</script>