1、创建文件
- 在
utils文件夹下新建request.ts(或request.js)
// 安装
yarn add axios
//Ts安装qs
yarn add @types/qs
yarn add qs
//Js安装qs
yarn add qs
2、默认配置
// 环境变量获取当前URL
const { VITE_BASE_URL } = import.meta.env
// 如果遇到跨域问题到vite.config配好代理后在baseURL处修改逻辑
const baseURL = VITE_BASE_URL
const instance = axios.create({
baseURL, // 请求地址
timeout: 8000, // 请求超时
withCredentials: true, // 跨域请求时发送Cookie
})
-
withCredentials 有什么用?
跨域请求是否提供凭据信息(cookie、HTTP认证及客户端SSL证明等),也可以简单的理解为,当前请求为跨域类型时是否在请求中协带cookie。
需要注意是,当配置了xhr.withCredentials = true时,必须在后端增加 response 头信息Access-Control-Allow-Origin,且必须指定域名,而不能指定为*。
如果在同域下配置xhr.withCredentials,无论配置true还是false,效果都会相同,且会一直提供凭据信息(cookie、HTTP认证及客户端SSL证明等) -
根据
post的data的类型来配置header的content-type
特别注意的是data为Form Data类型,由浏览器自己设定content-type,如果你自定义了,那么代码中会删除;同时,请求的时候,不要对formdata做处理 -
post提交数据方式
axios默认添加了对请求方法'post', 'put', 'patch'添加了默认请求头'Content-Type': 'application/x-www-form-urlencoded',但是如果传参data是json对象,那么请求头就会更改为Content-Type':application/json;charset=utf-8。
如果传参data不是json对象,这个时候我们就可以按照下面的方法,也就是transformRequest对请求数据改造,提交的数据按照 key1=val1&key2=val2 的方式进行编码,key 和 val都进行了URL转码。大部分服务端语言都对这种方式有很好的支持,同时我们可以不设置请求头,使用默认即可。 -
application/json(推荐)
application/json:Content-Type告诉服务端消息主体是序列化后的JSON字符串
instance.defaults.headers.post['Content-Type'] = 'application/json'
3、请求截器
- 在请求被
then或catch处理前拦截它们。 - paramsSerializer qs序列化
// 添加请求拦截器
instance.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
if (config.headers) {
// 是否需要处理token。在此写token逻辑
//const token = Storage.get(accessToken)
//if (token) {
//config.headers['x-sankuan-token'] = token
//}
config.headers['X-Requested-With'] = 'XMLHttpRequest'
}
if (config.method === 'get') {
// 为get请求paramsSerializer序列化
config.paramsSerializer = function (params) {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
}
return config
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error)
}
)
4、响应拦截器
-
在响应被
then或catch处理前拦截它们。 -
对状态码建议单独写一个enums
-
新建文件夹
enums后再新建httpEnum.ts文件
/**
* @description: 状态码
*/
export enum StateEnum {
/** 200 请求成功 */
OK = 200,
/** 201 创建成功 */
CREATED = 201,
/** 204 成功且不反回 body */
NO_CONTENT = 204,
/** 400 请求参数错误 */
INVALID_REQUEST = 400,
/** 401 未登录 */
UNAUTHORIZED = 401,
/** 403 权限不足 */
FORBIDDEN = 403,
/** 404 资源不存在或资源不属于该用户 */
NOT_FOUND = 404,
/** 500 服务器发生错误,用户将无法判断发出的请求是否成功 */
INTERNAL_SERVER_ERROR = 500,
}
instance.interceptors.response.use(
(response) => {
return response.data || {}
},
(error) => {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
const res = error.response || {}
const status = parseInt(res.status)
const message = res.data?.error || '网络错误'
switch (status) {
case StateEnum.INVALID_REQUEST:
alert(message)
break
case StateEnum.FORBIDDEN:
case StateEnum.UNAUTHORIZED:
alert(message)
router.push(`/login`)
break
case StateEnum.NOT_FOUND:
alert(message)
break
case StateEnum.INTERNAL_SERVER_ERROR:
alert('服务器发生错误,请稍后再试!')
break
default:
alert('请求超时,请检查您的网络!')
break
}
return Promise.reject(error)
}
)
5、导出
export default instance
6、最终request
import qs from 'qs'
import axios from 'axios'
import router from '@/router'
import { StateEnum } from '@/enums/httpEnum'
// 环境变量获取当前URL
const { VITE_BASE_URL } = import.meta.env
// 如果遇到跨域问题到vite.config配好代理后在baseURL处修改逻辑
const baseURL = VITE_BASE_URL
const instance = axios.create({
baseURL, // 请求地址
timeout: 8000, // 请求超时
withCredentials: true, // 跨域请求时发送Cookie
})
// 根据post的data的类型来配置header的content-type
instance.defaults.headers.post['Content-Type'] = 'application/json'
// 添加请求拦截器
instance.interceptors.request.use(
(config) => {
// 在发送请求之前做些什么
if (config.headers) {
// 是否需要处理token。在此写token逻辑
//const token = Storage.get(accessToken)
//if (token) {
//config.headers['x-sankuan-token'] = token
//}
config.headers['X-Requested-With'] = 'XMLHttpRequest'
}
if (config.method === 'get') {
// 为get请求paramsSerializer序列化
config.paramsSerializer = function (params) {
return qs.stringify(params, { arrayFormat: 'repeat' })
}
}
return config
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error)
}
)
// 响应拦截器
instance.interceptors.response.use(
(response) => {
return response.data || {}
},
(error) => {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
const res = error.response || {}
const status = parseInt(res.status)
const message = res.data?.error || '网络错误'
switch (status) {
case StateEnum.INVALID_REQUEST:
alert(message)
break
case StateEnum.FORBIDDEN:
case StateEnum.UNAUTHORIZED:
alert(message)
router.push(`/login`)
break
case StateEnum.NOT_FOUND:
alert(message)
break
case StateEnum.INTERNAL_SERVER_ERROR:
alert('服务器发生错误,请稍后再试!')
break
default:
alert('请求超时,请检查您的网络!')
break
}
return Promise.reject(error)
}
)
export default instance
6、使用request
-
新建文件夹
api后再新建user文件夹,再建两个ts文件index.ts和model.d.ts
index.ts用于封装axios请求。model.d.ts用于写请求的数据类型或获的数据类型(js忽略model.d.ts) -
index.ts
import request from '@/utils/request'
import { LoginReqParams, LoginResResult } from './model'
export const PostLogin = (params: LoginReqParams): Promise<LoginResResult> =>
request.post('/login', params)
export const GetMe = (): Promise<LoginResResult> =>
request.get('/me')
- model.d.ts
export interface LoginReqParams {
device: string
password: string
phone_number: string
}
export interface LoginResResult {
id: string
is_admin: boolean
name: string
x_sankuan_token: string
}
7、页面调用
<script setup lang="ts">
import { PostLogin, GetMe } from '@/api/user/index'
import { LoginReqParams } from '@/api/user/model'
const formInline = reactive<LoginReqParams>({
device: 'mobile',
password: '',
phone_number: '',
})
const onClick = async () => {
try {
// 成功的逻辑
await PostLogin(formInline)
const res = await GetMe()
} catch (e) {
// 失败的逻辑
}
}
</script>
8、建议
- 建议一个页面对应一个api下的文件夹(
login页面对应api下的user,news页面对应api下的news)便于后期维护