feat(api): 重构 API 调用并优化用户认证流程

- 新增 auth、forum 和 user API 文件夹,重新组织 API 调用结构
- 重构 AuthContext,使用新 API 进行用户认证和信息管理
- 更新 AdminPanel、ForumPage 等组件,使用新的 API 调用
- 删除旧的 authApi.js 文件,清理冗余代码
- 优化用户登录、注册和登出流程,改进错误处理和用户提示

Change-Id: If664193e1bf30036c197f164edc5b10df75f1331
diff --git a/src/features/forum/pages/ForumPage.jsx b/src/features/forum/pages/ForumPage.jsx
index 7e6e78f..54638fc 100644
--- a/src/features/forum/pages/ForumPage.jsx
+++ b/src/features/forum/pages/ForumPage.jsx
@@ -1,6 +1,20 @@
-import React, { useState, useEffect } from 'react';
-import { List, Avatar, Space, Tag, Typography, Button, message, Modal, Form, Input, Spin } from 'antd';
-import { getPosts, createPost } from '../services/forumApi';
+import React, { useState, useEffect } from "react";
+import { Link } from "react-router-dom";
+import {
+  List,
+  Avatar,
+  Space,
+  Tag,
+  Typography,
+  Button,
+  message,
+  Modal,
+  Form,
+  Input,
+  Spin,
+} from "antd";
+import { getPosts, createPost } from "@/api/forum";
+import { useAuth } from "@/features/auth/contexts/AuthContext";
 
 const { Title, Paragraph, Text } = Typography;
 const { TextArea } = Input;
@@ -10,68 +24,125 @@
   const [loading, setLoading] = useState(true);
   const [isModalOpen, setIsModalOpen] = useState(false);
   const [form] = Form.useForm();
-  
-  // 获取用户信息
-  const user = JSON.parse(localStorage.getItem('user') || '{}');
-  
+
+  // 使用 AuthProvider 获取用户信息
+  const { user, isAuthenticated } = useAuth();
+
   // 加载帖子数据
   useEffect(() => {
-    fetchPosts();
-  }, []);
-  
+    // 只有在用户已认证且有用户信息时才获取帖子
+    if (isAuthenticated && user?.username) {
+      fetchPosts();
+    }
+  }, [isAuthenticated, user]);
+
   // 获取帖子列表
   const fetchPosts = async () => {
     try {
       setLoading(true);
+      console.log("正在获取帖子列表,用户名:", user.username);
       const response = await getPosts({ username: user.username });
-      if (response.success) {
-        setPosts(response.data.posts || []);
+      console.log("获取帖子列表响应:", response);
+
+      if (response) {
+        const posts = response.data?.posts || [];
+        console.log("获取到的帖子数量:", posts.length);
+        console.log("帖子数据结构:", posts[0]); // 查看第一个帖子的数据结构
+        setPosts(posts);
+      } else {
+        console.error("获取帖子列表失败:", response.data);
+        message.error(response.data?.message || "获取帖子列表失败");
       }
     } catch (error) {
-      message.error(error.message || '获取帖子列表失败');
+      message.error(error.message || "获取帖子列表失败");
     } finally {
       setLoading(false);
     }
   };
-  
+
   // 显示新建帖子对话框
   const showModal = () => {
     setIsModalOpen(true);
   };
-  
+
   // 关闭对话框
   const handleCancel = () => {
     setIsModalOpen(false);
     form.resetFields();
   };
-  
+
   // 提交新帖子
   const handleSubmit = async () => {
     try {
-      const values = await form.validateFields();
-      
+      const params = await form.validateFields();
+
       // 添加作者信息
-      values.author = user.username;
-      
-      const response = await createPost(values);
-      if (response.success) {
-        message.success('帖子发布成功');
+      params.author = user.username;
+      console.log("提交的帖子数据:", params);
+
+      const response = await createPost(params);
+      if (response.message === "Post created successfully") {
+        message.success("帖子发布成功");
         setIsModalOpen(false);
         form.resetFields();
         fetchPosts(); // 重新加载帖子列表
+      } else {
+        message.error(response.message || "发布帖子失败");
       }
     } catch (error) {
-      message.error(error.message || '发布帖子失败');
+      console.error("发布帖子失败:", error);
+      message.error(error.message || "发布帖子失败");
     }
   };
 
+  // 如果用户未认证,显示提示信息
+  if (!isAuthenticated) {
+    return (
+      <div className="text-center py-8">
+        <Title level={3}>请先登录</Title>
+        <Paragraph>您需要登录后才能查看论坛内容</Paragraph>
+      </div>
+    );
+  }
+
   return (
     <div className="space-y-6">
       <Title level={2}>社区论坛</Title>
       <Paragraph className="text-slate-500">
         欢迎来到我们的社区论坛,这里是会员交流分享的地方。
       </Paragraph>
-      
+      <div className="text-center mt-4">
+        <Button type="primary" onClick={showModal}>
+          发布新主题
+        </Button>
+      </div>
+
+      {/* 新建帖子对话框 */}
+      <Modal
+        title="发布新主题"
+        open={isModalOpen}
+        onOk={handleSubmit}
+        onCancel={handleCancel}
+        okText="发布"
+        cancelText="取消"
+      >
+        <Form form={form} layout="vertical">
+          <Form.Item
+            name="title"
+            label="标题"
+            rules={[{ required: true, message: "请输入标题" }]}
+          >
+            <Input placeholder="请输入标题" />
+          </Form.Item>
+          <Form.Item
+            name="content"
+            label="内容"
+            rules={[{ required: true, message: "请输入帖子内容" }]}
+          >
+            <TextArea rows={6} placeholder="请输入帖子内容" />
+          </Form.Item>
+        </Form>
+      </Modal>
       {loading ? (
         <div className="flex justify-center py-8">
           <Spin size="large" tip="加载中..." />
@@ -86,15 +157,24 @@
               key={item.id}
               extra={
                 <Space>
-                  <Tag color="green">浏览: {item.views || 0}</Tag>
-                  <Tag color="blue">点赞: {item.likes || 0}</Tag>
-                  <Text type="secondary">{item.createTime}</Text>
+                  <Text type="secondary">{item.publishDate}</Text>
                 </Space>
               }
             >
               <List.Item.Meta
-                avatar={<Avatar src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${item.author}`} />}
-                title={<a href={`/post/${item.id}`}>{item.title}</a>}
+                avatar={
+                  <Avatar
+                    src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${item.author}`}
+                  />
+                }
+                title={
+                  <Link 
+                    to={`/post/${item.pid}`}
+                    className="text-blue-600 hover:text-blue-800 hover:underline"
+                  >
+                    {item.title}
+                  </Link>
+                }
                 description={<Text type="secondary">作者: {item.author}</Text>}
               />
               <Paragraph ellipsis={{ rows: 2 }}>{item.content}</Paragraph>
@@ -102,42 +182,8 @@
           )}
         />
       )}
-      
-      <div className="text-center mt-4">
-        <Button type="primary" onClick={showModal}>发布新主题</Button>
-      </div>
-      
-      {/* 新建帖子对话框 */}
-      <Modal
-        title="发布新主题"
-        open={isModalOpen}
-        onOk={handleSubmit}
-        onCancel={handleCancel}
-        okText="发布"
-        cancelText="取消"
-      >
-        <Form
-          form={form}
-          layout="vertical"
-        >
-          <Form.Item
-            name="title"
-            label="标题"
-            rules={[{ required: true, message: '请输入标题' }]}
-          >
-            <Input placeholder="请输入标题" />
-          </Form.Item>
-          <Form.Item
-            name="content"
-            label="内容"
-            rules={[{ required: true, message: '请输入帖子内容' }]}
-          >
-            <TextArea rows={6} placeholder="请输入帖子内容" />
-          </Form.Item>
-        </Form>
-      </Modal>
     </div>
   );
 };
 
-export default ForumPage; 
\ No newline at end of file
+export default ForumPage;
diff --git a/src/features/forum/pages/PostDetailPage.jsx b/src/features/forum/pages/PostDetailPage.jsx
new file mode 100644
index 0000000..5e28820
--- /dev/null
+++ b/src/features/forum/pages/PostDetailPage.jsx
@@ -0,0 +1,407 @@
+import React, { useState, useEffect } from 'react';
+import { useParams, useNavigate } from 'react-router-dom';
+import {
+  Card,
+  Avatar,
+  Typography,
+  Divider,
+  List,
+  Form,
+  Input,
+  Button,
+  message,
+  Spin,
+  Space,
+  Tag,
+  Empty
+} from 'antd';
+import { ArrowLeftOutlined, MessageOutlined, UserOutlined, CommentOutlined } from '@ant-design/icons';
+import { getComments, addComment } from '@/api/forum';
+import { useAuth } from '@/features/auth/contexts/AuthContext';
+
+const { Title, Paragraph, Text } = Typography;
+const { TextArea } = Input;
+
+const PostDetailPage = () => {
+  const { postId } = useParams();
+  const navigate = useNavigate();
+  const { user, isAuthenticated } = useAuth();
+  const [loading, setLoading] = useState(true);
+  const [commenting, setCommenting] = useState(false);
+  const [postContent, setPostContent] = useState('');
+  const [comments, setComments] = useState([]);
+  const [form] = Form.useForm();
+  const [replyForms] = Form.useForm(); // 用于回复的表单
+  const [replyingTo, setReplyingTo] = useState(null); // 当前正在回复的评论ID
+  const [replying, setReplying] = useState(false); // 回复中状态
+
+  // 获取帖子详情和评论
+  useEffect(() => {
+    if (isAuthenticated && user?.username && postId) {
+      fetchPostAndComments();
+    }
+  }, [isAuthenticated, user, postId]);
+
+  // 监听ESC键取消回复
+  useEffect(() => {
+    const handleKeyDown = (event) => {
+      if (event.key === 'Escape' && replyingTo) {
+        cancelReply();
+      }
+    };
+
+    document.addEventListener('keydown', handleKeyDown);
+    return () => {
+      document.removeEventListener('keydown', handleKeyDown);
+    };
+  }, [replyingTo]);
+
+  const fetchPostAndComments = async () => {
+    try {
+      setLoading(true);
+      
+      const params = {
+        postId: postId,
+        username: user.username
+      }
+      const response = await getComments(params);
+
+      if (response && response.data) {
+        setPostContent(response.data.content || '');
+        // 直接按发布时间排序,最新的在前面
+        const allComments = response.data.comments || [];
+        const sortedComments = allComments.sort((a, b) => 
+          new Date(b.publishDate) - new Date(a.publishDate)
+        );
+        setComments(sortedComments);
+      } else {
+        message.error('获取帖子详情失败');
+      }
+    } catch (error) {
+      console.error('获取帖子详情失败:', error);
+      message.error(error.message || '获取帖子详情失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 提交主评论
+  const handleSubmitComment = async () => {
+    try {
+      const values = await form.validateFields();
+      setCommenting(true);
+
+      const params = {
+        content: values.comment,
+        username: user.username,
+        postId: postId
+      };
+
+      console.log('提交评论数据:', params);
+      const response = await addComment(params);
+      
+      if (response && response.message === 'Comment added successfully') {
+        message.success('评论发布成功');
+        form.resetFields();
+        fetchPostAndComments();
+      } else {
+        message.error('评论发布失败');
+      }
+    } catch (error) {
+      console.error('发布评论失败:', error);
+      message.error(error.message || '发布评论失败');
+    } finally {
+      setCommenting(false);
+    }
+  };
+
+  // 提交回复评论
+  const handleSubmitReply = async (reviewerId) => {
+    try {
+      const values = await replyForms.validateFields();
+      setReplying(true);
+
+      const replyData = {
+        content: values.reply,
+        username: user.username,
+        postId: postId,
+        reviewer: reviewerId
+      };
+
+      console.log('提交回复数据:', replyData);
+      const response = await addComment(replyData);
+      
+      if (response && response.message === 'Comment added successfully') {
+        message.success('回复发布成功');
+        replyForms.resetFields();
+        setReplyingTo(null);
+        fetchPostAndComments();
+      } else {
+        message.error('回复发布失败');
+      }
+    } catch (error) {
+      console.error('发布回复失败:', error);
+      message.error(error.message || '发布回复失败');
+    } finally {
+      setReplying(false);
+    }
+  };
+
+  // 开始回复评论
+  const startReply = (commentId) => {
+    setReplyingTo(commentId);
+    replyForms.resetFields();
+  };
+
+  // 取消回复
+  const cancelReply = () => {
+    setReplyingTo(null);
+    replyForms.resetFields();
+  };
+
+  // 格式化日期
+  const formatDate = (dateString) => {
+    try {
+      return new Date(dateString).toLocaleString('zh-CN', {
+        year: 'numeric',
+        month: '2-digit',
+        day: '2-digit',
+        hour: '2-digit',
+        minute: '2-digit'
+      });
+    } catch {
+      return dateString;
+    }
+  };
+
+  // 查找被回复的评论
+  const getReviewedComment = (reviewerId) => {
+    return comments.find(comment => comment.commentId === reviewerId);
+  };
+
+  // 如果用户未认证
+  if (!isAuthenticated) {
+    return (
+      <div className="text-center py-8">
+        <Title level={3}>请先登录</Title>
+        <Paragraph>您需要登录后才能查看帖子详情</Paragraph>
+      </div>
+    );
+  }
+
+  return (
+    <div className="max-w-4xl mx-auto space-y-6">
+      {/* 返回按钮 */}
+      <Button 
+        icon={<ArrowLeftOutlined />} 
+        onClick={() => navigate('/forum')}
+        className="mb-4"
+      >
+        返回论坛
+      </Button>
+
+      {loading ? (
+        <div className="flex justify-center py-8">
+          <Spin size="large" tip="加载中..." />
+        </div>
+      ) : (
+        <>
+          {/* 帖子内容 */}
+          <Card title={
+            <Space>
+              <MessageOutlined />
+              <span>帖子详情</span>
+            </Space>
+          }>
+            <div className="mb-4">
+              <Title level={4}>帖子内容</Title>
+              <Paragraph style={{ fontSize: '16px', lineHeight: 1.6 }}>
+                {postContent || '暂无内容'}
+              </Paragraph>
+            </div>
+            <Divider />
+            <div className="flex justify-between items-center text-sm text-gray-500">
+              <span>帖子ID: {postId}</span>
+              <Space>
+                <Tag color="blue">
+                  <MessageOutlined /> {comments.length} 条评论
+                </Tag>
+              </Space>
+            </div>
+          </Card>
+
+          {/* 评论区 */}
+          <Card title={
+            <Space>
+              <MessageOutlined />
+              <span>评论区 ({comments.length})</span>
+            </Space>
+          }>
+            {/* 发表评论 */}
+            <div className="mb-6">
+              <Title level={5}>发表评论</Title>
+              <Form form={form} layout="vertical">
+                <Form.Item
+                  name="comment"
+                  rules={[{ required: true, message: '请输入评论内容' }]}
+                >
+                  <TextArea 
+                    rows={4} 
+                    placeholder="请输入您的评论..."
+                    maxLength={500}
+                    showCount
+                  />
+                </Form.Item>
+                <Form.Item>
+                  <Button 
+                    type="primary" 
+                    onClick={handleSubmitComment}
+                    loading={commenting}
+                  >
+                    发布评论
+                  </Button>
+                </Form.Item>
+              </Form>
+            </div>
+
+            <Divider />
+
+            {/* 评论列表 */}
+            {comments.length > 0 ? (
+              <List
+                itemLayout="vertical"
+                dataSource={comments}
+                renderItem={(comment) => (
+                  <List.Item 
+                    key={comment.commentId}
+                    className={`border-l-2 pl-4 hover:bg-gray-50 transition-colors ${
+                      comment.reviewerId ? 'border-l-orange-200 bg-orange-50' : 'border-l-blue-100'
+                    }`}
+                  >
+                    <List.Item.Meta
+                      avatar={
+                        <Avatar 
+                          src={`https://api.dicebear.com/7.x/avataaars/svg?seed=${comment.writer || 'anonymous'}`}
+                          icon={<UserOutlined />}
+                        />
+                      }
+                      title={
+                        <div className="flex flex-wrap items-center gap-2">
+                          <Text strong>{comment.writer || '匿名用户'}</Text>
+                          <Text type="secondary" className="text-sm">
+                            {formatDate(comment.publishDate)}
+                          </Text>
+                          <Tag size="small" color="blue">
+                            #{comment.commentId}
+                          </Tag>
+                          {comment.reviewerId && (
+                            <Tag size="small" color="orange" className="ml-2">
+                              回复 #{comment.reviewerId}
+                            </Tag>
+                          )}
+                        </div>
+                      }
+                      description={
+                        <div>
+                          {/* 显示被回复的评论 */}
+                          {comment.reviewerId && (
+                            <div className="mb-3 p-3 bg-gray-100 rounded border-l-4 border-l-orange-400">
+                              <Text type="secondary" className="text-xs">
+                                回复 #{comment.reviewerId}:
+                              </Text>
+                              {(() => {
+                                const reviewedComment = getReviewedComment(comment.reviewerId);
+                                return reviewedComment ? (
+                                  <div className="mt-1">
+                                    <Text type="secondary" className="text-sm">
+                                      {reviewedComment.writer || '匿名用户'}: 
+                                    </Text>
+                                    <Text className="text-sm ml-1">
+                                      {reviewedComment.content.length > 50 
+                                        ? reviewedComment.content.substring(0, 50) + '...' 
+                                        : reviewedComment.content}
+                                    </Text>
+                                  </div>
+                                ) : (
+                                  <Text type="secondary" className="text-sm">
+                                    原评论已被删除
+                                  </Text>
+                                );
+                              })()}
+                            </div>
+                          )}
+                          
+                          <Paragraph className="mt-2 mb-3">
+                            {comment.content}
+                          </Paragraph>
+                          
+                          {/* 回复按钮 */}
+                          <div className="mb-3">
+                            <Button
+                              type="link"
+                              icon={<CommentOutlined />}
+                              onClick={() => startReply(comment.commentId)}
+                              size="small"
+                              disabled={replyingTo === comment.commentId}
+                              className="p-0"
+                            >
+                              {replyingTo === comment.commentId ? '回复中...' : '回复'}
+                            </Button>
+                          </div>
+                          
+                          {/* 回复表单 */}
+                          {replyingTo === comment.commentId && (
+                            <div className="mt-4 p-4 bg-gray-50 rounded-lg">
+                              <Form form={replyForms} layout="vertical">
+                                <Form.Item
+                                  name="reply"
+                                  rules={[{ required: true, message: '请输入回复内容' }]}
+                                >
+                                  <TextArea 
+                                    rows={3} 
+                                    placeholder={`回复 ${comment.writer || '匿名用户'}...`}
+                                    maxLength={500}
+                                    showCount
+                                  />
+                                </Form.Item>
+                                <Form.Item className="mb-0">
+                                  <Space>
+                                    <Button 
+                                      type="primary" 
+                                      size="small"
+                                      onClick={() => handleSubmitReply(comment.commentId)}
+                                      loading={replying}
+                                    >
+                                      发布回复
+                                    </Button>
+                                    <Button 
+                                      size="small"
+                                      onClick={cancelReply}
+                                    >
+                                      取消
+                                    </Button>
+                                  </Space>
+                                </Form.Item>
+                              </Form>
+                            </div>
+                          )}
+                        </div>
+                      }
+                    />
+                  </List.Item>
+                )}
+              />
+            ) : (
+              <Empty 
+                description="暂无评论,快来发表第一条评论吧!"
+                image={Empty.PRESENTED_IMAGE_SIMPLE}
+              />
+            )}
+          </Card>
+        </>
+      )}
+    </div>
+  );
+};
+
+export default PostDetailPage; 
\ No newline at end of file