request.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  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.header.source = DEVICE_TYPE[platform] || 'PHONE_APP'
  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. return Promise.reject(data);
  129. } else {
  130. uni.showToast({
  131. title: data.msg || "请求失败",
  132. icon: "none",
  133. });
  134. return Promise.reject(data);
  135. }
  136. } else {
  137. // uni.showToast({
  138. // title: `网络错误: ${statusCode}`,
  139. // icon: "none",
  140. // });
  141. console.log('接口错误error:', error)
  142. return Promise.reject(response);
  143. }
  144. };
  145. // 错误处理
  146. const errorHandler = (error) => {
  147. uni.hideLoading();
  148. console.log('请求失败抛出error:', error)
  149. uni.showToast({
  150. title: "网络异常,请稍后重试",
  151. icon: "none",
  152. });
  153. return Promise.reject(error);
  154. };
  155. // 核心请求函数
  156. export const request = async (options) => {
  157. await whenDomainReady();
  158. const host = getHost(options.type || 'Host80');
  159. // 合并配置
  160. const config = {
  161. ...options,
  162. url: `${host}${options.url}`,
  163. method: options.method || "GET",
  164. timeout,
  165. };
  166. // 应用请求拦截器
  167. const processedConfig = requestInterceptor(config);
  168. return new Promise((resolve, reject) => {
  169. const needLoading = urlLoading.some(item => config.url.includes(item));
  170. if (!needLoading) {
  171. // showLoading();
  172. }
  173. uni.request({
  174. ...processedConfig,
  175. success: (response) => {
  176. try {
  177. const result = responseInterceptor(response, options);
  178. resolve(result);
  179. } catch (err) {
  180. reject(err);
  181. } finally {
  182. if (!needLoading) {
  183. // hideLoading();
  184. }
  185. }
  186. },
  187. fail: (error) => {
  188. const handledError = errorHandler(error);
  189. reject(handledError);
  190. if (!needLoading) {
  191. // hideLoading();
  192. }
  193. },
  194. });
  195. });
  196. };
  197. // ---------------------- 图片上传封装(新增)----------------------
  198. /**
  199. * 图片上传函数
  200. * @param {Object} options - 上传配置
  201. * @param {string} options.url - 上传接口路径(如 /Upload/Image)
  202. * @param {string|string[]} options.filePath - 图片临时路径(单文件:字符串;多文件:数组)
  203. * @param {string} [options.name=file] - 后端接收文件的字段名(默认file,需与后端一致)
  204. * @param {Object} [options.formData={}] - 额外表单参数(如业务ID、类型)
  205. * @param {Function} [options.onProgressUpdate] - 进度监听函数(可选)
  206. * @returns {Promise} - 上传结果
  207. */
  208. export const upload = async (options) => {
  209. await whenDomainReady();
  210. // 1. 处理基础配置
  211. const uploadConfig = {
  212. ...options,
  213. url: `${getHost(options.type || 'Host85')}${options.url}`, // 完整上传接口地址
  214. timeout,
  215. name: options.name || "file", // 后端接收文件的字段名(默认file)
  216. filePath: options.filePath, // 图片临时路径
  217. formData: options.formData || {}, // 额外表单参数
  218. onProgressUpdate: options.onProgressUpdate, // 进度监听(可选)
  219. };
  220. // 2. 应用请求拦截器(添加token等header)
  221. const processedConfig = requestInterceptor(uploadConfig);
  222. // 3. 区分单文件/多文件上传
  223. return new Promise((resolve, reject) => {
  224. // 多文件上传(filePath为数组)
  225. if (Array.isArray(processedConfig.filePath)) {
  226. // 批量调用uni.uploadFile,并行上传
  227. const uploadPromises = processedConfig.filePath.map((filePath) => {
  228. return new Promise((innerResolve, innerReject) => {
  229. uni.uploadFile({
  230. ...processedConfig,
  231. filePath, // 单个文件路径
  232. success: (res) => {
  233. // 注意:uni.uploadFile的res.data是字符串,需转为JSON
  234. res.data = JSON.parse(res.data || "{}");
  235. try {
  236. const result = responseInterceptor(res);
  237. innerResolve(result);
  238. } catch (err) {
  239. innerReject(err);
  240. }
  241. },
  242. fail: (err) => innerReject(errorHandler(err)),
  243. });
  244. });
  245. });
  246. // 所有文件上传完成后 resolve
  247. Promise.all(uploadPromises).then(resolve).catch(reject);
  248. }
  249. // 单文件上传(filePath为字符串)
  250. else {
  251. uni.uploadFile({
  252. ...processedConfig,
  253. success: (res) => {
  254. res.data = JSON.parse(res.data || "{}"); // 转为JSON
  255. try {
  256. const result = responseInterceptor(res);
  257. resolve(result);
  258. } catch (err) {
  259. reject(err);
  260. }
  261. },
  262. fail: (err) => reject(errorHandler(err)),
  263. });
  264. }
  265. });
  266. };
  267. /**
  268. * 兼容性上传函数(按需直接调用)
  269. * @param {string} url - 接口相对路径,如 `/wasabi/api/upload/file`
  270. * @param {string|Object|File} file - 文件路径(临时路径字符串)或包含 path/url/tempFilePath 的对象或 File
  271. * @param {Object} [data] - 额外表单字段
  272. * @param {Object} [header] - 额外请求头
  273. * @param {boolean} [checkCode=true] - 是否走响应码检查(默认走)
  274. */
  275. export const uploadFile = async (url, file, data = {}, header = {}, checkCode = true) => {
  276. await whenDomainReady();
  277. return new Promise((resolve, reject) => {
  278. try {
  279. // 提取文件路径
  280. let filePath = file;
  281. if (file && typeof file === "object") {
  282. filePath = file.path || file.url || file.tempFilePath || file.filePath || file;
  283. }
  284. const finalUrl = `${getHost('Host85')}${url}`;
  285. // 构建 headers,优先使用传入 header
  286. const headers = {
  287. ...(header || {}),
  288. };
  289. if (userToken.value) headers["Access-Token"] = `${userToken.value}`;
  290. if (lang.value) headers["Language"] = `${lang.value}`;
  291. if (CLIENT.value) headers["CLIENT"] = `${CLIENT.value}`;
  292. uni.uploadFile({
  293. url: finalUrl,
  294. filePath: filePath,
  295. name: "file",
  296. header: headers,
  297. formData: data || {},
  298. success: (res) => {
  299. try {
  300. // uni.uploadFile 的 res.data 是字符串
  301. res.data = JSON.parse(res.data || "{}");
  302. } catch (e) {
  303. res.data = {};
  304. }
  305. // 适配 responseInterceptor 接口
  306. const resp = { data: res.data, statusCode: res.statusCode };
  307. if (checkCode) {
  308. try {
  309. const result = responseInterceptor(resp);
  310. resolve(result);
  311. } catch (err) {
  312. reject(err);
  313. }
  314. } else {
  315. resolve(resp.data);
  316. }
  317. },
  318. fail: (err) => {
  319. reject(errorHandler(err));
  320. },
  321. });
  322. } catch (err) {
  323. reject(err);
  324. }
  325. });
  326. };
  327. // 快捷方法
  328. export const get = (url, data = {}, typeOrOptions = {}, options = {}) => {
  329. const mergedOptions =
  330. typeof typeOrOptions === "string"
  331. ? { type: typeOrOptions, ...(options || {}) }
  332. : (typeOrOptions || {});
  333. return request({
  334. url,
  335. method: "GET",
  336. data,
  337. ...mergedOptions,
  338. });
  339. };
  340. export const post = (url, data = {}, type, options = {}) => {
  341. return request({
  342. url,
  343. method: "POST",
  344. data,
  345. type,
  346. ...options,
  347. });
  348. };