|
|
@ -1,11 +1,11 @@
|
|
|
|
<template>
|
|
|
|
<template>
|
|
|
|
<div class="post-detail-container">
|
|
|
|
<div class="post-detail-container">
|
|
|
|
<!-- 作者信息栏(可保留或移除) -->
|
|
|
|
<!-- 作者信息栏 -->
|
|
|
|
<div class="author-info">
|
|
|
|
<div class="author-info" v-if="author && author.userName">
|
|
|
|
<img :src="author.userAvatar" alt="头像" class="author-avatar" />
|
|
|
|
<img :src="author.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="头像" class="author-avatar" />
|
|
|
|
<div class="author-details">
|
|
|
|
<div class="author-details">
|
|
|
|
<h3 class="author-name">{{ author.userName }}</h3>
|
|
|
|
<h3 class="author-name">{{ author.userName || '匿名用户' }}</h3>
|
|
|
|
<p class="author-stats">粉丝数:{{ author.followers }}</p>
|
|
|
|
<p class="author-stats">粉丝数:{{ author.followers ?? 0 }}</p>
|
|
|
|
<button @click="toggleFollow" class="follow-button">
|
|
|
|
<button @click="toggleFollow" class="follow-button">
|
|
|
|
{{ isFollowing ? '取消关注' : '关注' }}
|
|
|
|
{{ isFollowing ? '取消关注' : '关注' }}
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
@ -14,9 +14,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 帖子内容 -->
|
|
|
|
<!-- 帖子内容 -->
|
|
|
|
<div class="post-content">
|
|
|
|
<div class="post-content">
|
|
|
|
<img :src="author.userAvatar" alt="作者头像" class="post-author-avatar" />
|
|
|
|
<img :src="author?.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="作者头像" class="post-author-avatar" />
|
|
|
|
<h1 class="post-title">{{ postDetailStore.post?.title }}</h1>
|
|
|
|
<h1 class="post-title">{{ postDetailStore.post?.title || '' }}</h1>
|
|
|
|
<p class="post-body">{{ postDetailStore.post?.content }}</p>
|
|
|
|
<p class="post-body">{{ postDetailStore.post?.content || '' }}</p>
|
|
|
|
<div class="post-stats">
|
|
|
|
<div class="post-stats">
|
|
|
|
<span> 热度 {{ postDetailStore.post?.likeCount ?? 0 }}</span>
|
|
|
|
<span> 热度 {{ postDetailStore.post?.likeCount ?? 0 }}</span>
|
|
|
|
<span> 点赞 {{ postDetailStore.post?.favoriteCount ?? 0 }}</span>
|
|
|
|
<span> 点赞 {{ postDetailStore.post?.favoriteCount ?? 0 }}</span>
|
|
|
@ -34,25 +34,25 @@
|
|
|
|
<li v-for="comment in postDetailStore.comments" :key="comment.id" class="comment-item">
|
|
|
|
<li v-for="comment in postDetailStore.comments" :key="comment.id" class="comment-item">
|
|
|
|
<img :src="comment.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="评论者头像" class="comment-avatar" />
|
|
|
|
<img :src="comment.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="评论者头像" class="comment-avatar" />
|
|
|
|
<div class="comment-content">
|
|
|
|
<div class="comment-content">
|
|
|
|
<p class="comment-name">{{ comment.userName }}</p>
|
|
|
|
<p class="comment-name">{{ comment.userName || '匿名用户' }}</p>
|
|
|
|
<p class="comment-text">{{ comment.content }}</p>
|
|
|
|
<p class="comment-text">{{ comment.content || '' }}</p>
|
|
|
|
<div class="comment-meta">
|
|
|
|
<div class="comment-meta">
|
|
|
|
<span class="comment-time">{{ comment.createTime ? formatTime(comment.createTime) : '' }}</span>
|
|
|
|
<span class="comment-time">{{ comment.createTime ? formatTime(comment.createTime) : '' }}</span>
|
|
|
|
<span class="comment-likes">赞 {{ comment.likeCount ?? 0 }}</span>
|
|
|
|
<span class="comment-likes">赞 {{ comment.likeCount ?? 0 }}</span>
|
|
|
|
<span v-if="comment.replyCount > 0">
|
|
|
|
<span v-if="comment.replyCount > 0">
|
|
|
|
<button @click="loadReplies(comment)">
|
|
|
|
<button @click="loadReplies(comment)">
|
|
|
|
{{ comment.replies.length > 0 ? '收起回复' : '展开回复' }} ({{ comment.replyCount }})
|
|
|
|
{{ comment.replies && comment.replies.length > 0 ? '收起回复' : '展开回复' }} ({{ comment.replyCount }})
|
|
|
|
</button>
|
|
|
|
</button>
|
|
|
|
</span>
|
|
|
|
</span>
|
|
|
|
<button class="reply-btn" @click="startReply(comment)">回复</button>
|
|
|
|
<button class="reply-btn" @click="startReply(comment)">回复</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<!-- 子评论列表 -->
|
|
|
|
<!-- 子评论列表 -->
|
|
|
|
<ul v-if="comment.replies.length > 0" class="replies-list">
|
|
|
|
<ul v-if="comment.replies && comment.replies.length > 0" class="replies-list">
|
|
|
|
<li v-for="reply in comment.replies" :key="reply.id" class="comment-item reply-item">
|
|
|
|
<li v-for="reply in comment.replies" :key="reply.id" class="comment-item reply-item">
|
|
|
|
<img :src="reply.userAvatar" alt="评论者头像" class="comment-avatar" />
|
|
|
|
<img :src="reply.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="评论者头像" class="comment-avatar" />
|
|
|
|
<div class="comment-content">
|
|
|
|
<div class="comment-content">
|
|
|
|
<p class="comment-name">{{ reply.userName }}</p>
|
|
|
|
<p class="comment-name">{{ reply.userName || '匿名用户' }}</p>
|
|
|
|
<p class="comment-text">{{ reply.content }}</p>
|
|
|
|
<p class="comment-text">{{ reply.content || '' }}</p>
|
|
|
|
<div class="comment-meta">
|
|
|
|
<div class="comment-meta">
|
|
|
|
<span class="comment-time">{{ reply.createTime ? formatTime(reply.createTime) : '' }}</span>
|
|
|
|
<span class="comment-time">{{ reply.createTime ? formatTime(reply.createTime) : '' }}</span>
|
|
|
|
<span class="comment-likes">赞 {{ reply.likeCount ?? 0 }}</span>
|
|
|
|
<span class="comment-likes">赞 {{ reply.likeCount ?? 0 }}</span>
|
|
|
@ -71,7 +71,7 @@
|
|
|
|
<!-- 发送评论 -->
|
|
|
|
<!-- 发送评论 -->
|
|
|
|
<div class="comment-box">
|
|
|
|
<div class="comment-box">
|
|
|
|
<div v-if="replyingComment" class="replying-tip">
|
|
|
|
<div v-if="replyingComment" class="replying-tip">
|
|
|
|
正在回复 @{{ replyingComment.userName }}
|
|
|
|
正在回复 @{{ replyingComment.userName || '匿名用户' }}
|
|
|
|
<button class="cancel-reply-btn" @click="cancelReply">取消回复</button>
|
|
|
|
<button class="cancel-reply-btn" @click="cancelReply">取消回复</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<textarea
|
|
|
|
<textarea
|
|
|
@ -85,57 +85,55 @@
|
|
|
|
</template>
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="js" name="PostDetail">
|
|
|
|
<script setup lang="js" name="PostDetail">
|
|
|
|
import { ref, onMounted , onUnmounted} from 'vue';
|
|
|
|
import { ref, computed, onMounted , onUnmounted, watch } from 'vue';
|
|
|
|
import { useRoute } from 'vue-router';
|
|
|
|
import { useRoute } from 'vue-router';
|
|
|
|
import { usePostDetailStore } from '@/stores/postdetail.js';
|
|
|
|
import { usePostDetailStore } from '@/stores/postdetail.js';
|
|
|
|
import { useUserStore } from '@/stores/user.js';
|
|
|
|
import { useUserStore } from '@/stores/user.js';
|
|
|
|
|
|
|
|
|
|
|
|
const route = useRoute();
|
|
|
|
const route = useRoute();
|
|
|
|
const postDetailStore = usePostDetailStore();
|
|
|
|
const postDetailStore = usePostDetailStore();
|
|
|
|
const userStore = useUserStore(); // 如果需要用户信息
|
|
|
|
const userStore = useUserStore();
|
|
|
|
const newComment = ref('');
|
|
|
|
const newComment = ref('');
|
|
|
|
const replyingComment = ref(null); // 当前正在回复的评论对象,null 表示发一级评论
|
|
|
|
const replyingComment = ref(null);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 用计算属性保证 author 总是对象,避免 undefined
|
|
|
|
|
|
|
|
const author = computed(() => postDetailStore.post?.author || {});
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
|
|
|
const author = postDetailStore.post?.author ;
|
|
|
|
|
|
|
|
const isFollowing = ref(false);
|
|
|
|
const isFollowing = ref(false);
|
|
|
|
const toggleFollow = () => {
|
|
|
|
const toggleFollow = () => {
|
|
|
|
isFollowing.value = !isFollowing.value;
|
|
|
|
isFollowing.value = !isFollowing.value;
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 点击“回复”按钮时调用
|
|
|
|
|
|
|
|
function startReply(comment) {
|
|
|
|
function startReply(comment) {
|
|
|
|
replyingComment.value = comment;
|
|
|
|
replyingComment.value = comment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 取消回复
|
|
|
|
|
|
|
|
function cancelReply() {
|
|
|
|
function cancelReply() {
|
|
|
|
replyingComment.value = null;
|
|
|
|
replyingComment.value = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 时间格式化
|
|
|
|
|
|
|
|
function formatTime(timeStr) {
|
|
|
|
function formatTime(timeStr) {
|
|
|
|
const date = new Date(timeStr);
|
|
|
|
const date = new Date(timeStr);
|
|
|
|
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
|
|
|
|
return `${date.getFullYear()}年${date.getMonth() + 1}月${date.getDate()}日 ${date.getHours()}:${date.getMinutes().toString().padStart(2, '0')}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 一级评论滚动加载
|
|
|
|
// 只有 postDetailStore.post 存在且有 postId 时才加载评论
|
|
|
|
function handleScroll() {
|
|
|
|
function handleScroll() {
|
|
|
|
const el = document.documentElement;
|
|
|
|
const el = document.documentElement;
|
|
|
|
if (
|
|
|
|
if (
|
|
|
|
el.scrollTop + window.innerHeight >= el.scrollHeight - 100 &&
|
|
|
|
el.scrollTop + window.innerHeight >= el.scrollHeight - 100 &&
|
|
|
|
!postDetailStore.commentsLoading &&
|
|
|
|
!postDetailStore.commentsLoading &&
|
|
|
|
!postDetailStore.commentsFinished
|
|
|
|
!postDetailStore.commentsFinished &&
|
|
|
|
|
|
|
|
postDetailStore.post &&
|
|
|
|
|
|
|
|
(postDetailStore.post.id || postDetailStore.post.postId)
|
|
|
|
) {
|
|
|
|
) {
|
|
|
|
postDetailStore.fetchComments();
|
|
|
|
postDetailStore.fetchComments();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
// 子评论加载(点击“展开回复”时调用)
|
|
|
|
|
|
|
|
function loadReplies(comment) {
|
|
|
|
function loadReplies(comment) {
|
|
|
|
if (!comment.repliesLoading && !comment.repliesFinished) {
|
|
|
|
if (!comment.repliesLoading && !comment.repliesFinished) {
|
|
|
|
postDetailStore.fetchReplies(comment.id, comment);
|
|
|
|
postDetailStore.fetchReplies(comment.id, comment);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发送评论
|
|
|
|
|
|
|
|
const sendComment = () => {
|
|
|
|
const sendComment = () => {
|
|
|
|
if (!userStore.isLoggedIn) {
|
|
|
|
if (!userStore.isLoggedIn) {
|
|
|
|
alert('请先登录后再评论');
|
|
|
|
alert('请先登录后再评论');
|
|
|
@ -150,8 +148,8 @@ const sendComment = () => {
|
|
|
|
createTime: new Date().toISOString(),
|
|
|
|
createTime: new Date().toISOString(),
|
|
|
|
likeCount: 0,
|
|
|
|
likeCount: 0,
|
|
|
|
replyCount: 0,
|
|
|
|
replyCount: 0,
|
|
|
|
postId: postDetailStore.post.id,
|
|
|
|
postId: postDetailStore.post?.id || postDetailStore.post?.postId,
|
|
|
|
parentCommentId: replyingComment.value ? replyingComment.value.id : null, // 如果是回复评论,则设置 parentCommentId
|
|
|
|
parentCommentId: replyingComment.value ? replyingComment.value.id : null,
|
|
|
|
topId: null,
|
|
|
|
topId: null,
|
|
|
|
isLike: false,
|
|
|
|
isLike: false,
|
|
|
|
}
|
|
|
|
}
|
|
|
@ -160,6 +158,17 @@ const sendComment = () => {
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 监听 postDetailStore.post 加载完成后再允许滚动加载评论
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
|
|
|
() => postDetailStore.post,
|
|
|
|
|
|
|
|
(val) => {
|
|
|
|
|
|
|
|
if (val && (val.id || val.postId)) {
|
|
|
|
|
|
|
|
postDetailStore.fetchComments();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
},
|
|
|
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
onMounted(() => {
|
|
|
|
postDetailStore.fetchPostDetail(route.params.id);
|
|
|
|
postDetailStore.fetchPostDetail(route.params.id);
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
|
|
window.addEventListener('scroll', handleScroll);
|
|
|
|