feat(server): modularize backend and admin console
This commit is contained in:
@@ -0,0 +1,134 @@
|
||||
import { getToken } from "@/utils/auth";
|
||||
|
||||
export class AdminApiError extends Error {
|
||||
status: number;
|
||||
body: unknown;
|
||||
|
||||
constructor(message: string, status: number, body: unknown) {
|
||||
super(message);
|
||||
this.name = "AdminApiError";
|
||||
this.status = status;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
|
||||
export function adminToken() {
|
||||
return getToken()?.accessToken || localStorage.getItem("admin_token") || "";
|
||||
}
|
||||
|
||||
export function friendlyError(error: unknown) {
|
||||
const anyError = error as any;
|
||||
if (anyError?.body?.detail) {
|
||||
return typeof anyError.body.detail === "string"
|
||||
? anyError.body.detail
|
||||
: JSON.stringify(anyError.body.detail);
|
||||
}
|
||||
if (typeof anyError?.body === "string" && anyError.body) return anyError.body;
|
||||
return anyError?.message || String(error || "未知错误");
|
||||
}
|
||||
|
||||
export async function adminRequest<T = any>(
|
||||
path: string,
|
||||
options: {
|
||||
method?: string;
|
||||
json?: unknown;
|
||||
body?: BodyInit;
|
||||
headers?: Record<string, string>;
|
||||
} = {}
|
||||
): Promise<T> {
|
||||
const headers = new Headers(options.headers || {});
|
||||
const token = adminToken();
|
||||
if (token) headers.set("X-Admin-Token", token);
|
||||
|
||||
let body = options.body;
|
||||
if (options.json !== undefined) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
body = JSON.stringify(options.json);
|
||||
}
|
||||
|
||||
const response = await fetch(path, {
|
||||
method: options.method || "GET",
|
||||
headers,
|
||||
body,
|
||||
cache: "no-store"
|
||||
});
|
||||
|
||||
const text = await response.text();
|
||||
let parsed: unknown = null;
|
||||
if (text) {
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch {
|
||||
parsed = text;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new AdminApiError(
|
||||
`HTTP ${response.status}: ${text || response.statusText}`,
|
||||
response.status,
|
||||
parsed
|
||||
);
|
||||
}
|
||||
|
||||
return parsed as T;
|
||||
}
|
||||
|
||||
export async function copyText(text: string) {
|
||||
if (navigator.clipboard && window.isSecureContext) {
|
||||
await navigator.clipboard.writeText(text);
|
||||
return;
|
||||
}
|
||||
const textarea = document.createElement("textarea");
|
||||
textarea.value = text;
|
||||
textarea.style.position = "fixed";
|
||||
textarea.style.opacity = "0";
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand("copy");
|
||||
textarea.remove();
|
||||
}
|
||||
|
||||
export async function downloadAdminFile(
|
||||
path: string,
|
||||
filename: string,
|
||||
options: {
|
||||
method?: string;
|
||||
json?: unknown;
|
||||
body?: BodyInit;
|
||||
headers?: Record<string, string>;
|
||||
} = {}
|
||||
) {
|
||||
const headers = new Headers(options.headers || {});
|
||||
headers.set("X-Admin-Token", adminToken());
|
||||
|
||||
let body = options.body;
|
||||
if (options.json !== undefined) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
body = JSON.stringify(options.json);
|
||||
}
|
||||
|
||||
const response = await fetch(path, {
|
||||
method: options.method || "GET",
|
||||
cache: "no-store",
|
||||
headers,
|
||||
body
|
||||
});
|
||||
if (!response.ok) {
|
||||
const text = await response.text();
|
||||
throw new AdminApiError(
|
||||
`HTTP ${response.status}: ${text || response.statusText}`,
|
||||
response.status,
|
||||
text
|
||||
);
|
||||
}
|
||||
const blob = await response.blob();
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement("a");
|
||||
link.href = url;
|
||||
link.download = filename;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
URL.revokeObjectURL(url);
|
||||
}
|
||||
Reference in New Issue
Block a user