request.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. // 基础配置
  2. import { showLoading, hideLoading } from '@/hooks/useLoading'
  3. import config1 from "@/config";
  4. import ls from "@/utils/store2";
  5. import { whenDomainReady } from '@/utils/dynamicDomain';
  6. const SystemError = {
  7. "cn": "网络状态不佳,请稍后重试。",
  8. "en": "The network is not in good condition. Please try again later.",
  9. "vn": "Mạng không được tốt lắm. Vui lòng thử lại sau.",
  10. }
  11. const timeout = 60000;
  12. const getHost = (type = 'Host80') => config1[type] || config1.Host80;
  13. // 不加loading
  14. const urlLoading = ['/list', '/page', '/field/params', '/dropdown', '/single', '/detail']
  15. import { CLIENT, lang, userToken, shopToken } from "@/composables/config";
  16. const LOGIN_PAGE_PATH = "/pages/login/index";
  17. // import useGlobalStore from "@/stores/use-global-store";
  18. import useUserStore from "@/stores/use-user-store";
  19. // const globalStore = useGlobalStore()
  20. export const getCurrentPageUrl = () => {
  21. const pages = getCurrentPages(); // UniApp获取当前页面栈
  22. const currentPage = pages[pages.length - 1];
  23. return currentPage.route; // 返回当前页面路径(如:pages/login/index)
  24. };
  25. // 请求拦截器
  26. const requestInterceptor = (config) => {
  27. if (!config.header) {
  28. config.header = {};
  29. }
  30. switch (config.type) {
  31. case 'HostShop':
  32. if (shopToken.value) {
  33. config.header["Access-Token"] = `${shopToken.value}`;
  34. }
  35. break;
  36. default:
  37. if (userToken.value) {
  38. config.header["Access-Token"] = `${userToken.value}`;
  39. }
  40. break;
  41. }
  42. if (lang.value) {
  43. config.header.Language = `${lang.value}`;
  44. }
  45. if (CLIENT.value) {
  46. config.header.CLIENT = `${CLIENT.value}`;
  47. }
  48. config.header["X-System"] = config.header["X-System"] || 'B';
  49. // #ifdef APP-PLUS
  50. if (config.url.includes('/custom/login')) {
  51. const { platform } = uni.getSystemInfoSync()
  52. const DEVICE_TYPE = {
  53. ios: 'PHONE_IOS',
  54. android: 'PHONE_ANDROID'
  55. }
  56. config.data.source = DEVICE_TYPE[platform] || ''
  57. }
  58. // #endif
  59. const userStore = useUserStore();
  60. const cId = userStore.userInfo?.cId;
  61. const method = String(config.method || "GET").toUpperCase();
  62. if (method === "GET") {
  63. config.data = { ...(config.data || {}) };
  64. } else {
  65. config.data = { ...(config.data || {}) };
  66. }
  67. if (!config.header["Content-Type"]) {
  68. config.header["Content-Type"] = "application/json";
  69. }
  70. return config;
  71. };
  72. // 记录是否正在跳转登录页
  73. let isRedirectingToLogin = false;
  74. // 响应拦截器
  75. const responseInterceptor = (response, options = {}) => {
  76. const { data, statusCode } = response;
  77. if (statusCode === 200) {
  78. if (options.responseType === "arraybuffer" || data instanceof ArrayBuffer) {
  79. return data;
  80. }
  81. if (data.code === 600) {
  82. const currentPage = getCurrentPageUrl();
  83. if (currentPage === LOGIN_PAGE_PATH) {
  84. return new Promise(() => { });
  85. }
  86. uni.$emit('logout');
  87. return new Promise(() => { });
  88. } else if (data.code === 500) {
  89. return {
  90. code: 500,
  91. msg: SystemError[lang.value] || SystemError['en']
  92. }
  93. } else if (data.code === 404) {
  94. return {
  95. code: 400,
  96. msg: SystemError[lang.value] || SystemError['en']
  97. }
  98. } else {
  99. return data
  100. }
  101. } else {
  102. return {
  103. code: 400,
  104. msg: SystemError[lang.value] || SystemError['en']
  105. }
  106. }
  107. };
  108. // 错误处理
  109. const errorHandler = (error) => {
  110. uni.hideLoading();
  111. uni.showToast({
  112. title: error.msg || SystemError[lang.value] || SystemError['en'],
  113. icon: "none",
  114. });
  115. return Promise.reject(error);
  116. };
  117. // 核心请求函数
  118. export const request = async (options) => {
  119. await whenDomainReady();
  120. const host = getHost(options.type || 'Host80');
  121. // 合并配置
  122. const config = {
  123. ...options,
  124. url: `${host}${options.url}`,
  125. method: options.method || "GET",
  126. timeout,
  127. };
  128. // 应用请求拦截器
  129. const processedConfig = requestInterceptor(config);
  130. return new Promise((resolve, reject) => {
  131. const needLoading = urlLoading.some(item => config.url.includes(item));
  132. // console.log(needLoading,config.url)
  133. if (!needLoading) {
  134. // uni.showLoading({
  135. // mask:true
  136. // })
  137. // showLoading();
  138. }
  139. uni.request({
  140. ...processedConfig,
  141. success: (response) => {
  142. try {
  143. const result = responseInterceptor(response, options);
  144. resolve(result);
  145. } catch (err) {
  146. reject(err);
  147. } finally {
  148. if (!needLoading) {
  149. // uni.hideLoading()
  150. // hideLoading();
  151. }
  152. }
  153. },
  154. fail: (error) => {
  155. const handledError = errorHandler(error);
  156. reject(handledError);
  157. if (!needLoading) {
  158. // uni.hideLoading()
  159. // hideLoading();
  160. }
  161. },
  162. });
  163. });
  164. };
  165. // ---------------------- 图片上传封装(新增)----------------------
  166. /**
  167. * 图片上传函数
  168. * @param {Object} options - 上传配置
  169. * @param {string} options.url - 上传接口路径(如 /Upload/Image)
  170. * @param {string|string[]} options.filePath - 图片临时路径(单文件:字符串;多文件:数组)
  171. * @param {string} [options.name=file] - 后端接收文件的字段名(默认file,需与后端一致)
  172. * @param {Object} [options.formData={}] - 额外表单参数(如业务ID、类型)
  173. * @param {Function} [options.onProgressUpdate] - 进度监听函数(可选)
  174. * @returns {Promise} - 上传结果
  175. */
  176. export const upload = async (options) => {
  177. await whenDomainReady();
  178. // 1. 处理基础配置
  179. const uploadConfig = {
  180. ...options,
  181. url: `${getHost(options.type || 'Host85')}${options.url}`, // 完整上传接口地址
  182. timeout,
  183. name: options.name || "file", // 后端接收文件的字段名(默认file)
  184. filePath: options.filePath, // 图片临时路径
  185. formData: options.formData || {}, // 额外表单参数
  186. onProgressUpdate: options.onProgressUpdate, // 进度监听(可选)
  187. };
  188. // 2. 应用请求拦截器(添加token等header)
  189. const processedConfig = requestInterceptor(uploadConfig);
  190. // 3. 区分单文件/多文件上传
  191. return new Promise((resolve, reject) => {
  192. // 多文件上传(filePath为数组)
  193. if (Array.isArray(processedConfig.filePath)) {
  194. // 批量调用uni.uploadFile,并行上传
  195. const uploadPromises = processedConfig.filePath.map((filePath) => {
  196. return new Promise((innerResolve, innerReject) => {
  197. uni.uploadFile({
  198. ...processedConfig,
  199. filePath, // 单个文件路径
  200. success: (res) => {
  201. // 注意:uni.uploadFile的res.data是字符串,需转为JSON
  202. res.data = JSON.parse(res.data || "{}");
  203. try {
  204. const result = responseInterceptor(res);
  205. innerResolve(result);
  206. } catch (err) {
  207. innerReject(err);
  208. }
  209. },
  210. fail: (err) => innerReject(errorHandler(err)),
  211. });
  212. });
  213. });
  214. // 所有文件上传完成后 resolve
  215. Promise.all(uploadPromises).then(resolve).catch(reject);
  216. }
  217. // 单文件上传(filePath为字符串)
  218. else {
  219. uni.uploadFile({
  220. ...processedConfig,
  221. success: (res) => {
  222. res.data = JSON.parse(res.data || "{}"); // 转为JSON
  223. try {
  224. const result = responseInterceptor(res);
  225. resolve(result);
  226. } catch (err) {
  227. reject(err);
  228. }
  229. },
  230. fail: (err) => reject(errorHandler(err)),
  231. });
  232. }
  233. });
  234. };
  235. /**
  236. * 兼容性上传函数(按需直接调用)
  237. * @param {string} url - 接口相对路径,如 `/wasabi/api/upload/file`
  238. * @param {string|Object|File} file - 文件路径(临时路径字符串)或包含 path/url/tempFilePath 的对象或 File
  239. * @param {Object} [data] - 额外表单字段
  240. * @param {Object} [header] - 额外请求头
  241. * @param {boolean} [checkCode=true] - 是否走响应码检查(默认走)
  242. */
  243. export const uploadFile = async (url, file, data = {}, header = {}, checkCode = true) => {
  244. await whenDomainReady();
  245. return new Promise((resolve, reject) => {
  246. try {
  247. // 提取文件路径
  248. let filePath = file;
  249. if (file && typeof file === "object") {
  250. filePath = file.path || file.url || file.tempFilePath || file.filePath || file;
  251. }
  252. const finalUrl = `${getHost('Host85')}${url}`;
  253. // 构建 headers,优先使用传入 header
  254. const headers = {
  255. ...(header || {}),
  256. };
  257. if (userToken.value) headers["Access-Token"] = `${userToken.value}`;
  258. if (lang.value) headers["Language"] = `${lang.value}`;
  259. if (CLIENT.value) headers["CLIENT"] = `${CLIENT.value}`;
  260. uni.uploadFile({
  261. url: finalUrl,
  262. filePath: filePath,
  263. name: "file",
  264. header: headers,
  265. formData: data || {},
  266. success: (res) => {
  267. try {
  268. // uni.uploadFile 的 res.data 是字符串
  269. res.data = JSON.parse(res.data || "{}");
  270. } catch (e) {
  271. res.data = {};
  272. }
  273. // 适配 responseInterceptor 接口
  274. const resp = { data: res.data, statusCode: res.statusCode };
  275. if (checkCode) {
  276. try {
  277. const result = responseInterceptor(resp);
  278. resolve(result);
  279. } catch (err) {
  280. reject(err);
  281. }
  282. } else {
  283. resolve(resp.data);
  284. }
  285. },
  286. fail: (err) => {
  287. reject(errorHandler(err));
  288. },
  289. });
  290. } catch (err) {
  291. reject(err);
  292. }
  293. });
  294. };
  295. // 快捷方法
  296. export const get = (url, data = {}, typeOrOptions = {}, options = {}) => {
  297. const mergedOptions =
  298. typeof typeOrOptions === "string"
  299. ? { type: typeOrOptions, ...(options || {}) }
  300. : (typeOrOptions || {});
  301. return request({
  302. url,
  303. method: "GET",
  304. data,
  305. ...mergedOptions,
  306. });
  307. };
  308. export const post = (url, data = {}, type, options = {}) => {
  309. return request({
  310. url,
  311. method: "POST",
  312. data,
  313. type,
  314. ...options,
  315. });
  316. };