feat(comment): 添加歌手详情页评论回复功能
- 新增回复评论功能,支持嵌套回复 - 增加展开/收起子评论的功能 - 优化评论列表展示,支持子评论显示- 调整评论输入框和提交按钮布局 - 优化评论样式,区分主评论和子评论
This commit is contained in:
@@ -32,6 +32,18 @@ const comments = ref([])
|
||||
// 评论内容
|
||||
const commentContent = ref('')
|
||||
|
||||
// 回复内容
|
||||
const replyContent = ref('')
|
||||
|
||||
// 回复对象
|
||||
const replyTo = ref(null)
|
||||
|
||||
// 正在回复的评论
|
||||
const replyingTo = ref(null)
|
||||
|
||||
// 展开的评论
|
||||
const expandedComments = ref([])
|
||||
|
||||
// 加载状态
|
||||
const loading = ref(false)
|
||||
|
||||
@@ -178,15 +190,48 @@ const toggleCollect = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 切换回复可见性
|
||||
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);
|
||||
setReplyTo(comment);
|
||||
}
|
||||
};
|
||||
|
||||
// 设置回复
|
||||
const setReplyTo = (comment) => {
|
||||
replyingTo.value = comment.id;
|
||||
replyTo.value = comment;
|
||||
replyContent.value = `@${comment.user.username} `;
|
||||
if (!expandedComments.value.includes(comment.id)) {
|
||||
expandedComments.value.push(comment.id);
|
||||
}
|
||||
}
|
||||
|
||||
// 取消回复
|
||||
const cancelReply = () => {
|
||||
replyingTo.value = null
|
||||
replyTo.value = null
|
||||
replyContent.value = ''
|
||||
}
|
||||
|
||||
// 提交评论
|
||||
const submitComment = async () => {
|
||||
const submitComment = async (isReply = false) => {
|
||||
if (!userStore.isLoggedIn) {
|
||||
ElMessage.warning('请先登录')
|
||||
router.push('/login')
|
||||
return
|
||||
}
|
||||
|
||||
if (!commentContent.value.trim()) {
|
||||
const content = isReply ? replyContent.value : commentContent.value
|
||||
if (!content.trim()) {
|
||||
ElMessage.warning('评论内容不能为空')
|
||||
return
|
||||
}
|
||||
@@ -195,14 +240,17 @@ const submitComment = async () => {
|
||||
const res = await addComment({
|
||||
targetId: singerId.value,
|
||||
targetType: 3, // 3表示歌手
|
||||
content: commentContent.value
|
||||
content: content,
|
||||
parentId: isReply ? replyingTo.value : null
|
||||
})
|
||||
|
||||
if (res.code === 200) {
|
||||
// 添加成功,评论将通过 WebSocket 推送,无需手动刷新
|
||||
// await fetchComments() // 移除手动刷新
|
||||
if (isReply) {
|
||||
replyContent.value = ''
|
||||
} else {
|
||||
commentContent.value = ''
|
||||
ElMessage.success('评论已发送') // 提示语可以调整
|
||||
}
|
||||
ElMessage.success('评论已发送')
|
||||
} else {
|
||||
ElMessage.error(res.message || '评论失败')
|
||||
}
|
||||
@@ -283,16 +331,23 @@ onMounted(() => {
|
||||
connect(() => {
|
||||
const destination = `/topic/comments/3/${singerId.value}`; // 3 代表歌手类型
|
||||
commentSubscription = subscribe(destination, (newComment) => {
|
||||
// 收到新评论,添加到列表开头
|
||||
if (newComment && newComment.id) {
|
||||
// 处理新评论的用户头像
|
||||
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); // 添加到开头
|
||||
console.log('New comment received via WebSocket:', newComment);
|
||||
comments.value.unshift(newComment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn("Received invalid comment data via WebSocket:", newComment);
|
||||
@@ -431,10 +486,11 @@ onUnmounted(() => {
|
||||
:rows="3"
|
||||
placeholder="发表你的评论..."
|
||||
/>
|
||||
<el-button type="primary" @click="submitComment">发表评论</el-button>
|
||||
<div class="comment-submit-actions">
|
||||
<el-button type="primary" @click="submitComment(false)">发表评论</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 评论列表 -->
|
||||
<!-- 评论列表 -->
|
||||
<div class="comment-list">
|
||||
<div v-for="comment in comments" :key="comment.id" class="comment-item">
|
||||
@@ -445,6 +501,7 @@ onUnmounted(() => {
|
||||
<div class="comment-header">
|
||||
<div class="comment-user">{{ comment.user.username }}</div>
|
||||
<div class="comment-actions">
|
||||
<el-button type="text" size="small" @click="setReplyTo(comment)">回复</el-button>
|
||||
<el-button
|
||||
v-if="userStore.isAdmin || comment.user.id === userStore.user?.id"
|
||||
type="danger"
|
||||
@@ -456,7 +513,55 @@ onUnmounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-text">{{ comment.content }}</div>
|
||||
<div class="comment-footer">
|
||||
<div class="comment-time">{{ comment.createTime }}</div>
|
||||
<el-button
|
||||
v-if="comment.children && comment.children.length"
|
||||
type="text"
|
||||
size="small"
|
||||
@click="toggleReplies(comment)"
|
||||
>
|
||||
{{ expandedComments.includes(comment.id) ? '收起回复' : `查看 ${comment.children.length} 条回复` }}
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 子评论 -->
|
||||
<div v-if="expandedComments.includes(comment.id)" class="child-comment-list">
|
||||
<!-- 回复输入框 -->
|
||||
<div v-if="replyingTo === comment.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" @click="submitComment(true)">回复</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-for="child in [...(comment.children || [])].reverse()" :key="child.id" class="comment-item">
|
||||
<div class="comment-avatar">
|
||||
<el-avatar :src="child.user.avatar" :size="30" />
|
||||
</div>
|
||||
<div class="comment-content">
|
||||
<div class="comment-header">
|
||||
<div class="comment-user">{{ child.user.username }}</div>
|
||||
<div class="comment-actions">
|
||||
<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">{{ child.content }}</div>
|
||||
<div class="comment-time">{{ child.createTime }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -578,7 +683,12 @@ onUnmounted(() => {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.comment-submit-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.comment-list {
|
||||
@@ -611,13 +721,26 @@ onUnmounted(() => {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.child-comment-list {
|
||||
margin-top: 15px;
|
||||
padding-left: 15px;
|
||||
border-left: 2px solid #eee;
|
||||
}
|
||||
|
||||
.child-comment-input {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.comment-text {
|
||||
margin-bottom: 5px;
|
||||
color: #333;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.comment-time {
|
||||
.comment-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
font-size: 0.8rem;
|
||||
color: #999;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user