论坛,聊天室前后端对接
Change-Id: I90740329ab40dc050e8a791a382ab187900d673a
diff --git a/src/views/forum/ForumTopicView.vue b/src/views/forum/ForumTopicView.vue
index e65b297..170e313 100644
--- a/src/views/forum/ForumTopicView.vue
+++ b/src/views/forum/ForumTopicView.vue
@@ -1,5 +1,6 @@
<template>
<div class="topic-detail-page">
+ <Navbar />
<div class="page-container">
<!-- 面包屑导航 -->
<div class="breadcrumb">
@@ -41,7 +42,7 @@
<el-avatar :size="32">{{ topic.author.charAt(0) }}</el-avatar>
<div class="author-details">
<span class="author-name">{{ topic.author }}</span>
- <span class="post-time">发表于 {{ formatDateTime(topic.createTime) }}</span>
+
</div>
</div>
@@ -73,9 +74,6 @@
</el-button>
<template #dropdown>
<el-dropdown-menu>
- <el-dropdown-item command="favorite">
- {{ isFavorited ? '取消收藏' : '收藏主题' }}
- </el-dropdown-item>
<el-dropdown-item command="share">分享主题</el-dropdown-item>
<el-dropdown-item command="report" divided>举报主题</el-dropdown-item>
</el-dropdown-menu>
@@ -101,9 +99,7 @@
</div>
</div>
</div>
- <div class="post-time">
- {{ formatDateTime(topic.createTime) }}
- </div>
+
</div>
<div class="post-content">
@@ -111,10 +107,6 @@
</div>
<div class="post-actions">
- <el-button type="text" size="small" @click="likePost(topic.id)">
- <el-icon><Like /></el-icon>
- {{ topic.likes || 0 }}
- </el-button>
<el-button type="text" size="small" @click="quotePost(topic)">
<el-icon><ChatDotRound /></el-icon>
引用
@@ -145,16 +137,14 @@
</div>
</div>
</div>
- <div class="post-time">
- {{ formatDateTime(reply.createTime) }}
- </div>
+
</div>
<div class="post-content">
<div v-if="reply.quotedPost" class="quoted-content">
<div class="quote-header">
<el-icon><ChatDotRound /></el-icon>
- <span>{{ reply.quotedPost.author }} 发表于 {{ formatDateTime(reply.quotedPost.time) }}</span>
+ <span>{{ reply.quotedPost.author }}</span>
</div>
<div class="quote-text">{{ reply.quotedPost.content }}</div>
</div>
@@ -162,10 +152,6 @@
</div>
<div class="post-actions">
- <el-button type="text" size="small" @click="likePost(reply.id)">
- <el-icon><Like /></el-icon>
- {{ reply.likes || 0 }}
- </el-button>
<el-button type="text" size="small" @click="quotePost(reply)">
<el-icon><ChatDotRound /></el-icon>
引用
@@ -262,127 +248,131 @@
import { ref, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
+import axios from 'axios'
import {
Edit,
More,
View,
Comment,
- Like,
ChatDotRound,
Flag,
ArrowDown
} from '@element-plus/icons-vue'
+import Navbar from "@/components/Navbar.vue";
export default {
name: 'ForumTopicView',
+ components: {Navbar},
setup() {
const route = useRoute()
const router = useRouter()
const replyFormRef = ref(null)
-
+
const showReplyDialog = ref(false)
const submittingReply = ref(false)
- const isFavorited = ref(false)
+ const loading = ref(false)
const currentPage = ref(1)
const pageSize = ref(20)
const totalReplies = ref(0)
const quickReplyContent = ref('')
const quotedContent = ref(null)
-
+
const topic = ref({
- id: 1,
- title: '2024年度最佳PT站点推荐与对比分析',
- sectionId: 1,
- sectionName: '站务讨论',
- author: 'PTExpert',
- authorTitle: '资深会员',
- authorPosts: 1256,
- authorReputation: 2890,
- createTime: '2025-06-01T10:30:00',
- content: `
- <p>大家好,作为一个使用PT站点多年的老用户,我想和大家分享一下2024年各大PT站点的使用体验和对比分析。</p>
-
- <h3>评测标准</h3>
- <ul>
- <li>资源丰富度:种子数量、更新速度、稀有资源</li>
- <li>用户体验:界面设计、功能完善度、响应速度</li>
- <li>社区氛围:用户活跃度、互帮互助程度</li>
- <li>规则友好性:考核难度、分享率要求、保种要求</li>
- </ul>
-
- <h3>推荐站点</h3>
- <p>经过综合评测,以下几个站点值得推荐:</p>
- <ol>
- <li><strong>站点A</strong>:资源最全,更新最快,适合影视爱好者</li>
- <li><strong>站点B</strong>:音乐资源丰富,无损居多,音质发烧友首选</li>
- <li><strong>站点C</strong>:软件资源全面,更新及时,开发者必备</li>
- </ol>
-
- <p>具体的详细评测报告我会在后续回复中逐一介绍,欢迎大家讨论和补充!</p>
- `,
- views: 2856,
- replies: 147,
- likes: 89,
- tags: ['PT站点', '推荐', '对比'],
- pinned: true,
- hot: true,
+ id: null,
+ title: '',
+ sectionId: null,
+ sectionName: '',
+ author: '',
+ authorTitle: '',
+ authorPosts: 0,
+ authorReputation: 0,
+ createTime: '',
+ content: '',
+ views: 0,
+ replies: 0,
+ likes: 0,
+ tags: [],
+ pinned: false,
+ hot: false,
closed: false
})
-
- const replies = ref([
- {
- id: 2,
- author: 'MovieLover88',
- authorTitle: '影视达人',
- authorPosts: 567,
- authorReputation: 1234,
- createTime: '2025-06-01T11:15:00',
- content: '感谢楼主的详细分析!特别期待站点A的详细评测,最近正在寻找好的影视资源站点。',
- likes: 12
- },
- {
- id: 3,
- author: 'TechGuru',
- authorTitle: '技术专家',
- authorPosts: 890,
- authorReputation: 2156,
- createTime: '2025-06-01T12:30:00',
- content: '站点C确实不错,软件资源很全面。不过楼主能不能也评测一下游戏类的PT站点?',
- likes: 8,
- quotedPost: {
- author: 'PTExpert',
- time: '2025-06-01T10:30:00',
- content: '站点C:软件资源全面,更新及时,开发者必备'
- }
- }
- ])
-
+
+ const replies = ref([])
+
const replyForm = reactive({
content: ''
})
-
+
const replyRules = {
content: [
{ required: true, message: '请输入回复内容', trigger: 'blur' },
{ min: 5, max: 5000, message: '内容长度在 5 到 5000 个字符', trigger: 'blur' }
]
}
-
+
+ // 初始化数据
onMounted(() => {
const topicId = route.params.id
fetchTopicDetail(topicId)
+ fetchReplies(topicId)
})
-
- const fetchTopicDetail = async (id) => {
+
+ // 获取主题详情
+ const fetchTopicDetail = async (topicId) => {
+ loading.value = true
+ console.log(`[fetchTopicDetail] 请求主题详情: /api/posts/${topicId}`)
try {
- console.log('获取主题详情:', id)
- totalReplies.value = 147
+ const response = await axios.get(`/api/posts/${topicId}`)
+ console.log('[fetchTopicDetail] 响应数据:', response.data)
+ topic.value = {
+ ...response.data,
+ tags: response.data.tags ? response.data.tags.split(',') : [],
+ authorTitle: response.data.authorTitle || '会员'
+ }
} catch (error) {
+ console.error('[fetchTopicDetail] 请求失败:', error)
ElMessage.error('获取主题详情失败')
router.back()
+ } finally {
+ loading.value = false
}
}
-
+
+
+ // 获取主题的所有回复(平铺结构)
+ const fetchReplies = async (topicId) => {
+ loading.value = true
+ const url = `/api/posts/topic/${topicId}`
+ const params = { page: currentPage.value, size: pageSize.value }
+ console.log(`[fetchReplies] 请求回复列表: ${url}`, params)
+ try {
+ const response = await axios.get(url, { params })
+ console.log('[fetchReplies] 响应数据:', response.data)
+ replies.value = response.data.posts.map(post => ({
+ id: post.id,
+ author: post.author,
+ authorTitle: post.authorTitle || '会员',
+ authorPosts: post.authorPosts || 0,
+ authorReputation: post.authorReputation || 0,
+ createTime: post.createTime,
+ content: post.content,
+ likes: post.likes || 0,
+ quotedPost: post.quotedPost ? {
+ author: post.quotedPost.author,
+ time: post.quotedPost.time,
+ content: post.quotedPost.content
+ } : null
+ }))
+ totalReplies.value = response.data.total
+ } catch (error) {
+ console.error('[fetchReplies] 请求失败:', error)
+ ElMessage.error('获取回复列表失败')
+ } finally {
+ loading.value = false
+ }
+ }
+
+
const formatDateTime = (dateString) => {
const date = new Date(dateString)
return date.toLocaleString('zh-CN', {
@@ -393,17 +383,13 @@
minute: '2-digit'
})
}
-
+
const formatContent = (content) => {
return content.replace(/\n/g, '<br>')
}
-
+
const handleTopicAction = (command) => {
switch (command) {
- case 'favorite':
- isFavorited.value = !isFavorited.value
- ElMessage.success(isFavorited.value ? '已收藏' : '已取消收藏')
- break
case 'share':
navigator.clipboard.writeText(window.location.href)
ElMessage.success('链接已复制到剪贴板')
@@ -413,19 +399,9 @@
break
}
}
-
- const likePost = (postId) => {
- if (postId === topic.value.id) {
- topic.value.likes = (topic.value.likes || 0) + 1
- } else {
- const reply = replies.value.find(r => r.id === postId)
- if (reply) {
- reply.likes = (reply.likes || 0) + 1
- }
- }
- ElMessage.success('点赞成功')
- }
-
+
+
+
const quotePost = (post) => {
quotedContent.value = {
author: post.author,
@@ -434,36 +410,36 @@
}
showReplyDialog.value = true
}
-
+
const reportPost = async (postId) => {
try {
- await ElMessageBox.prompt('请说明举报原因', '举报内容', {
+ const { value } = await ElMessageBox.prompt('请说明举报原因', '举报内容', {
confirmButtonText: '提交举报',
cancelButtonText: '取消',
inputType: 'textarea',
inputPlaceholder: '请详细说明举报原因...'
})
-
+ // 假设有举报API,实际需根据后端实现
ElMessage.success('举报已提交,我们会尽快处理')
} catch {
// 用户取消
}
}
-
+
const clearQuote = () => {
quotedContent.value = null
}
-
+
const handleCloseReplyDialog = () => {
if (replyForm.content) {
ElMessageBox.confirm(
- '确定要关闭吗?未保存的内容将会丢失。',
- '提示',
- {
- confirmButtonText: '确定',
- cancelButtonText: '取消',
- type: 'warning'
- }
+ '确定要关闭吗?未保存的内容将会丢失。',
+ '提示',
+ {
+ confirmButtonText: '确定',
+ cancelButtonText: '取消',
+ type: 'warning'
+ }
).then(() => {
resetReplyForm()
showReplyDialog.value = false
@@ -475,66 +451,76 @@
showReplyDialog.value = false
}
}
-
+
const submitReply = async () => {
try {
await replyFormRef.value?.validate()
-
submittingReply.value = true
-
- await new Promise(resolve => setTimeout(resolve, 1500))
-
- const newReply = {
- id: Date.now(),
- author: localStorage.getItem('username') || '用户',
- authorTitle: '会员',
- authorPosts: 0,
- authorReputation: 0,
- createTime: new Date().toISOString(),
+
+ const postData = {
+ topicId: topic.value.id,
content: replyForm.content,
- likes: 0,
- quotedPost: quotedContent.value
+ quotedPost: quotedContent.value ? {
+ author: quotedContent.value.author,
+ time: quotedContent.value.time,
+ content: quotedContent.value.content
+ } : null,
+ author: localStorage.getItem('username') || '用户'
}
-
- replies.value.push(newReply)
+
+ const response = await axios.post('/api/posts', postData)
+ replies.value.push({
+ id: response.data.id,
+ author: response.data.author,
+ authorTitle: response.data.authorTitle || '会员',
+ authorPosts: response.data.authorPosts || 0,
+ authorReputation: response.data.authorReputation || 0,
+ createTime: response.data.createTime,
+ content: response.data.content,
+ likes: response.data.likes || 0,
+ quotedPost: response.data.quotedPost
+ })
topic.value.replies += 1
-
+
ElMessage.success('回复发表成功!')
resetReplyForm()
showReplyDialog.value = false
-
} catch (error) {
- console.error('表单验证失败:', error)
+ console.error('表单验证或提交失败:', error)
+ ElMessage.error('发表回复失败')
} finally {
submittingReply.value = false
}
}
-
+
const submitQuickReply = async () => {
if (!quickReplyContent.value.trim()) {
ElMessage.warning('请输入回复内容')
return
}
-
+
submittingReply.value = true
try {
- await new Promise(resolve => setTimeout(resolve, 1000))
-
- const newReply = {
- id: Date.now(),
- author: localStorage.getItem('username') || '用户',
- authorTitle: '会员',
- authorPosts: 0,
- authorReputation: 0,
- createTime: new Date().toISOString(),
+ const postData = {
+ topicId: topic.value.id,
content: quickReplyContent.value,
- likes: 0
+ author: localStorage.getItem('username') || '用户'
}
-
- replies.value.push(newReply)
+
+ const response = await axios.post('/api/posts', postData)
+ replies.value.push({
+ id: response.data.id,
+ author: response.data.author,
+ authorTitle: response.data.authorTitle || '会员',
+ authorPosts: response.data.authorPosts || 0,
+ authorReputation: response.data.authorReputation || 0,
+ createTime: response.data.createTime,
+ content: response.data.content,
+ likes: response.data.likes || 0
+ })
topic.value.replies += 1
quickReplyContent.value = ''
-
+
ElMessage.success('回复发表成功!')
} catch (error) {
ElMessage.error('发表回复失败')
@@ -542,30 +528,32 @@
submittingReply.value = false
}
}
-
+
const clearQuickReply = () => {
quickReplyContent.value = ''
}
-
+
const resetReplyForm = () => {
replyFormRef.value?.resetFields()
replyForm.content = ''
quotedContent.value = null
}
-
+
const handleSizeChange = (size) => {
pageSize.value = size
currentPage.value = 1
+ fetchReplies(topic.value.id)
}
-
+
const handleCurrentChange = (page) => {
currentPage.value = page
+ fetchReplies(topic.value.id)
}
-
+
return {
showReplyDialog,
submittingReply,
- isFavorited,
+ loading,
currentPage,
pageSize,
totalReplies,
@@ -579,7 +567,6 @@
formatDateTime,
formatContent,
handleTopicAction,
- likePost,
quotePost,
reportPost,
clearQuote,
@@ -593,7 +580,6 @@
More,
View,
Comment,
- Like,
ChatDotRound,
Flag,
ArrowDown