合集 - Vite4+Typescript+Vue3+Pinia 从零搭建
项目代码同步至码云 weiz-vue3-template
基于 axios
封装请求,支持多域名请求地址
安装
封装
utils
目录下新建 request
文件夹,并新建 index.ts
、request.ts
和 status.ts
文件。
1. status.ts
文件主要是封装状态码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| export const ErrMessage = (status: number | string): string => { let message: string = '' switch (status) { case 400: message = '请求错误!请您稍后重试' break case 401: message = '未授权!请您重新登录' break case 403: message = '当前账号无访问权限!' break case 404: message = '访问的资源不存在!请您稍后重试' break case 405: message = '请求方式错误!请您稍后重试' break case 408: message = '请求超时!请您稍后重试' break case 500: message = '服务异常!请您稍后重试' break case 501: message = '不支持此请求!请您稍后重试' break case 502: message = '网关错误!请您稍后重试' break case 503: message = '服务不可用!请您稍后重试' break case 504: message = '网关超时!请您稍后重试' break default: message = '请求失败!请您稍后重试' } return message }
|
此时,eslint会报 switch
前面的空格错误,需要修改 .eslintrc.cjs
里的 indent
,修改后,错误消失。
1 2 3 4
| rules: { indent: ['error', 2, { SwitchCase: 1 }] }
|
2. request.ts
主要是封装 axios
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
import axios, { AxiosInstance, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from 'axios' import { ErrMessage } from './status'
interface Data<T> { data: T code: string success: boolean }
interface RequestInternalAxiosRequestConfig extends InternalAxiosRequestConfig { showLoading?: boolean }
interface InterceptorHooks { requestInterceptor?: (config: RequestInternalAxiosRequestConfig) => RequestInternalAxiosRequestConfig requestInterceptorCatch?: (error: any) => any responseInterceptor?: (response: AxiosResponse) => AxiosResponse responseInterceptorCatch?: (error: any) => any }
interface RequestConfig extends AxiosRequestConfig { showLoading?: boolean interceptorHooks?: InterceptorHooks }
class Request { config: RequestConfig instance: AxiosInstance loading?: boolean
constructor(options: RequestConfig) { this.config = options this.instance = axios.create(options) this.setupInterceptor() }
request<T = any>(config: RequestConfig): Promise<T> { return new Promise((resolve, reject) => { this.instance .request<any, Data<T>>(config) .then((res) => { resolve(res.data) }) .catch((err) => { reject(err) }) }) }
get<T = any>(url: string, params?: object, _object = {}): Promise<T> { return this.request({ url, params, ..._object, method: 'GET' }) }
post<T = any>(url: string, params?: object, _object = {}): Promise<T> { return this.request({ url, params, ..._object, method: 'POST' }) }
delete<T = any>(url: string, params?: object, _object = {}): Promise<T> { return this.request({ url, params, ..._object, method: 'DELETE' }) }
patch<T = any>(url: string, params?: object, _object = {}): Promise<T> { return this.request({ url, params, ..._object, method: 'PATCH' }) }
put<T = any>(url: string, params?: object, _object = {}): Promise<T> { return this.request({ url, params, ..._object, method: 'PUT' }) }
setupInterceptor(): void {
this.instance.interceptors.request.use((config: RequestInternalAxiosRequestConfig) => { if (config.showLoading) { this.loading = true } return config }) this.instance.interceptors.response.use( (res) => { if (this.loading) this.loading = false return res }, (err) => { const { response, message } = err if (this.loading) this.loading = false const messageStr = response ? ErrMessage(response.status) : message || '请求失败,请重试' window.alert(messageStr) return Promise.reject(err) } )
this.instance.interceptors.request.use( this.config?.interceptorHooks?.requestInterceptor, this.config?.interceptorHooks?.requestInterceptorCatch ) this.instance.interceptors.response.use( this.config?.interceptorHooks?.responseInterceptor, this.config?.interceptorHooks?.responseInterceptorCatch ) } }
export default Request
|
3. index.ts
主要是创建 Request
实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
|
import Request from './request' import { getToken } from '@/utils/auth'
const defRequest = new Request({ baseURL: 'https://mock.mengxuegu.com/mock/65421527a6dde808a695e96d/official/', timeout: 5000, showLoading: true, interceptorHooks: { requestInterceptor: (config) => { const token = getToken() if (token) { config.headers.Authorization = token } return config }, requestInterceptorCatch: (err) => { return err }, responseInterceptor: (res) => { return res.data }, responseInterceptorCatch: (err) => { return Promise.reject(err) } } })
export { defRequest }
|
使用
src
目录下新建 api
文件夹,并新建 login.ts
1. login.ts
1 2 3 4 5 6
| import { defRequest } from '../utils/request'
export const loginApi = (params: any) => { return defRequest.post<any>('/login', params, { showLoading: false, timeout: 1000 }) }
|
2. 修改 login.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| <script setup lang="ts"> import { ref } from 'vue' import { storeToRefs } from 'pinia' import { useUserStore } from '@store/user' import { loginApi } from '@/api/login'
defineOptions({ name: 'V-login' })
const userStore = useUserStore() const { userInfo, token } = storeToRefs(userStore) let userName = ref(userInfo.value.name) let userToken = ref(token)
const updateUserName = () => { userStore.setUserInfo({ name: userName.value }) } const updateUserToken = () => { userStore.setToken(userToken.value) }
const login = () => { loginApi({ name: userName.value }) .then((res) => { userName.value = res.name userToken.value = res.token updateUserToken() }) .catch((err) => { console.log(err) }) } </script>
<template> <div>login page</div> name: <input type="text" v-model="userName" @input="updateUserName" /> <br /> token: <input type="text" v-model="userToken" /> <hr /> <button @click="login">login</button> </template>
<style scoped></style>
|
点击 login
按钮,即可看到请求。
说明
对于 axios
的封装和使用,这里要说明几点:
1. 为什么要使用 InternalAxiosRequestConfig
axios 源码有修改,拦截器传入和返回的参数不再是 AxiosRequestConfig
,而是这个新类型 InternalAxiosRequestConfig
想要具体了解,可以查看这篇博文 https://blog.csdn.net/huangfengnt/article/details/131490913
2. Request
里的 config
参数
constructor 里的 this.config
会接受所有实例参数,所以通用实例拦截里使用的是 this.config?.xxx
通用拦截里使用的是 config.showLoading
,而不是 this.config.showLoading
,是为了我们在实际的 api/login.ts
里可以再传入 showLoading
,以满足我们单个请求的要求。而通过 this.config
里获取的配置是 request/index.ts
里传入的配置。在 config.showLoading
之前我们可以打印下这两个 config
,console.log(this.config, config)
结果如下:
如果在 login.ts
里不传入 showLoading
,那么 config.showLoading
会去拿通用实例 request/index.ts
里的 showLoading
。
当然如果不需要全局加载动画,整个 loading
也都可以去掉
3. 总结下 request/index.ts
和 api/login.ts
里的参数有什么不同
request/index.ts
里可以建多个实例,一般以 baseURL
来判断是否要多个,它的参数是当前url下的通用参数,拦截规则也是;
api/login.ts
是具体的请求,它的大部分参数是url和请求传参。同一个 baseURL
下有的请求有特殊的要求,那你就可以去加一些参数。
总的来说,request/index.ts
是对 baseURL
一样的请求的封装,request/request.ts
是对所有请求的封装
4. 优化
- 因为 Easy Mock 的接口支持跨域,所以没有配到代理里去,如果是正常开发接口,还需要修改
vite.config.ts
里的 proxy
。不过我们之前的教程里已有代理配置说明,这里便不再赘述
baseURL
还可以放在 env
变量里,以便区分开发环境和生产环境
- 删除
loading
,这里只是为了提供一种思路😂