添加了相关测试文件,引入Tailwindcss
Change-Id: I12054143571bb688590af0357125a0ed26ff2050
diff --git a/src/pages/AdminPanel.jsx b/src/pages/AdminPanel.jsx
new file mode 100644
index 0000000..1f641f2
--- /dev/null
+++ b/src/pages/AdminPanel.jsx
@@ -0,0 +1,64 @@
+import React from 'react';
+import { Card, Table, Button, Space, Typography } from 'antd';
+import { UserOutlined, UploadOutlined, SettingOutlined } from '@ant-design/icons';
+
+const { Title } = Typography;
+
+const AdminPanel = () => {
+ // 模拟用户数据
+ const users = [
+ { key: '1', username: 'admin', role: '管理员', status: '正常', registrationDate: '2023-01-01' },
+ { key: '2', username: 'user1', role: '新人', status: '正常', registrationDate: '2023-02-15' },
+ { key: '3', username: 'user2', role: '老手', status: '禁用', registrationDate: '2023-03-20' },
+ { key: '4', username: 'user3', role: '黑户', status: '封禁', registrationDate: '2023-04-10' },
+ { key: '5', username: 'mod1', role: '版主', status: '正常', registrationDate: '2023-05-05' },
+ ];
+
+ const columns = [
+ { title: '用户名', dataIndex: 'username', key: 'username' },
+ { title: '角色', dataIndex: 'role', key: 'role' },
+ { title: '状态', dataIndex: 'status', key: 'status' },
+ { title: '注册日期', dataIndex: 'registrationDate', key: 'registrationDate' },
+ {
+ title: '操作',
+ key: 'action',
+ render: (_, record) => (
+ <Space size="middle">
+ <Button type="link">编辑</Button>
+ <Button type="link" danger>禁用</Button>
+ </Space>
+ ),
+ },
+ ];
+
+ return (
+ <div className="p-6">
+ <Title level={2}>管理员控制面板</Title>
+ <div className="flex gap-4 mb-6">
+ <Card title="用户统计" className="w-1/3">
+ <div className="flex items-center">
+ <UserOutlined className="text-2xl mr-2" />
+ <span className="text-xl">5 名用户</span>
+ </div>
+ </Card>
+ <Card title="资源统计" className="w-1/3">
+ <div className="flex items-center">
+ <UploadOutlined className="text-2xl mr-2" />
+ <span className="text-xl">25 个资源</span>
+ </div>
+ </Card>
+ <Card title="系统状态" className="w-1/3">
+ <div className="flex items-center">
+ <SettingOutlined className="text-2xl mr-2" />
+ <span className="text-xl">运行正常</span>
+ </div>
+ </Card>
+ </div>
+ <Card title="用户管理">
+ <Table columns={columns} dataSource={users} />
+ </Card>
+ </div>
+ );
+};
+
+export default AdminPanel;
\ No newline at end of file
diff --git a/src/pages/Login.jsx b/src/pages/Login.jsx
new file mode 100644
index 0000000..f27ac02
--- /dev/null
+++ b/src/pages/Login.jsx
@@ -0,0 +1,114 @@
+import React, { useState } from 'react';
+import { useNavigate, Link } from 'react-router-dom';
+import { Form, Input, Button, Checkbox, Card, message, Typography, Space, Divider } from 'antd';
+import { UserOutlined, LockOutlined } from '@ant-design/icons';
+import request from '../utils/request'; // 替换为我们的请求工具
+
+const { Title, Text } = Typography;
+
+const Login = () => {
+ const [loading, setLoading] = useState(false);
+ const navigate = useNavigate();
+
+ const onFinish = async (values) => {
+ setLoading(true);
+ try {
+ const response = await request.post('/api/auth/login', {
+ username: values.username,
+ password: values.password
+ });
+
+ if (response.data.code === 200) {
+ // 登录成功
+ localStorage.setItem('token', response.data.data.token);
+ localStorage.setItem('user', JSON.stringify(response.data.data.user));
+
+ // 存储用户权限信息
+ if (response.data.data.permissions) {
+ localStorage.setItem('permissions', JSON.stringify(response.data.data.permissions));
+ }
+
+ message.success('登录成功');
+ navigate('/');
+ } else {
+ message.error(response.data.message || '登录失败');
+ }
+ } catch (error) {
+ console.error('登录错误:', error);
+ // 错误处理已在拦截器中完成,这里不需要额外处理
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <div className="flex justify-center items-center min-h-screen bg-gray-100 p-4">
+ <Card className="w-full max-w-md shadow-lg">
+ <div className="text-center mb-6">
+ <Title level={2} className="mb-2">PT网站登录</Title>
+ <Text type="secondary">欢迎回来,请登录您的账号</Text>
+ </div>
+
+ <Form
+ name="login"
+ initialValues={{ remember: true }}
+ onFinish={onFinish}
+ size="large"
+ layout="vertical"
+ >
+ <Form.Item
+ name="username"
+ rules={[{ required: true, message: '请输入用户名!' }]}
+ >
+ <Input prefix={<UserOutlined />} placeholder="用户名" />
+ </Form.Item>
+
+ <Form.Item
+ name="password"
+ rules={[{ required: true, message: '请输入密码!' }]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="密码" />
+ </Form.Item>
+
+ <Form.Item>
+ <div className="flex justify-between items-center">
+ <Form.Item name="remember" valuePropName="checked" noStyle>
+ <Checkbox>记住我</Checkbox>
+ </Form.Item>
+ <a className="text-blue-500 hover:text-blue-700" href="#">
+ 忘记密码
+ </a>
+ </div>
+ </Form.Item>
+
+ <Form.Item>
+ <Button type="primary" htmlType="submit" className="w-full" loading={loading}>
+ 登录
+ </Button>
+ </Form.Item>
+
+ <Divider plain>或者</Divider>
+
+ <div className="text-center">
+ <Text type="secondary" className="mr-2">还没有账号?</Text>
+ <Link to="/register" className="text-blue-500 hover:text-blue-700">立即注册</Link>
+ </div>
+ </Form>
+
+ <div className="mt-6 p-4 bg-gray-50 rounded-md">
+ <Text type="secondary" className="block mb-2">提示:测试账号</Text>
+ <ul className="space-y-1 text-sm text-gray-600">
+ <li>管理员: admin / admin123</li>
+ <li>普通用户: user / user123</li>
+ <li>版主: moderator / mod123</li>
+ <li>老手: veteran / vet123</li>
+ <li>新人: newbie / new123</li>
+ <li>黑户: blacklisted / black123</li>
+ </ul>
+ </div>
+ </Card>
+ </div>
+ );
+};
+
+export default Login;
\ No newline at end of file
diff --git a/src/pages/NotFound.jsx b/src/pages/NotFound.jsx
new file mode 100644
index 0000000..9dd4cdb
--- /dev/null
+++ b/src/pages/NotFound.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { Button, Result } from 'antd';
+import { Link } from 'react-router-dom';
+
+const NotFound = () => {
+ return (
+ <Result
+ status="404"
+ title="404"
+ subTitle="抱歉,您访问的页面不存在。"
+ extra={
+ <Button type="primary">
+ <Link to="/">返回首页</Link>
+ </Button>
+ }
+ />
+ );
+};
+
+export default NotFound;
\ No newline at end of file
diff --git a/src/pages/NotFound.test.jsx b/src/pages/NotFound.test.jsx
new file mode 100644
index 0000000..edfaf05
--- /dev/null
+++ b/src/pages/NotFound.test.jsx
@@ -0,0 +1,18 @@
+import { describe, it, expect } from 'vitest';
+import { render, screen } from '@testing-library/react';
+import { BrowserRouter } from 'react-router-dom';
+import NotFound from './NotFound';
+
+describe('NotFound 组件', () => {
+ it('应该渲染 404 页面', () => {
+ render(
+ <BrowserRouter>
+ <NotFound />
+ </BrowserRouter>
+ );
+
+ expect(screen.getByText('404')).toBeInTheDocument();
+ expect(screen.getByText('抱歉,您访问的页面不存在。')).toBeInTheDocument();
+ expect(screen.getByText('返回首页')).toBeInTheDocument();
+ });
+});
\ No newline at end of file
diff --git a/src/pages/Register.jsx b/src/pages/Register.jsx
new file mode 100644
index 0000000..65de07e
--- /dev/null
+++ b/src/pages/Register.jsx
@@ -0,0 +1,118 @@
+import React, { useState } from 'react';
+import { useNavigate, Link } from 'react-router-dom';
+import { Form, Input, Button, Card, message, Typography, Divider } from 'antd';
+import { UserOutlined, LockOutlined, MailOutlined } from '@ant-design/icons';
+import axios from 'axios';
+// 删除 import './Register.css';
+
+const { Title, Text } = Typography;
+
+const Register = () => {
+ const [loading, setLoading] = useState(false);
+ const navigate = useNavigate();
+
+ const onFinish = async (values) => {
+ setLoading(true);
+ try {
+ const response = await axios.post('/api/auth/register', {
+ username: values.username,
+ email: values.email,
+ password: values.password
+ });
+
+ if (response.data.code === 200) {
+ message.success('注册成功!');
+ navigate('/login');
+ } else {
+ message.error(response.data.message || '注册失败');
+ }
+ } catch (error) {
+ console.error('注册错误:', error);
+ message.error('注册失败,请检查网络连接');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+ <div className="flex justify-center items-center min-h-screen bg-gray-100">
+ <Card className="w-full max-w-md">
+ <div className="text-center mb-6">
+ <Title level={2} className="mb-3">注册账号</Title>
+ <Text type="secondary">创建您的PT网站账号</Text>
+ </div>
+
+ <Form
+ name="register"
+ onFinish={onFinish}
+ size="large"
+ layout="vertical"
+ >
+ <Form.Item
+ name="username"
+ rules={[
+ { required: true, message: '请输入用户名!' },
+ { min: 3, message: '用户名至少3个字符' },
+ { max: 20, message: '用户名最多20个字符' }
+ ]}
+ >
+ <Input prefix={<UserOutlined />} placeholder="用户名" />
+ </Form.Item>
+
+ <Form.Item
+ name="email"
+ rules={[
+ { required: true, message: '请输入邮箱!' },
+ { type: 'email', message: '请输入有效的邮箱地址!' }
+ ]}
+ >
+ <Input prefix={<MailOutlined />} placeholder="邮箱" />
+ </Form.Item>
+
+ <Form.Item
+ name="password"
+ rules={[
+ { required: true, message: '请输入密码!' },
+ { min: 6, message: '密码至少6个字符' }
+ ]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="密码" />
+ </Form.Item>
+
+ <Form.Item
+ name="confirm"
+ dependencies={['password']}
+ rules={[
+ { required: true, message: '请确认密码!' },
+ ({ getFieldValue }) => ({
+ validator(_, value) {
+ if (!value || getFieldValue('password') === value) {
+ return Promise.resolve();
+ }
+ return Promise.reject(new Error('两次输入的密码不一致!'));
+ },
+ }),
+ ]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="确认密码" />
+ </Form.Item>
+
+ <Form.Item>
+ <Button type="primary" htmlType="submit" className="w-full" loading={loading}>
+ 注册
+ </Button>
+ </Form.Item>
+
+ <Divider plain>或者</Divider>
+
+ <div className="text-center mt-2">
+ <Text type="secondary">已有账号?</Text>
+ <Link to="/login">立即登录</Link>
+ </div>
+ </Form>
+ </Card>
+ </div>
+ );
+};
+
+export default Register;
\ No newline at end of file
diff --git a/src/pages/Unauthorized.jsx b/src/pages/Unauthorized.jsx
new file mode 100644
index 0000000..6e93266
--- /dev/null
+++ b/src/pages/Unauthorized.jsx
@@ -0,0 +1,20 @@
+import React from 'react';
+import { Result, Button } from 'antd';
+import { Link } from 'react-router-dom';
+
+const Unauthorized = () => {
+ return (
+ <Result
+ status="403"
+ title="无权限访问"
+ subTitle="抱歉,您没有权限访问此页面。"
+ extra={
+ <Button type="primary">
+ <Link to="/">返回首页</Link>
+ </Button>
+ }
+ />
+ );
+};
+
+export default Unauthorized;
\ No newline at end of file