index.ts 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305
  1. // axios配置 可自行根据项目进行更改,只需更改该文件即可,其他文件可以不动
  2. import { VAxios } from './Axios';
  3. import { AxiosTransform } from './axiosTransform';
  4. import axios, { AxiosResponse } from 'axios';
  5. import { checkStatus } from './checkStatus';
  6. import { joinTimestamp, formatRequestDate } from './helper';
  7. import { RequestEnum, ResultEnum, ContentTypeEnum } from '@/enums/httpEnum';
  8. import { PageEnum } from '@/enums/pageEnum';
  9. import { ElMessage, ElMessageBox } from 'element-plus';
  10. import { useGlobSetting } from '@/hooks/setting';
  11. import { isString } from '@/utils/is/';
  12. import { deepMerge, isUrl } from '@/utils';
  13. import { setObjToUrlParams } from '@/utils/urlUtils';
  14. import { RequestOptions, Result, CreateAxiosOptions } from './types';
  15. import { useUserStoreWidthOut } from '@/store/modules/user';
  16. const globSetting = useGlobSetting();
  17. const urlPrefix = globSetting.urlPrefix || '';
  18. import router from '@/router';
  19. import { storage } from '@/utils/Storage';
  20. /**
  21. * @description: 数据处理,方便区分多种处理方式
  22. */
  23. const transform: AxiosTransform = {
  24. /**
  25. * @description: 处理请求数据
  26. */
  27. transformRequestData: (res: AxiosResponse<Result>, options: RequestOptions) => {
  28. const {
  29. isShowMessage = true,
  30. isShowErrorMessage,
  31. isShowSuccessMessage,
  32. successMessageText,
  33. errorMessageText,
  34. isTransformResponse,
  35. isReturnNativeResponse,
  36. } = options;
  37. // 是否返回原生响应头 比如:需要获取响应头时使用该属性
  38. if (isReturnNativeResponse) {
  39. return res;
  40. }
  41. // 不进行任何处理,直接返回
  42. // 用于页面代码可能需要直接获取code,data,message这些信息时开启
  43. if (!isTransformResponse) {
  44. return res.data;
  45. }
  46. const { data } = res;
  47. if (!data) {
  48. // return '[HTTP] Request has no return value';
  49. throw new Error('请求出错,请稍候重试');
  50. }
  51. // 这里 code,result,message为 后台统一的字段,需要修改为项目自己的接口返回格式
  52. const { code, data: result, msg: message } = data;
  53. // 请求成功
  54. const hasSuccess = data && Reflect.has(data, 'code') && code === ResultEnum.SUCCESS;
  55. // 是否显示提示信息
  56. if (isShowMessage) {
  57. if (hasSuccess && (successMessageText || isShowSuccessMessage)) {
  58. // 是否显示自定义信息提示
  59. ElMessage({
  60. type: 'info',
  61. message: successMessageText || message || '操作成功!',
  62. });
  63. } else if (!hasSuccess && (errorMessageText || isShowErrorMessage)) {
  64. // 是否显示自定义信息提示
  65. ElMessage.error(message || errorMessageText || '操作失败!');
  66. } else if (!hasSuccess && options.errorMessageMode === 'modal') {
  67. // errorMessageMode=‘custom-modal’的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
  68. ElMessageBox.confirm(message, '提示', {
  69. confirmButtonText: '确定',
  70. showCancelButton: false,
  71. type: 'warning',
  72. })
  73. .then(() => {
  74. ElMessage({
  75. type: 'success',
  76. message: 'Delete completed',
  77. });
  78. })
  79. .catch(() => {
  80. ElMessage({
  81. type: 'info',
  82. message: 'Delete canceled',
  83. });
  84. });
  85. }
  86. }
  87. // 接口请求成功,直接返回结果
  88. if (code === ResultEnum.SUCCESS) {
  89. return result;
  90. }
  91. // 接口请求错误,统一提示错误信息 这里逻辑可以根据项目进行修改
  92. let errorMsg = message;
  93. switch (code) {
  94. // 请求失败
  95. case ResultEnum.ERROR:
  96. ElMessage.error(errorMsg);
  97. break;
  98. // 没有权限
  99. case 502:
  100. ElMessage.error(errorMsg);
  101. break;
  102. // 登录超时
  103. case ResultEnum.TIMEOUT:
  104. const LoginName = PageEnum.BASE_LOGIN_NAME;
  105. const LoginPath = PageEnum.BASE_LOGIN;
  106. if (router.currentRoute.value?.name === LoginName) return;
  107. // 到登录页
  108. errorMsg = '登录超时,请重新登录!';
  109. ElMessageBox.confirm('登录身份已失效,请重新登录!', '提示', {
  110. confirmButtonText: '确定',
  111. showCancelButton: false,
  112. type: 'warning',
  113. })
  114. .then(() => {
  115. storage.clear();
  116. window.location.href = import.meta.env.VITE_PUBLIC_PATH + '#login';
  117. window.location.reload();
  118. })
  119. .catch(() => {});
  120. break;
  121. }
  122. throw new Error(errorMsg);
  123. },
  124. // 请求之前处理config
  125. beforeRequestHook: (config, options) => {
  126. const { apiUrl, joinPrefix, joinParamsToUrl, formatDate, joinTime = true, urlPrefix } = options;
  127. const isUrlStr = isUrl(config.url as string);
  128. if (!isUrlStr && joinPrefix) {
  129. config.url = `${urlPrefix}${config.url}`;
  130. }
  131. if (!isUrlStr && apiUrl && isString(apiUrl)) {
  132. config.url = `${apiUrl}${config.url}`;
  133. }
  134. const params = config.params || {};
  135. const data = config.data || false;
  136. if (config.method?.toUpperCase() === RequestEnum.GET) {
  137. if (!isString(params)) {
  138. // 给 get 请求加上时间戳参数,避免从缓存中拿数据。
  139. config.params = Object.assign(params || {}, joinTimestamp(joinTime, false));
  140. } else {
  141. // 兼容restful风格
  142. config.url = config.url + params + `${joinTimestamp(joinTime, true)}`;
  143. config.params = undefined;
  144. }
  145. } else {
  146. if (!isString(params)) {
  147. formatDate && formatRequestDate(params);
  148. if (
  149. Reflect.has(config, 'data') &&
  150. config.data &&
  151. (Object.keys(config.data).length > 0 || config.data instanceof FormData)
  152. ) {
  153. config.data = data;
  154. config.params = params;
  155. } else {
  156. config.data = params;
  157. config.params = undefined;
  158. }
  159. if (joinParamsToUrl) {
  160. config.url = setObjToUrlParams(
  161. config.url as string,
  162. Object.assign({}, config.params, config.data),
  163. );
  164. }
  165. } else {
  166. // 兼容restful风格
  167. config.url = config.url + params;
  168. config.params = undefined;
  169. }
  170. }
  171. return config;
  172. },
  173. /**
  174. * @description: 请求拦截器处理
  175. */
  176. requestInterceptors: (config, options) => {
  177. // 请求之前处理config
  178. const userStore = useUserStoreWidthOut();
  179. const token = userStore.getToken;
  180. const tenantId = userStore.getTenantId;
  181. if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
  182. // jwt token
  183. (config as Recordable).headers.satoken = options.authenticationScheme
  184. ? `${options.authenticationScheme} ${token}`
  185. : token;
  186. }
  187. // 租户id
  188. if (tenantId) {
  189. (config as Recordable).headers.tenantId = tenantId;
  190. }
  191. return config;
  192. },
  193. /**
  194. * @description: 响应错误处理
  195. */
  196. responseInterceptorsCatch: (error: any) => {
  197. const { response, code, message } = error || {};
  198. // TODO 此处要根据后端接口返回格式修改
  199. const msg: string =
  200. response && response.data && response.data.message ? response.data.message : '';
  201. const err: string = error.toString();
  202. try {
  203. if (code === 'ECONNABORTED' && message.indexOf('timeout') !== -1) {
  204. ElMessage.error('接口请求超时,请刷新页面重试!');
  205. return;
  206. }
  207. if (err && err.includes('Network Error')) {
  208. ElMessageBox.confirm('请检查您的网络连接是否正常', '网络异常', {
  209. confirmButtonText: '确定',
  210. showCancelButton: false,
  211. type: 'warning',
  212. })
  213. // eslint-disable-next-line prettier/prettier
  214. .then(() => {})
  215. // eslint-disable-next-line prettier/prettier
  216. .catch(() => {});
  217. return Promise.reject(error);
  218. }
  219. } catch (error) {
  220. throw new Error(error as any);
  221. }
  222. // 请求是否被取消
  223. const isCancel = axios.isCancel(error);
  224. if (!isCancel) {
  225. checkStatus(error.response && error.response.status, msg);
  226. } else {
  227. console.warn(error, '请求被取消!');
  228. }
  229. //return Promise.reject(error);
  230. return Promise.reject(response?.data);
  231. },
  232. };
  233. function createAxios(opt?: Partial<CreateAxiosOptions>) {
  234. return new VAxios(
  235. deepMerge(
  236. {
  237. timeout: 10 * 1000,
  238. authenticationScheme: '',
  239. // 接口前缀
  240. prefixUrl: urlPrefix,
  241. headers: { 'Content-Type': ContentTypeEnum.JSON },
  242. // 数据处理方式
  243. transform,
  244. // 配置项,下面的选项都可以在独立的接口请求中覆盖
  245. requestOptions: {
  246. // 默认将prefix 添加到url
  247. joinPrefix: true,
  248. // 是否返回原生响应头 比如:需要获取响应头时使用该属性
  249. isReturnNativeResponse: false,
  250. // 需要对返回数据进行处理
  251. isTransformResponse: true,
  252. // post请求的时候添加参数到url
  253. joinParamsToUrl: false,
  254. // 格式化提交参数时间
  255. formatDate: true,
  256. // 消息提示类型
  257. errorMessageMode: 'none',
  258. // 接口地址
  259. apiUrl: globSetting.apiUrl,
  260. // 接口拼接地址
  261. urlPrefix: urlPrefix,
  262. // 是否加入时间戳
  263. joinTime: true,
  264. // 忽略重复请求
  265. ignoreCancelToken: true,
  266. // 是否携带token
  267. withToken: true,
  268. },
  269. withCredentials: false,
  270. },
  271. opt || {},
  272. ),
  273. );
  274. }
  275. export const http = createAxios();
  276. // 项目,多个不同 api 地址,直接在这里导出多个
  277. // src/api ts 里面接口,就可以单独使用这个请求,
  278. // import { httpTwo } from '@/utils/http/axios'
  279. // export const httpTwo = createAxios({
  280. // requestOptions: {
  281. // apiUrl: 'http://localhost:9001',
  282. // urlPrefix: 'api',
  283. // },
  284. // });