request.js 9.2 KB

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