feat(留言板): 实现留言点赞功能并优化留言显示
- 新增留言点赞API接口及前端处理逻辑 - 优化留言时间显示格式,使用统一格式化函数 - 修复留言列表props传递问题,支持外部传入articleid - 移除无用图标和冗余代码,清理样式
This commit is contained in:
@@ -31,7 +31,7 @@
|
||||
<!-- 搜索功能 -->
|
||||
<div class="search-wrapper">
|
||||
<button class="search-icon-btn" @click="toggleSearchBox" :class="{ 'active': isSearchBoxOpen }">
|
||||
<i class="el-icon-search">1</i>
|
||||
|
||||
</button>
|
||||
<div class="search-box-container" :class="{ 'open': isSearchBoxOpen }">
|
||||
<el-input
|
||||
|
||||
@@ -45,7 +45,7 @@ class MessageService {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getRepliesByParentId(parentId) {
|
||||
return apiService.get(`/messages/parent/${parentId}`)
|
||||
return apiService.get(`/messages/${parentId}/replies`)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,7 +63,7 @@ class MessageService {
|
||||
* @returns {Promise}
|
||||
*/
|
||||
getMessageCountByArticleId(articleId) {
|
||||
return apiService.get(`/messages/count/${articleId}`)
|
||||
return apiService.get(`/messages/count/article/${articleId}`)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -83,6 +83,15 @@ class MessageService {
|
||||
deleteMessage(id) {
|
||||
return apiService.delete(`/messages/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 点赞留言
|
||||
* @param {number} id - 留言ID
|
||||
* @returns {Promise}
|
||||
*/
|
||||
likeMessage(id) {
|
||||
return apiService.post(`/messages/${id}/like`)
|
||||
}
|
||||
}
|
||||
|
||||
// 导出留言服务实例
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
<!-- 文章操作 -->
|
||||
<div class="article-actions">
|
||||
<el-button type="primary" icon="el-icon-arrow-left" @click="goBack" plain>
|
||||
<el-button type="primary" @click="goBack" plain>
|
||||
返回
|
||||
</el-button>
|
||||
</div>
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
<!-- 评论区 -->
|
||||
<div>
|
||||
<messageboard class="message-board" v-if="article && Object.keys(article).length > 0" v-model:comments="article.articleid" />
|
||||
<messageboard class="message-board" v-if="article && Object.keys(article).length > 0" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -84,6 +84,8 @@
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { articleService } from '@/services'
|
||||
import { messageService } from '@/services'
|
||||
import {categoryAttributeService} from '@/services'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import type { Article } from '@/types'
|
||||
import { formatDate } from '@/utils/dateUtils'
|
||||
@@ -118,7 +120,7 @@ const fetchArticleDetail = async () => {
|
||||
|
||||
if (res.data) {
|
||||
article.value = res.data
|
||||
|
||||
article.value.categoryName = await categoryAttributeService.getAttributeById(article.value.categoryId)|| '未分类'
|
||||
// 增加文章浏览量
|
||||
try {
|
||||
await articleService.incrementArticleViews(Number(articleId))
|
||||
|
||||
@@ -17,12 +17,15 @@
|
||||
<img :src="getAvatar()" class="avatar">
|
||||
<div class="user-info">
|
||||
<div class="username">{{ comment.displayName || comment.nickname }}</div>
|
||||
<div class="time">{{ comment.createdAt || '刚刚' }}</div>
|
||||
<div class="time">{{ formatDate(comment.createdAt) || '刚刚' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="comment-content" v-html="comment.content"></div>
|
||||
<div class="comment-actions">
|
||||
<!-- <span v-if="comment.likes" class="likes">{{ comment.likes }} 赞</span> -->
|
||||
<span class="likes-btn" @click="handleLike(comment)">
|
||||
<span v-if="comment.likes && comment.likes > 0" class="likes-count">{{ comment.likes }}</span>
|
||||
👍 赞
|
||||
</span>
|
||||
<span class="reply-btn" @click="handleReply(null, comment)">回复</span>
|
||||
</div>
|
||||
<!-- 回复列表 -->
|
||||
@@ -32,11 +35,15 @@
|
||||
<img :src="getAvatar()" class="avatar">
|
||||
<div class="user-info">
|
||||
<div class="username">{{ reply.displayName || reply.nickname }}</div>
|
||||
<div class="time">{{ reply.createdAt || '刚刚' }}</div>
|
||||
<div class="time">{{ formatDate(reply.createdAt) || '刚刚' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reply-content">{{ reply.content }}</div>
|
||||
<div class="reply-actions">
|
||||
<span class="likes-btn" @click="handleLike(reply)">
|
||||
<span v-if="reply.likes && reply.likes > 0" class="likes-count">{{ reply.likes }}</span>
|
||||
👍 赞
|
||||
</span>
|
||||
<span class="reply-btn" @click="handleReply(comment, reply)">回复</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,10 +113,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted } from 'vue'
|
||||
import { reactive, ref, onMounted, watch } from 'vue'
|
||||
import { messageService } from '@/services'
|
||||
import { ElMessage, ElForm } from 'element-plus'
|
||||
import { useGlobalStore } from '@/store/globalStore'
|
||||
import { formatDate } from '@/utils/dateUtils'
|
||||
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
comments: {
|
||||
type: Number,
|
||||
default: null
|
||||
}
|
||||
})
|
||||
const globalStore = useGlobalStore()
|
||||
const messageBoardData = ref([]) // 留言板留言(articleid为空的主留言及其回复)
|
||||
const loading = ref(false)
|
||||
@@ -222,9 +238,15 @@ const fetchMessages = async () => {
|
||||
loading.value = true
|
||||
let res = null
|
||||
|
||||
// 优先使用props传递的articleid,其次使用globalStore中的数据
|
||||
let articleid = props.comments || null
|
||||
|
||||
if (!articleid) {
|
||||
// 安全获取文章ID,如果globalStore中没有articlebutn则返回null
|
||||
const articleData = globalStore.getValue('articlebutn')
|
||||
const articleid = (articleData && typeof articleData === 'object' && 'id' in articleData) ? articleData.id : null
|
||||
articleid = (articleData && typeof articleData === 'object' && 'id' in articleData) ? articleData.id : null
|
||||
}
|
||||
|
||||
form.articleid = articleid
|
||||
|
||||
// 根据是否有文章ID选择不同的API调用
|
||||
@@ -327,6 +349,13 @@ const cancelReply = () => {
|
||||
form.content = ''
|
||||
}
|
||||
|
||||
// 监听articleid变化,重新加载留言
|
||||
watch(() => props.comments, (newVal) => {
|
||||
if (newVal) {
|
||||
fetchMessages()
|
||||
}
|
||||
}, { immediate: false })
|
||||
|
||||
// 组件挂载时获取留言列表
|
||||
onMounted(() => {
|
||||
fetchMessages()
|
||||
@@ -410,6 +439,29 @@ const post_comment_reply_cancel = () => {
|
||||
replyingTo.value = { id: null, nickname: '', content: '' }
|
||||
}
|
||||
|
||||
// 处理点赞
|
||||
const handleLike = async (msg) => {
|
||||
try {
|
||||
// 显示加载状态或禁用按钮
|
||||
msg.isLiking = true
|
||||
|
||||
const res = await messageService.likeMessage(msg.messageid)
|
||||
|
||||
if (res.success && res.data) {
|
||||
// 更新点赞数
|
||||
msg.likes = res.data.likes || 0
|
||||
ElMessage.success('点赞成功')
|
||||
} else {
|
||||
ElMessage.error('点赞失败:' + (res.message || '未知错误'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('点赞失败:', error)
|
||||
ElMessage.error('网络错误,请稍后重试')
|
||||
} finally {
|
||||
msg.isLiking = false
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -508,6 +560,29 @@ const post_comment_reply_cancel = () => {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.likes-btn {
|
||||
margin-right: 15px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.likes-btn:hover {
|
||||
color: #e74c3c;
|
||||
background-color: rgba(231, 76, 60, 0.1);
|
||||
}
|
||||
|
||||
.likes-btn:active {
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
.likes-count {
|
||||
margin-right: 4px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.replies {
|
||||
margin-top: 15px;
|
||||
padding-left: 20px;
|
||||
|
||||
Reference in New Issue
Block a user