feat/info-comment-jump
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -294,9 +294,8 @@ const resolveActionText = (item) => {
|
||||
};
|
||||
|
||||
const resolveJumpText = (item) => {
|
||||
if(item.title.includes("已提交审核") || item.title.includes("未通过")) return "";
|
||||
if(item.title.includes("已提交审核") || item.title.includes("未通过") || item.title.includes("评分项")) return "";
|
||||
|
||||
if (item.itemId) return "查看评分详情";
|
||||
if (item.topicId) return "查看话题";
|
||||
return "查看消息";
|
||||
};
|
||||
@@ -405,9 +404,16 @@ const setItemReadState = (id, isRead = true) => {
|
||||
};
|
||||
|
||||
const handleNavigate = (item) => {
|
||||
const isCommentInteraction = item?.type === 'comment' || item?.type === 'comment_like';
|
||||
const targetCommentId =
|
||||
item?.commentId ||
|
||||
(item?.targetType === 'comment' ? item?.targetId : '') ||
|
||||
item?.metadata?.commentId ||
|
||||
'';
|
||||
|
||||
if (item.itemId) {
|
||||
uni.navigateTo({
|
||||
url: `/pages/rating/detail?topicId=${item.topicId || ""}&itemId=${item.itemId}`,
|
||||
url: `/pages/rating/detail?topicId=${item.topicId || ""}&itemId=${item.itemId}${isCommentInteraction && targetCommentId ? `&commentId=${targetCommentId}` : ""}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
@@ -438,7 +444,7 @@ const markSingleAsRead = async (item) => {
|
||||
|
||||
const handleItemTap = async (item) => {
|
||||
await markSingleAsRead(item);
|
||||
if(item.title.includes("未通过") || item.title.includes("已提交审核")) return
|
||||
if(item.title.includes("未通过") || item.title.includes("已提交审核") || item.title.includes("评分项")) return
|
||||
handleNavigate(item);
|
||||
};
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ const commentSectionRef = ref(null);
|
||||
const isLoggedIn = computed(() => !!userStore.token || !!userStore.userInfo?.nickName);
|
||||
const isSendingComment = ref(false);
|
||||
const pkPreview = ref(null);
|
||||
const pendingAnchorCommentId = ref('');
|
||||
const commentRequestKey = computed(() => {
|
||||
if (!detailData.value.topicId || !itemId.value) return '';
|
||||
return `${detailData.value.topicId}_${itemId.value}`;
|
||||
@@ -360,6 +361,14 @@ const handleCommentPublished = ({ mode, content, replyTarget }) => {
|
||||
});
|
||||
};
|
||||
|
||||
const scrollToPendingComment = async () => {
|
||||
if (!pendingAnchorCommentId.value) return;
|
||||
const found = await commentSectionRef.value?.scrollToComment?.(pendingAnchorCommentId.value);
|
||||
if (found) {
|
||||
pendingAnchorCommentId.value = '';
|
||||
}
|
||||
};
|
||||
|
||||
const handleSendComment = async (payload) => {
|
||||
if (!isLoggedIn.value) {
|
||||
openLoginPopup();
|
||||
@@ -424,6 +433,7 @@ const getDetailData = async () => {
|
||||
currentAction.value = data.userAction || '';
|
||||
|
||||
await commentSectionRef.value?.refreshComments?.();
|
||||
await scrollToPendingComment();
|
||||
getPkPreviewData();
|
||||
} catch (error) {
|
||||
console.error('获取详情失败:', error);
|
||||
@@ -446,6 +456,9 @@ onLoad((options) => {
|
||||
if (options.topicId) {
|
||||
detailData.value.topicId = options.topicId;
|
||||
}
|
||||
if (options.commentId) {
|
||||
pendingAnchorCommentId.value = options.commentId;
|
||||
}
|
||||
if (options.itemId) {
|
||||
itemId.value = options.itemId;
|
||||
getDetailData();
|
||||
|
||||
Reference in New Issue
Block a user