Skip to content

请求

项目示例 api

项目 api 接口是真实运行在服务器的接口,点击访问后端仓库,后端使用了fastapi来开发

点击查看接口文档,密码为rengar-admin,此文档会不定期更新密码。

request 封装

因为后端的规范是各式各样的,每一个公司甚至是每一个团队都有一套自己的规范,所以renga-admin提供了一个父类(代码位于packages/axios/index.ts),你可以继承这个父类,然后重写initializeRequestInterceptorinitializeResponseInterceptor方法,来实现自己的请求拦截器和响应拦截器。

rengar-admin的示例请求的自定义请求拦截如下,你可以根据后端的实际情况来实现自己的请求拦截器,代码位于src/api/request.ts

ts
import BaseHttpClient from "@rengar-admin/axios";
import type { AxiosRequestConfig } from "axios";
import { useRouterHook } from "@/hooks/router";
import { useAuthStore } from "@/stores";
import router from "@/router";
import { useAuthStore } from "@/stores";

import { getServiceBaseUrl } from "@/utils/service";

function showErrorMessage(message: string) {
  window.$message?.error?.(message);
}

class HttpClient extends BaseHttpClient {
  constructor(config: AxiosRequestConfig) {
    super(config);
  }

  // 自定义请求拦截
  protected initializeRequestInterceptor(): number {
    return this.instance.interceptors.request.use(
      (config) => {
        const authStore = useAuthStore();
        if (authStore.user.token) {
          config.headers.Authorization = `Bearer ${authStore.user.token}`;
        }
        // ✅ 记录发起请求时的路由路径
        config.meta = config.meta || {};
        config.meta.routerFullPath = router.currentRoute.value.fullPath;
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
  }

  private handleUnauthorized(
    message: string = "未授权,请重新登录",
    path?: string
  ) {
    const { routerReplaceToLogin } = useRouterHook(false);
    // ✅ 关键:取消所有请求,避免后续 401 弹窗或跳转
    this.cancelAll();
    showErrorMessage(message);
    const authStore = useAuthStore();
    authStore.reset();
    routerReplaceToLogin(path);
    return Promise.reject(new Error(message));
  }

  // 自定义响应拦截
  protected initializeResponseInterceptor(): number {
    return this.instance.interceptors.response.use(
      (response) => {
        if (response.status === 200 && response.data.code === "000000") {
          return response.data.data;
        } else if (response.data.code === "401") {
          return this.handleUnauthorized(
            undefined,
            response.config.meta?.routerFullPath
          );
        } else {
          showErrorMessage(response.data.message || "请求失败");
          return Promise.reject(new Error(response.data.message || "请求失败"));
        }
      },
      (error) => {
        if (error.response?.status === 401) {
          return this.handleUnauthorized(
            undefined,
            error.config?.meta?.routerFullPath
          );
        }
        showErrorMessage(error?.response?.data?.message || "请求失败");
        return Promise.reject(error);
      }
    );
  }
}

const baseHttp = new HttpClient({
  baseURL: getServiceBaseUrl("default", import.meta.env),
  timeout: 1000 * 10,
});

export { baseHttp };

代理

.env.development中配置以下可以开启 vite proxy 代理:

shell
# 开启代理
VITE_HTTP_PROXY=Y   # Y = 开启代理,N 或其他 = 关闭

原理是在src/utils/service.ts中定义了createServiceConfig方法放回相关的 proxy 配置,然后在build/proxy/index.ts中转化成代理配置。然后给axios赋值 baseURL的时候使用getServiceBaseUrl方法获取代理地址。

src/utils/service.ts

ts
type ServiceKey = "default";

interface ServiceConfig {
  key: ServiceKey;
  url: string; // 真实 API 地址
  proxyPattern: string; // 代理匹配路径,如 /proxy-default
}
export function createServiceConfig(env: ImportMetaEnv): ServiceConfig[] {
  return [
    {
      key: "default",
      url: env.VITE_API_URL,
      proxyPattern: "/proxy-default",
    },
  ];
}

export function getServiceConfig(key: ServiceKey, env: ImportMetaEnv) {
  const serviceConfig = createServiceConfig(env);
  const service = serviceConfig.find((item) => item.key === key);
  if (!service) {
    throw new Error(`Service ${key} not found`);
  }
  return service;
}

export function getServiceBaseUrl(key: ServiceKey, env: ImportMetaEnv) {
  const service = getServiceConfig(key, env);
  return env.DEV && env.VITE_HTTP_PROXY === "Y"
    ? service.proxyPattern
    : service.url;
}

build/proxy/index.ts

ts
import { createServiceConfig } from "../../src/utils/service";
import type { ProxyOptions } from "vite";
export function createViteProxy(
  env: ImportMetaEnv,
  mode: string
): Record<string, ProxyOptions> | undefined {
  if (mode !== "development" || env.VITE_HTTP_PROXY !== "Y") return undefined;

  const serivceConfigs = createServiceConfig(env);

  const proxy: Record<string, ProxyOptions> = {};

  for (const service of serivceConfigs) {
    proxy[service.proxyPattern] = {
      target: service.url,
      changeOrigin: true,
      rewrite: (path: string) =>
        path.replace(new RegExp(`^${service.proxyPattern}`), ""),
    };
  }

  return proxy;
}

多个后端 api

如果你的项目中需要对接多个后端 api,可以参照如下步骤:

  1. .env.xxx里维护一个环境变量来什么你的 api 地址,假设就叫VITE_API_OHTER_URL

    shell
     VITE_API_OHTER_URL=http://other-api.com
  2. typings/common/env.d.ts中申明 ts 类型

    ts
    declare interface ImportMetaEnv {
      readonly VITE_API_OHTER_URL: string;
    }
  3. src/uitls/service.ts中添加配置:

    ts
    type ServiceKey = "default" | "other";
    export function createServiceConfig(env: ImportMetaEnv): ServiceConfig[] {
      return [
        {
          key: "other",
          url: env.VITE_API_OHTER_UR,
          proxyPattern: "/proxy-other",
        },
      ];
    }
  4. 创建自己的otherHttp实例到src/api/other-request.ts:

ts
import BaseHttpClient from "@rengar-admin/axios";
import type { AxiosRequestConfig } from "axios";
import { useRouterHook } from "@/hooks/router";
import router from "@/router";
import { getServiceBaseUrl } from "@/utils/service";

function showErrorMessage(message: string) {
  window.$message?.error?.(message);
}

class HttpClient extends BaseHttpClient {
  constructor(config: AxiosRequestConfig) {
    super(config);
  }

  protected initializeRequestInterceptor(): number {
    return this.instance.interceptors.request.use(
      (config) => {
        // 这里定义你自己的请求拦截器
        // ✅ 记录发起请求时的路由路径
        config.meta = config.meta || {};
        config.meta.routerFullPath = router.currentRoute.value.fullPath;
        return config;
      },
      (error) => {
        return Promise.reject(error);
      }
    );
  }

  private handleUnauthorized(
    message: string = "未授权,请重新登录",
    path?: string
  ) {
    const { routerReplaceToLogin } = useRouterHook(false);
    // ✅ 关键:取消所有请求,避免后续 401 弹窗或跳转
    this.cancelAll();
    showErrorMessage(message);
    const authStore = useAuthStore();
    authStore.reset();
    routerReplaceToLogin(path);
    return Promise.reject(new Error(message));
  }

  protected initializeResponseInterceptor(): number {
    return this.instance.interceptors.response.use(
      (response) => {
        // 这里定义你自己的响应拦截器
        if (response.status === 200 && response.data.code === "000000") {
          return response.data.data;
        } else if (response.data.code === "401") {
          return this.handleUnauthorized(
            undefined,
            response.config.meta?.routerFullPath
          );
        } else {
          showErrorMessage(response.data.message || "请求失败");
          return Promise.reject(new Error(response.data.message || "请求失败"));
        }
      },
      (error) => {
        if (error.response?.status === 401) {
          return this.handleUnauthorized(
            undefined,
            error.config?.meta?.routerFullPath
          );
        }
        showErrorMessage(error?.response?.data?.message || "请求失败");
        return Promise.reject(error);
      }
    );
  }
}

const otherHttp = new HttpClient({
  baseURL: getServiceBaseUrl("other", import.meta.env),
  timeout: 1000 * 10,
});

export { otherHttp };
  1. 导入otherHttp使用:
    ts
    export function getOtherData() {
      return otherHttp.request({});
    }