request.js 11 KB

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