feat: rebuild CSP practice workflow, UX and automation
这个提交包含在:
54
frontend/src/lib/api.ts
普通文件
54
frontend/src/lib/api.ts
普通文件
@@ -0,0 +1,54 @@
|
||||
export const API_BASE =
|
||||
process.env.NEXT_PUBLIC_API_BASE ??
|
||||
(process.env.NODE_ENV === "development" ? "http://localhost:8080" : "/admin139");
|
||||
|
||||
type ApiEnvelope<T> =
|
||||
| { ok: true; data?: T; [k: string]: unknown }
|
||||
| { ok: false; error?: string; [k: string]: unknown };
|
||||
|
||||
export async function apiFetch<T>(
|
||||
path: string,
|
||||
init?: RequestInit,
|
||||
token?: string
|
||||
): Promise<T> {
|
||||
const headers = new Headers(init?.headers);
|
||||
if (token) headers.set("Authorization", `Bearer ${token}`);
|
||||
if (init?.body && !headers.has("Content-Type")) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
|
||||
const resp = await fetch(`${API_BASE}${path}`, {
|
||||
...init,
|
||||
headers,
|
||||
cache: "no-store",
|
||||
});
|
||||
|
||||
const text = await resp.text();
|
||||
let payload: unknown = null;
|
||||
if (text) {
|
||||
try {
|
||||
payload = JSON.parse(text) as unknown;
|
||||
} catch {
|
||||
payload = text;
|
||||
}
|
||||
}
|
||||
|
||||
if (!resp.ok) {
|
||||
const msg =
|
||||
typeof payload === "object" && payload !== null && "error" in payload
|
||||
? String((payload as { error?: unknown }).error ?? `HTTP ${resp.status}`)
|
||||
: `HTTP ${resp.status}`;
|
||||
throw new Error(msg);
|
||||
}
|
||||
|
||||
if (typeof payload === "object" && payload !== null && "ok" in payload) {
|
||||
const env = payload as ApiEnvelope<T>;
|
||||
if (!env.ok) {
|
||||
throw new Error(env.error ?? "request failed");
|
||||
}
|
||||
if ("data" in env) return (env.data as T) ?? ({} as T);
|
||||
return payload as T;
|
||||
}
|
||||
|
||||
return payload as T;
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户