|
|
@ -0,0 +1,623 @@ |
|
|
|
<template> |
|
|
|
<view class="comment-detail-container"> |
|
|
|
<navbar :title="pageTitle" leftClick @leftClick="navigateBack"/> |
|
|
|
|
|
|
|
<!-- 加载状态 --> |
|
|
|
<view class="loading-container" v-if="loading"> |
|
|
|
<view class="loading-spinner"></view> |
|
|
|
<text class="loading-text">加载中...</text> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 错误状态 --> |
|
|
|
<view class="error-container" v-if="!loading && hasError"> |
|
|
|
<text class="error-text">加载失败,请重试</text> |
|
|
|
<button class="retry-button" @click="loadCommentDetail">重新加载</button> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 主评论内容 --> |
|
|
|
<view class="main-comment" v-if="!loading && !hasError && mainComment"> |
|
|
|
<view class="comment-header"> |
|
|
|
<image class="avatar" :src="mainComment.userHead || '/static/image/center/default-avatar.png'" mode="aspectFill"></image> |
|
|
|
<view class="user-info"> |
|
|
|
<text class="username">{{mainComment.userName}}</text> |
|
|
|
<text class="time">{{formatTime(mainComment.createTime)}}</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="comment-content"> |
|
|
|
<text>{{mainComment.userValue}}</text> |
|
|
|
</view> |
|
|
|
<!-- 评论图片 --> |
|
|
|
<view class="comment-images" v-if="mainComment.userImage"> |
|
|
|
<view class="image-grid"> |
|
|
|
<image |
|
|
|
v-for="(img, index) in mainComment.userImage.split(',')" |
|
|
|
:key="index" |
|
|
|
:src="img" |
|
|
|
mode="aspectFill" |
|
|
|
@click="previewImage(mainComment.userImage.split(','), index)" |
|
|
|
class="comment-image" |
|
|
|
></image> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="comment-footer"> |
|
|
|
<text class="reply-count">{{totalReplies}}条回复</text> |
|
|
|
<view class="reply-btn" @click="showReplyInput(mainComment.id, mainComment.userName)"> |
|
|
|
<uv-icon name="chat" color="#666" size="32rpx"></uv-icon> |
|
|
|
<text>回复</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 回复列表 --> |
|
|
|
<view class="replies-container" v-if="!loading && !hasError && commentList.length > 0"> |
|
|
|
<view class="reply-item" v-for="(item, index) in commentList" :key="index"> |
|
|
|
<view class="reply-header"> |
|
|
|
<image class="avatar" :src="item.userHead || '/static/image/center/default-avatar.png'" mode="aspectFill"></image> |
|
|
|
<view class="user-info"> |
|
|
|
<text class="username">{{item.userName}}</text> |
|
|
|
<text class="time">{{formatTime(item.createTime)}}</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="reply-content"> |
|
|
|
<view v-if="item.replyToUserName" class="reply-to"> |
|
|
|
<text>回复 </text> |
|
|
|
<text class="reply-username">@{{item.replyToUserName}}</text> |
|
|
|
<text>:</text> |
|
|
|
</view> |
|
|
|
<text>{{item.userValue}}</text> |
|
|
|
</view> |
|
|
|
<!-- 回复图片 --> |
|
|
|
<view class="reply-images" v-if="item.userImage"> |
|
|
|
<view class="image-grid"> |
|
|
|
<image |
|
|
|
v-for="(img, imgIndex) in item.userImage.split(',')" |
|
|
|
:key="imgIndex" |
|
|
|
:src="img" |
|
|
|
mode="aspectFill" |
|
|
|
@click="previewImage(item.userImage.split(','), imgIndex)" |
|
|
|
class="reply-image" |
|
|
|
></image> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
<view class="reply-footer"> |
|
|
|
<view class="reply-info"> |
|
|
|
<text v-if="item.replyNum > 0" class="sub-reply-count" @click="navigateToSubComment(item)"> |
|
|
|
{{item.replyNum}}条回复 > |
|
|
|
</text> |
|
|
|
</view> |
|
|
|
<view class="reply-btn" @click="showReplyInput(item.id, item.userName)"> |
|
|
|
<uv-icon name="chat" color="#666" size="28rpx"></uv-icon> |
|
|
|
<text>回复</text> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 加载更多 --> |
|
|
|
<view class="load-more" v-if="!loading && !hasError && hasMore"> |
|
|
|
<text @click="loadMoreReplies">加载更多</text> |
|
|
|
</view> |
|
|
|
|
|
|
|
<!-- 回复输入框 --> |
|
|
|
<view class="reply-input-container" v-if="showInput"> |
|
|
|
<view class="input-wrapper"> |
|
|
|
<input |
|
|
|
class="reply-input" |
|
|
|
v-model="replyContent" |
|
|
|
:placeholder="replyPlaceholder" |
|
|
|
focus |
|
|
|
confirm-type="send" |
|
|
|
@confirm="submitReply" |
|
|
|
/> |
|
|
|
<button class="send-btn" :disabled="!replyContent.trim()" @click="submitReply">发送</button> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</view> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
export default { |
|
|
|
data() { |
|
|
|
return { |
|
|
|
commentId: '', // 当前评论ID |
|
|
|
parentId: '', // 父评论ID,用于无限层级 |
|
|
|
sourceType: '', // 评论来源类型(文章、帖子等) |
|
|
|
sourceId: '', // 评论来源ID |
|
|
|
pageTitle: '评论详情', |
|
|
|
loading: true, |
|
|
|
hasError: false, |
|
|
|
mainComment: null, // 主评论 |
|
|
|
commentList: [], // 回复列表 |
|
|
|
page: 1, |
|
|
|
pageSize: 20, |
|
|
|
hasMore: false, |
|
|
|
totalReplies: 0, |
|
|
|
|
|
|
|
// 回复相关 |
|
|
|
showInput: false, |
|
|
|
replyContent: '', |
|
|
|
replyToId: '', // 回复的评论ID |
|
|
|
replyToUserName: '', // 回复的用户名 |
|
|
|
replyPlaceholder: '写回复...' |
|
|
|
} |
|
|
|
}, |
|
|
|
onLoad(options) { |
|
|
|
// 获取参数 |
|
|
|
if (options.id) { |
|
|
|
this.commentId = options.id; |
|
|
|
} |
|
|
|
if (options.parentId) { |
|
|
|
this.parentId = options.parentId; |
|
|
|
} |
|
|
|
if (options.sourceType) { |
|
|
|
this.sourceType = options.sourceType; |
|
|
|
} |
|
|
|
if (options.sourceId) { |
|
|
|
this.sourceId = options.sourceId; |
|
|
|
} |
|
|
|
|
|
|
|
// 加载评论详情 |
|
|
|
this.loadCommentDetail(); |
|
|
|
// 加载回复列表 |
|
|
|
this.loadReplies(); |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
// 加载评论详情 |
|
|
|
loadCommentDetail() { |
|
|
|
this.loading = true; |
|
|
|
this.hasError = false; |
|
|
|
|
|
|
|
// 调用API获取评论详情 |
|
|
|
this.$api('getCommentDetail', { |
|
|
|
id: this.commentId |
|
|
|
}, res => { |
|
|
|
this.loading = false; |
|
|
|
|
|
|
|
if (res.code === 200) { |
|
|
|
this.mainComment = res.result; |
|
|
|
// 获取回复总数 |
|
|
|
this.totalReplies = res.result.replyNum || 0; |
|
|
|
} else { |
|
|
|
this.hasError = true; |
|
|
|
uni.showToast({ |
|
|
|
title: res.message || '加载失败', |
|
|
|
icon: 'none' |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 加载回复列表 |
|
|
|
loadReplies() { |
|
|
|
// 使用现有的评论列表接口,传入pid参数 |
|
|
|
this.$api('getCommentPage', { |
|
|
|
pid: this.commentId, |
|
|
|
page: this.page, |
|
|
|
pageSize: this.pageSize |
|
|
|
}, res => { |
|
|
|
if (res.code === 200) { |
|
|
|
if (this.page === 1) { |
|
|
|
this.commentList = res.result.records || []; |
|
|
|
} else { |
|
|
|
this.commentList = [...this.commentList, ...(res.result.records || [])]; |
|
|
|
} |
|
|
|
|
|
|
|
// 判断是否有更多数据 |
|
|
|
this.hasMore = this.commentList.length < res.result.total; |
|
|
|
} else { |
|
|
|
uni.showToast({ |
|
|
|
title: res.message || '加载回复失败', |
|
|
|
icon: 'none' |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 加载更多回复 |
|
|
|
loadMoreReplies() { |
|
|
|
this.page++; |
|
|
|
this.loadReplies(); |
|
|
|
}, |
|
|
|
|
|
|
|
// 显示回复输入框 |
|
|
|
showReplyInput(commentId, userName = '') { |
|
|
|
this.showInput = true; |
|
|
|
this.replyToId = commentId; |
|
|
|
this.replyToUserName = userName; |
|
|
|
this.replyPlaceholder = userName ? `回复 @${userName}` : '写回复...'; |
|
|
|
this.replyContent = ''; |
|
|
|
}, |
|
|
|
|
|
|
|
// 提交回复 |
|
|
|
submitReply() { |
|
|
|
if (!this.replyContent.trim()) return; |
|
|
|
|
|
|
|
// 调用API提交回复 |
|
|
|
this.$api('addComment', { |
|
|
|
userValue: this.replyContent, |
|
|
|
pid: this.commentId, |
|
|
|
replyToId: this.replyToId, |
|
|
|
replyToUserName: this.replyToUserName, |
|
|
|
type: this.sourceType, |
|
|
|
orderId: this.sourceId |
|
|
|
}, res => { |
|
|
|
if (res.code === 200) { |
|
|
|
// 清空输入框 |
|
|
|
this.replyContent = ''; |
|
|
|
this.showInput = false; |
|
|
|
|
|
|
|
// 重新加载回复列表 |
|
|
|
this.page = 1; |
|
|
|
this.loadReplies(); |
|
|
|
|
|
|
|
// 更新回复总数 |
|
|
|
this.totalReplies++; |
|
|
|
|
|
|
|
uni.showToast({ |
|
|
|
title: '回复成功', |
|
|
|
icon: 'success' |
|
|
|
}); |
|
|
|
} else { |
|
|
|
uni.showToast({ |
|
|
|
title: res.message || '回复失败', |
|
|
|
icon: 'none' |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 导航到子评论详情 |
|
|
|
navigateToSubComment(comment) { |
|
|
|
if (comment.replyNum > 0) { |
|
|
|
uni.navigateTo({ |
|
|
|
url: `/pages_order/comment/commentDetail?id=${comment.id}&parentId=${this.commentId}&sourceType=${this.sourceType}&sourceId=${this.sourceId}` |
|
|
|
}); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 返回上一页 |
|
|
|
navigateBack() { |
|
|
|
uni.navigateBack(); |
|
|
|
}, |
|
|
|
|
|
|
|
// 预览图片 |
|
|
|
previewImage(images, index) { |
|
|
|
uni.previewImage({ |
|
|
|
urls: images, |
|
|
|
current: index |
|
|
|
}); |
|
|
|
}, |
|
|
|
|
|
|
|
// 格式化时间 |
|
|
|
formatTime(timestamp) { |
|
|
|
if (!timestamp) return ''; |
|
|
|
|
|
|
|
const now = new Date().getTime(); |
|
|
|
const diff = now - timestamp; |
|
|
|
|
|
|
|
// 小于1分钟 |
|
|
|
if (diff < 60000) { |
|
|
|
return '刚刚'; |
|
|
|
} |
|
|
|
|
|
|
|
// 小于1小时 |
|
|
|
if (diff < 3600000) { |
|
|
|
return Math.floor(diff / 60000) + '分钟前'; |
|
|
|
} |
|
|
|
|
|
|
|
// 小于24小时 |
|
|
|
if (diff < 86400000) { |
|
|
|
return Math.floor(diff / 3600000) + '小时前'; |
|
|
|
} |
|
|
|
|
|
|
|
// 小于30天 |
|
|
|
if (diff < 2592000000) { |
|
|
|
return Math.floor(diff / 86400000) + '天前'; |
|
|
|
} |
|
|
|
|
|
|
|
// 大于30天显示具体日期 |
|
|
|
const date = new Date(timestamp); |
|
|
|
return `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |
|
|
|
|
|
|
|
<style lang="scss" scoped> |
|
|
|
.comment-detail-container { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
min-height: 100vh; |
|
|
|
background-color: #f5f5f5; |
|
|
|
padding-bottom: 100rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.loading-container { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
margin-top: 200rpx; |
|
|
|
|
|
|
|
.loading-spinner { |
|
|
|
width: 80rpx; |
|
|
|
height: 80rpx; |
|
|
|
border: 6rpx solid #f3f3f3; |
|
|
|
border-top: 6rpx solid $uni-color-primary; |
|
|
|
border-radius: 50%; |
|
|
|
animation: spin 1s linear infinite; |
|
|
|
margin-bottom: 20rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.loading-text { |
|
|
|
font-size: 28rpx; |
|
|
|
color: #666; |
|
|
|
} |
|
|
|
|
|
|
|
@keyframes spin { |
|
|
|
0% { transform: rotate(0deg); } |
|
|
|
100% { transform: rotate(360deg); } |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.error-container { |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: center; |
|
|
|
margin-top: 200rpx; |
|
|
|
|
|
|
|
.error-text { |
|
|
|
font-size: 28rpx; |
|
|
|
color: #999; |
|
|
|
margin-bottom: 30rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.retry-button { |
|
|
|
background-color: $uni-color-primary; |
|
|
|
color: #fff; |
|
|
|
font-size: 28rpx; |
|
|
|
padding: 16rpx 40rpx; |
|
|
|
border-radius: 40rpx; |
|
|
|
border: none; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.main-comment { |
|
|
|
background-color: #fff; |
|
|
|
padding: 30rpx; |
|
|
|
margin-bottom: 20rpx; |
|
|
|
|
|
|
|
.comment-header { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 20rpx; |
|
|
|
|
|
|
|
.avatar { |
|
|
|
width: 80rpx; |
|
|
|
height: 80rpx; |
|
|
|
border-radius: 50%; |
|
|
|
margin-right: 20rpx; |
|
|
|
background-color: #f0f0f0; |
|
|
|
} |
|
|
|
|
|
|
|
.user-info { |
|
|
|
flex: 1; |
|
|
|
|
|
|
|
.username { |
|
|
|
font-size: 28rpx; |
|
|
|
font-weight: bold; |
|
|
|
color: #333; |
|
|
|
margin-bottom: 6rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.time { |
|
|
|
font-size: 24rpx; |
|
|
|
color: #999; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.comment-content { |
|
|
|
font-size: 30rpx; |
|
|
|
color: #333; |
|
|
|
line-height: 1.6; |
|
|
|
margin-bottom: 20rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.comment-images { |
|
|
|
margin-bottom: 20rpx; |
|
|
|
|
|
|
|
.image-grid { |
|
|
|
display: flex; |
|
|
|
flex-wrap: wrap; |
|
|
|
|
|
|
|
.comment-image { |
|
|
|
width: 200rpx; |
|
|
|
height: 200rpx; |
|
|
|
margin-right: 10rpx; |
|
|
|
margin-bottom: 10rpx; |
|
|
|
border-radius: 8rpx; |
|
|
|
background-color: #f0f0f0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.comment-footer { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
padding-top: 20rpx; |
|
|
|
border-top: 1px solid #f0f0f0; |
|
|
|
|
|
|
|
.reply-count { |
|
|
|
font-size: 26rpx; |
|
|
|
color: #666; |
|
|
|
} |
|
|
|
|
|
|
|
.reply-btn { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
|
|
|
|
text { |
|
|
|
font-size: 26rpx; |
|
|
|
color: #666; |
|
|
|
margin-left: 6rpx; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.replies-container { |
|
|
|
background-color: #fff; |
|
|
|
|
|
|
|
.reply-item { |
|
|
|
padding: 30rpx; |
|
|
|
border-bottom: 1px solid #f0f0f0; |
|
|
|
|
|
|
|
&:active { |
|
|
|
background-color: #f9f9f9; |
|
|
|
} |
|
|
|
|
|
|
|
.reply-header { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
margin-bottom: 16rpx; |
|
|
|
|
|
|
|
.avatar { |
|
|
|
width: 70rpx; |
|
|
|
height: 70rpx; |
|
|
|
border-radius: 50%; |
|
|
|
margin-right: 16rpx; |
|
|
|
background-color: #f0f0f0; |
|
|
|
} |
|
|
|
|
|
|
|
.user-info { |
|
|
|
flex: 1; |
|
|
|
|
|
|
|
.username { |
|
|
|
font-size: 26rpx; |
|
|
|
font-weight: bold; |
|
|
|
color: #333; |
|
|
|
margin-bottom: 4rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.time { |
|
|
|
font-size: 22rpx; |
|
|
|
color: #999; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.reply-content { |
|
|
|
font-size: 28rpx; |
|
|
|
color: #333; |
|
|
|
line-height: 1.5; |
|
|
|
margin-bottom: 16rpx; |
|
|
|
padding-left: 86rpx; |
|
|
|
|
|
|
|
.reply-to { |
|
|
|
display: inline; |
|
|
|
|
|
|
|
.reply-username { |
|
|
|
color: $uni-color-primary; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.reply-images { |
|
|
|
padding-left: 86rpx; |
|
|
|
margin-bottom: 16rpx; |
|
|
|
|
|
|
|
.image-grid { |
|
|
|
display: flex; |
|
|
|
flex-wrap: wrap; |
|
|
|
|
|
|
|
.reply-image { |
|
|
|
width: 150rpx; |
|
|
|
height: 150rpx; |
|
|
|
margin-right: 10rpx; |
|
|
|
margin-bottom: 10rpx; |
|
|
|
border-radius: 8rpx; |
|
|
|
background-color: #f0f0f0; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.reply-footer { |
|
|
|
display: flex; |
|
|
|
justify-content: space-between; |
|
|
|
align-items: center; |
|
|
|
padding-left: 86rpx; |
|
|
|
|
|
|
|
.reply-info { |
|
|
|
.sub-reply-count { |
|
|
|
font-size: 24rpx; |
|
|
|
color: $uni-color-primary; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.reply-btn { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
|
|
|
|
text { |
|
|
|
font-size: 24rpx; |
|
|
|
color: #666; |
|
|
|
margin-left: 4rpx; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.load-more { |
|
|
|
text-align: center; |
|
|
|
padding: 30rpx 0; |
|
|
|
|
|
|
|
text { |
|
|
|
font-size: 26rpx; |
|
|
|
color: #666; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
.reply-input-container { |
|
|
|
position: fixed; |
|
|
|
bottom: 0; |
|
|
|
left: 0; |
|
|
|
right: 0; |
|
|
|
background-color: #fff; |
|
|
|
padding: 20rpx 30rpx; |
|
|
|
box-shadow: 0 -2rpx 10rpx rgba(0, 0, 0, 0.05); |
|
|
|
z-index: 100; |
|
|
|
padding-bottom: env(safe-area-inset-bottom); |
|
|
|
|
|
|
|
.input-wrapper { |
|
|
|
display: flex; |
|
|
|
align-items: center; |
|
|
|
|
|
|
|
.reply-input { |
|
|
|
flex: 1; |
|
|
|
height: 70rpx; |
|
|
|
background-color: #f5f5f5; |
|
|
|
border-radius: 35rpx; |
|
|
|
padding: 0 30rpx; |
|
|
|
font-size: 28rpx; |
|
|
|
} |
|
|
|
|
|
|
|
.send-btn { |
|
|
|
margin-left: 20rpx; |
|
|
|
background-color: $uni-color-primary; |
|
|
|
color: #fff; |
|
|
|
font-size: 28rpx; |
|
|
|
height: 70rpx; |
|
|
|
line-height: 70rpx; |
|
|
|
padding: 0 30rpx; |
|
|
|
border-radius: 35rpx; |
|
|
|
|
|
|
|
&[disabled] { |
|
|
|
background-color: #cccccc; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
</style> |