request.js 12 KB

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