Merge remote-tracking branch 'origin/main'

# Conflicts:
#	珞珈岛-项目相关文件/luojia-island/service/src/main/java/com/luojia_channel/modules/post/service/impl/CommentServiceImpl.java
main
forely 4 weeks ago
commit a467856a7c

@ -1,21 +1,21 @@
##本地开发环境
lj:
db:
host: 192.168.59.129
password: Forely123!
redis:
host: 192.168.59.129
port: 6379
password: Forely123!
rabbitmq:
host: 192.168.59.129
port: 5672
username: admin
password: Forely123!
minio:
endpoint: http://192.168.59.129:9000
accessKey: forely
secretKey: Forely123!
#lj:
# db:
# host: 192.168.59.129
# password: Forely123!
# redis:
# host: 192.168.59.129
# port: 6379
# password: Forely123!
# rabbitmq:
# host: 192.168.59.129
# port: 5672
# username: admin
# password: Forely123!
# minio:
# endpoint: http://192.168.59.129:9000
# accessKey: forely
# secretKey: Forely123!
#lj:
# db:
@ -34,3 +34,21 @@ lj:
# endpoint: http://192.168.125.128:9000
# accessKey: minio_admin
# secretKey: Minio@1234
lj:
db:
host: localhost
password: 123456
redis:
host: localhost
port: 6379
password: 123456
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
minio:
endpoint: http://localhost:9005
accessKey: leezt
secretKey: lzt264610

@ -1,3 +1,4 @@
USE luojia_channel; -- 指定目标数据库
## 用户表
DROP TABLE IF EXISTS `user`;

@ -92,7 +92,7 @@ const colleges = ref([
]);
//
const defaultAvatar = require("@/assets/default-avatar/boy_4.png");
const defaultAvatar = require("@/assets/default-avatar/boy_1.png");
//
const isLoggedIn = computed(() => userStore.isLoggedIn);

@ -180,17 +180,6 @@ async function sendCode() {
}
//
//function Login1() {
// userStore.login({
// avatar:require('@/assets/default-avatar/boy_1.png'),
// userName: '',
// userid:1
// });
// emit('LoginSuccess');
//}
//
async function login() {
if (!isvalidForm.value) return;

@ -27,9 +27,9 @@
</div>
<p class="post-summary">{{ post.summary }}</p>
<div class="post-stats">
<span>热度 {{ post.likes }}</span>
<span>浏览量 {{ post.viewCount }}</span>
<span>评论 {{ post.comments }}</span>
<span> {{ post.favorites }}</span>
<span> {{ post.likes }}</span>
</div>
</div>
</div>

@ -34,7 +34,7 @@ const routes = [
component: PostDetail
},
{
path: '/user',
path: '/user/:userId',
name: 'UserPage',
component: UserPage,
meta: { requiresAuth: true }

@ -2,6 +2,20 @@ import { defineStore } from "pinia";
import request from "@/utils/request";
import { ElMessage } from "element-plus";
// 递归删除子评论的工具函数
function removeReply(list, commentId) {
for (let i = 0; i < list.length; i++) {
if (list[i].id === commentId) {
list.splice(i, 1);
return true;
}
if (list[i].replies && list[i].replies.length > 0) {
if (removeReply(list[i].replies, commentId)) return true;
}
}
return false;
}
export const usePostDetailStore = defineStore("postDetail", {
state: () => ({
post: null, // 帖子主要信息
@ -16,7 +30,6 @@ export const usePostDetailStore = defineStore("postDetail", {
commentsFinished: false, // 是否加载完全部评论
}),
actions: {
// ...existing code...
async fetchComments() {
if (this.commentsLoading || this.commentsFinished) return;
this.commentsLoading = true;
@ -35,6 +48,7 @@ async fetchComments() {
const comments = (response.data.records || []).map(item => ({
...item,
replies: [],
showReplies: false, // 是否显示子评论
repliesLastVal: Date.now(),
repliesOffset: 0,
repliesSize: 5,
@ -62,21 +76,21 @@ async fetchComments() {
this.commentsLoading = false;
}
},
// ...existing code...
// 获取某条评论的子评论
async fetchReplies(parentCommentId, commentObj) {
if (commentObj.repliesLoading || commentObj.repliesFinished) return;
commentObj.repliesLoading = true;
// 请求子评论数据
const RequestReplyData = {
lastVal: commentObj.repliesLastVal,
offset: commentObj.repliesOffset,
size: commentObj.repliesSize,
postId: this.post?.postId,
parentCommentId: parentCommentId,
};
/// 拼接参数到URL
const params = [
`lastVal=${commentObj.repliesLastVal}`,
`offset=${commentObj.repliesOffset}`,
`size=${commentObj.repliesSize}`,
`postId=${this.post?.postId}`,
`parentCommentId=${parentCommentId}`
].join('&');
const url = `/comment/list/reply?${params}`;
try {
const res = await request.post('/comment/list/reply', RequestReplyData);
const res = await request.get(url);
if (res.code === 200) {
const records = (res.data.records || []).map(item => ({
...item,
@ -106,6 +120,12 @@ async fetchComments() {
async fetchPostDetail(postId) {
this.detailLoading = true;
// 先重置评论相关状态,防止串数据
this.comments = [];
this.lastVal = Date.now();
this.offset = 0;
this.commentsFinished = false;
this.commentsLoading = false;
try {
// 获取帖子详情
const postRes = await request.get('/post/detail', { params: { id: postId } });
@ -169,15 +189,17 @@ async fetchComments() {
async sendComment(newCommentData) {
if (!newCommentData.content || !this.post?.postId) return;
const RequestData = {
id:null,
postId: newCommentData.postId, // 帖子ID
content: newCommentData.content, // 评论内容
parentCommentId: newCommentData.parentCommentId,
};
try {
const res = await request.post('/comment', RequestData);
console.log("发送评论返回:", res.data);
if (res.code === 200) {
const commentObj = {
id: res.data.id,
id: res.data,
content: newCommentData.content,
userId: newCommentData.userId,
userName: newCommentData.userName,
@ -191,6 +213,7 @@ async fetchComments() {
topId: newCommentData.topId,
isLike: 0,
};
console.log("发送评论数据:", commentObj.id);
// 新增评论后刷新评论列表或插入到对应位置
if (!newCommentData.parentCommentId) {
// 一级评论,插入到最前面
@ -202,16 +225,16 @@ async fetchComments() {
commentObj.repliesLoading = false;
commentObj.repliesFinished = false;
this.comments.unshift(commentObj);
this.post.commentCount = (this.post.commentCount || 0) + 1; // 更新帖子评论数
} else {
// 回复,只插入到一级评论的 replies
let parent = this.comments.find(c => c.id === newCommentData.parentCommentId);
let parent = this.comments.find(c => c.id === newCommentData.topId);
if (parent) {
parent.replies.unshift(commentObj);
parent.replies.push(commentObj);
parent.replyCount = (parent.replyCount || 0) + 1;
}
}
console.log("评论成功:", res);
this.post.commentCount = (this.post.commentCount || 0) + 1; // 更新帖子评论数
console.log("评论成功:", commentObj);
}else {
console.error("评论失败:", res);
ElMessage.error(res.message || '评论失败');
@ -220,5 +243,29 @@ async fetchComments() {
alert(e.response?.message || '发送评论失败,请稍后重试');
}
},
// 删除评论或回复
async deleteComment(commentId, parentCommentId = null) {
try {
const res = await request.delete('/comment', { params: { id: commentId} });
if (res.code === 200) {
// 一级评论
if (!parentCommentId) {
this.comments = this.comments.filter(c => c.id !== commentId);
} else {
// 回复评论,递归删除
removeReply(this.comments,commentId);
}
// 更新评论数
if (this.post && this.post.commentCount > 0) {
this.post.commentCount--;
}
ElMessage.success('删除成功');
} else {
ElMessage.error(res.message || '删除失败');
}
} catch (e) {
ElMessage.error(e.response?.message || '删除失败,请稍后重试');
}
},
},
});

@ -52,9 +52,9 @@ export const usePostListStore = defineStore('postList', {
avatar: post.userAvatar || require('@/assets/default-avatar/boy_1.png'),
title: post.title,
summary: post.content ? post.content.slice(0, 40) + (post.content.length > 40 ? '...' : '') : '',
viewCount: post.viewCount || 0,
likes: post.likeCount || 0,
comments: post.commentCount || 0,
favorites: post.favoriteCount || 0,
category: post.category || '全部',
createTime: post.createTime,
userName: post.userName,
@ -70,6 +70,7 @@ export const usePostListStore = defineStore('postList', {
}
} else {
// 处理错误情况
}
} catch (error) {
// 处理异常情况

@ -2,7 +2,13 @@
<div class="post-detail-container">
<!-- 作者信息栏 -->
<div class="author-info" v-if="author && author.userName">
<img :src="author.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="头像" class="author-avatar" />
<img
:src="author.userAvatar || require('@/assets/default-avatar/boy_1.png')"
alt="头像"
class="author-avatar"
@click="goUserHome(author.userId)"
style="cursor: pointer;"
/>
<div class="author-details">
<h3 class="author-name">{{ author.userName || '匿名用户' }}</h3>
<p class="author-stats">粉丝数{{ author.followers ?? 0 }}</p>
@ -18,8 +24,8 @@
<h1 class="post-title">{{ postDetailStore.post?.title || '' }}</h1>
<p class="post-body">{{ postDetailStore.post?.content || '' }}</p>
<div class="post-stats">
<span> 热度 {{ postDetailStore.post?.likeCount ?? 0 }}</span>
<span> 点赞 {{ postDetailStore.post?.favoriteCount ?? 0 }}</span>
<span> 浏览量 {{ postDetailStore.post?.viewCount ?? 0 }}</span>
<span> 点赞 {{ postDetailStore.post?.likeCount ?? 0 }}</span>
<span> 评论 {{ postDetailStore.post?.commentCount ?? 0 }}</span>
</div>
<div class="post-time">
@ -32,7 +38,13 @@
<h2 class="comments-title">评论</h2>
<ul class="comments-list">
<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"
@click="goUserHome(comment.userId)"
style="cursor: pointer;"
/>
<div class="comment-content">
<p class="comment-name">{{ comment.userName || '匿名用户' }}</p>
<p class="comment-text">{{ comment.content || '' }}</p>
@ -41,15 +53,26 @@
<span class="comment-likes"> {{ comment.likeCount ?? 0 }}</span>
<span v-if="comment.replyCount > 0">
<button @click="loadReplies(comment)">
{{ comment.replies && comment.replies.length > 0 ? '收起回复' : '展开回复' }} ({{ comment.replyCount }})
{{ comment.showReplies> 0 ? '收起回复' : '展开回复' }} ({{ comment.replyCount }})
</button>
</span>
<button class="reply-btn" @click="startReply(comment)"></button>
<button
class="delete-btn"
v-if="String(comment.userId) === String(userStore.userInfo?.userid)"
@click="handleDelete(comment)"
>删除</button>
</div>
<!-- 子评论列表 -->
<ul v-if="comment.replies && comment.replies.length > 0" class="replies-list">
<ul v-if="comment.showReplies && comment.replies && comment.replies.length > 0" class="replies-list">
<li v-for="reply in comment.replies" :key="reply.id" class="comment-item reply-item">
<img :src="reply.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="评论者头像" class="comment-avatar" />
<img
:src="reply.userAvatar || require('@/assets/default-avatar/boy_1.png')"
alt="评论者头像"
class="comment-avatar"
@click="goUserHome(reply.userId)"
style="cursor: pointer;"
/>
<div class="comment-content">
<p class="comment-name">
{{ reply.userName || '匿名用户' }}
@ -60,6 +83,11 @@
<span class="comment-time">{{ reply.createTime ? formatTime(reply.createTime) : '' }}</span>
<span class="comment-likes"> {{ reply.likeCount ?? 0 }}</span>
<button class="reply-btn" @click="startReply(reply)"></button>
<button
class="delete-btn"
v-if="String(reply.userId) === String(userStore.userInfo?.userid)"
@click="handleDelete(reply, comment.id)"
>删除</button>
</div>
</div>
</li>
@ -89,10 +117,12 @@
<script setup lang="js" name="PostDetail">
import { ref, computed, onMounted , onUnmounted, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useRoute,useRouter } from 'vue-router';
import { ElMessageBox } from 'element-plus';
import { usePostDetailStore } from '@/stores/postdetail.js';
import { useUserStore } from '@/stores/user.js';
const router = useRouter();
const route = useRoute();
const postDetailStore = usePostDetailStore();
const userStore = useUserStore();
@ -106,7 +136,9 @@ const isFollowing = ref(false);
const toggleFollow = () => {
isFollowing.value = !isFollowing.value;
};
const goUserHome = (userId) => {
if (userId) router.push({name: 'UserPage',params: { userId: userId }})
}
function startReply(comment) {
replyingComment.value = comment;
}
@ -132,7 +164,14 @@ function handleScroll() {
}
}
function loadReplies(comment) {
if (!comment.repliesLoading && !comment.repliesFinished) {
//
if (comment.showReplies) {
comment.showReplies = false;
return;
}
//
comment.showReplies = true;
if (!comment.repliesLoading && !comment.repliesFinished && comment.replies.length === 0) {
postDetailStore.fetchReplies(comment.id, comment);
}
}
@ -157,11 +196,24 @@ const sendComment = () => {
topId: replyingComment.value ? replyingComment.value.topId : null,
isLike: false,
}
postDetailStore.addComment(newCommentData);
postDetailStore.sendComment(newCommentData);
newComment.value = '';
}
};
function handleDelete(comment, parentCommentId = null) {
ElMessageBox.confirm(
'确定要删除这条评论吗?',
'提示',
{
confirmButtonText: '删除',
cancelButtonText: '取消',
type: 'warning',
}
).then(() => {
postDetailStore.deleteComment(comment.id, parentCommentId);
}).catch(() => {});
}
// postDetailStore.post
watch(
() => postDetailStore.post,
@ -477,4 +529,15 @@ onUnmounted(() => {
.reply-btn:hover {
text-decoration: underline;
}
.delete-btn {
background: none;
border: none;
color: #f56c6c;
cursor: pointer;
font-size: 13px;
margin-left: 10px;
}
.delete-btn:hover {
text-decoration: underline;
}
</style>

@ -103,9 +103,10 @@
</div>
<p class="post-summary">{{ post.summary }}</p>
<div class="post-stats">
<span><i class="icon-view"></i> {{ post.viewCount || 0 }}</span>
<span><i class="icon-like"></i> {{ post.likeCount || 0 }}</span>
<span><i class="icon-comment"></i> {{ post.commentCount || 0 }}</span>
<span><i class="icon-view"></i> 热度 {{ post.viewCount || 0 }}</span>
<span><i class="icon-comment"></i> 评论 {{ post.commentCount || 0 }}</span>
<span><i class="icon-like"></i> {{ post.likeCount || 0 }}</span>
<span v-if="isCurrentUser" class="delete-btn" @click.stop="deletePost(post.id)"></span>
</div>
</div>
</div>
@ -213,7 +214,7 @@ const privacySettings = ref({
const recommendations = ref([]);
//
const defaultAvatar = require('@/assets/default-avatar/boy_4.png');
const defaultAvatar = require('@/assets/default-avatar/boy_1.png');
//
const handleAvatarError = "this.onerror=null;this.src='" + defaultAvatar + "'";
@ -306,6 +307,26 @@ const loadUserPosts = async () => {
}
};
//
const deletePost = async (postId) => {
if (!postId) return;
if (!confirm('确定要删除这条帖子吗?')) return;
try {
const res = await request.delete('/post', { params: { id: postId } });
if (res && res.code === 200) {
//
userPosts.value = userPosts.value.filter(post => post.id !== postId);
ElMessage.success('删除成功');
} else {
ElMessage.error(res.message || '删除失败');
}
} catch (e) {
ElMessage.error(e.response?.message || '删除失败');
}
};
//
const loadMorePosts = () => {
loadUserPosts();
@ -752,4 +773,16 @@ onUnmounted(() => {
color: #999;
margin-top: 4px;
}
.delete-btn {
right: 16px;
bottom: 12px;
color: #fff;
background: #f56c6c;
border: none;
border-radius: 4px;
padding: 4px 12px;
cursor: pointer;
font-size: 12px;
}
</style>
Loading…
Cancel
Save