optimize commenting experience

This commit is contained in:
2024-11-29 13:01:31 +08:00
parent 20d519c928
commit 594bd5b280
9 changed files with 94 additions and 70 deletions

View File

@@ -19,5 +19,16 @@ const app = useStore().theme;
const lang = useStore().lang; const lang = useStore().lang;
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.animation-enter {
opacity: 0; /* 初始透明 */
}
.animation-enter-active {
transition: opacity 0.2s; /* 渐入效果 */
}
.animation-leave-active {
transition: opacity 0.2s; /* 渐出效果 */
}
.animation-leave-to {
opacity: 0; /* 离开时透明 */
}
</style> </style>

View File

@@ -91,9 +91,7 @@
{{ item.likes }} {{ item.likes }}
</AButton> </AButton>
</AFlex> </AFlex>
<AButton @click="()=>{ <AButton @click="comment.toggleReplyVisibility(topicId,1,5,item.id)" type="text" size="small"
comment.handleShowCommentReply(item.id);
replyListThrottled(item.id)}" type="text" size="small"
:icon="h(MessageOutlined)" :icon="h(MessageOutlined)"
:disabled="item.reply_count === 0" :disabled="item.reply_count === 0"
v-show="item.reply_count > 0" v-show="item.reply_count > 0"
@@ -139,7 +137,9 @@
<!-- 回复输入框 --> <!-- 回复输入框 -->
<ReplyInput :item="item" v-show="comment.showReplyInput && item.id === comment.showReplyInput"/> <ReplyInput :item="item" v-show="comment.showReplyInput && item.id === comment.showReplyInput"/>
<!-- 子回复列表 --> <!-- 子回复列表 -->
<ReplyList :item="item" v-show="comment.showCommentReply && item.id === comment.showCommentReply"/> <transition name="fade">
<ReplyList :item="item" v-if="comment.replyVisibility[item.id]?.visible"/>
</transition>
</AFlex> </AFlex>
</AFlex> </AFlex>
</div> </div>
@@ -231,27 +231,6 @@ function formatTimeAgo(dateString: string) {
} }
/**
* 获取回复列表 throttled
*/
const replyListThrottled = useThrottleFn(getReplyList, 1000);
/**
* 获取回复列表
* @param reply_id
* @param page
* @param size
*/
async function getReplyList(reply_id: number, page: number = 1, size: number = 5) {
const params: any = {
topic_id: topicId.value,
page: page,
size: size,
comment_id: reply_id,
};
await comment.getReplyList(params);
}
const commentLikeThrottled = useThrottleFn(commentLike, 1000); const commentLikeThrottled = useThrottleFn(commentLike, 1000);
/** /**

View File

@@ -40,6 +40,22 @@
.reply-content { .reply-content {
margin-left: 20px; margin-left: 20px;
.fade-enter {
opacity: 0;
}
.fade-enter-active {
transition: opacity 0.3s;
}
.fade-leave-to {
opacity: 0;
}
.fade-leave-active {
transition: opacity 0.3s;
}
.reply-name { .reply-name {
font-size: 14px; font-size: 14px;
font-weight: 600; font-weight: 600;

View File

@@ -204,12 +204,12 @@ async function replySubmit(point: any) {
nickname: user.user.nickname, nickname: user.user.nickname,
avatar: user.user.avatar, avatar: user.user.avatar,
is_liked: false, is_liked: false,
reply_username: props.item.nickname, reply_nickname: props.item.nickname,
}; };
if (!comment.replyList.comments) { if (!comment.replyVisibility[props.item.id].data.comments) {
comment.replyList.comments = []; // 初始化 comments 数组 comment.replyVisibility[props.item.id].data.comments = []; // 初始化 comments 数组
} }
comment.replyList.comments.unshift(tmpData); comment.replyVisibility[props.item.id].data.comments.unshift(tmpData);
comment.commentMap[props.item.id].reply_count++; comment.commentMap[props.item.id].reply_count++;
comment.closeReplyInput(); comment.closeReplyInput();
replyContent.value = ""; replyContent.value = "";

View File

@@ -1,8 +1,9 @@
<template> <template>
<AFlex :vertical="true" class="reply-item-child"> <AFlex :vertical="true" class="reply-item-child">
<ASpin :spinning="comment.replyLoading" size="default"> <ASpin :spinning="comment.replyLoading[item.id]" size="default">
<AFlex :vertical="true" v-if="comment.replyList.comments"> <AFlex :vertical="true" v-if="comment.replyVisibility[item.id]?.data.comments">
<AFlex :vertical="false" style="margin-top: 5px" v-for="(child, index) in comment.replyList.comments" <AFlex :vertical="false" style="margin-top: 5px"
v-for="(child, index) in comment.replyVisibility[item.id]?.data.comments"
:key="index"> :key="index">
<AFlex :vertical="true"> <AFlex :vertical="true">
<Popover trigger="click" :arrow="false" :offset-x="170" :contentStyle="{padding: 0}"> <Popover trigger="click" :arrow="false" :offset-x="170" :contentStyle="{padding: 0}">
@@ -118,15 +119,16 @@
</AFlex> </AFlex>
</AFlex> </AFlex>
</AFlex> </AFlex>
<APagination v-if="comment.replyList.total > 5" class="reply-pagination-child" size="small" <APagination v-if="comment.replyVisibility[item.id]?.data.total > 5" class="reply-pagination-child" size="small"
:total="comment.replyList.total" :total="comment.replyVisibility[item.id]?.data.total"
:current="comment.replyList.current" :page-size="comment.replyList.size" :current="comment.replyVisibility[item.id]?.data.current"
:default-page-size="comment.replyList.size" :page-size="comment.replyVisibility[item.id]?.data.size"
:default-page-size="comment.replyVisibility[item.id]?.data.size"
@change="replyListThrottled" @change="replyListThrottled"
/> />
</AFlex> </AFlex>
<AEmpty :description="null" v-show="!comment.replyList.comments"/> <AEmpty :description="null" v-show="!comment.replyVisibility[item.id]?.data.comments"/>
</ASpin> </ASpin>
</AFlex> </AFlex>
</template> </template>

View File

@@ -58,7 +58,7 @@
.reply-card-child { .reply-card-child {
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1); box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
width: 530px; width: 525px;
.reply-action-item-child { .reply-action-item-child {
margin-top: 10px; margin-top: 10px;

View File

@@ -207,13 +207,13 @@ async function replyReplySubmit(point: any) {
nickname: user.user.nickname, nickname: user.user.nickname,
avatar: user.user.avatar, avatar: user.user.avatar,
is_liked: false, is_liked: false,
reply_username: props.item.nickname, reply_nickname: props.item.nickname,
reply_to: result.data.reply_to, reply_to: result.data.reply_to,
}; };
if (!comment.replyList.comments) { if (!comment.replyVisibility[props.item.id].data.comments) {
comment.replyList.comments = []; // 初始化 comments 数组 comment.replyVisibility[props.item.id].data.comments = []; // 初始化 comments 数组
} }
comment.replyList.comments.unshift(tmpData); comment.replyVisibility[props.item.id].data.comments.unshift(tmpData);
comment.commentMap[props.item.id].reply_count++; comment.commentMap[props.item.id].reply_count++;
replyReplyContent.value = ""; replyReplyContent.value = "";
await comment.clearFileList(); await comment.clearFileList();

View File

@@ -25,7 +25,7 @@
//margin-left: 10px; //margin-left: 10px;
.comment-text-reply-child { .comment-text-reply-child {
width: 530px; width: 525px;
} }
.comment-actions-reply-child { .comment-actions-reply-child {

View File

@@ -14,10 +14,10 @@ export const useCommentStore = defineStore(
() => { () => {
const commentList = ref<Comment>({} as Comment); const commentList = ref<Comment>({} as Comment);
const commentLoading = ref<boolean>(true); const commentLoading = ref<boolean>(true);
const replyLoading = ref<boolean>(true); const replyLoading = reactive<{ [key: number]: boolean }>({});
const showReplyInput = ref<number | null>(null); const showReplyInput = ref<number | null>(null);
const showCommentReply = ref<number | null>(null); const showCommentReply = ref<number | null>(null);
const replyList = ref<Comment>({} as Comment); const replyVisibility = ref<{ [key: number]: { visible: boolean; data: Comment } }>({});
const commentMap = reactive<any>({}); const commentMap = reactive<any>({});
const slideCaptchaData = reactive({ const slideCaptchaData = reactive({
captKey: "", captKey: "",
@@ -54,16 +54,14 @@ export const useCommentStore = defineStore(
const result: any = await commentListApi(data); const result: any = await commentListApi(data);
if (result.code === 200 && result.data) { if (result.code === 200 && result.data) {
commentList.value = result.data; commentList.value = result.data;
commentLoading.value = false;
if (Array.isArray(commentList.value.comments)) { if (Array.isArray(commentList.value.comments)) {
commentList.value.comments.map((item: any) => { commentList.value.comments.map((item: any) => {
commentMap[item.id] = item; commentMap[item.id] = item;
replyLoading[item.id] = false;
}); });
} }
} else {
commentLoading.value = false;
} }
commentLoading.value = false;
} }
/** /**
@@ -78,34 +76,52 @@ export const useCommentStore = defineStore(
const closeReplyInput = () => { const closeReplyInput = () => {
showReplyInput.value = null; showReplyInput.value = null;
}; };
/** /**
* 是否显示回复 * 切换回复可见性
* @param topic_id
* @param page
* @param size
* @param commentId
*/ */
const handleShowCommentReply = (index: any) => { async function toggleReplyVisibility(topic_id: string, page: number, size: number, commentId: number) {
showCommentReply.value = showCommentReply.value === index ? null : index; replyLoading[commentId] = true;
}; const params: any = {
topic_id: topic_id,
page: page,
size: size,
comment_id: commentId,
};
if (!replyVisibility.value[commentId]) {
// 如果不存在这个评论的状态,初始化
replyVisibility.value[commentId] = {visible: false, data: {} as Comment};
await getReplyList(params);
} else {
// 切换可见性
replyVisibility.value[commentId].visible = !replyVisibility.value[commentId].visible;
// 在展开时检查数据是否需要更新
if (replyVisibility.value[commentId].visible) {
await getReplyList(params); // 重新获取最新的子评论
}
}
replyLoading[commentId] = false;
}
/** /**
* 获取回复列表 * 获取回复列表
* @param data * @param data
*/ */
async function getReplyList(data: any) { async function getReplyList(data: any) {
const params: any = {
topic_id: data.topic_id,
page: data.page,
size: data.size,
comment_id: data.comment_id,
};
replyLoading.value = true;
replyList.value = {} as Comment;
// 获取评论列表 // 获取评论列表
const result: any = await replyListApi(params); const result: any = await replyListApi(data);
if (result.code === 200 && result.data) { if (result.code === 200 && result.data) {
replyList.value = result.data; if (replyVisibility.value[data.comment_id].data !== result.data) {
replyLoading.value = false; replyVisibility.value[data.comment_id].data = result.data;
} else { }
replyLoading.value = false; replyVisibility.value[data.comment_id].visible = true;
} }
replyLoading[data.comment_id] = false;
} }
/** /**
@@ -278,7 +294,7 @@ export const useCommentStore = defineStore(
commentLoading, commentLoading,
showReplyInput, showReplyInput,
showCommentReply, showCommentReply,
replyList, replyVisibility,
replyLoading, replyLoading,
slideCaptchaData, slideCaptchaData,
commentMap, commentMap,
@@ -293,7 +309,7 @@ export const useCommentStore = defineStore(
getCommentList, getCommentList,
handleShowReplyInput, handleShowReplyInput,
closeReplyInput, closeReplyInput,
handleShowCommentReply, toggleReplyVisibility,
getReplyList, getReplyList,
commentLike, commentLike,
cancelCommentLike, cancelCommentLike,
@@ -312,7 +328,7 @@ export const useCommentStore = defineStore(
persist: { persist: {
key: 'comment', key: 'comment',
storage: localStorage, storage: localStorage,
pick: ["emojiList", "commentList", "replyList", "commentMap"], pick: ["emojiList", "commentList", "replyVisibility", "commentMap"],
} }
} }
); );