1.创建 service
目录——存放接口相关文件
service
创建目录结构如下
-
:前端直接与后端通信的层controller
-
:后端传给前端的dto
-
:前端传给后端的vo
-
:封装请求接口的request
2.安装axios依赖
使用 NPM:
npm install axios
使用 Yarn:
yarn axios
使用 PNPM:
pnpm i aixos
安装成功
3.封装axios请求类
在
service
目录下创建
instance.ts
文件,创建一个axios请求类
export default class AxiosInstance {
}
创建类型
/** 请求的相关类型 */
declare namespace Service {
/**
* 请求的错误类型:
* - axios: axios错误:网络错误, 请求超时, 默认的兜底错误
* - http: 请求成功,响应的http状态码非200的错误
* - backend: 请求成功,响应的http状态码为200,由后端定义的业务错误
*/
type RequestErrorType = 'axios' | 'http' | 'backend';
/** 请求错误 */
interface RequestError {
/** 请求服务的错误类型 */
type: RequestErrorType;
/** 错误码 */
code: string | number;
/** 错误信息 */
msg: string;
}
/** 后端接口返回的数据结构配置 */
interface BackendResultConfig {
/** 表示后端请求状态码的属性字段 */
codeKey: string;
/** 表示后端请求数据的属性字段 */
dataKey: string;
/** 表示后端消息的属性字段 */
msgKey: string;
/** 后端业务上定义的成功请求的状态 */
successCode: number | string;
}
}
定义属性
instance: AxiosInstance;
backendConfig: Service.BackendResultConfig;
定义构造方法
/**
*
* @param axiosConfig - axios配置
* @param backendConfig - 后端返回的数据配置
*/
constructor(
axiosConfig: AxiosRequestConfig,
backendConfig: Service.BackendResultConfig = {
codeKey: 'code',
dataKey: 'data',
msgKey: 'message',
successCode: 200
}
) {
this.backendConfig = backendConfig;
this.instance = axios.create(axiosConfig);
}
4.设置请求拦截器
搭建整体架构
/** 设置请求拦截器 */
setInterceptor(){
// 拦截请求
this.instance.interceptors.request.use(
)
// 拦截响应
this.instance.interceptors.response.use(
);
}
①拦截请求
处理正常请求
在拦截请求处可添加对 token
或者数据的转换
async (config:AxiosRequestConfig<any>) => {
const handleConfig = {...config};
if (handleConfig.headers) {
// 数据转换
// 可在此处添加设置token
}
},
添加基本配置config文件
放在utils文件目录下
/** 请求超时时间 */
export const REQUEST_TIMEOUT = 60 * 1000;
/** 错误信息的显示时间 */
export const ERROR_MSG_DURATION = 3 * 1000;
/** 默认的请求错误code */
export const DEFAULT_REQUEST_ERROR_CODE = 'DEFAULT';
/** 默认的请求错误文本 */
export const DEFAULT_REQUEST_ERROR_MSG = '请求错误~';
/** 请求超时的错误code(为固定值:ECONNABORTED) */
export const REQUEST_TIMEOUT_CODE = 'ECONNABORTED';
/** 请求超时的错误文本 */
export const REQUEST_TIMEOUT_MSG = '请求超时~';
/** 网络不可用的code */
export const NETWORK_ERROR_CODE = 'NETWORK_ERROR';
/** 网络不可用的错误文本 */
export const NETWORK_ERROR_MSG = '网络不可用~';
/** 请求不成功各种状态的错误 */
export const ERROR_STATUS = {
400: '400: 请求出现语法错误~',
401: '401: 用户未授权~',
403: '403: 服务器拒绝访问~',
404: '404: 请求的资源不存在~',
405: '405: 请求方法未允许~',
408: '408: 网络请求超时~',
500: '500: 服务器内部错误~',
501: '501: 服务器未实现请求功能~',
502: '502: 错误网关~',
503: '503: 服务不可用~',
504: '504: 网关超时~',
505: '505: http版本不支持该请求~',
[DEFAULT_REQUEST_ERROR_CODE]: DEFAULT_REQUEST_ERROR_MSG
};
/** 不弹出错误信息的code */
export const NO_ERROR_MSG_CODE: (string | number)[] = [];
/** token失效需要刷新token的code(这里的66666只是个例子,需要将后端表示token过期的code填进来) */
export const REFRESH_TOKEN_CODE: (string | number)[] = [66666];
统一处理请求异常
(axiosError: AxiosError) => {
console.log('axiosError',axiosError)
}
②拦截响应
// 拦截响应
this.instance.interceptors.response.use(
(response:AxiosResponse<any, any>)=>{
// 网络返回码
const { status } = response;
if (status === 200 || status < 300 || status === 304) {
const backend = response.data;
const { codeKey, dataKey, successCode } = this.backendConfig;
// 请求成功
if (backend[codeKey] === successCode) {
return backend[dataKey];
}
}
}
);
5.添加创建请求接口
添加请求接口类型
添加请求参数类型
interface RequestParam {
url: string;
method?: RequestMethod;
data?: any;
axiosConfig?: AxiosRequestConfig;
}
创建请求
/**
* 创建请求
* @param axiosConfig - axios配置
* @param backendConfig - 后端接口字段配置
*/
export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {
const customInstance = new CustomAxiosInstance(axiosConfig, backendConfig);
/**
* 异步promise请求
* @param param - 请求参数
* - url: 请求地址
* - method: 请求方法(默认get)
* - data: 请求的body的data
* - axiosConfig: axios配置
*/
async function asyncRequest<T>(param: RequestParam): Promise<Service.RequestResult<T>> {
const { url } = param;
const method = param.method || 'get';
const { instance } = customInstance;
const res = await getRequestResponse({
instance,
method,
url,
data: param.data,
config: param.axiosConfig
});
return res;
}
}
async function getRequestResponse(params: {
instance: AxiosInstance;
method: RequestMethod;
url: string;
data?: any;
config?: AxiosRequestConfig;
}) {
const { instance, method, url, data, config } = params;
let res: any;
if (method === 'get' || method === 'delete') {
res = await instance[method](url, config);
} else {
res = await instance[method](url, data, config);
}
return res;
}
添加请求方法(get、post等)
/**
* get请求
* @param url - 请求地址
* @param config - axios配置
*/
function get<T>(url: string, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, method: 'get', axiosConfig: config });
}
/**
* post请求
* @param url - 请求地址
* @param data - 请求的body的data
* @param config - axios配置
*/
function post<T>(url: string, data?: any, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, method: 'post', data, axiosConfig: config });
}
/**
* put请求
* @param url - 请求地址
* @param data - 请求的body的data
* @param config - axios配置
*/
function put<T>(url: string, data?: any, config?: AxiosRequestConfig) {
return asyncRequest<T>({ url, method: 'put', data, axiosConfig: config });
}
/**
* delete请求
* @param url - 请求地址
* @param config - axios配置
*/
function handleDelete<T>(url: string, config: AxiosRequestConfig) {
return asyncRequest<T>({ url, method: 'delete', axiosConfig: config });
}
return {
get,
post,
put,
delete: handleDelete
};
6.创建请求接口
创建环境配置
在根目录下创建
.env-config.ts
对请求服务进行配置
/** 请求服务的环境配置 */
type ServiceEnv = Record<ServiceEnvType, ServiceEnvConfig>;
/** 不同请求服务的环境配置 */
const serviceEnv: ServiceEnv = {
dev: {
url: 'http://localhost:8080',
urlPattern: '/url-pattern',
secondUrl: 'http://localhost:8081',
secondUrlPattern: '/second-url-pattern'
},
test: {
url: 'http://localhost:8080',
urlPattern: '/url-pattern',
secondUrl: 'http://localhost:8081',
secondUrlPattern: '/second-url-pattern'
},
prod: {
url: 'http://localhost:8080',
urlPattern: '/url-pattern',
secondUrl: 'http://localhost:8081',
secondUrlPattern: '/second-url-pattern'
}
};
/**
* 获取当前环境模式下的请求服务的配置
* @param env 环境
*/
export function getServiceEnvConfig(env: ImportMetaEnv) {
const { VITE_SERVICE_ENV = 'dev' } = env;
const config = serviceEnv[VITE_SERVICE_ENV];
return config;
}
设置对应的类型
在typing中创建
.env.d.ts
/**
*后台服务的环境类型
* - dev: 后台开发环境
* - test: 后台测试环境
* - prod: 后台生产环境
*/
type ServiceEnvType = 'dev' | 'test' | 'prod';
/** 后台服务的环境配置 */
interface ServiceEnvConfig {
/** 请求地址 */
url: string;
/** 匹配路径的正则字符串, 用于拦截地址转发代理(任意以 /开头 + 字符串, 单个/不起作用) */
urlPattern: '/url-pattern';
/** 另一个后端请求地址(有多个不同的后端服务时) */
secondUrl: string;
/** 匹配路径的正则字符串, 用于拦截地址转发代理(任意以 /开头 + 字符串, 单个/不起作用) */
secondUrlPattern: '/second-url-pattern';
}
interface ImportMetaEnv {
/** 项目基本地址 */
readonly VITE_BASE_URL: string;
/** 项目名称 */
readonly VITE_APP_NAME: string;
/** 项目标题 */
readonly VITE_APP_TITLE: string;
/** 项目描述 */
readonly VITE_APP_DESC: string;
/** iconify图标作为组件的前缀 */
readonly VITE_ICON_PREFFIX: string;
/**
* 本地SVG图标作为组件的前缀, 请注意一定要包含 VITE_ICON_PREFFIX
* - 格式 {VITE_ICON_PREFFIX}-{本地图标集合名称}
* - 例如:icon-local
*/
readonly VITE_ICON_LOCAL_PREFFIX: string;
/** 后端服务的环境类型 */
readonly VITE_SERVICE_ENV?: ServiceEnvType;
/** hash路由模式 */
readonly VITE_HASH_ROUTE?: 'Y' | 'N';
/** 是否是部署的vercel */
readonly VITE_VERCEL?: 'Y' | 'N';
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
创建接口
import { getServiceEnvConfig } from '~/.env-config';
import { createRequest } from "./request";
const { url } = getServiceEnvConfig(import.meta.env);
export const web = createRequest({baseURL: url})
7.测试登录接口
创建user子目录
import { web } from "../../request";
export function login(){
return web.post<String>('/user/login')
}
统一导出
使用
<script setup lang="ts">
const reuqest = () => userRequest.login()
</script>
<template>
<h1 class="text-25px text-#ff6700 bg-#ccc">{{ msg }}</h1>
<button type="button" @click="reuqest">请求接口</button>
<div class="card">
<button type="button" @click="count++">count is {{ count }}</button>
<p>
Edit
<code>components/HelloWorld.vue</code> to test HMR
</p>
</div>
</template>
<style scoped>
.read-the-docs {
color: #888;
}
</style>
完成~