request.js 11 KB

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