index.ts 11 KB

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