client.ts 2.01 KB
import axios, { AxiosError } from "axios";
import { message } from "antd";

const TOKEN_KEY = "xly.access";

export function getToken(): string | null {
  return sessionStorage.getItem(TOKEN_KEY);
}
export function setToken(t: string): void {
  sessionStorage.setItem(TOKEN_KEY, t);
}
export function clearToken(): void {
  sessionStorage.removeItem(TOKEN_KEY);
}

export interface ApiEnvelope<T = unknown> {
  code: number;
  msg: string;
  data: T;
}

export class ApiError extends Error {
  code: number;
  constructor(code: number, msg: string) {
    super(msg);
    this.code = code;
  }
}

export const http = axios.create({
  baseURL: "/api",
  timeout: 15000,
  headers: { "Content-Type": "application/json" },
});

http.interceptors.request.use((config) => {
  const tok = getToken();
  if (tok) {
    config.headers = config.headers ?? {};
    (config.headers as Record<string, string>).Authorization = `Bearer ${tok}`;
  }
  return config;
});

http.interceptors.response.use(
  (response) => {
    const env = response.data as ApiEnvelope;
    if (env && typeof env.code === "number" && env.code !== 0) {
      message.error(env.msg || "请求失败");
      throw new ApiError(env.code, env.msg || "请求失败");
    }
    return response;
  },
  (err: AxiosError<ApiEnvelope>) => {
    if (err.response?.status === 401) {
      clearToken();
      // Redirect to /login. Use location to avoid coupling axios with React Router.
      if (window.location.pathname !== "/login") {
        window.location.href = "/login";
      }
      return Promise.reject(new ApiError(401, "未登录或会话过期"));
    }
    const env = err.response?.data;
    const msg = env?.msg || err.message || "网络错误";
    message.error(msg);
    return Promise.reject(new ApiError(env?.code ?? -1, msg));
  }
);

// Convenience: call http and unwrap envelope to data.
export async function request<T = unknown>(
  config: Parameters<typeof http.request>[0]
): Promise<T> {
  const resp = await http.request<ApiEnvelope<T>>(config);
  return resp.data.data;
}