request.js 9.1 KB

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