feat(comment): 优化评论加载效率并添加回复功能
- 在 CommentRepository 中添加 findByParentIdInOrderByCreateTimeAsc 方法,实现批量查询子评论 - 重构 CommentServiceImpl 中的 findCommentsByMusicId 方法,提升评论加载效率 - 在前端 MusicDetail 组件中增加回复功能,支持二级评论和回复展开/收起 - 优化 WebSocket订阅逻辑,实现实时更新评论列表
This commit is contained in:
@@ -129,8 +129,9 @@
|
||||
|
||||
<div class="comment-list">
|
||||
<div v-for="comment in comments" :key="comment.id" class="comment-item">
|
||||
<!-- 一级评论 -->
|
||||
<div class="comment-avatar">
|
||||
<el-avatar :src="comment.user.avatar" :size="40" />
|
||||
<el-avatar :src="processImageUrl(comment.user.avatar)" :size="40" />
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-header">
|
||||
@@ -150,19 +151,69 @@
|
||||
<div class="comment-text">{{ comment.content }}</div>
|
||||
<div class="comment-footer">
|
||||
<div class="comment-time">{{ comment.createTime }}</div>
|
||||
<el-button type="text" size="small" @click="toggleReplies(comment)">
|
||||
{{ expandedComments.includes(comment.id) ? '收起回复' : `查看回复 (${comment.children ? comment.children.length : 0})` }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div v-if="replyingTo && replyingTo.id === comment.id" class="comment-input child-comment-input">
|
||||
|
||||
<!-- 回复输入框 -->
|
||||
<div v-if="replyingTo === comment.id" class="comment-input child-comment-input">
|
||||
<el-input
|
||||
v-model="replyContent"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:placeholder="`回复 @${replyingTo.user.username}`"
|
||||
:placeholder="`回复 @${replyTo.user.username}`"
|
||||
/>
|
||||
<div class="comment-submit-actions">
|
||||
<el-button type="primary" size="small" @click="submitComment(true)">回复</el-button>
|
||||
<el-button size="small" @click="cancelReply">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 二级评论 -->
|
||||
<div v-if="expandedComments.includes(comment.id) && comment.children && comment.children.length > 0" class="child-comments">
|
||||
<div v-for="child in comment.children" :key="child.id" class="comment-item">
|
||||
<div class="comment-avatar">
|
||||
<el-avatar :src="processImageUrl(child.user.avatar)" :size="32" />
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-header">
|
||||
<div class="comment-user">{{ child.user.username }}</div>
|
||||
<div class="comment-actions">
|
||||
<el-button type="text" size="small" @click="setReplyTo(child)">回复</el-button>
|
||||
<el-button
|
||||
v-if="userStore.isAdmin || child.user.id === userStore.user?.id"
|
||||
type="danger"
|
||||
size="small"
|
||||
@click="deleteComment(child.id)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-text">
|
||||
<span v-if="child.parentCommentUser" class="reply-to">@{{ child.parentCommentUser.username }}</span>
|
||||
{{ child.content }}
|
||||
</div>
|
||||
<div class="comment-footer">
|
||||
<div class="comment-time">{{ child.createTime }}</div>
|
||||
</div>
|
||||
<!-- 二级回复输入框 -->
|
||||
<div v-if="replyingTo === child.id" class="comment-input child-comment-input">
|
||||
<el-input
|
||||
v-model="replyContent"
|
||||
type="textarea"
|
||||
:rows="2"
|
||||
:placeholder="`回复 @${replyTo.user.username}`"
|
||||
/>
|
||||
<div class="comment-submit-actions">
|
||||
<el-button type="primary" size="small" @click="submitComment(true, child.id)">回复</el-button>
|
||||
<el-button size="small" @click="cancelReply">取消</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-empty v-if="!comments.length" description="还没有评论,快来抢沙发吧!" />
|
||||
@@ -210,6 +261,7 @@ import { getCommentList, addComment, deleteComment as apiDeleteComment } from '.
|
||||
import { connect, subscribe, unsubscribe, disconnect } from '../utils/websocket'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { VideoPlay, VideoPause, Star, StarFilled, Plus } from '@element-plus/icons-vue'
|
||||
import { processImageUrl } from '../utils/imageUtils'
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
@@ -226,6 +278,7 @@ const comments = ref([])
|
||||
const commentContent = ref('')
|
||||
const replyContent = ref('')
|
||||
const replyingTo = ref(null)
|
||||
const replyTo = ref(null)
|
||||
const expandedComments = ref([])
|
||||
let commentSubscription = null;
|
||||
|
||||
@@ -398,15 +451,34 @@ const deleteComment = async (commentId) => {
|
||||
|
||||
// 设置回复对象
|
||||
const setReplyTo = (comment) => {
|
||||
replyingTo.value = comment;
|
||||
replyingTo.value = comment.id;
|
||||
replyTo.value = comment;
|
||||
if (!expandedComments.value.includes(comment.id)) {
|
||||
expandedComments.value.push(comment.id);
|
||||
}
|
||||
};
|
||||
|
||||
// 取消回复
|
||||
const cancelReply = () => {
|
||||
replyingTo.value = null;
|
||||
replyTo.value = null;
|
||||
replyContent.value = '';
|
||||
};
|
||||
|
||||
// 切换回复可见性
|
||||
const toggleReplies = (comment) => {
|
||||
const commentId = comment.id;
|
||||
const index = expandedComments.value.indexOf(commentId);
|
||||
if (index > -1) {
|
||||
expandedComments.value.splice(index, 1);
|
||||
if (replyingTo.value === commentId) {
|
||||
cancelReply();
|
||||
}
|
||||
} else {
|
||||
expandedComments.value.push(commentId);
|
||||
}
|
||||
};
|
||||
|
||||
// WebSocket 订阅
|
||||
const setupWebSocket = () => {
|
||||
if (!music.value) return;
|
||||
@@ -414,10 +486,30 @@ const setupWebSocket = () => {
|
||||
const destination = `/topic/comments/1/${music.value.id}`; // 1 代表音乐类型
|
||||
commentSubscription = subscribe(destination, (newComment) => {
|
||||
if (newComment && newComment.id) {
|
||||
// 简单处理:直接重新拉取整个评论列表
|
||||
fetchComments();
|
||||
if (newComment.user && newComment.user.avatar) {
|
||||
newComment.user.avatar = processImageUrl(newComment.user.avatar);
|
||||
}
|
||||
|
||||
if (newComment.parentId) {
|
||||
const parentComment = comments.value.find(c => c.id === newComment.parentId);
|
||||
if (parentComment) {
|
||||
if (!parentComment.children) {
|
||||
parentComment.children = [];
|
||||
}
|
||||
parentComment.children.push(newComment);
|
||||
}
|
||||
} else {
|
||||
if (!comments.value.some(c => c.id === newComment.id)) {
|
||||
comments.value.unshift(newComment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn("Received invalid comment data via WebSocket:", newComment);
|
||||
}
|
||||
});
|
||||
}, (error) => {
|
||||
console.error("WebSocket connection error:", error);
|
||||
// ElMessage.error('评论服务连接失败,实时更新不可用');
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user