import { API_BASE_URL } from "@/shared/config/env.js"; const TOKEN_KEY = "hyapp-admin.access-token"; const APP_CODE_KEY = "hyapp-admin.app-code"; const APP_CODE_HEADER = "X-App-Code"; let accessToken = window.localStorage.getItem(TOKEN_KEY) || ""; let selectedAppCode = normalizeAppCode(window.localStorage.getItem(APP_CODE_KEY) || "lalu"); let refreshHandler: (() => Promise) | null = null; let refreshInFlight: Promise | null = null; let unauthorizedHandler: (() => void) | null = null; export type QueryValue = string | number | boolean | null | undefined; export type QueryParams = Record; export type HttpMethod = "DELETE" | "GET" | "PATCH" | "POST" | "PUT"; export interface ApiRequestOptions { body?: TBody; headers?: Record; method?: HttpMethod; query?: QueryParams; raw?: boolean; retry?: boolean; skipAuth?: boolean; } interface ApiEnvelope { code?: number; data?: TData; message?: string; } export function getAccessToken() { return accessToken; } export function setAccessToken(token: string) { accessToken = token || ""; if (accessToken) { window.localStorage.setItem(TOKEN_KEY, accessToken); } else { window.localStorage.removeItem(TOKEN_KEY); } } export function getSelectedAppCode() { return selectedAppCode; } export function setSelectedAppCode(appCode: string) { selectedAppCode = normalizeAppCode(appCode); if (selectedAppCode) { window.localStorage.setItem(APP_CODE_KEY, selectedAppCode); } else { window.localStorage.removeItem(APP_CODE_KEY); } } export function setRefreshHandler(handler: (() => Promise) | null) { refreshHandler = handler; } export function setUnauthorizedHandler(handler: (() => void) | null) { unauthorizedHandler = handler; } export async function apiRequest( path: string, options: ApiRequestOptions & { raw: true } ): Promise; export async function apiRequest( path: string, options?: ApiRequestOptions & { raw?: false } ): Promise; export async function apiRequest( path: string, options?: ApiRequestOptions ): Promise; export async function apiRequest( path: string, options: ApiRequestOptions = {} ): Promise { const { body, headers = {}, method = body ? "POST" : "GET", query, retry = true, raw = false, skipAuth = false } = options; const url = buildURL(path, query); const requestHeaders = { ...headers }; if (body !== undefined && !(body instanceof FormData)) { requestHeaders["Content-Type"] = "application/json"; } if (accessToken && !skipAuth) { requestHeaders.Authorization = `Bearer ${accessToken}`; } if (selectedAppCode && !requestHeaders[APP_CODE_HEADER]) { requestHeaders[APP_CODE_HEADER] = selectedAppCode; } const response = await fetch(url, { method, headers: requestHeaders, body: body === undefined ? undefined : body instanceof FormData ? body : JSON.stringify(body), credentials: "include" }); if ((response.status === 401 || response.status === 403) && retry && refreshHandler && !skipAuth) { const refreshed = await refreshOnce(); if (refreshed) { return apiRequest(path, { ...options, retry: false }); } } if (response.status === 401 && unauthorizedHandler && !skipAuth) { unauthorizedHandler(); } if (raw) { if (!response.ok) { throw new Error(response.statusText || "请求失败"); } return response; } const payload = await readJSON(response); if (!response.ok || payload.code !== 0) { throw new Error(resolveErrorMessage(response, payload.message)); } return payload.data as TData; } function refreshOnce() { if (!refreshHandler) { return Promise.resolve(false); } if (!refreshInFlight) { const handler = refreshHandler; refreshInFlight = handler().finally(() => { refreshInFlight = null; }); } return refreshInFlight; } function resolveErrorMessage(response: Response, message?: string) { if (response.status === 404 && (!message || message.includes("404 page not found"))) { return "接口不存在或后端服务未更新"; } return message || response.statusText || "请求失败"; } function buildURL(path: string, query?: QueryParams) { const url = new URL(`${API_BASE_URL}${path}`, window.location.origin); if (query) { Object.entries(query).forEach(([key, value]) => { if (value !== undefined && value !== null && value !== "") { url.searchParams.set(key, String(value)); } }); } return url.toString(); } async function readJSON(response: Response): Promise> { const text = await response.text(); if (!text) { return {}; } try { return JSON.parse(text) as ApiEnvelope; } catch { return { code: response.ok ? 0 : response.status, message: text, data: text as TData }; } } function normalizeAppCode(value: string) { return value.trim().toLowerCase(); }