feat(admin): 实现管理员登录和删除功能

- 新增管理员登录接口和相关逻辑
- 实现帖子和评论的删除功能
- 更新用户权限检查逻辑
- 优化登录页面,增加管理员登录入口
- 调整论坛页面布局,增加删除按钮

Change-Id: I6b81fa7296ec9642ca14e249ede517f2fec3d077
diff --git a/src/api/forum.js b/src/api/forum.js
index 61518be..8c3c736 100644
--- a/src/api/forum.js
+++ b/src/api/forum.js
@@ -9,10 +9,8 @@
   return request.get("/posts/list", {params});
 };
 
-export const deletePost = (username, pid) => {
-  return request.delete("/posts/delete", {
-    data: { username, pid },
-  });
+export const deletePost = (params) => {
+  return request.delete("/posts/delete", { params });
 };
 
 // 评论相关API
@@ -24,8 +22,6 @@
   return request.get('/comment/get', {params});
 };
 
-export const deleteComment = (username, commentId) => {
-  return request.delete("/comment/delete", {
-    data: { username, commentId },
-  });
+export const deleteComment = (params) => {
+  return request.delete("/comment/delete", { params });
 }; 
\ No newline at end of file
diff --git a/src/features/admin/pages/AdminPanel.jsx b/src/features/admin/pages/AdminPanel.jsx
index 3717b52..bffa5bf 100644
--- a/src/features/admin/pages/AdminPanel.jsx
+++ b/src/features/admin/pages/AdminPanel.jsx
@@ -2,15 +2,17 @@
 import { Card, Table, Button, Space, Typography, message, Popconfirm, Spin } from 'antd';
 import { UserOutlined, UploadOutlined, SettingOutlined } from '@ant-design/icons';
 import { getUserList, deleteUser } from '@/api/auth';
+import { useAuth } from '@/features/auth/contexts/AuthContext';
 
 const { Title } = Typography;
 
 const AdminPanel = () => {
   const [users, setUsers] = useState([]);
   const [loading, setLoading] = useState(true);
+  const { user } = useAuth();
   
   // 获取管理员信息
-  const adminUser = JSON.parse(localStorage.getItem('user') || '{}');
+  const adminUser = user;
   
   // 加载用户数据
   useEffect(() => {
@@ -21,20 +23,24 @@
   const fetchUsers = async () => {
     try {
       setLoading(true);
-      const response = await getUserList(adminUser.username);
-      if (response.success) {
+      const params = {
+        username: adminUser.username,
+      };
+      const response = await getUserList(params);
+      if (response && response.data) {
         const userList = response.data.users || [];
+        const totalNumber = response.data.amount || 0;
         // 添加key属性
         const formattedUsers = userList.map(user => ({
           ...user,
-          key: user.id,
-          role: user.userType === 1 ? '管理员' : '普通用户',
+          key: user.uid,
+          role: user.level,
           status: '正常'
         }));
         setUsers(formattedUsers);
       }
     } catch (error) {
-      message.error(error.message || '获取用户列表失败');
+      message.error('获取用户列表失败');
     } finally {
       setLoading(false);
     }
@@ -43,9 +49,13 @@
   // 删除用户
   const handleDelete = async (username) => {
     try {
-      const response = await deleteUser(adminUser.username, username);
-      if (response.success) {
-        message.success('用户删除成功');
+      const params = {
+        username: adminUser.username,
+        targetUsername: username,
+      };
+      const response = await deleteUser(params);
+      if (response.message) {
+        message.success(response.message || '用户删除成功');
         fetchUsers(); // 重新加载用户列表
       }
     } catch (error) {
diff --git a/src/features/auth/contexts/AuthContext.jsx b/src/features/auth/contexts/AuthContext.jsx
index b0afd8e..3c53c9c 100644
--- a/src/features/auth/contexts/AuthContext.jsx
+++ b/src/features/auth/contexts/AuthContext.jsx
@@ -5,7 +5,7 @@
   useEffect,
   useCallback,
 } from "react";
-import { userLogin, registerUser, logoutUser } from "@/api/auth";
+import { userLogin, registerUser, logoutUser, adminLogin as adminLoginAPI } from "@/api/auth";
 import { message } from "antd";
 import { useNavigate } from "react-router-dom"; // 导入 useNavigate
 
@@ -88,6 +88,36 @@
     }
   };
 
+  const adminLogin = async (params) => {
+    setLoading(true);
+    try {
+      const response = await adminLoginAPI(params);
+
+      if (response && response.data) {
+        const token = response.data.token;
+        const adminData = response.data;
+        
+        console.log("Saving user data:", adminData);
+        
+        localStorage.setItem("token", token);
+        localStorage.setItem("user", JSON.stringify(adminData));
+        
+        setUser(adminData);
+        setIsAuthenticated(true);
+        
+        message.success(response?.message || "登录成功");
+        return adminData;
+      }
+    } catch (error) {
+      console.log("admin login error", error);
+      setIsAuthenticated(false);
+      setUser(null);
+      message.error(error.message || "管理员登录失败,请检查用户名和密码");
+    } finally {
+      setLoading(false);
+    }
+  };
+
   const register = async (userData) => {
     setLoading(true);
     try {
@@ -134,7 +164,11 @@
 
   const hasRole = useCallback(
     (roleName) => {
-      return user?.role === roleName;
+      if (user?.uid.includes('admin')) {
+        return true;
+      } else {
+        return user?.role === roleName;
+      }
     },
     [user]
   );
@@ -144,6 +178,7 @@
     isAuthenticated,
     loading,
     login,
+    adminLogin,
     register,
     logout,
     hasRole,
diff --git a/src/features/auth/pages/AdminLoginPage.jsx b/src/features/auth/pages/AdminLoginPage.jsx
new file mode 100644
index 0000000..248fcb1
--- /dev/null
+++ b/src/features/auth/pages/AdminLoginPage.jsx
@@ -0,0 +1,157 @@
+import React, { useState } from "react";
+import { useNavigate, Link } from "react-router-dom";
+import {
+  Form,
+  Input,
+  Button,
+  Card,
+  Typography,
+  Space,
+  Divider,
+  message,
+  Alert,
+} from "antd";
+import { UserOutlined, LockOutlined, CrownOutlined } from "@ant-design/icons";
+import { useAuth } from "../contexts/AuthContext";
+
+const { Title, Text } = Typography;
+
+const AdminLoginPage = () => {
+  const [loading, setLoading] = useState(false);
+  const navigate = useNavigate();
+  const { adminLogin, isAuthenticated, user } = useAuth();
+
+  React.useEffect(() => {
+    // 如果已经登录且是管理员,重定向到管理面板
+    if (isAuthenticated && user && user.role === 'admin') {
+      navigate("/admin");
+    }
+  }, [isAuthenticated, user, navigate]);
+
+  const onFinish = async (values) => {
+    setLoading(true);
+    try {
+      const params = {
+        username: values.username,
+        password: values.password,
+      };
+      const userData = await adminLogin(params);
+      
+      console.log("admin login userData:", userData);
+      // 管理员登录成功后直接导航到管理面板
+      if (userData && userData.uid.includes('admin')) {
+        navigate("/admin");
+      } else {
+        message.error("该账号不是管理员账号");
+      }
+    } catch (error) {
+      console.error("Admin login page error:", error);
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  return (
+    <div className="flex justify-center items-center min-h-screen bg-gradient-to-br from-orange-100 to-red-100 p-4">
+      <Card className="w-full max-w-md shadow-xl rounded-lg border-t-4 border-t-orange-500">
+        <div className="text-center mb-8">
+          <div className="mb-4">
+            <CrownOutlined className="text-4xl text-orange-500" />
+          </div>
+          <Title level={2} className="!mb-2 text-orange-700">
+            管理员登录
+          </Title>
+          <Text type="secondary">请使用管理员账号登录系统</Text>
+        </div>
+
+        <Alert
+          message="管理员登录"
+          description="此页面仅供系统管理员使用,请确认您拥有管理员权限。"
+          type="warning"
+          showIcon
+          className="mb-6"
+        />
+
+        <Form
+          name="admin_login_form"
+          initialValues={{ remember: true }}
+          onFinish={onFinish}
+          size="large"
+          layout="vertical"
+          className="space-y-6"
+        >
+          <Form.Item
+            name="username"
+            rules={[{ required: true, message: "请输入管理员用户名!" }]}
+          >
+            <Input
+              prefix={<UserOutlined className="site-form-item-icon" />}
+              placeholder="管理员用户名"
+            />
+          </Form.Item>
+          <Form.Item
+            name="password"
+            rules={[{ required: true, message: "请输入管理员密码!" }]}
+          >
+            <Input.Password
+              prefix={<LockOutlined className="site-form-item-icon" />}
+              placeholder="管理员密码"
+            />
+          </Form.Item>
+          <Form.Item>
+            <Button
+              type="primary"
+              htmlType="submit"
+              className="w-full !text-base bg-orange-500 border-orange-500 hover:bg-orange-600 hover:border-orange-600"
+              loading={loading}
+            >
+              管理员登录
+            </Button>
+          </Form.Item>
+        </Form>
+
+        <Divider plain>
+          <span className="text-slate-500">其他选项</span>
+        </Divider>
+
+        <div className="text-center space-y-3">
+          <div>
+            <Link
+              to="/login"
+              className="font-medium text-blue-600 hover:text-blue-700 hover:underline"
+            >
+              普通用户登录
+            </Link>
+          </div>
+          <div>
+            <Text type="secondary" className="mr-1">
+              还没有账号?
+            </Text>
+            <Link
+              to="/register"
+              className="font-medium text-blue-600 hover:text-blue-700 hover:underline"
+            >
+              立即注册
+            </Link>
+          </div>
+        </div>
+
+        {/* 管理员测试账号提示 */}
+        <div className="mt-8 p-4 bg-orange-50 rounded-md border border-orange-200">
+          <Text
+            type="secondary"
+            className="block mb-2 font-semibold text-orange-700"
+          >
+            管理员测试账号
+          </Text>
+          <ul className="space-y-1 text-sm text-orange-600 list-disc list-inside">
+            <li>admin / admin123</li>
+            <li>IcyIron / 111111</li>
+          </ul>
+        </div>
+      </Card>
+    </div>
+  );
+};
+
+export default AdminLoginPage; 
\ No newline at end of file
diff --git a/src/features/auth/pages/LoginPage.jsx b/src/features/auth/pages/LoginPage.jsx
index 3a8cdbc..1daf947 100644
--- a/src/features/auth/pages/LoginPage.jsx
+++ b/src/features/auth/pages/LoginPage.jsx
@@ -126,16 +126,26 @@
             <span className="text-slate-500">或</span>
           </Divider>{" "}
           {/* Tailwind: text-slate-500 */}
-          <div className="text-center">
-            <Text type="secondary" className="mr-1">
-              还没有账号?
-            </Text>
-            <Link
-              to="/register"
-              className="font-medium text-blue-600 hover:text-blue-700 hover:underline"
-            >
-              立即注册
-            </Link>
+          <div className="text-center space-y-3">
+            <div>
+              <Text type="secondary" className="mr-1">
+                还没有账号?
+              </Text>
+              <Link
+                to="/register"
+                className="font-medium text-blue-600 hover:text-blue-700 hover:underline"
+              >
+                立即注册
+              </Link>
+            </div>
+            <div>
+              <Link
+                to="/admin/login"
+                className="font-medium text-orange-600 hover:text-orange-700 hover:underline"
+              >
+                管理员登录
+              </Link>
+            </div>
           </div>
         </Form>
         {/* 提示信息部分可以保留或移除 */}
diff --git a/src/features/forum/pages/ForumPage.jsx b/src/features/forum/pages/ForumPage.jsx
index 54638fc..2f3b8d7 100644
--- a/src/features/forum/pages/ForumPage.jsx
+++ b/src/features/forum/pages/ForumPage.jsx
@@ -13,7 +13,8 @@
   Input,
   Spin,
 } from "antd";
-import { getPosts, createPost } from "@/api/forum";
+import { DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getPosts, createPost, deletePost } from "@/api/forum";
 import { useAuth } from "@/features/auth/contexts/AuthContext";
 
 const { Title, Paragraph, Text } = Typography;
@@ -26,7 +27,10 @@
   const [form] = Form.useForm();
 
   // 使用 AuthProvider 获取用户信息
-  const { user, isAuthenticated } = useAuth();
+  const { user, isAuthenticated, hasRole } = useAuth();
+  
+  // 判断是否为管理员
+  const isAdmin = hasRole('admin') || (user && user.uid && user.uid.includes('admin'));
 
   // 加载帖子数据
   useEffect(() => {
@@ -95,6 +99,43 @@
     }
   };
 
+  // 删除帖子(管理员功能)
+  const handleDeletePost = (post) => {
+    Modal.confirm({
+      title: '确认删除帖子',
+      icon: <ExclamationCircleOutlined />,
+      content: (
+        <div>
+          <p>您确定要删除这篇帖子吗?</p>
+          <p><strong>标题:</strong>{post.title}</p>
+          <p><strong>作者:</strong>{post.author}</p>
+          <p className="text-red-500 mt-2">此操作不可撤销!</p>
+        </div>
+      ),
+      okText: '确认删除',
+      okType: 'danger',
+      cancelText: '取消',
+      async onOk() {
+        try {
+          const params = {
+            username: user.username,
+            pid: post.pid,
+          };
+          const response = await deletePost(params);
+          if (response.message) {
+            message.success(response.message || '帖子删除成功');
+            fetchPosts(); // 重新加载帖子列表
+          } else {
+            message.error(response.message || '删除帖子失败');
+          }
+        } catch (error) {
+          console.error('删除帖子失败:', error);
+          message.error(error.message || '删除帖子失败');
+        }
+      },
+    });
+  };
+
   // 如果用户未认证,显示提示信息
   if (!isAuthenticated) {
     return (
@@ -158,6 +199,18 @@
               extra={
                 <Space>
                   <Text type="secondary">{item.publishDate}</Text>
+                  {isAdmin && (
+                    <Button
+                      type="text"
+                      danger
+                      icon={<DeleteOutlined />}
+                      onClick={() => handleDeletePost(item)}
+                      title="删除帖子(管理员)"
+                      size="small"
+                    >
+                      删除
+                    </Button>
+                  )}
                 </Space>
               }
             >
diff --git a/src/features/forum/pages/PostDetailPage.jsx b/src/features/forum/pages/PostDetailPage.jsx
index 5e28820..ba4da2f 100644
--- a/src/features/forum/pages/PostDetailPage.jsx
+++ b/src/features/forum/pages/PostDetailPage.jsx
@@ -13,10 +13,11 @@
   Spin,
   Space,
   Tag,
-  Empty
+  Empty,
+  Modal
 } from 'antd';
-import { ArrowLeftOutlined, MessageOutlined, UserOutlined, CommentOutlined } from '@ant-design/icons';
-import { getComments, addComment } from '@/api/forum';
+import { ArrowLeftOutlined, MessageOutlined, UserOutlined, CommentOutlined, DeleteOutlined, ExclamationCircleOutlined } from '@ant-design/icons';
+import { getComments, addComment, deleteComment } from '@/api/forum';
 import { useAuth } from '@/features/auth/contexts/AuthContext';
 
 const { Title, Paragraph, Text } = Typography;
@@ -25,7 +26,10 @@
 const PostDetailPage = () => {
   const { postId } = useParams();
   const navigate = useNavigate();
-  const { user, isAuthenticated } = useAuth();
+  const { user, isAuthenticated, hasRole } = useAuth();
+  
+  // 判断是否为管理员
+  const isAdmin = hasRole('admin') || (user && user.uid && user.uid.includes('admin'));
   const [loading, setLoading] = useState(true);
   const [commenting, setCommenting] = useState(false);
   const [postContent, setPostContent] = useState('');
@@ -159,6 +163,43 @@
     replyForms.resetFields();
   };
 
+  // 删除评论(管理员功能)
+  const handleDeleteComment = (comment) => {
+    Modal.confirm({
+      title: '确认删除评论',
+      icon: <ExclamationCircleOutlined />,
+      content: (
+        <div>
+          <p>您确定要删除这条评论吗?</p>
+          <p><strong>作者:</strong>{comment.writer}</p>
+          <p><strong>内容:</strong>{comment.content.length > 50 ? comment.content.substring(0, 50) + '...' : comment.content}</p>
+          <p className="text-red-500 mt-2">此操作不可撤销!</p>
+        </div>
+      ),
+      okText: '确认删除',
+      okType: 'danger',
+      cancelText: '取消',
+      async onOk() {
+        try {
+          const params = {
+            username: user.username,
+            commentId: comment.commentId
+          };
+          const response = await deleteComment(params);
+          if (response.message) {
+            message.success(response.message || '评论删除成功');
+            fetchPostAndComments(); // 重新加载评论
+          } else {
+            message.error(response.message || '删除评论失败');
+          }
+        } catch (error) {
+          console.error('删除评论失败:', error);
+          message.error(error.message || '删除评论失败');
+        }
+      },
+    });
+  };
+
   // 格式化日期
   const formatDate = (dateString) => {
     try {
@@ -299,6 +340,19 @@
                               回复 #{comment.reviewerId}
                             </Tag>
                           )}
+                          {isAdmin && (
+                            <Button
+                              type="text"
+                              danger
+                              size="small"
+                              icon={<DeleteOutlined />}
+                              onClick={() => handleDeleteComment(comment)}
+                              title="删除评论(管理员)"
+                              className="ml-2"
+                            >
+                              删除
+                            </Button>
+                          )}
                         </div>
                       }
                       description={
diff --git a/src/features/profile/pages/ProfilePage.jsx b/src/features/profile/pages/ProfilePage.jsx
index bdbdb0a..6957580 100644
--- a/src/features/profile/pages/ProfilePage.jsx
+++ b/src/features/profile/pages/ProfilePage.jsx
@@ -55,9 +55,7 @@
     completedCount: 156,
     invites: 3,
     warnings: 0,
-    hitAndRuns: 0,
-    joinDate: '2023-05-15',
-    lastActive: '2024-12-28 15:30:00'
+    hitAndRuns: 0
   });
 
   // 获取用户统计信息
@@ -235,12 +233,10 @@
                   color={getUserClassColor(ptStats.userClass)} 
                   className="text-lg px-3 py-1"
                 >
-                  {ptStats.userClass}
+                  用户等级{user.level}
                 </Tag>
                 
                 <Text type="secondary">邮箱:{user?.email || '未设置'}</Text>
-                <Text type="secondary">注册时间:{ptStats.joinDate}</Text>
-                <Text type="secondary">最后活跃:{ptStats.lastActive}</Text>
               </Space>
             </div>
           </Card>
diff --git a/src/features/tools/pages/ToolsPage.jsx b/src/features/tools/pages/ToolsPage.jsx
deleted file mode 100644
index 90572bc..0000000
--- a/src/features/tools/pages/ToolsPage.jsx
+++ /dev/null
@@ -1,45 +0,0 @@
-import React from 'react';
-import { Card, Typography, Button } from 'antd';
-import { ToolOutlined, CloudDownloadOutlined, AppstoreOutlined } from '@ant-design/icons';
-
-const { Title, Paragraph } = Typography;
-
-const ToolsPage = () => (
-  <div className="space-y-6">
-    <Title level={2}>工具箱</Title>
-    <Paragraph className="text-slate-500">
-      这里提供了一些有用的PT相关工具和资源。
-    </Paragraph>
-    
-    <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
-      <Card 
-        title="分享率计算器" 
-        extra={<ToolOutlined />}
-        className="shadow-sm hover:shadow-md transition-shadow"
-      >
-        <p>计算所需上传量以达到目标分享率。</p>
-        <Button type="link" className="mt-2">使用工具</Button>
-      </Card>
-      
-      <Card 
-        title="批量下载工具" 
-        extra={<CloudDownloadOutlined />}
-        className="shadow-sm hover:shadow-md transition-shadow"
-      >
-        <p>一键下载多个种子文件。</p>
-        <Button type="link" className="mt-2">使用工具</Button>
-      </Card>
-      
-      <Card 
-        title="MediaInfo 解析器" 
-        extra={<AppstoreOutlined />}
-        className="shadow-sm hover:shadow-md transition-shadow"
-      >
-        <p>解析并美化展示MediaInfo信息。</p>
-        <Button type="link" className="mt-2">使用工具</Button>
-      </Card>
-    </div>
-  </div>
-);
-
-export default ToolsPage; 
\ No newline at end of file
diff --git a/src/layouts/MainLayout.jsx b/src/layouts/MainLayout.jsx
index fa4ccd4..fc9a1d0 100644
--- a/src/layouts/MainLayout.jsx
+++ b/src/layouts/MainLayout.jsx
@@ -9,7 +9,6 @@
   CommentOutlined,
   CloudDownloadOutlined,
   UploadOutlined,
-  ToolOutlined,
 } from "@ant-design/icons";
 import { useNavigate, Link, useLocation } from "react-router-dom";
 import { useAuth } from "../features/auth/contexts/AuthContext";
@@ -86,11 +85,6 @@
       key: "5",
       icon: <UploadOutlined />,
       label: <Link to="/upload">发布种子</Link>,
-    },
-    {
-      key: "6",
-      icon: <ToolOutlined />,
-      label: <Link to="/tools">工具箱</Link>,
     }
   ];
 
diff --git a/src/routes/index.jsx b/src/routes/index.jsx
index f4b7942..5caee9e 100644
--- a/src/routes/index.jsx
+++ b/src/routes/index.jsx
@@ -6,6 +6,7 @@
 
 // 导入页面
 import LoginPage from '../features/auth/pages/LoginPage';
+import AdminLoginPage from '../features/auth/pages/AdminLoginPage';
 import RegisterPage from '../features/auth/pages/RegisterPage';
 import AdminPanel from '../features/admin/pages/AdminPanel';
 import NotFoundPage from '../pages/NotFoundPage';
@@ -18,7 +19,6 @@
 import PTPage from '../features/pt/pages/PTPage';
 import TorrentListPage from '../features/torrents/pages/TorrentListPage';
 import UploadTorrentPage from '../features/torrents/pages/UploadTorrentPage';
-import ToolsPage from '../features/tools/pages/ToolsPage';
 import ProfilePage from '../features/profile/pages/ProfilePage';
 
 // 导入路由守卫
@@ -30,6 +30,7 @@
     <Routes>
       {/* 公共路由 */}
       <Route path="/login" element={<LoginPage />} />
+      <Route path="/admin/login" element={<AdminLoginPage />} />
       <Route path="/register" element={<RegisterPage />} />
       <Route path="/unauthorized" element={<UnauthorizedPage />} />
       <Route path="*" element={<NotFoundPage />} />
@@ -102,17 +103,6 @@
       />
       
       <Route 
-        path="/tools" 
-        element={
-          <ProtectedRoute>
-            <MainLayout>
-              <ToolsPage />
-            </MainLayout>
-          </ProtectedRoute>
-        } 
-      />
-      
-      <Route 
         path="/profile" 
         element={
           <ProtectedRoute>
diff --git a/src/utils/request.js b/src/utils/request.js
index 9e5df58..478b9fb 100644
--- a/src/utils/request.js
+++ b/src/utils/request.js
@@ -18,10 +18,6 @@
       config.headers["token"] = token;
     }
 
-    // 角色检查
-    if (config.url.startsWith("/api/admin") && !hasAdminRole()) {
-      return Promise.reject(new Error("无权限执行此操作"));
-    }
 
     console.log("发出的请求", config);
     return config;
@@ -31,11 +27,6 @@
   }
 );
 
-// 辅助函数:检查是否有管理员角色
-function hasAdminRole() {
-  const user = JSON.parse(localStorage.getItem("user") || "{}");
-  return user.role === "admin";
-}
 
 // 响应拦截器
 request.interceptors.response.use(
@@ -68,7 +59,7 @@
       message.error("网络错误,请检查您的网络连接");
     } else {
       // 请求配置出错
-      message.error("请求配置错误");
+      message.error(error.message || "请求配置错误");
     }
 
     return Promise.reject(error);