feat: 实现管理后台 W1-W3 假数据界面

This commit is contained in:
Nixevol
2026-09-30 07:02:33 +08:00
parent 8dde564517
commit 1cff321f2a
33 changed files with 4773 additions and 31 deletions
+85
View File
@@ -0,0 +1,85 @@
import { message } from "@/utils/notify";
import type { ApiEnvelope, ApiErrorBody } from "./types";
export class ApiError extends Error {
readonly code: string;
readonly httpStatus: number;
readonly data?: unknown;
constructor(code: string, msg: string, httpStatus = 400, data?: unknown) {
super(msg);
this.name = "ApiError";
this.code = code;
this.httpStatus = httpStatus;
this.data = data;
}
}
export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
export interface RequestOptions {
method?: HttpMethod;
body?: unknown;
headers?: Record<string, string>;
/** 为 true 时不弹出错误提示(由调用方处理) */
silent?: boolean;
rawBody?: BodyInit | null;
contentType?: string | null;
}
/**
* 管理接口请求封装:自动带 credentials 与 X-Nixmsg-Request: 1,错误直接 message 显示。
* W4 接真实后端时页面不必改,只需让 admin 模块走本函数。
*/
export async function requestAdmin<T>(path: string, opts: RequestOptions = {}): Promise<T> {
const method = opts.method ?? (opts.body != null || opts.rawBody != null ? "POST" : "GET");
const headers: Record<string, string> = {
"X-Nixmsg-Request": "1",
...opts.headers,
};
let body: BodyInit | undefined | null = opts.rawBody;
if (opts.rawBody === undefined && opts.body !== undefined) {
headers["Content-Type"] = opts.contentType ?? "application/json";
body = JSON.stringify(opts.body);
} else if (opts.contentType) {
headers["Content-Type"] = opts.contentType;
}
const res = await fetch(path, {
method,
credentials: "include",
headers,
body: body ?? undefined,
});
let envelope: ApiEnvelope<T>;
try {
envelope = (await res.json()) as ApiEnvelope<T>;
} catch {
const err = new ApiError("internal", `响应不是 JSON(HTTP ${res.status})`, res.status);
if (!opts.silent) {
message.error(err.message);
}
throw err;
}
if (typeof envelope !== "object" || envelope === null) {
const err = new ApiError("internal", "响应格式无效", res.status);
if (!opts.silent) {
message.error(err.message);
}
throw err;
}
if (envelope.ok) {
return envelope.data;
}
const errBody: ApiErrorBody = envelope.error ?? { code: "internal", message: "未知错误" };
const err = new ApiError(errBody.code, errBody.message, res.status, "data" in envelope ? envelope.data : undefined);
if (!opts.silent) {
message.error(err.message);
}
throw err;
}