完善验证页面和后端接口的链接
> 1. 配置了开发环境的端口转发 -> localhost:8080\
> 2. 完成了注册,登录,忘记密码页的功能
> 3. 为项目配置了vitest测试框架
> 4. 对这三个页面进行了测试
> 重写了/test/setup.ts
Change-Id: I46c600ce06d698dae6953b2e1e3ff4a98b0f3de4
diff --git a/src/api/authApi.ts b/src/api/authApi.ts
index 3a8eb17..4a08c2d 100644
--- a/src/api/authApi.ts
+++ b/src/api/authApi.ts
@@ -1,78 +1,42 @@
-import axios from 'axios';
+import axios, { type AxiosResponse } from 'axios';
+import type { RejisterRequest , CommonResponse, ResetPasswordRequest} from './type';
+import type{ LoginRequest } from './type';
+class authAPI {
- class authAPI {
-
- // static getUserById(userId) {
- // // 例如 GET http://localhost:8080/123
- // return axios.get(`/${userId}`);
- // }
-
-
- // static updateUser(userId, data) {
- // // 例如 PUT http://localhost:8080/123 Body: { username: 'xxx', ... }
- // return axios.put(`/${userId}`, data);
- // }
- //
- //
- // static deleteUser(userId:string) {
- // // 例如 DELETE http://localhost:8080/123
- // return axios.delete(`/${userId}`);
- // }
-
-
- static sendVerificationCode(email: string) {
- // Body: { email: 'xxx@yyy.com'}
+ static sendVerificationCode(email: string): Promise<AxiosResponse<CommonResponse>> {
return axios.post('/api/sendVerification', { email });
}
+ static register(request: RejisterRequest): Promise<AxiosResponse<CommonResponse>> {
+ return axios.post('/api/register', request);
+ }
- static sendResetCode(email: string) {
- // Body: { email: 'xxx@yyy.com' }
+ static sendResetCode(email: string):Promise<AxiosResponse<CommonResponse>> {
return axios.post('/api/sendResetCode', { email });
}
- //
- // static resetPassword({ email, code, newPassword }) {
- // // Body: { email, code, newPassword }
- // return axios.post('/resetPassword', { email, code, newPassword });
- // }
- //
- //
- // static register({ username, email, verificationCode, password }) {
- // // Body: { username, email, verificationCode, password, identificationNumber? }
- // const body = {
- // username,
- // email,
- // verificationCode,
- // password,
- // };
- // return axios.post('/register', body);
- // }
- //
- // /**
- // * 刷新 JWT Token(POST /refreshToken)
- // * @param {string} oldToken 旧的 JWT(放在 header: token)
- // * @returns {Promise<AxiosResponse>}
- // */
- // static refreshToken(oldToken : string) {
- // // 因为后端是从 header 中读取旧 token,这里直接把 token 放进 headers
- // return axios.post(
- // '/refreshToken',
- // {}, // 请求体空
- // {
- // headers: {
- // token: oldToken,
- // },
- // }
- // );
- // }
- //
- //
- // static login({ email, password } : {email: string, password:string}) {
- // // Body: { email, password }
- // return axios.post('/login', { email, password });
- // }
+ static resetPassword( request: ResetPasswordRequest ):Promise<AxiosResponse<CommonResponse>> {
+ return axios.post('/api/resetPassword', request);
+ }
+
+
+ static refreshToken(oldToken : string): Promise<AxiosResponse<CommonResponse<string>>> {
+ return axios.post(
+ '/api/refreshToken',
+ {}, // 请求体空
+ {
+ headers: {
+ token: oldToken,
+ },
+ }
+ );
+ }
+
+
+ static login(loginRequest: LoginRequest): Promise<AxiosResponse<CommonResponse<string>>> {
+ return axios.post('/api/login', loginRequest);
+ }
}
diff --git a/src/api/interceptors.ts b/src/api/interceptors.ts
index f56a8d4..3945bc3 100644
--- a/src/api/interceptors.ts
+++ b/src/api/interceptors.ts
@@ -1,4 +1,4 @@
-import axios from "axios";
+import axios, { type AxiosResponse } from "axios";
// 为所有auth外请求添加token头
axios.interceptors.request.use((config) => {
@@ -14,4 +14,33 @@
return error;
} );
+
+// 统一响应拦截器
+axios.interceptors.response.use(
+ (response: AxiosResponse) => {
+ const { code, msg, data } = response.data;
+
+ return {
+ ...response, // 保留原本的响应信息
+ data: {
+ code,
+ message: msg,
+ data,
+ success: code === 0, // 根据 code 判断请求是否成功
+ },
+ };
+ },
+ (error) => {
+ return {
+ ...error.response, // 保留原本的错误响应信息
+ data: {
+ code: -1,
+ message: error.message || '请求失败',
+ data: null,
+ success: false,
+ },
+ };
+ }
+);
+
export default axios
\ No newline at end of file
diff --git a/src/api/type.ts b/src/api/type.ts
index a5dd1ca..c1acc22 100644
--- a/src/api/type.ts
+++ b/src/api/type.ts
@@ -2,9 +2,22 @@
email: string;
password: string;
}
-
-export interface LoginResponse {
- user: string;
- token: string;
- refreshToken: string;
-}
\ No newline at end of file
+
+export interface RejisterRequest {
+ username: string,
+ email: string,
+ verificationCode: string,
+ password: string,
+}
+
+export interface ResetPasswordRequest {
+ email: string,
+ code: string,
+ newPassword: string,
+}
+
+export interface CommonResponse<T= null> {
+ code: number;
+ message: string;
+ data: T;
+ }
diff --git a/src/feature/auth/AuthLayout.tsx b/src/feature/auth/AuthLayout.tsx
index cceaf2d..656d869 100644
--- a/src/feature/auth/AuthLayout.tsx
+++ b/src/feature/auth/AuthLayout.tsx
@@ -17,12 +17,9 @@
position: 'relative',
}}
>
- {/* <h1>
- 登录创驿
- </h1>
- <p>
- 与众多用户和创作者一起交流
- </p> */}
+ <Card style={{ padding: 0, margin: 0, background: 'rgba(255,255,255,0)', border: 'none' }}>
+ <img src={slogan} width="100%" />
+ </Card>
</div>
<Flex
style={{ width: 400, height: '80vh' }}
@@ -33,9 +30,7 @@
<Card>
<Outlet></Outlet>
</Card>
- <Card style={{ padding: 0, margin: 0 }}>
- <img src={slogan} width="100%" />
- </Card>
+
</Flex>
</Flex>
);
diff --git a/src/feature/auth/Forget.tsx b/src/feature/auth/Forget.tsx
index 25bf6dc..a98ffcf 100644
--- a/src/feature/auth/Forget.tsx
+++ b/src/feature/auth/Forget.tsx
@@ -1,16 +1,74 @@
import { MailOutlined, LockOutlined } from '@ant-design/icons';
-import { Button, Form, Input, message, Row, Col } from 'antd';
-import { NavLink } from 'react-router';
+import { Button, Form, Input, message, Row, Col, Modal } from 'antd';
+import { NavLink, useNavigate } from 'react-router';
import { useState, useEffect } from 'react';
+import { useForm } from 'antd/es/form/Form';
+import authApi from '../../api/authApi';
+
+// 定义表单值的类型
+interface FormValues {
+ email: string;
+ code: string;
+ password: string;
+ confirmPassword: string;
+}
+
function Forget() {
const [countdown, setCountdown] = useState(0);
- const [emailSent,] = useState(false);
+ const [emailSent, setEmailSent] = useState(false); // 是否已发送验证码
+ const [form] = useForm<FormValues>();
+ const emailValue = Form.useWatch('email', form);
+ const [messageApi, contextHolder] = message.useMessage();
+ const nav = useNavigate(); // 页面跳转
- const onFinish = async () => {
+ // 校验邮箱格式
+ function isValidEmail(email: string): boolean {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+ console.log(emailRegex.test(email))
+ console.log(email)
+ return emailRegex.test(email);
+ }
+ // 发送验证码
+ const sendResetCode = async () => {
+ if (!isValidEmail(emailValue)) {
+ form.validateFields(['email']);
+ return;
+ }
+
+ await authApi.sendResetCode(emailValue).then(() => {
+ setEmailSent(true);
+ setCountdown(60);
+ }).catch((error) => {
+ messageApi.error(error?.message || '验证码发送失败');
+ });
};
+ const [modal, modalContext] = Modal.useModal();
+
+ const countDownNav = (onOk: () => void) => {
+ let secondsToGo = 5;
+ const instance = modal.success({
+ title: '重置成功',
+ content: `系统将在 ${secondsToGo} 后跳转到登陆页面.`,
+ okText: "立即跳转",
+ onOk
+ });
+
+ const timer = setInterval(() => {
+ secondsToGo -= 1;
+ instance.update({
+ content: `将在 ${secondsToGo} 后回到登陆页面.`,
+ });
+ }, 1000);
+
+ setTimeout(() => {
+ clearInterval(timer);
+ instance.destroy();
+ }, secondsToGo * 1000);
+ };
+ // 倒计时发送
useEffect(() => {
let countdownTimer = null;
@@ -27,92 +85,117 @@
};
}, [countdown]);
+ // 重新发送验证码
const resendCode = () => {
if (countdown > 0) return;
setCountdown(60);
- message.info('验证码已重新发送');
+ sendResetCode(); // 重新发送验证码
+ };
+
+ // 表单提交处理
+ const onFinish = (values: FormValues) => {
+ if (!emailSent) {
+ sendResetCode();
+ } else {
+ console.log(values);
+ authApi.resetPassword({
+ email: values.email,
+ code: values.code,
+ newPassword: values.password,
+ }).then((response) => {
+ if (response.data.code == 0) {
+ countDownNav(() => nav('/login'))
+ } else {
+ messageApi.error(response.data.message);
+ }
+ })
+ }
};
return (
- <Form
- name="forget"
- initialValues={{ remember: true }}
- style={{ maxWidth: 360 }}
- onFinish={onFinish}
- >
- <h2>重置密码</h2>
- <p>请输入您注册时使用的邮箱地址</p>
-
- <Form.Item
- name="email"
- rules={[
- { required: true, message: '请输入您的邮箱!' },
- { type: 'email', message: '请输入正确的邮箱格式' }
- ]}
+ <>
+ <Form
+ form={form}
+ name="forget"
+ initialValues={{ remember: false }}
+ onFinish={onFinish}
>
- <Input prefix={<MailOutlined />} placeholder="注册邮箱" />
- </Form.Item>
+ {contextHolder}
+ <h2>重置密码</h2>
+ <p>请输入您注册时使用的邮箱地址</p>
- {emailSent && (
- <>
- <Form.Item
- name="code"
- rules={[{ required: true, message: '请输入验证码!' }]}
- >
- <Row gutter={8}>
- <Col span={16}>
- <Input placeholder="验证码" />
- </Col>
- <Col span={8}>
- <Button
- disabled={countdown > 0}
- onClick={resendCode}
- style={{ width: '100%' }}
- >
- {countdown > 0 ? `${countdown}s后重试` : '重新发送'}
- </Button>
- </Col>
- </Row>
- </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>
+ {emailSent && (
+ <>
+ <Form.Item
+ name="code"
+ rules={[{ required: true, message: '请输入验证码!' }]}
+ >
+ <Row gutter={8}>
+ <Col span={16}>
+ <Input placeholder="验证码" />
+ </Col>
+ <Col span={8}>
+ <Button
+ disabled={countdown > 0}
+ onClick={resendCode}
+ style={{ width: '100%' }}
+ >
+ {countdown > 0 ? `${countdown}s后重试` : '重新发送'}
+ </Button>
+ </Col>
+ </Row>
+ </Form.Item>
- <Form.Item
- name="confirmPassword"
- 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
+ name="password"
+ rules={[
+ { required: true, message: '请设置新密码!' },
+ { min: 6, message: '密码长度至少为6位' }
+ ]}
+ >
+ <Input.Password prefix={<LockOutlined />} placeholder="新密码" />
+ </Form.Item>
- <Form.Item>
- <Button block type="primary" htmlType="submit">
- {emailSent ? '确认重置' : '获取验证码'}
- </Button>
- 或 <NavLink to='/login'>返回登录</NavLink>
- </Form.Item>
- </Form>
+ <Form.Item
+ name="confirmPassword"
+ 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 block type="primary" htmlType="submit">
+ {emailSent ? '确认重置' : '获取验证码'}
+ </Button>
+ 或 <NavLink to='/login'>返回登录</NavLink>
+ </Form.Item>
+ </Form>
+ {modalContext}
+ </>
);
}
-export default Forget;
\ No newline at end of file
+export default Forget;
diff --git a/src/feature/auth/Login.tsx b/src/feature/auth/Login.tsx
index 14bf6c8..8043007 100644
--- a/src/feature/auth/Login.tsx
+++ b/src/feature/auth/Login.tsx
@@ -1,19 +1,48 @@
+import { Loading3QuartersOutlined, LockOutlined, MailOutlined } from '@ant-design/icons';
+import { Button, Checkbox, Flex, Form, Input } from 'antd';
+import { NavLink, useNavigate } from 'react-router';
+import { useAppDispatch, useAppSelector } from '../../store/hooks';
+import { loginUser } from './authSlice';
+import { useEffect, useRef } from 'react';
+import useMessage from 'antd/es/message/useMessage';
-import { LockOutlined, MailOutlined } from '@ant-design/icons';
-import { Button, Checkbox, Form, Input, Flex } from 'antd';
-import { NavLink } from 'react-router';
+// 定义 Form 表单的字段类型
+interface FormValues {
+ email: string;
+ password: string;
+ remember: boolean;
+}
+
function Login() {
- const onFinish = (values: unknown) => {
- console.log('Received values of form: ', values);
+ const dispatch = useAppDispatch();
+ const auth = useAppSelector(state => (state.auth));
+ const [messageApi, Message] = useMessage()
+ const nav = useRef(useNavigate())
+
+ useEffect(() => {
+ if (auth.isAuth) {
+ nav.current('/');
+ }
+ if (!auth.loading && auth.error) {
+ messageApi.error(auth.error);
+ }
+ }, [auth, messageApi, nav])
+ // 给 onFinish 参数添加类型
+ const onFinish = async (values: FormValues) => {
+ try {
+ await dispatch(loginUser({ email: values.email, password: values.password }));
+ } catch (error) {
+ console.error('登录失败', error);
+ }
};
return (
<Form
name="login"
initialValues={{ remember: true }}
- style={{ maxWidth: 360 }}
onFinish={onFinish}
>
+ {Message}
<h2>登录</h2>
<Form.Item
name="email"
@@ -32,19 +61,23 @@
<Form.Item name="remember" valuePropName="checked" noStyle>
<Checkbox>自动登录</Checkbox>
</Form.Item>
- <NavLink to='/forget'> 忘记密码 </NavLink>
-
+ <NavLink to="/forget"> 忘记密码 </NavLink>
</Flex>
</Form.Item>
<Form.Item>
<Button block type="primary" htmlType="submit">
- 登录
+ {auth.loading ? (
+ <><Loading3QuartersOutlined /></>
+ ) : (
+ <>登录</>
+ )
+ }
</Button>
- 或 <NavLink to='/register'>注册</NavLink>
+ 或 <NavLink to="/register">注册</NavLink>
</Form.Item>
</Form>
);
-};
+}
-export default Login;
\ No newline at end of file
+export default Login;
diff --git a/src/feature/auth/Register.tsx b/src/feature/auth/Register.tsx
index 08edc70..0023b71 100644
--- a/src/feature/auth/Register.tsx
+++ b/src/feature/auth/Register.tsx
@@ -1,9 +1,12 @@
import { useEffect, useState } from 'react';
import { LockOutlined, MailOutlined, NumberOutlined, UserOutlined } from '@ant-design/icons';
-import {Button, Checkbox, Form, Input, Space} from 'antd';
-import { NavLink } from 'react-router';
-import authApi from "../../api/authApi.ts";
+import { Button, Checkbox, Form, Input, message, Space } from 'antd';
+import { NavLink, useNavigate } from 'react-router';
+import authApi from "../../api/authApi";
+import type { RejisterRequest } from "../../api/type";
+import type { AxiosResponse } from 'axios';
+// 定义表单字段的类型
interface FormValues {
name: string;
email: string;
@@ -16,42 +19,65 @@
function Register() {
const [countdown, setCountdown] = useState(0);
const [form] = Form.useForm<FormValues>();
- const emailValue = Form.useWatch('email', form)
+ const emailValue = Form.useWatch('email', form);
+ const [messageApi, contextHolder] = message.useMessage();
+ const nav = useNavigate(); // 用于页面跳转
- //
+ // 校验邮箱格式
function isValidEmail(email: string): boolean {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
-
return emailRegex.test(email);
}
+ // 发送验证码
const sendVerificationCode = () => {
- // 如果邮箱校验不通过,则触发表单校验提示,并中断
if (!isValidEmail(emailValue)) {
form.validateFields(['email']);
return;
}
- // 发起 POST 请求到后端 /sendVerification
- authApi.sendVerificationCode(emailValue).catch()
- setCountdown(60);
+ authApi.sendVerificationCode(emailValue).then(() => {
+ setCountdown(60); // 开始倒计时
+ }).catch((error) => {
+ messageApi.error(error?.message || '验证码发送失败');
+ });
};
- // 发送表单倒计时
+ // 倒计时处理
useEffect(() => {
- if (countdown > 0) {
- const timer = setTimeout(() => {
- setCountdown(prev => prev - 1);
- }, 1000);
+ if (countdown === 0) return;
+ const timer = setInterval(() => {
+ setCountdown(prev => {
+ if (prev === 1) clearInterval(timer); // 倒计时结束
+ return prev - 1;
+ });
+ }, 1000);
- return () => clearTimeout(timer);
- }
+ return () => clearInterval(timer);
}, [countdown]);
-
// 表单提交
const onFinish = (values: FormValues) => {
- console.log('注册成功:', values);
+ const registerRequest: RejisterRequest = {
+ username: values.name,
+ email: values.email,
+ verificationCode: values.verifyCode,
+ password: values.password,
+ };
+
+ authApi.register(registerRequest).then((response: AxiosResponse) => {
+ if (response.data.code === 200) {
+ messageApi.success("注册成功");
+ form.resetFields(); // 清空表单
+ setTimeout(() => {
+ nav('/login'); // 注册成功后跳转到登录页面
+ }, 1500);
+ } else {
+ messageApi.error(response.data.message);
+ }
+ }).catch((error) => {
+ messageApi.error(error?.message || '注册失败,请重试');
+ });
};
return (
@@ -62,6 +88,7 @@
scrollToFirstError
>
<h2>注册</h2>
+ {contextHolder}
<Form.Item
name="name"
@@ -72,7 +99,6 @@
<Form.Item
name="email"
-
rules={[{ required: true, message: '请输入邮箱' }, { type: 'email', message: '邮箱格式错误' }]}
>
<Input
@@ -163,4 +189,4 @@
);
}
-export default Register;
\ No newline at end of file
+export default Register;
diff --git a/src/feature/auth/authSlice.ts b/src/feature/auth/authSlice.ts
index 01cd4e5..f1ab2a0 100644
--- a/src/feature/auth/authSlice.ts
+++ b/src/feature/auth/authSlice.ts
@@ -1,17 +1,98 @@
-import { createSlice } from "@reduxjs/toolkit";
+import { createAsyncThunk, createSlice, type PayloadAction } from "@reduxjs/toolkit";
import type { AuthState } from "../../store/types";
+import type { LoginRequest } from "../../api/type";
+import authAPI from "../../api/authApi";
const initialState: AuthState = {
token: '',
loading: false,
isAuth: false,
+ error: ''
}
+export const loginUser = createAsyncThunk<
+ {token: string},
+ LoginRequest,
+ { rejectValue: string }
+>(
+ 'auth/login',
+ async (loginRequest: LoginRequest, { rejectWithValue }) => {
+ try {
+ const response = await authAPI.login(loginRequest);
+ if(response.data.code == 0) {
+ return {token: response.data.data};
+ }
+ else
+ return rejectWithValue(response.data.message);
+ } catch {
+ return rejectWithValue('登录失败');
+ }
+ }
+);
+
+export const refreshToken = createAsyncThunk<
+ {token: string},
+ string,
+ { rejectValue: string }
+>(
+
+ 'auth/refresh',
+ async (oldToken: string, { rejectWithValue }) => {
+ try {
+ const response = await authAPI.refreshToken(oldToken);
+ if(response.data.code == 0)
+ return {token: response.data.data};
+ else
+ return rejectWithValue(response.data.message);
+ } catch {
+ return rejectWithValue('刷新失败');
+ }
+ }
+);
+
const authSlice = createSlice({
name: 'auth',
initialState,
- reducers: {},
+ reducers: {
+ logout: (state) => {
+ state.token = '';
+ state.isAuth = false;
+ localStorage.clear()
+ },
+ },extraReducers: (builder) => {
+ // 处理登录的异步操作
+ builder
+ .addCase(loginUser.pending, (state) => {
+ state.loading = true;
+ })
+ .addCase(loginUser.fulfilled, (state, action: PayloadAction<{token: string}>) => {
+ state.loading = false;
+ state.token = action.payload.token;
+ state.isAuth = true;
+
+ localStorage.setItem('token', state.token);
+ })
+ .addCase(loginUser.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload ? action.payload : '' // 错误处理
+ })
+
+ // 处理刷新 token 的异步操作
+ .addCase(refreshToken.pending, (state) => {
+ state.loading = true;
+ })
+ .addCase(refreshToken.fulfilled, (state, action) => {
+ state.loading = false;
+ state.token = action.payload.token;
+ localStorage.setItem('token', state.token);
+ })
+ .addCase(refreshToken.rejected, (state, action) => {
+ state.loading = false;
+ state.error = action.payload ? action.payload : ''
+ });
+ },
+
});
export default authSlice.reducer;
\ No newline at end of file
diff --git a/src/main.tsx b/src/main.tsx
index 31a6d39..48b3777 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -8,6 +8,7 @@
import routes from './routes.ts';
import { RouterProvider } from 'react-router';
// 组件库 ant
+import '@ant-design/v5-patch-for-react-19';
import { ConfigProvider } from 'antd';
import zhCN from 'antd/locale/zh_CN';
diff --git a/src/store/store.ts b/src/store/store.ts
index ebf1408..5f3841e 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -1,9 +1,10 @@
import { configureStore } from '@reduxjs/toolkit'
-
+import authReducer from "../feature/auth/authSlice"
export const store = configureStore({
reducer: {
-
- }
+ auth: authReducer,
+ },
+ middleware: (getDefaultMiddleware) => getDefaultMiddleware().concat(),
})
// 从 store 本身推断出 `RootState` 和 `AppDispatch` 类型
diff --git a/src/store/types.ts b/src/store/types.ts
index 372c089..3e2d796 100644
--- a/src/store/types.ts
+++ b/src/store/types.ts
@@ -12,4 +12,5 @@
token: string;
loading: boolean;
isAuth: boolean;
+ error: string
}
\ No newline at end of file
diff --git a/src/test/auth/Forget.test.tsx b/src/test/auth/Forget.test.tsx
new file mode 100644
index 0000000..b2ba158
--- /dev/null
+++ b/src/test/auth/Forget.test.tsx
@@ -0,0 +1,24 @@
+import { render, screen } from '@testing-library/react'
+import Forget from '../../feature/auth/Forget'
+import { Provider } from 'react-redux'
+import { store } from '../../store/store'
+import { MemoryRouter } from 'react-router'
+
+describe('Forget Password Page', () => {
+ it('renders forget password form', () => {
+ render(
+ <MemoryRouter>
+ <Provider store={store}>
+ <Forget />
+ </Provider>
+ </MemoryRouter>
+
+ )
+
+ const emailInput = screen.getByPlaceholderText('注册邮箱')
+ const getCodeButton = screen.getByText('获取验证码')
+
+ expect(emailInput).toBeInTheDocument()
+ expect(getCodeButton).toBeInTheDocument()
+ })
+})
\ No newline at end of file
diff --git a/src/test/auth/Login.test.tsx b/src/test/auth/Login.test.tsx
new file mode 100644
index 0000000..ade54e8
--- /dev/null
+++ b/src/test/auth/Login.test.tsx
@@ -0,0 +1,27 @@
+import { render, screen } from '@testing-library/react'
+import Login from '../../feature/auth/Login'
+import { Provider } from 'react-redux'
+import { store } from '../../store/store'
+import { MemoryRouter } from 'react-router'
+
+describe('Login Page', () => {
+ it('renders login form', () => {
+ render(
+ <MemoryRouter>
+ <Provider store={store}>
+ <Login />
+ </Provider>
+ </MemoryRouter>
+
+ )
+
+ const emailInput = screen.getByPlaceholderText('账号(注册邮箱)')
+ const passwordInput = screen.getByPlaceholderText('密码')
+ const loginButton = screen.getByRole('button', { name: /登录/i })
+
+
+ expect(emailInput).toBeInTheDocument()
+ expect(passwordInput).toBeInTheDocument()
+ expect(loginButton).toBeInTheDocument()
+ })
+})
\ No newline at end of file
diff --git a/src/test/auth/Register.test.tsx b/src/test/auth/Register.test.tsx
new file mode 100644
index 0000000..c118a8b
--- /dev/null
+++ b/src/test/auth/Register.test.tsx
@@ -0,0 +1,32 @@
+import { render, screen } from '@testing-library/react'
+import Register from '../../feature/auth/Register'
+import { Provider } from 'react-redux'
+import { store } from '../../store/store'
+import { MemoryRouter } from 'react-router'
+
+describe('Register Page', () => {
+ it('renders register form', () => {
+ render(
+ <MemoryRouter>
+ <Provider store={store}>
+ <Register />
+ </Provider>
+ </MemoryRouter>
+
+ )
+
+ const nameInput = screen.getByPlaceholderText('请输入用户名')
+ const emailInput = screen.getByPlaceholderText('请输入邮箱')
+ const verifyCodeInput = screen.getByPlaceholderText('请输入验证码')
+ const passwordInput = screen.getByPlaceholderText('请输入密码')
+ const confirmPasswordInput = screen.getByPlaceholderText('请确认密码')
+ const registerButton = screen.getByText('注册')
+
+ expect(nameInput).toBeInTheDocument()
+ expect(emailInput).toBeInTheDocument()
+ expect(verifyCodeInput).toBeInTheDocument()
+ expect(passwordInput).toBeInTheDocument()
+ expect(confirmPasswordInput).toBeInTheDocument()
+ expect(registerButton).toBeInTheDocument()
+ })
+})
\ No newline at end of file
diff --git a/src/test/setup.ts b/src/test/setup.ts
new file mode 100644
index 0000000..bb15cf4
--- /dev/null
+++ b/src/test/setup.ts
@@ -0,0 +1,9 @@
+import '@testing-library/jest-dom';
+
+globalThis.matchMedia = globalThis.matchMedia || function() {
+ return {
+ matches: false, // 模拟返回值
+ addListener: () => {}, // 不执行任何操作
+ removeListener: () => {} // 不执行任何操作
+ };
+ };
\ No newline at end of file