docker化项目
> 还删除无关的public/vite.svg文件
Change-Id: If26b139f8a984aec1518c0233b52d2a6a25a5bb7
diff --git a/src/api/client.ts b/src/api/client.ts
deleted file mode 100644
index 623ee78..0000000
--- a/src/api/client.ts
+++ /dev/null
@@ -1,148 +0,0 @@
-type RequestOptions = {
- method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
- headers?: Record<string, string>;
- body?: unknown;
- queryParams?: Record<string, string | number>;
-};
-
-type Interceptor = {
- request?: (config: RequestInit) => RequestInit;
- requestError?: (error: unknown) => Promise<never>; // 错误拦截器应始终抛出
- response?: (response: Response) => Response | Promise<Response>;
- // 错误拦截器必须抛出或返回与请求相同类型的结果
- responseError?: <T>(error: unknown) => Promise<T>;
-};
-
-export class HttpClient {
- private static instance: HttpClient;
- private baseUrl: string;
- private interceptors: Interceptor[] = [];
-
- private constructor(baseUrl: string) {
- this.baseUrl = baseUrl;
- }
-
- public static getInstance(baseUrl: string): HttpClient {
- if (!HttpClient.instance) {
- HttpClient.instance = new HttpClient(baseUrl);
- }
- return HttpClient.instance;
- }
-
- // 添加拦截器
- addInterceptor(interceptor: Interceptor) {
- this.interceptors.push(interceptor);
- return this; // 支持链式调用
- }
-
- async request<T>(endpoint: string, options: RequestOptions = {}): Promise<T> {
- const {
- method = 'GET',
- headers = {},
- body,
- queryParams = {}
- } = options;
-
- const url = new URL(`${this.baseUrl}${endpoint}`);
- Object.entries(queryParams).forEach(([key, value]) => {
- url.searchParams.append(key, String(value));
- });
-
- let config: RequestInit = {
- method,
- headers: {
- 'Content-Type': 'application/json',
- ...headers
- }
- };
-
- if (body) {
- config.body = JSON.stringify(body);
- }
-
- // 请求拦截器处理
- for (const interceptor of this.interceptors) {
- if (interceptor.request) {
- config = interceptor.request(config);
- }
- }
-
- try {
- let response = await fetch(url.toString(), config);
-
- // 响应拦截器处理
- for (const interceptor of this.interceptors) {
- if (interceptor.response) {
- response = await interceptor.response(response);
- }
- }
-
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(`HTTP error! Status: ${response.status}, Message: ${errorData.message || 'Unknown error'}`);
- }
-
- return await response.json();
- } catch (error) {
- // 改进的错误拦截器处理
- let lastError = error;
-
- for (const interceptor of this.interceptors) {
- if (interceptor.responseError) {
- try {
- // 错误拦截器必须返回Promise<T>或抛出
- return await interceptor.responseError<T>(lastError);
- } catch (e) {
- // 保存最新错误并继续下一个拦截器
- lastError = e;
- console.error('Interceptor error:', e);
- }
- }
- }
-
- // 如果所有拦截器都未能处理错误,则抛出最后一个错误
- throw lastError;
- }
- }
- get<T>(endpoint: string, queryParams?: Record<string, string | number>) {
- return this.request<T>(endpoint, { queryParams });
- }
-
- post<T>(endpoint: string, body?: unknown, headers?: Record<string, string>) {
- return this.request<T>(endpoint, { method: 'POST', body, headers });
- }
-
- put<T>(endpoint: string, body?: unknown, headers?: Record<string, string>) {
- return this.request<T>(endpoint, { method: 'PUT', body, headers });
- }
-
- delete<T>(endpoint: string, headers?: Record<string, string>) {
- return this.request<T>(endpoint, { method: 'DELETE', headers });
- }
-}
-
-const client = HttpClient.getInstance('http://localhost:8080/');
-// 添加Token注入拦截器
-client.addInterceptor({
- request: (config) => {
- const token = localStorage.getItem('token'); // 从存储中获取token
- if (token) {
- config.headers = {
- ...config.headers,
- 'Authorization': `Bearer ${token}`
- };
- }
- return config;
- },
- responseError: async (error) => {
- if (error instanceof Error && error.message.includes('401')) {
- console.log('Token expired, redirecting to login...');
- // 示例:重定向到登录页
- window.location.href = '/login';
- // 必须返回Promise<never>或抛出
- return new Promise(() => {}); // 永远不解决的Promise
- }
- throw error;
- }
-});
-export default client as HttpClient;
\ No newline at end of file
diff --git a/src/api/interceptors.ts b/src/api/interceptors.ts
new file mode 100644
index 0000000..3b31740
--- /dev/null
+++ b/src/api/interceptors.ts
@@ -0,0 +1,20 @@
+import axios from "axios";
+import { useNavigate } from "react-router";
+
+// 为所有auth外请求添加token头
+axios.interceptors.request.use((config) => {
+ const requestUrl = config.url;
+ if (requestUrl?.includes("/auth/")) {
+ config.url = requestUrl.replace("/auth/","/");
+ } else {
+ const token = localStorage.getItem('token');
+ if (!token) {
+ const navigate = useNavigate();
+ navigate("/login")
+ }
+ config.headers['Authorization'] = `Bearer ${token}`;
+ }
+ return config;
+}, (error) => {
+ return error;
+} );
\ No newline at end of file
diff --git a/src/feature/auth/Register.tsx b/src/feature/auth/Register.tsx
index 5b90c59..197d265 100644
--- a/src/feature/auth/Register.tsx
+++ b/src/feature/auth/Register.tsx
@@ -15,9 +15,20 @@
function Register() {
const [countdown, setCountdown] = useState(0);
const [form] = Form.useForm<FormValues>();
- const [emailStatues] = useState(false);
+ const emailValue = Form.useWatch('email', form)
+
+ //
+ function isValidEmail(email: string): boolean {
+ const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+
+ return emailRegex.test(email);
+ }
const sendVerificationCode = () => {
+ if (!isValidEmail(emailValue)) {
+ form.validateFields(['email'])
+ return;
+ }
setCountdown(60)
}
@@ -76,8 +87,9 @@
style={{ flex: 1 }}
/>
<Button
- disabled={countdown > 0 || !emailStatues}
+ disabled={countdown > 0}
onClick={sendVerificationCode}
+ color="primary"
style={{ width: 120 }}
>
{countdown > 0 ? `${countdown}s后重试` : '发送验证码'}
diff --git a/src/routes.ts b/src/routes.ts
index 1356b1e..e0dd020 100644
--- a/src/routes.ts
+++ b/src/routes.ts
@@ -19,7 +19,7 @@
{ path: "register", Component: Register },
{ path: "forget", Component: Forget },
],
- }
+ },
]
}]);
\ No newline at end of file