This commit is contained in:
2026-04-14 16:27:47 +08:00
commit 4b38a4c952
134 changed files with 7478 additions and 0 deletions

View File

@@ -0,0 +1,83 @@
import { getAccessToken } from "./storage";
const BASE_URL = import.meta.env.VITE_API_BASE_URL ?? "/api";
const DEFAULT_TIMEOUT = 10000;
type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
interface RequestOptions {
method?: HttpMethod;
body?: unknown;
headers?: Record<string, string>;
timeout?: number;
}
function buildUrl(path: string) {
if (/^https?:\/\//.test(path)) {
return path;
}
return `${BASE_URL}${path.startsWith("/") ? path : `/${path}`}`;
}
async function parseResponse(response: Response) {
if (response.status === 204) {
return null;
}
const contentType = response.headers.get("content-type") ?? "";
if (contentType.includes("application/json")) {
return response.json();
}
return response.text();
}
async function request<T>(path: string, options: RequestOptions = {}): Promise<T> {
const controller = new AbortController();
const timeoutId = window.setTimeout(() => controller.abort(), options.timeout ?? DEFAULT_TIMEOUT);
const token = getAccessToken();
try {
const response = await fetch(buildUrl(path), {
method: options.method ?? "GET",
headers: {
"Content-Type": "application/json",
...(token ? { Authorization: `Bearer ${token}` } : {}),
...options.headers
},
body: options.body === undefined ? undefined : JSON.stringify(options.body),
signal: controller.signal
});
const payload = await parseResponse(response);
if (!response.ok) {
const message =
typeof payload === "object" && payload !== null && "message" in payload
? String(payload.message)
: `Request failed with status ${response.status}`;
throw new Error(message);
}
return payload as T;
} finally {
window.clearTimeout(timeoutId);
}
}
export const http = {
get<T>(path: string, options?: Omit<RequestOptions, "method" | "body">) {
return request<T>(path, { ...options, method: "GET" });
},
post<T>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) {
return request<T>(path, { ...options, method: "POST", body });
},
put<T>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) {
return request<T>(path, { ...options, method: "PUT", body });
},
patch<T>(path: string, body?: unknown, options?: Omit<RequestOptions, "method" | "body">) {
return request<T>(path, { ...options, method: "PATCH", body });
},
delete<T>(path: string, options?: Omit<RequestOptions, "method" | "body">) {
return request<T>(path, { ...options, method: "DELETE" });
}
};

View File

@@ -0,0 +1,13 @@
const ACCESS_TOKEN_KEY = "k12study.access-token";
export function getAccessToken() {
return localStorage.getItem(ACCESS_TOKEN_KEY);
}
export function setAccessToken(token: string) {
localStorage.setItem(ACCESS_TOKEN_KEY, token);
}
export function clearAccessToken() {
localStorage.removeItem(ACCESS_TOKEN_KEY);
}