request.js 9.6 KB

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