♿ optimize commenting experience
This commit is contained in:
13
src/App.vue
13
src/App.vue
@@ -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>
|
||||||
|
@@ -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);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -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;
|
||||||
|
@@ -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 = "";
|
||||||
|
@@ -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>
|
||||||
|
@@ -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;
|
||||||
|
@@ -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();
|
||||||
|
@@ -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 {
|
||||||
|
@@ -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"],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user