@@ -0,0 +1,105 @@
|
||||
export type UploadProgress = {
|
||||
loaded: number;
|
||||
total: number;
|
||||
};
|
||||
|
||||
export type AdminApiOptions = {
|
||||
csrf?: string;
|
||||
};
|
||||
|
||||
const exactMessages: Record<string, string> = {
|
||||
"current password is invalid": "当前密码不正确",
|
||||
"new password is required": "新密码不能为空",
|
||||
"new password must be at least 8 characters": "新密码至少需要 8 位",
|
||||
"new password cannot be admin": "新密码不能为 admin",
|
||||
"new password must be different from current password": "新密码不能与当前密码相同",
|
||||
"invalid password or captcha": "密码或验证码不正确",
|
||||
"login required": "需要登录后继续操作",
|
||||
"csrf token required": "页面安全令牌已失效,请刷新后重试",
|
||||
"csrf token invalid": "页面安全令牌无效,请刷新后重试",
|
||||
"code is required": "缺少反馈编号",
|
||||
"revisionid is required": "请选择要恢复的历史版本",
|
||||
"post required": "该操作需要使用 POST 请求",
|
||||
"get required": "该操作需要使用 GET 请求",
|
||||
"file is required": "请选择要上传的文件",
|
||||
"invalid filename": "文件名不合法",
|
||||
"path escape rejected": "文件路径不合法",
|
||||
"check job not found": "未找到心跳检测任务",
|
||||
"streaming is not supported": "当前运行环境不支持实时事件流",
|
||||
};
|
||||
|
||||
const codeMessages: Record<string, string> = {
|
||||
UNAUTHORIZED: "需要登录后继续操作",
|
||||
LOGIN_FAILED: "登录失败,请检查密码和验证码",
|
||||
PASSWORD_CHANGE_FAILED: "密码修改失败",
|
||||
INVALID_PAYLOAD: "提交内容格式不正确",
|
||||
DATABASE_TEST_FAILED: "数据库连接测试失败",
|
||||
DATABASE_IMPORT_FAILED: "SQLite 导入远端库失败",
|
||||
DATABASE_SYNC_FAILED: "远端库同步回本地失败",
|
||||
LEGACY_SAVE_FAILED: "兼容 JSON 保存失败",
|
||||
LEGACY_VALIDATE_FAILED: "兼容 JSON 校验失败",
|
||||
LEGACY_RESTORE_FAILED: "兼容 JSON 恢复失败",
|
||||
NOTICE_SAVE_FAILED: "版本日志保存失败",
|
||||
NOTICE_VALIDATE_FAILED: "版本日志校验失败",
|
||||
NOTICE_RESTORE_FAILED: "版本日志恢复失败",
|
||||
PACKAGE_UPLOAD_FAILED: "发布包上传失败",
|
||||
SOURCE_SAVE_FAILED: "接口源保存失败",
|
||||
CHECK_FAILED: "接口健康检测失败",
|
||||
SYNC_FAILED: "同步操作失败",
|
||||
FORBIDDEN: "没有权限执行该操作",
|
||||
METHOD_NOT_ALLOWED: "请求方法不正确",
|
||||
};
|
||||
|
||||
export async function adminFetch<T>(target: string, init: RequestInit = {}, options: AdminApiOptions = {}): Promise<T> {
|
||||
const headers = new Headers(init.headers);
|
||||
if (!headers.has("Content-Type") && init.body && !(init.body instanceof FormData)) {
|
||||
headers.set("Content-Type", "application/json");
|
||||
}
|
||||
if (options.csrf) headers.set("X-CSRF-Token", options.csrf);
|
||||
const res = await fetch(target, { ...init, headers, credentials: "include" });
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok || data.ok === false) {
|
||||
throw new Error(toChineseError(data.message || data.error || `HTTP ${res.status}`));
|
||||
}
|
||||
return data as T;
|
||||
}
|
||||
|
||||
export function uploadAdminFile<T>(target: string, form: FormData, options: AdminApiOptions, onProgress: (progress: UploadProgress) => void): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("POST", target);
|
||||
xhr.withCredentials = true;
|
||||
if (options.csrf) xhr.setRequestHeader("X-CSRF-Token", options.csrf);
|
||||
xhr.upload.onprogress = (event) => {
|
||||
if (event.lengthComputable) onProgress({ loaded: event.loaded, total: event.total });
|
||||
};
|
||||
xhr.onload = () => {
|
||||
const data = parseJSONSafe(xhr.responseText, {});
|
||||
if (xhr.status < 200 || xhr.status >= 300 || data.ok === false) {
|
||||
reject(new Error(toChineseError(data.message || data.error || `HTTP ${xhr.status}`)));
|
||||
return;
|
||||
}
|
||||
resolve(data as T);
|
||||
};
|
||||
xhr.onerror = () => reject(new Error("网络异常,发布包上传失败"));
|
||||
xhr.onabort = () => reject(new Error("发布包上传已取消"));
|
||||
xhr.send(form);
|
||||
});
|
||||
}
|
||||
|
||||
export function toChineseError(value: string) {
|
||||
const raw = String(value || "").trim();
|
||||
const lower = raw.toLowerCase();
|
||||
if (exactMessages[lower]) return exactMessages[lower];
|
||||
if (codeMessages[raw]) return codeMessages[raw];
|
||||
if (/^HTTP\s+\d+/.test(raw)) return `请求失败:${raw}`;
|
||||
return raw || "操作失败";
|
||||
}
|
||||
|
||||
function parseJSONSafe(value: string, fallback: any) {
|
||||
try {
|
||||
return JSON.parse(value || "{}");
|
||||
} catch {
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user