indexStore.ts 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
  2. import { showFullScreenLoading, tryHideFullScreenLoading } from "@/components/Loading/fullScreen";
  3. import { ElMessage } from "element-plus";
  4. import { ResultData } from "@/api/interface";
  5. import { ResultEnum } from "@/enums/httpEnum";
  6. import { checkStatus } from "./helper/checkStatus";
  7. import { AxiosCanceler } from "./helper/axiosCancel";
  8. import { useUserStore } from "@/stores/modules/user";
  9. import { localGet } from "@/utils";
  10. import router from "@/routers";
  11. import { cryptoUtil } from "@/utils/crypto";
  12. import { cryptoStrategy } from "@/utils/cryptoStrategy";
  13. import { applyAuthExpiredSideEffects, isAuthExpiredCode } from "@/api/helper/handleAuthExpired";
  14. export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
  15. loading?: boolean;
  16. cancel?: boolean;
  17. encrypt?: boolean; // 是否加密请求参数
  18. }
  19. const config = {
  20. // 默认地址请求地址,可在 .env.** 文件中修改
  21. baseURL: import.meta.env.VITE_API_URL_PLATFORM as string,
  22. // 设置超时时间
  23. timeout: ResultEnum.TIMEOUT as number,
  24. // 跨域时候允许携带凭证
  25. withCredentials: true
  26. };
  27. const axiosCanceler = new AxiosCanceler();
  28. class RequestHttp {
  29. service: AxiosInstance;
  30. public constructor(config: AxiosRequestConfig) {
  31. // instantiation
  32. this.service = axios.create(config);
  33. /**
  34. * @description 请求拦截器
  35. * 客户端发送请求 -> [请求拦截器] -> 服务器
  36. * token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
  37. */
  38. this.service.interceptors.request.use(
  39. (config: CustomAxiosRequestConfig) => {
  40. const userStore = useUserStore();
  41. // 重复请求不需要取消,在 api 服务中通过指定的第三个参数: { cancel: false } 来控制
  42. config.cancel ??= true;
  43. config.cancel && axiosCanceler.addPending(config);
  44. // 当前请求不需要显示 loading,在 api 服务中通过指定的第三个参数: { loading: false } 来控制
  45. config.loading ??= true;
  46. config.loading && showFullScreenLoading();
  47. if (config.headers && typeof config.headers.set === "function") {
  48. config.headers.set("Authorization", userStore.token);
  49. }
  50. // 加密处理
  51. if (cryptoStrategy.shouldEncrypt(config) && config.data) {
  52. try {
  53. config.data = cryptoUtil.encrypt(config.data);
  54. } catch (error) {
  55. return Promise.reject(error);
  56. }
  57. }
  58. return config;
  59. },
  60. (error: AxiosError) => {
  61. return Promise.reject(error);
  62. }
  63. );
  64. /**
  65. * @description 响应拦截器
  66. * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
  67. */
  68. this.service.interceptors.response.use(
  69. (response: AxiosResponse & { config: CustomAxiosRequestConfig }) => {
  70. const { data, config, headers } = response;
  71. axiosCanceler.removePending(config);
  72. config.loading && tryHideFullScreenLoading();
  73. // 解密处理
  74. if (cryptoStrategy.shouldDecrypt(config, headers, data)) {
  75. try {
  76. const decryptedData = cryptoUtil.decrypt(data);
  77. response.data = decryptedData;
  78. } catch (error) {
  79. console.error("解密失败:", error);
  80. ElMessage.error("响应数据解密失败");
  81. return Promise.reject(new Error("响应数据解密失败"));
  82. }
  83. }
  84. const processedData = response.data;
  85. // 登录失效 / 账号在别处登录
  86. if (isAuthExpiredCode(processedData.code)) {
  87. applyAuthExpiredSideEffects(processedData.msg, processedData.code);
  88. return Promise.reject(processedData);
  89. }
  90. // 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错)
  91. if (processedData.code && processedData.code !== ResultEnum.SUCCESS) {
  92. ElMessage.error(processedData.msg);
  93. return Promise.reject(processedData);
  94. }
  95. // 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑)
  96. return processedData;
  97. },
  98. async (error: AxiosError) => {
  99. const { response } = error;
  100. tryHideFullScreenLoading();
  101. // 请求超时 && 网络错误单独判断,没有 response
  102. if (error.message.indexOf("timeout") !== -1) ElMessage.error("请求超时!请您稍后重试");
  103. if (error.message.indexOf("Network Error") !== -1) ElMessage.error("网络错误!请您稍后重试");
  104. // 根据服务器响应的错误状态码,做不同的处理
  105. if (response?.data?.message) {
  106. ElMessage.error(response.data.message);
  107. } else {
  108. if (response) checkStatus(response.status);
  109. }
  110. // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面
  111. if (!window.navigator.onLine) router.replace("/500");
  112. return Promise.reject(error);
  113. }
  114. );
  115. }
  116. /**
  117. * @description 常用请求方法封装
  118. */
  119. get<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
  120. return this.service.get(url, { params, ..._object });
  121. }
  122. post<T>(url: string, params?: object | string, _object = {}): Promise<ResultData<T>> {
  123. console.log(config.baseURL, url, params);
  124. return this.service.post(url, params, _object);
  125. }
  126. put<T>(url: string, params?: object, _object = {}): Promise<ResultData<T>> {
  127. return this.service.put(url, params, _object);
  128. }
  129. delete<T>(url: string, params?: any, _object = {}): Promise<ResultData<T>> {
  130. return this.service.delete(url, { params, ..._object });
  131. }
  132. download(url: string, params?: object, _object = {}): Promise<BlobPart> {
  133. return this.service.post(url, params, { ..._object, responseType: "blob" });
  134. }
  135. /**
  136. * @description 文件上传方法(基于 XMLHttpRequest)
  137. * @param url 上传地址
  138. * @param formData FormData 对象
  139. * @param onProgress 上传进度回调函数 (progress: number) => void
  140. * @param baseURL 可选的基础URL,不传则使用默认baseURL
  141. * @returns Promise<ResultData<T>>
  142. */
  143. upload<T = any>(
  144. url: string,
  145. formData: FormData,
  146. onProgress?: (progress: number) => void,
  147. baseURL?: string
  148. ): Promise<ResultData<T>> {
  149. return new Promise((resolve, reject) => {
  150. const xhr = new XMLHttpRequest();
  151. const userStore = useUserStore();
  152. // 如果传入了 baseURL,使用传入的 baseURL;如果 URL 是完整 URL(以 http 开头),直接使用;否则使用默认 baseURL
  153. const fullUrl = baseURL ? `${baseURL}${url}` : url.startsWith("http") ? url : `${config.baseURL}${url}`;
  154. // 监听上传进度
  155. if (onProgress) {
  156. xhr.upload.addEventListener("progress", e => {
  157. if (e.lengthComputable) {
  158. const percentComplete = Math.round((e.loaded / e.total) * 100);
  159. onProgress(percentComplete);
  160. }
  161. });
  162. }
  163. // 监听请求完成
  164. xhr.addEventListener("load", () => {
  165. if (xhr.status >= 200 && xhr.status < 300) {
  166. try {
  167. const response = JSON.parse(xhr.responseText);
  168. // 统一处理响应,与 axios 拦截器保持一致
  169. if (isAuthExpiredCode(response.code)) {
  170. applyAuthExpiredSideEffects(response.msg, response.code);
  171. reject(response);
  172. return;
  173. }
  174. if (response.code && response.code !== ResultEnum.SUCCESS) {
  175. ElMessage.error(response.msg);
  176. reject(response);
  177. return;
  178. }
  179. resolve(response);
  180. } catch (error) {
  181. reject(new Error("响应解析失败"));
  182. }
  183. } else {
  184. const errorMsg = `上传失败: ${xhr.status} ${xhr.statusText}`;
  185. ElMessage.error(errorMsg);
  186. reject(new Error(errorMsg));
  187. }
  188. });
  189. // 监听请求错误
  190. xhr.addEventListener("error", () => {
  191. const errorMsg = "网络错误!请您稍后重试";
  192. ElMessage.error(errorMsg);
  193. reject(new Error(errorMsg));
  194. });
  195. // 监听请求中止
  196. xhr.addEventListener("abort", () => {
  197. reject(new Error("上传已取消"));
  198. });
  199. // 打开请求
  200. xhr.open("POST", fullUrl, true);
  201. // 设置请求头
  202. const token = userStore.token || localGet("geeker-user")?.token;
  203. if (token) {
  204. xhr.setRequestHeader("Authorization", token);
  205. }
  206. // 设置超时
  207. xhr.timeout = config.timeout;
  208. xhr.addEventListener("timeout", () => {
  209. const errorMsg = "请求超时!请您稍后重试";
  210. ElMessage.error(errorMsg);
  211. reject(new Error(errorMsg));
  212. });
  213. // 发送请求
  214. xhr.withCredentials = config.withCredentials;
  215. xhr.send(formData);
  216. });
  217. }
  218. }
  219. export default new RequestHttp(config);