目录
1、为什么要封装axios
axios.get().then() 这样的书写,会有缺陷,在以下缺点
1、请求头能不能统一处理
解决: 创建一个 request/request.js 文件夹,
在里面可以使用axios.create创建实例对象
也可以在里面设置 请求 与 响应 拦截器2、不便于接口的统一管理
解决:在 request 文件夹加多一个api文件来管理所有接口,
(会先导入rerequest.js的实例)
并使用函数,不然每次发请求时都会跑一次api文件3、容易出现回调地狱
LogoutAPI () 最终的结果是返回proise对象
解决:acync + await
await 后面一般放promise对象
注意:但封装axios后还是可以用 .then()
2、安装
npm install axios
3、封装request
先在 src 下创建一个 request 文件夹,并添加一个 request.ts 文件
import axios from 'axios'
// 创建axios实例
const request = axios.create({
baseURL: '',// 所有的请求地址前缀部分
timeout: 80000, // 请求超时时间(毫秒)
withCredentials: true,// 异步请求携带cookie
// headers: {
// 设置后端需要的传参类型
// 'Content-Type': 'application/json',
// 'token': x-auth-token',//一开始就要token
// 'X-Requested-With': 'XMLHttpRequest',
// },
})
// request拦截器
request.interceptors.request.use(
config => {
// 如果你要去localStor获取token,(如果你有)
// let token = localStorage.getItem("x-auth-token");
// if (token) {
//添加请求头
//config.headers["Authorization"]="Bearer "+ token
// }
return config
},
error => {
// 对请求错误做些什么
Promise.reject(error)
}
)
// response 拦截器
request.interceptors.response.use(
response => {
// 对响应数据做点什么
return response.data
},
error => {
// 对响应错误做点什么
return Promise.reject(error)
}
)
export default request
4、使用步骤
注意:因为get请求的参数需要`params`,它是即将与请求一起发送的 URL 参数,为了简写采用了ES6的解构,就是把下面的 params 解构,只有get 请求需要加多一层`params`。
其它请求,如 post 等请求等就不用解构,形参是什么都行。
步骤1:请求数据 ( 举例下面几个常用的 )
在 request 文件夹,再添加一个 api.ts 文件
定义接口格式:
export const 自定义接口名 = (形参:请求类型):返回类型 => instance.方法(路径,后端要的参数);
import instance from "./request";
//一般情况下,接口类型会放到一个文件
// 下面两个TS接口,表示要传的参数
interface ReqLogin {
name: string
paw: string
}
interface ReqStatus {
id: string
navStatus: string
}
// Res是返回的参数,T是泛型,需要自己定义,返回对数统一管理***
type Res<T> = Promise<ItypeAPI<T>>;
// 一般情况下响应数据返回的这三个参数,
// 但不排除后端返回其它的可能性,
interface ItypeAPI<T> {
data: T,//请求的数据,用泛型
msg: string | null // 返回状态码的信息,如请求成功等
code: number //返回后端自定义的200,404,500这种状态码
}
// post请求 ,没参数
export const LogoutAPI = (): Res<null> =>
instance.post("/admin/logout");
// post请求,有参数,如传用户名和密码
export const loginAPI = (data: ReqLogin): Res<string> =>
instance.post("/admin/login", data);
// post请求 ,没参数,但要路径传参
export const StatusAPI = (data: ReqStatus): Res<null> =>
instance.post(`/productCategory?ids=${data.id}&navStatus=${data.navStatus}`);
// get请求,没参数,
export const FlashSessionListApi = (): Res<null> =>
instance.get("/flashSession/list");
// get请求,有参数,路径也要传参 (也可能直接在这写类型,不过不建议,大点的项目会维护一麻烦)
export const ProductCategoryApi = (params: { parentId: number }): any =>
instance.get(`/productCategory/list/${params.parentId}`, { params });
// get请求,有参数,(如果你不会写类型也可以使用any,不过不建议,因为用了之后 和没写TS一样)
export const AdminListAPI = (params:any): any =>
instance.get("/admin/list", { params });
步骤2:在要请求的组件上使用
使用方式一:直接使用(和vue2在cretae上用一样,setup自带async,await在顶层可以直接使用)
<script setup lang="ts">
import { indexAPI} from "../../request/api";
//直接使用,一般用在进入页面入请求数据的接口
let res = await indexAPI()
console.log( "***" ,res);
</script>
使用方式二:使用 async / await,(setup虽然自带async,但单独用await只能在顶层使用,如果在函数下还是要async / await一起写)
<script setup lang="ts">
import { returnApplyListAPi } from "../../request/api";
const search = async(val: IUseTableParam) => {
let res = await returnApplyListAPi({
...val,
})
console.log( "***" ,res);
let { list, pageNum, pageSize, total } = res.data
console.log(list, pageNum, pageSize, total);
}
</script>
使用方式三:使用.then
<script setup lang="ts">
import { returnApplyListAPi} from "../../request/api";
const logout = () => {
returnApplyListAPi({
...val,
}).then((res) => {
console.log('***',res );
let { list, pageNum, pageSize, total } = res.data
})
};
</script>
5、代理
需要代理才写
上面的request 文件中
const request = axios.create({
// 这时你要代理
// 填写统一的前缀,就是你每次调用这个asiox返回的`request`时都会自动添加的前缀
// 比如:
// /admin/logout => /api/admin/logout
// /admin/login => /api/admin/login
// 当然你也可以写其它的
baseURL: '/api',
})
vite.config.ts 文件
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
//...
],
server: {
proxy: {
// 匹配请求的一整个路径的前面是否有符合的,和调用String.startsWith方法一样
// 比如上面请求 `'/api/admin/logout'.startsWith('/api')`,所以匹配成功
'/api': {
target: '后端请求的地址', // 代理的后端给的目标地址
// 开发模式,默认的127.0.0.1,开启后代理服务会把origin修改为目标地址
changeOrigin: true,
// secure: true, // 是否https接口
// ws: true, // 是否代理websockets
// 路径重写 ****
// 例子:
// 没有写 路径重写之前 的请求: http://localhost:8000/api/admin/logout
// 写了 路径重写之后 的请求:你写的target + admin/logout
rewrite: (path) => path.replace(/^\/api/, '')
}
}
}
})
6、更多请求配置
今天做了一个上传请求进度条的需求,顺便做下记录
在上面的封装基础上,在上传时我们有时要做一个进度百分比的功能,
这是因为不想单独把这个接口写到某个页面,封装到一起不好吗?找也好找改也好改
export const uploadDocument = (
params: { name: string, documentBase64: string },
onUploadProgress: (progressEvent:AxiosProgressEvent) => void
): Res<any> =>
//第二个是参数,和上面一样那样写,没有就写null
request.post('UploadFile/xxxx', params, {
onUploadProgress,//上传方法
});
onUploadProgress是asiox自己定制好的了,不要乱写
具体可以写什么,你可以按ctrl+左键点击post,然后再点击AxiosRequestConfig就知道能写什么了(使用的是vscode,其它的软件可能不太一样)
<el-upload
:auto-upload="false"
multiple
:show-file-list="false"
:limit="5"
:on-change="onChange">
<el-button type="primary">批量上传</el-button>
</el-upload>
// 每个上传文件时
const onChange = async (uploadFile: UploadFile, uploadFiles: UploadFiles) => {
// 转成base64
let base64: string = await new Promise((resolve, reject) => {
// 创建一个新的 FileReader 对象
let reader = new FileReader();
// 读取 File 对象
let raw = uploadFile.raw as File;
reader.readAsDataURL(raw);
// 加载完成后
reader.onload = function () {
// 将读取的数据转换为 base64 编码的字符串
const base64String = (reader.result! as string).split(',')[1];
// 解析为 Promise 对象,并返回 base64 编码的字符串
resolve(base64String);
};
// 加载失败时
reader.onerror = function () {
ElMessage.error('加载文件失败');
reject(new Error('Failed to load file'));
};
});
// 发起上传请求
let res = await uploadDocument(
{
name: uploadFile.name,
documentBase64: base64
},
// 做进度
(progressEvent) => {
let progress = (progressEvent.loaded / (progressEvent?.total ?? 0)) * 100;
// 该上传的进度
const progressFloor = Math.floor(progress);
console.log(uploadFile.name,'文件,目前进度为:',progressFloor);
}
);
// 你的后续处理。。。
};
然后具体的显示,在哪显示进度就自己写了,小文件可能一下字就完成了,可以找一些大的文件上传,