论坛,聊天室前后端对接

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