407 lines
9.0 KiB
Vue
407 lines
9.0 KiB
Vue
<template>
|
|
<view class="comment-list">
|
|
<!-- 评论列表 -->
|
|
<view v-for="comment in comments" :key="comment.id" class="comment-item">
|
|
<!-- 一级评论 -->
|
|
<view class="comment-main">
|
|
<view class="user-info">
|
|
<image
|
|
:src="comment.user.avatar || defaultAvatar"
|
|
class="avatar"
|
|
mode="aspectFill"
|
|
/>
|
|
<text class="username">{{ comment.user.name }}</text>
|
|
</view>
|
|
|
|
<view class="comment-content">
|
|
<view class="content-html" v-html="comment.content"></view>
|
|
|
|
<!-- 图片列表 -->
|
|
<view v-if="comment.imgList && comment.imgList.length > 0" class="image-list">
|
|
<image
|
|
v-for="(img, imgIndex) in comment.imgList"
|
|
:key="imgIndex"
|
|
:src="img"
|
|
class="comment-image"
|
|
mode="aspectFill"
|
|
@click="previewImage(img, comment.imgList)"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="comment-actions">
|
|
<text class="time">{{ comment.createTime }}</text>
|
|
<view class="action-btns">
|
|
<wd-button
|
|
size="small"
|
|
custom-class="action-btn"
|
|
:type="comment.support ? 'primary' : 'default'"
|
|
@click="handleLike(comment.id)"
|
|
>
|
|
<wd-icon name="thumb-up" />
|
|
<text class="btn-text">{{ comment.supportCount || 0 }}</text>
|
|
</wd-button>
|
|
<wd-button
|
|
size="small"
|
|
custom-class="action-btn"
|
|
@click="handleReply(comment)"
|
|
>
|
|
<wd-icon name="chat" />
|
|
<text class="btn-text">回复</text>
|
|
</wd-button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 子评论列表 -->
|
|
<view v-if="comment.Bchildren && comment.Bchildren.length > 0" class="sub-comments">
|
|
<view v-for="subComment in comment.Bchildren" :key="subComment.id" class="sub-comment-item">
|
|
<view class="sub-user-info">
|
|
<image
|
|
:src="subComment.user.avatar || defaultAvatar"
|
|
class="sub-avatar"
|
|
mode="aspectFill"
|
|
/>
|
|
<text class="sub-username">{{ subComment.user.name }}</text>
|
|
</view>
|
|
|
|
<view class="sub-comment-content">
|
|
<view class="content-html" v-html="subComment.content"></view>
|
|
|
|
<!-- 子评论图片 -->
|
|
<view v-if="subComment.imgList && subComment.imgList.length > 0" class="image-list">
|
|
<image
|
|
v-for="(img, imgIndex) in subComment.imgList"
|
|
:key="imgIndex"
|
|
:src="img"
|
|
class="comment-image"
|
|
mode="aspectFill"
|
|
@click="previewImage(img, subComment.imgList)"
|
|
/>
|
|
</view>
|
|
</view>
|
|
|
|
<view class="sub-comment-actions">
|
|
<text class="time">{{ subComment.createTime }}</text>
|
|
<view class="action-btns">
|
|
<wd-button
|
|
size="small"
|
|
custom-class="action-btn"
|
|
:type="subComment.support ? 'primary' : 'default'"
|
|
@click="handleLike(subComment.id)"
|
|
>
|
|
<wd-icon name="thumb-up" />
|
|
<text class="btn-text">{{ subComment.supportCount || 0 }}</text>
|
|
</wd-button>
|
|
<wd-button
|
|
size="small"
|
|
custom-class="action-btn"
|
|
@click="handleReply(subComment)"
|
|
>
|
|
<wd-icon name="chat" />
|
|
<text class="btn-text">回复</text>
|
|
</wd-button>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 查看更多回复 -->
|
|
<view
|
|
v-if="comment.children && comment.children.length > comment.Bchildren.length"
|
|
class="load-more-replies"
|
|
@click="loadMoreReplies(comment)"
|
|
>
|
|
<text>查看更多回复 ({{ comment.children.length - comment.Bchildren.length }})</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 加载更多 -->
|
|
<view v-if="hasMore" class="load-more-btn">
|
|
<wd-button
|
|
@click="handleLoadMore"
|
|
:loading="loading"
|
|
block
|
|
>
|
|
加载更多
|
|
</wd-button>
|
|
</view>
|
|
|
|
<!-- 已加载全部 -->
|
|
<view v-else-if="comments.length > 0" class="no-more">
|
|
<wd-divider>已加载全部</wd-divider>
|
|
</view>
|
|
|
|
<!-- 暂无评论 -->
|
|
<view v-else-if="!loading" class="no-comments">
|
|
<text>暂无留言数据</text>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { ref } from 'vue'
|
|
import type { IComment } from '@/types/comment'
|
|
|
|
interface Props {
|
|
comments: IComment[]
|
|
loading: boolean
|
|
hasMore: boolean
|
|
type: 'course' | 'book'
|
|
}
|
|
|
|
const props = defineProps<Props>()
|
|
|
|
const emit = defineEmits<{
|
|
like: [commentId: number]
|
|
reply: [comment: IComment]
|
|
loadMore: []
|
|
}>()
|
|
|
|
const defaultAvatar = '/static/icon/default-avatar.png'
|
|
|
|
/**
|
|
* 预览图片
|
|
*/
|
|
const previewImage = (current: string, urls: string[]) => {
|
|
uni.previewImage({
|
|
current,
|
|
urls,
|
|
longPressActions: {
|
|
itemList: ['很抱歉,暂不支持保存图片到本地'],
|
|
},
|
|
})
|
|
}
|
|
|
|
/**
|
|
* 点赞
|
|
*/
|
|
const handleLike = (commentId: number) => {
|
|
emit('like', commentId)
|
|
}
|
|
|
|
/**
|
|
* 回复
|
|
*/
|
|
const handleReply = (comment: IComment) => {
|
|
emit('reply', comment)
|
|
}
|
|
|
|
/**
|
|
* 加载更多
|
|
*/
|
|
const handleLoadMore = () => {
|
|
emit('loadMore')
|
|
}
|
|
|
|
/**
|
|
* 加载更多回复
|
|
*/
|
|
const loadMoreReplies = (comment: IComment) => {
|
|
// 显示所有子评论
|
|
comment.Bchildren = [...comment.children]
|
|
}
|
|
</script>
|
|
|
|
<style lang="scss" scoped>
|
|
.comment-list {
|
|
padding: 20rpx;
|
|
background-color: #fff;
|
|
}
|
|
|
|
.comment-item {
|
|
margin-bottom: 30rpx;
|
|
padding-bottom: 30rpx;
|
|
border-bottom: 1px solid #f0f0f0;
|
|
|
|
&:last-child {
|
|
border-bottom: none;
|
|
}
|
|
}
|
|
|
|
.comment-main {
|
|
.user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 20rpx;
|
|
|
|
.avatar {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border-radius: 50%;
|
|
margin-right: 20rpx;
|
|
}
|
|
|
|
.username {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
font-weight: 500;
|
|
}
|
|
}
|
|
|
|
.comment-content {
|
|
margin-left: 80rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.content-html {
|
|
font-size: 28rpx;
|
|
line-height: 1.6;
|
|
color: #666;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.image-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
margin-top: 20rpx;
|
|
gap: 10rpx;
|
|
|
|
.comment-image {
|
|
width: 200rpx;
|
|
height: 200rpx;
|
|
border-radius: 8rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.comment-actions {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-left: 80rpx;
|
|
|
|
.time {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.action-btns {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
|
|
.action-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8rpx;
|
|
|
|
.btn-text {
|
|
font-size: 24rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.sub-comments {
|
|
margin-left: 80rpx;
|
|
margin-top: 20rpx;
|
|
padding: 20rpx;
|
|
background-color: #f7f8f9;
|
|
border-radius: 8rpx;
|
|
|
|
.sub-comment-item {
|
|
margin-bottom: 20rpx;
|
|
|
|
&:last-child {
|
|
margin-bottom: 0;
|
|
}
|
|
|
|
.sub-user-info {
|
|
display: flex;
|
|
align-items: center;
|
|
margin-bottom: 15rpx;
|
|
|
|
.sub-avatar {
|
|
width: 40rpx;
|
|
height: 40rpx;
|
|
border-radius: 50%;
|
|
margin-right: 15rpx;
|
|
}
|
|
|
|
.sub-username {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
}
|
|
}
|
|
|
|
.sub-comment-content {
|
|
margin-left: 55rpx;
|
|
margin-bottom: 15rpx;
|
|
|
|
.content-html {
|
|
font-size: 26rpx;
|
|
line-height: 1.5;
|
|
color: #666;
|
|
word-break: break-all;
|
|
}
|
|
|
|
.image-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
margin-top: 15rpx;
|
|
gap: 10rpx;
|
|
|
|
.comment-image {
|
|
width: 150rpx;
|
|
height: 150rpx;
|
|
border-radius: 8rpx;
|
|
}
|
|
}
|
|
}
|
|
|
|
.sub-comment-actions {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
margin-left: 55rpx;
|
|
|
|
.time {
|
|
font-size: 22rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.action-btns {
|
|
display: flex;
|
|
gap: 15rpx;
|
|
|
|
.action-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6rpx;
|
|
|
|
.btn-text {
|
|
font-size: 22rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.load-more-replies {
|
|
text-align: center;
|
|
padding: 15rpx 0;
|
|
margin-top: 15rpx;
|
|
|
|
text {
|
|
font-size: 24rpx;
|
|
color: #2979ff;
|
|
}
|
|
}
|
|
}
|
|
|
|
.load-more-btn {
|
|
margin-top: 30rpx;
|
|
}
|
|
|
|
.no-more {
|
|
margin-top: 30rpx;
|
|
}
|
|
|
|
.no-comments {
|
|
text-align: center;
|
|
padding: 80rpx 0;
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
</style>
|