思路

index.ts 我们需要在src的根目录下创建一个axios文件夹,其中创建一个index.ts文件,这个文件主要用来封装axios的配置(实例化请求配置、请求拦截器、相应拦截器)及相应的方法(登录跳转、消息提示、错误处理等)
base.ts 这个文件主要用于项目扩展的情况下 不同模块需要调用不同接口(请求的base地址 baseURL )而前期做的准备,便于后期的维护
request.ts 主要用于封装基于axios配置的get/post/put/delete等使用方法。
api.ts 在后面的 main.ts 中引入该模块,包括所有接口数据信息写入该文件中。

  1. // index.ts
  2. import axios, { AxiosRequestConfig, Method } from "axios";
  3. import router from "@/router";
  4. import store from "@/store";
  5. import { message } from 'ant-design-vue'
  6. import { storage } from "../storage/storage";
  7. import { dataList } from "@/components/aspin/data";
  8. /**
  9. * 跳转登录页
  10. * 携带当前页面路由,以期在登录页面完成登录后返回当前页面
  11. */
  12. const toLogin = () => {
  13. router.replace({
  14. name: 'LoginPage',
  15. });
  16. }
  17. /**
  18. * 请求失败后的错误统一处理
  19. * @param {Number} status 请求失败的状态码
  20. */
  21. const errorHandle = (status: number, other: string) => {
  22. // 状态码判断
  23. switch (status) {
  24. case 302: message.error('接口重定向了!');
  25. break;
  26. case 400:
  27. message.error("发出的请求有错误,服务器没有进行新建或修改数据的操作==>" + status)
  28. break;
  29. // 401: 未登录
  30. // 未登录则跳转登录页面,并携带当前页面的路径
  31. // 在登录成功后返回当前页面,这一步需要在登录页操作。
  32. case 401: //重定向
  33. message.error("token:登录失效==>" + status + ":" + store.state.Roles)
  34. storage.remove(store.state.Roles)
  35. storage.get(store.state.Roles)
  36. router.replace({
  37. path: '/Login',
  38. });
  39. break;
  40. // 403 token过期
  41. // 清除token并跳转登录页
  42. case 403:
  43. message.error("登录过期,用户得到授权,但是访问是被禁止的==>" + status)
  44. // store.commit('token', null);
  45. setTimeout(() => {
  46. router.replace({
  47. path: '/Login',
  48. });
  49. }, 1000);
  50. break;
  51. case 404:
  52. message.error("网络请求不存在==>" + status)
  53. break;
  54. case 406:
  55. message.error("请求的格式不可得==>" + status)
  56. break;
  57. case 408: message.error(" 请求超时!")
  58. break;
  59. case 410:
  60. message.error("请求的资源被永久删除,且不会再得到的==>" + status)
  61. break;
  62. case 422:
  63. message.error("当创建一个对象时,发生一个验证错误==>" + status)
  64. break;
  65. case 500:
  66. message.error("服务器发生错误,请检查服务器==>" + status)
  67. break;
  68. case 502:
  69. message.error("网关错误==>" + status)
  70. break;
  71. case 503:
  72. message.error("服务不可用,服务器暂时过载或维护==>" + status)
  73. break;
  74. case 504:
  75. message.error("网关超时==>" + status)
  76. break;
  77. default:
  78. message.error("其他错误错误==>" + status)
  79. }
  80. }
  81. // 定义接口
  82. interface PendingType {
  83. url?: string;
  84. method?: Method;
  85. params: any;
  86. data: any;
  87. cancel: any;
  88. }
  89. // 取消重复请求
  90. const pending: Array<PendingType> = [];
  91. const CancelToken = axios.CancelToken;
  92. // 移除重复请求
  93. const removePending = (config: AxiosRequestConfig) => {
  94. for (const key in pending) {
  95. const item: number = +key;
  96. const list: PendingType = pending[key];
  97. // 当前请求在数组中存在时执行函数体
  98. if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) {
  99. // 执行取消操作
  100. list.cancel('操作太频繁,请稍后再试');
  101. // 从数组中移除记录
  102. pending.splice(item, 1);
  103. }
  104. }
  105. };
  106. /* 实例化请求配置 */
  107. const instance = axios.create({
  108. headers: {
  109. //php 的 post 传输请求头一定要这个 不然报错 接收不到值
  110. "Content-Type": "application/json;charset=UTF-8",
  111. "Access-Control-Allow-Origin-Type": '*'
  112. },
  113. // 请求时长
  114. timeout: 1000 * 30,
  115. // 请求的base地址 TODO:这块以后根据不同的模块调不同的api
  116. baseURL: process.env.VUE_APP_API_URL,
  117. // ? "测试"
  118. // : "正式",
  119. // 表示跨域请求时是否需要使用凭证
  120. withCredentials: false,
  121. })
  122. /**
  123. * 请求拦截器
  124. * 每次请求前,如果存在token则在请求头中携带token
  125. */
  126. instance.interceptors.request.use(
  127. config => {
  128. removePending(config);
  129. config.cancelToken = new CancelToken((c) => {
  130. pending.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c });
  131. });
  132. // 登录流程控制中,根据本地是否存在token判断用户的登录情况
  133. // 但是即使token存在,也有可能token是过期的,所以在每次的请求头中携带token
  134. // 后台根据携带的token判断用户的登录情况,并返回给我们对应的状态码
  135. // 而后我们可以在响应拦截器中,根据状态码进行一些统一的操作。
  136. // const token = store.state.token;
  137. // localStorage.setItem('token', token);
  138. if (storage.get(store.state.Roles)) {
  139. store.state.Roles
  140. config.headers.Authorization = storage.get(store.state.Roles);
  141. }
  142. return config;
  143. },
  144. error => {
  145. message.error(error.data.error.message);
  146. return Promise.reject(error.data.error.message);
  147. }
  148. )
  149. // 响应拦截器
  150. instance.interceptors.response.use(function (config) {
  151. dataList.show = true
  152. removePending(config.config);
  153. // 请求成功
  154. if (config.status === 200 || config.status === 204) {
  155. setTimeout(() => {
  156. dataList.show = false
  157. }, 400)
  158. return Promise.resolve(config);
  159. } else {
  160. return Promise.reject(config);
  161. }
  162. // 请求失败
  163. }, function (error) {
  164. const { response } = error;
  165. if (response) {
  166. errorHandle(response.status, response.data.message);
  167. // 超时重新请求
  168. const config = error.config;
  169. // 全局的请求次数,请求的间隙
  170. const [RETRY_COUNT, RETRY_DELAY] = [3, 1000];
  171. if (config && RETRY_COUNT) {
  172. // 设置用于跟踪重试计数的变量
  173. config.__retryCount = config.__retryCount || 0;
  174. // 检查是否已经把重试的总数用完
  175. if (config.__retryCount >= RETRY_COUNT) {
  176. return Promise.reject(response || { message: error.message });
  177. }
  178. // 增加重试计数
  179. config.__retryCount++;
  180. // 创造新的Promise来处理指数后退
  181. const backoff = new Promise<void>((resolve) => {
  182. setTimeout(() => {
  183. resolve();
  184. }, RETRY_DELAY || 1);
  185. });
  186. // instance重试请求的Promise
  187. return backoff.then(() => {
  188. return instance(config);
  189. });
  190. }
  191. return Promise.reject(response);
  192. } else {
  193. // 处理断网的情况
  194. // eg:请求超时或断网时,更新state的network状态
  195. // network状态在app.vue中控制着一个全局的断网提示组件的显示隐藏
  196. // 后续增加断网情况下做的一些操作
  197. store.commit('networkState', false);
  198. }
  199. }
  200. )
  201. // 只需要考虑单一职责,这块只封装axios
  202. export default instance
  1. // base.ts
  2. export class Base {
  3. /* 公共模块 */
  4. static env = process.env.NODE_ENV === "development"
  5. ? "http://localhost:8087"
  6. : "https://produceCommon.com(生产线地址)"
  7. }

也可以直接在index.ts中设置这样就不需要base.ts

  1. const instance = axios.create({
  2. // 请求的base地址 TODO:这块以后根据不同的模块调不同的api
  3. baseURL: process.env.VUE_APP_API_URL,
  4. })

需要配置根目录
.env.development

  1. NODE_ENV = 'development'
  2. # VUE_APP_API_URL = 'https://localhost:5001/'
  3. VUE_APP_API_URL = 'http://localhost:8087/'

.env.production

  1. # 生产环境的请求接口
  2. NODE_ENV = 'production'
  3. VUE_APP_API_URL = 'http://129.xxxxx/'
  1. // request.ts
  2. import axios from "./index";
  3. import qs from "qs";
  4. export class Request {
  5. /**
  6. * get方法
  7. * @param {string} url 路径
  8. * @param {object} params 参数
  9. */
  10. static get = (url: string, params?: any) => {
  11. return new Promise((resolve, reject) => {
  12. axios.get(url, { params: params }).then(res => {
  13. resolve(res);
  14. }).catch(err => {
  15. reject(err);
  16. })
  17. })
  18. }
  19. static post = (url: string, params?: any) => {
  20. return new Promise((resolve, reject) => {
  21. axios.post(url, qs.stringify(params)).then(res => {
  22. resolve(res);
  23. }).catch(err => {
  24. reject(err);
  25. })
  26. })
  27. }
  28. }
  1. // 其中使用 install 的目的在于 ts在main.ts中
  2. // 不能通过Vue.prototype.$Api这个方式直接调用
  3. //,在全局方法中会说到使用 插件的方式去挂载。
  4. // api.ts
  5. import { Base } from "./base";
  6. import { Request } from "./request";
  7. class api {
  8. /* api接口模块 */
  9. public static article = {
  10. // 直接在index.ts中设置不需要Base模块
  11. genre: () => Request.get('/api/SnArticle/GetCountAsync'),
  12. // 基于Base模块封装调用
  13. genres: () => Request.get(`${Base.env}/api/SnArticle/GetCountAsync`),
  14. }
  15. }
  16. export {
  17. api
  18. }
  1. import { api } from '../../utils/api/api'
  2. import { onMounted } from 'vue'
  3. onMounted(async () => {
  4. await QueryAll()
  5. api.article.genre().then((res: any) => {
  6. console.log('genre' + res.data)
  7. })
  8. api.article.genres().then((res: any) => {
  9. console.log('genres' + res.data)
  10. })
  11. })