This commit is contained in:
WanP
2025-10-27 15:03:40 +08:00
parent 45ceb9c77d
commit 5eb5c59e4b
18 changed files with 1340 additions and 636 deletions

View File

@@ -1,129 +1,131 @@
<template>
<!-- 评论区域 -->
<view v-if="comments" class="comment">
<!-- 评论头部信息 -->
<view class="commenthead">
<text class="commentcount">{{ totalCommentCount }}条评论</text>
<view class="headswitch">
<text class="inact" @tap="handleOpenApp">默认</text>
<view class="act" @tap="handleOpenApp">最新</view>
</view>
</view>
<!-- 评论主体 -->
<view v-for="commentItem in highlightedList" :key="commentItem.id" class="commentdetail" @tap="onDelegateTap">
<!-- 头像 -->
<view class="commentdetailleft">
<image src="/static/logo.png" mode="aspectFill" alt="用户头像"></image>
</view>
<!-- 右侧主评论 + 子评论 + 展开条 -->
<view class="commentdetailright">
<!-- 父评论 -->
<view class="maincomment">
<view class="commentdetailcontent">
<text class="commentusername">{{ commentItem.userName }}</text>
<rich-text class="commentusercontent" :nodes="commentItem.renderNodes" />
<view class="date-reply">
<uni-dateformat :date="Date.parse(commentItem.date.replace(/-/g, '/'))" :threshold="[0, 0]"
format="yyyy-MM-dd" class="date-text" />
<text class="replytext" @tap.stop="handleOpenApp">回复</text>
</view>
</view>
<view class="spacerview"></view>
<view class="commentlike">
<uni-icons type="heart" size="16" color="#999" @tap.stop="handleOpenApp"></uni-icons>
<text class="commentlikecount">{{ formatCount(commentItem.likeCount) }}</text>
</view>
<view class="comment">
<main>
<!-- 评论头部信息 -->
<view class="commenthead">
<text class="commentcount">{{ comments.length }}条评论</text>
<view class="headswitch">
<text class="inact" @tap="handleOpenApp">默认</text>
<view class="act" @tap="handleOpenApp">最新</view>
</view>
</view>
<!-- 子评论列表仅展开时显示 -->
<view v-if="commentItem.showChild" class="commentchildcontainer">
<view v-for="child in commentItem.children" :key="child.id" class="commentchild">
<view class="commentchildleft">
<image src="/static/logo.png" mode="aspectFill" alt="用户头像"></image>
</view>
<view class="commentchildright">
<text class="commentusername">{{ child.userName }}</text>
<rich-text class="commentusercontent" :nodes="child.renderNodes" />
<view class="date-reply">
<uni-dateformat :date="Date.parse(child.date.replace(/-/g, '/'))" :threshold="[0, 0]"
format="yyyy-MM-dd" class="date-text" />
<text class="replytext" @tap.stop="handleOpenApp">回复</text>
<!-- 评论主体 -->
<view v-if="comments.list.length > 0">
<view v-for="commentItem in comments.list" :key="commentItem.id" class="commentdetail" @tap="onDelegateTap">
<!-- 头像 -->
<view class="commentdetailleft">
<image :src="commentItem.userAvatar" mode="aspectFill" alt="用户头像"></image>
</view>
<!-- 右侧主评论 + 子评论 + 展开条 -->
<view class="commentdetailright">
<!-- 父评论 -->
<view class="maincomment">
<view class="commentdetailcontent">
<text class="commentusername">{{ commentItem.userName }}</text>
<rich-text class="commentusercontent" :nodes="commentItem.renderNodes" />
<view class="date-reply">
<uni-dateformat v-if="commentItem.createdAt"
:date="Date.parse(commentItem.createdAt.replace(/-/g, '/'))" :threshold="[0, 0]" format="yyyy-MM-dd"
class="date-text" />
<text v-else class="date-text">未知时间</text>
<text class="replytext" @tap.stop="handleOpenApp">回复</text>
</view>
</view>
<view class="spacerview"></view>
<view class="commentlike">
<uni-icons type="heart" size="16" color="#999" @tap.stop="handleOpenApp"></uni-icons>
<text class="commentlikecount">{{ formatCount(commentItem.likeCount) }}</text>
</view>
</view>
<view class="commentlike">
<uni-icons type="heart" size="16" color="#999" @tap.stop="handleOpenApp"></uni-icons>
<text class="commentlikecount">{{ formatCount(child.likeCount) }}</text>
<!-- 子评论列表仅展开时显示 -->
<view v-if="commentItem.showChild" class="commentchildcontainer">
<view v-for="child in commentItem.children" :key="child.id" class="commentchild">
<view class="commentchildleft">
<image :src="child.userAvatar || '/static/logo.png'" mode="aspectFill" alt="用户头像"></image>
</view>
<view class="commentchildright">
<text class="commentusername">{{ child.userName }}</text>
<rich-text class="commentusercontent" :nodes="child.renderNodes" />
<view class="date-reply">
<uni-dateformat v-if="child.date" :date="Date.parse(child.date.replace(/-/g, '/'))"
:threshold="[0, 0]" format="yyyy-MM-dd" class="date-text" />
<text v-else class="date-text">未知时间</text>
<text class="replytext" @tap.stop="handleOpenApp">回复</text>
</view>
</view>
<view class="commentlike">
<uni-icons type="heart" size="16" color="#999" @tap.stop="handleOpenApp"></uni-icons>
<text class="commentlikecount">{{ formatCount(child.likeCount) }}</text>
</view>
</view>
</view>
<!-- 展开/收起按钮 -->
<view v-if="commentItem.reply.length" class="expandcomment">
<view style="width:20px;height:1px;background:rgba(65,60,67,.2)"></view>
<text class="expandcommenttext" :data-cid="commentItem.id">
{{ commentItem.showChild ? '收起' : `展开${commentItem.reply.length}条回复` }}
</text>
</view>
</view>
</view>
<!-- 展开/收起按钮 -->
<view v-if="commentItem.children.length" class="expandcomment">
<view style="width:20px;height:1px;background:rgba(65,60,67,.2)"></view>
<text class="expandcommenttext" :data-cid="commentItem.id">
{{ commentItem.showChild ? '收起' : `展开${commentItem.children.length}条回复` }}
</text>
</view>
</view>
</view>
<view v-else class="nocomments">
<image src="/static/imgs/nocomments/nocomments@3x.webp" mode="aspectFit" alt="暂无评论"></image>
<text class="nocommentstext">空空如也~</text>
</view>
<!-- 占位视图 -->
<view class="spacerview"></view>
</main>
<!-- 互动区域 -->
<Intereact />
</view>
<!-- 占位 -->
<view class="spacerview"></view>
<!-- 互动区域 -->
<view v-if="showInteraction" class="interaction">
<view class="editarea" @tap="handleOpenApp">
<image src="/static/imgs/editicon/icon@2x.webp" mode="aspectFit" class="editicon" alt="编辑标签"></image>
<text class="edittext">快来互动吧</text>
</view>
<view class="spacerview"></view>
<view class="collection" @tap="handleOpenApp">
<image src="@/static/imgs/staricon/icon@3x.webp" mode="aspectFit" class="collectionicon" alt="收藏标签"></image>
<text class="collectioncount">{{ formatCount(collectsum) }}</text>
</view>
<view class="spacerview"></view>
<view class="like" @tap="handleOpenApp">
<image src="@/static/imgs/likeicon/icon@3x.webp" mode="aspectFit" class="likeicon" alt="点赞标签"></image>
<text class="likecount">{{ formatCount(likesum) }}</text>
</view>
</view>
<!-- Findmore -->
<Findmore />
</template>
<script setup>
import { computed } from 'vue'
import { reactive } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
import { getCommentList, getUserImg } from '@/api/api.js'
import { useCommonStore } from '@/stores/common.js'
const props = defineProps({
comments: {
type: Array,
default: () => []
},
showInteraction: {
type: Boolean,
default: false
},
collectsum: {
type: Number,
default: 0
},
likesum: {
type: Number,
default: 0
}
})
import Findmore from '@/pages/findmore/findmore.vue'
import Intereact from '@/pages/intereact/intereact.vue'
const common = useCommonStore()
const comments = reactive({
list: [],
length: 0
})
const props = defineProps({
postid: {
type: [String, Number],
default: ''
}
})
// 处理评论点击事件
function onDelegateTap(e) {
const cid = e.target?.dataset?.cid
if (!cid || !props.comments) return
const item = props.comments.find(v => v.id == cid)
if (!item || !item.children || !item.children.length) return
if (!cid || !comments.list) return
const item = comments.list.find(v => v.id == cid)
if (!item || !item.reply || !item.reply.length) return
item.showChild = !item.showChild
}
@@ -137,31 +139,146 @@ function formatCount(count) {
return common.formatCount(count)
}
// 处理评论数据,添加渲染节点
const highlightedList = computed(() =>
props.comments ? props.comments.map(c => ({
...c,
renderNodes: [
...common.atUsersToNodes(c.atUsers),
{ type: 'text', text: c.content }
],
children: c.children ? c.children.map(child => ({
...child,
renderNodes: [
...common.atUsersToNodes(child.atUsers),
{ type: 'text', text: child.content }
]
})) : []
})) : []
)
onLoad(() => {
const params = {
postId: props.postid
}
getCommentList(params).then(res => {
try {
// 检查响应状态
if (res.statusCode === 200 && res.data) {
console.log(res.data)
comments.list = res.data.list || []
comments.length = res.data.total || 0
// 处理每条评论
comments.list.forEach(comment => {
processComment(comment)
})
} else {
throw new Error(`请求失败: ${res.statusCode}`)
}
} catch (error) {
console.error('处理评论数据时出错:', error)
uni.showToast({
title: '加载评论失败',
icon: 'error'
})
// 设置默认空数据
comments.list = []
comments.length = 0
}
}).catch(error => {
console.error('获取评论列表失败:', error)
uni.showToast({
title: '网络连接异常',
icon: 'error'
})
// 设置默认空数据
comments.list = []
comments.length = 0
})
})
/**
* 处理单条评论数据
* @param {Object} comment - 评论对象
*/
function processComment(comment) {
// 确保comment对象有必要的属性
if (!comment.children || comment.children.length === 0) {
comment.showChild = false
}
// 确保user对象存在
if (!comment.user) {
comment.user = {}
}
// 设置评论基本属性
comment.content = comment.content || ''
comment.likeCount = comment.likeCount || 0
comment.userName = comment.user.nickName || '匿名用户'
comment.createdAt = comment.createdAt || comment.date || ''
// 生成renderNodes用于富文本渲染
comment.renderNodes = common.contentToRenderNodes(comment.content, comment.atUsers)
// 处理用户头像
handleCommentAvatar(comment)
// 处理子评论
if (comment.reply && comment.reply.length > 0) {
// 将reply赋值给children确保模板和代码一致
comment.children = comment.reply
comment.children.forEach(childComment => {
processChildComment(childComment)
})
}
}
/**
* 处理子评论数据
* @param {Object} childComment - 子评论对象
*/
function processChildComment(childComment) {
// 确保user对象存在
if (!childComment.user) {
childComment.user = {}
}
// 设置子评论基本属性
childComment.content = childComment.content || ''
childComment.likeCount = childComment.likeCount || 0
childComment.userName = childComment.user.nickName || '匿名用户'
childComment.date = childComment.createdAt || childComment.date || ''
// 处理子评论头像
handleCommentAvatar(childComment)
// 为子评论生成renderNodes
childComment.renderNodes = common.contentToRenderNodes(childComment.content, childComment.atUsers)
}
/**
* 处理评论头像
* @param {Object} comment - 评论对象(主评论或子评论)
*/
function handleCommentAvatar(comment) {
// 如果已经有头像URL直接使用
if (comment.userAvatar) {
return
}
// 确保有头像路径时才请求用户信息
const avatarPath = comment.user.avatar
if (avatarPath) {
getUserImg(avatarPath).then(res => {
try {
if (res.statusCode === 200 && res.data) {
// 将arrayBuffer转换为base64
const base64 = uni.arrayBufferToBase64(res.data)
const userImgUrl = 'data:image/webp;base64,' + base64
comment.userAvatar = userImgUrl
} else {
// 使用原始头像URL作为备选
comment.userAvatar = avatarPath || ''
}
} catch (error) {
console.warn('处理用户头像信息失败:', error)
comment.userAvatar = avatarPath || ''
}
}).catch(error => {
console.warn('获取用户头像失败:', error)
comment.userAvatar = avatarPath || ''
})
} else {
// 没有头像路径时使用空字符串
comment.userAvatar = ''
}
}
// 总评论数 = 主评论条数 + 所有子评论条数
const totalCommentCount = computed(() =>
props.comments ? props.comments.reduce(
(sum, c) => sum + 1 + (c.children ? c.children.length : 0),
0
) : 0
)
</script>
@@ -175,11 +292,16 @@ const totalCommentCount = computed(() =>
.comment {
width: 100%;
max-width: 430px;
padding: 16px;
height: 50vh;
display: flex;
flex-direction: column;
margin: 0 auto;
box-sizing: border-box;
position: relative;
}
main {
padding: 16px;
}
.commenthead {
@@ -354,13 +476,12 @@ const totalCommentCount = computed(() =>
width: 100%;
display: flex;
flex-direction: column;
margin: 14px 0 0;
}
.commentchild {
display: flex;
flex-direction: row;
margin-bottom: 8px;
/* margin-bottom: 8px; */
}
.commentchildleft {
@@ -388,6 +509,8 @@ const totalCommentCount = computed(() =>
align-items: center;
flex-direction: row;
gap: 8px;
transition: all 0.5s ease;
cursor: pointer;
}
.expandcommenttext {
@@ -402,83 +525,25 @@ const totalCommentCount = computed(() =>
color: #110c13;
}
.interaction {
width: 100%;
max-width: 430px;
border: solid 1px #faf9fb;
background-color: #faf9fb;
.nocomments {
display: flex;
align-items: center;
padding: 11.5px 16px;
flex-direction: row;
box-sizing: border-box;
flex-direction: column;
text-align: center;
margin: 0 auto;
}
.editarea {
flex: 1;
height: 40px;
background-color: #fff;
border-radius: 24px;
max-width: 100%;
gap: 12px;
padding: 0 20px;
box-sizing: border-box;
display: flex;
align-items: center;
cursor: pointer;
padding: 12px;
}
.editicon {
width: 13.3px;
height: 14.6px;
flex-shrink: 0;
align-self: center;
}
.edittext {
flex: 1;
flex-grow: 0;
.nocommentstext {
font-size: 14px;
font-family: 'PingFangSC';
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: 40px;
letter-spacing: normal;
text-align: left;
color: #918e93;
white-space: nowrap;
}
.collection,
.like {
height: 40px;
display: flex;
flex-direction: row;
align-items: center;
gap: 5px;
}
.collectionicon,
.likeicon {
width: 24px;
height: 24px;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.collectioncount,
.likecount {
height: 17px;
font-family: 'SFPro';
font-size: 14px;
font-weight: 500;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
text-align: left;
color: #000;
text-align: center;
color: #918e93;
}
</style>