request.js 9.9 KB

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