Files

84 lines
2.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
export class ApiError extends Error {
constructor(message, status = 0, code = "error") {
super(message);
this.name = "ApiError";
this.status = status;
this.code = code;
}
}
function buildUrl(path, params) {
const url = new URL(path, window.location.origin);
if (params) {
for (const [key, value] of Object.entries(params)) {
if (value === null || value === undefined || value === "") continue;
url.searchParams.set(key, String(value));
}
}
return url;
}
export async function api(path, params) {
const response = await fetch(buildUrl(path, params), {
headers: { Accept: "application/json" },
credentials: "same-origin",
});
if (response.status === 401) {
window.location.href = "/login";
throw new ApiError("登录已失效,请重新登录", 401, "unauthorized");
}
let payload;
try {
payload = await response.json();
} catch {
throw new ApiError(`接口返回异常(HTTP ${response.status})`, response.status);
}
if (!response.ok || !payload.ok) {
const error = payload.error || {};
throw new ApiError(
error.message || `请求失败(HTTP ${response.status})`,
response.status,
error.code || "error",
);
}
return payload.data;
}
export async function health() {
const response = await fetch("/api/health", { credentials: "same-origin" });
if (!response.ok) throw new ApiError("无法连接看板服务", response.status);
const payload = await response.json();
return payload?.data ?? payload;
}
export async function session() {
const response = await fetch("/api/session", { credentials: "same-origin" });
if (!response.ok) throw new ApiError("无法连接看板服务", response.status);
const payload = await response.json();
return payload?.data ?? payload;
}
export async function stats(params) {
return api("/api/stats", params);
}
export function visit(path) {
// 访问统计埋点:优先 sendBeacon(不阻塞导航),失败时用 keepalive fetch 兜底。
const body = JSON.stringify({ path });
try {
if (typeof navigator !== "undefined" && typeof navigator.sendBeacon === "function") {
const blob = new Blob([body], { type: "application/json" });
if (navigator.sendBeacon("/api/visit", blob)) return;
}
} catch {
// 忽略,走 fetch 兜底
}
fetch("/api/visit", {
method: "POST",
headers: { "Content-Type": "application/json" },
body,
credentials: "same-origin",
keepalive: true,
}).catch(() => {});
}