request.js 10 KB

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