request.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. import env from '../config/env.js'
  2. // Loading 管理器
  3. const loadingManager = {
  4. loadingCount: 0,
  5. loadingTimer: null,
  6. isShowing: false, // 添加状态标记
  7. // 安全的隐藏toast方法
  8. safeHideToast() {
  9. try {
  10. // 不要直接调用hideToast,因为如果没有toast在显示会报错
  11. // 只是静默忽略这个错误
  12. } catch (error) {
  13. // 静默忽略错误
  14. }
  15. },
  16. // 显示 loading
  17. show(options = {}) {
  18. const config = {
  19. title: '加载中...',
  20. mask: true,
  21. delay: 300,
  22. ...options
  23. }
  24. // 如果已经有 loading 在显示,只增加计数
  25. if (this.loadingCount > 0) {
  26. this.loadingCount++
  27. return
  28. }
  29. // 延迟显示,避免快速请求造成闪烁
  30. this.loadingTimer = setTimeout(() => {
  31. if (this.loadingCount > 0 && !this.isShowing) {
  32. try {
  33. // 不要先隐藏toast,直接显示loading
  34. uni.showLoading({
  35. title: config.title,
  36. mask: config.mask
  37. })
  38. this.isShowing = true
  39. } catch (error) {
  40. console.warn('showLoading failed:', error)
  41. }
  42. }
  43. }, config.delay)
  44. this.loadingCount++
  45. },
  46. // 隐藏 loading
  47. hide() {
  48. this.loadingCount = Math.max(0, this.loadingCount - 1)
  49. if (this.loadingCount === 0) {
  50. // 清除延迟显示的定时器
  51. if (this.loadingTimer) {
  52. clearTimeout(this.loadingTimer)
  53. this.loadingTimer = null
  54. }
  55. // 隐藏 loading
  56. if (this.isShowing) {
  57. try {
  58. uni.hideLoading()
  59. } catch (error) {
  60. console.warn('hideLoading failed:', error)
  61. } finally {
  62. this.isShowing = false
  63. }
  64. }
  65. }
  66. },
  67. // 强制隐藏所有 loading
  68. forceHide() {
  69. this.loadingCount = 0
  70. if (this.loadingTimer) {
  71. clearTimeout(this.loadingTimer)
  72. this.loadingTimer = null
  73. }
  74. if (this.isShowing) {
  75. try {
  76. uni.hideLoading()
  77. } catch (error) {
  78. console.warn('forceHide failed:', error)
  79. } finally {
  80. this.isShowing = false
  81. }
  82. }
  83. },
  84. // 安全显示toast(确保loading已隐藏)
  85. safeShowToast(options) {
  86. // 如果有loading在显示,先隐藏它
  87. if (this.isShowing) {
  88. this.forceHide()
  89. // 等待loading完全隐藏后显示toast
  90. setTimeout(() => {
  91. uni.showToast(options)
  92. }, 100)
  93. } else {
  94. // 没有loading,直接显示toast
  95. uni.showToast(options)
  96. }
  97. }
  98. }
  99. const request = {
  100. async get(url, params = {}, options = {}) {
  101. return this.request(url, 'GET', params, options)
  102. },
  103. async post(url, data = {}, options = {}) {
  104. return this.request(url, 'POST', data, options)
  105. },
  106. async put(url, data = {}, options = {}) {
  107. return this.request(url, 'PUT', data, options)
  108. },
  109. async delete(url, data = {}, options = {}) {
  110. return this.request(url, 'DELETE', data, options)
  111. },
  112. async request(url, method, data, options = {}) {
  113. // 解析 loading 配置
  114. let loadingConfig
  115. if (options.loading === false) {
  116. // 如果明确设置为 false,则不显示 loading
  117. loadingConfig = false
  118. } else {
  119. // 否则使用默认配置并合并用户配置
  120. loadingConfig = {
  121. show: true,
  122. title: '加载中...',
  123. mask: true,
  124. delay: 300,
  125. timeout: 10000,
  126. ...(typeof options.loading === 'object' ? options.loading : {})
  127. }
  128. }
  129. // 解析请求配置
  130. const requestConfig = {
  131. timeout: 15000, // 默认网络超时 15 秒
  132. ...options.request
  133. }
  134. // 如果 loading 配置为 false,则不显示 loading
  135. const shouldShowLoading = loadingConfig !== false && loadingConfig.show !== false
  136. // 显示 loading
  137. if (shouldShowLoading) {
  138. loadingManager.show(loadingConfig)
  139. }
  140. // 获取设备信息
  141. const deviceInfo = uni.getStorageSync("deviceInfo") || {};
  142. const devId = deviceInfo.deviceId || '';
  143. const sbmc = deviceInfo.model || '';
  144. // 处理URL,添加设备参数
  145. const separator = url.includes('?') ? '&' : '?';
  146. const finalUrl = `${url}${separator}devId=${devId}&sbmc=${sbmc}`;
  147. // 超时处理
  148. let timeoutTimer = null
  149. if (shouldShowLoading && loadingConfig.timeout) {
  150. timeoutTimer = setTimeout(() => {
  151. loadingManager.hide()
  152. // 使用安全的toast显示方法
  153. loadingManager.safeShowToast({
  154. title: '请求超时',
  155. icon: 'none'
  156. })
  157. }, loadingConfig.timeout)
  158. }
  159. // 数据格式转换
  160. let requestData = data;
  161. if (options.formData && data && typeof data === 'object') {
  162. // 转换为表单格式
  163. requestData = Object.keys(data).map(key => {
  164. const value = data[key];
  165. if (Array.isArray(value)) {
  166. // 数组数据转换为多个同名参数
  167. return value.map(item => `${encodeURIComponent(key)}=${encodeURIComponent(item)}`).join('&');
  168. } else {
  169. return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
  170. }
  171. }).join('&');
  172. }
  173. return new Promise((resolve, reject) => {
  174. uni.request({
  175. url: `${env.baseUrl}${finalUrl}`,
  176. method,
  177. data: requestData,
  178. timeout: requestConfig.timeout, // 设置网络超时
  179. header: (() => {
  180. const headers = {};
  181. // 根据选项决定Content-Type
  182. if (options.formData) {
  183. // 使用表单格式,让浏览器自动设置Content-Type
  184. headers['content-type'] = 'application/x-www-form-urlencoded';
  185. } else {
  186. // 默认使用JSON格式
  187. headers['content-type'] = 'application/json';
  188. }
  189. const jsessionId = uni.getStorageSync('JSESSIONID');
  190. if (jsessionId) {
  191. headers['Cookie'] = `JSESSIONID=${jsessionId}`;
  192. }
  193. return headers;
  194. })(),
  195. success: (res) => {
  196. // 清除超时定时器
  197. if (timeoutTimer) {
  198. clearTimeout(timeoutTimer)
  199. }
  200. // 获取响应头
  201. const headers = res.header;
  202. // console.log('响应头:', headers);
  203. // 处理Set-Cookie,获取JSESSIONID
  204. const setCookie = headers['set-cookie'] || headers['Set-Cookie'];
  205. if (setCookie) {
  206. const match = setCookie.match(/JSESSIONID=([^;]+)/);
  207. if (match && match[1]) {
  208. // console.log('获取到新的JSESSIONID:', match[1]);
  209. uni.setStorageSync('JSESSIONID', match[1]);
  210. }
  211. }
  212. if (res.statusCode === 200) {
  213. // console.log('请求成功:', res.data);
  214. // 检查登录过期 - 根据实际响应体格式
  215. if (res && typeof res.data === 'string' && res.data.includes('页面执行时错误')) {
  216. // 统一处理错误响应
  217. reject({
  218. message: '服务器处理错误' + res.data,
  219. })
  220. loadingManager.safeShowToast({
  221. title: '服务器处理错误',
  222. icon: 'none'
  223. })
  224. return
  225. }
  226. if (res.data && (
  227. res.data.errorcode === 1 ||
  228. res.data.msg === '登录已失效,请重新登录' ||
  229. res.data.message === '登录过期' ||
  230. res.data.error === 'UNAUTHORIZED'
  231. )) {
  232. console.log('🔒 检测到登录过期,跳转自动登录');
  233. console.log('过期响应数据:', res.data);
  234. handleLoginExpired();
  235. reject({ code: 401, message: res.data.msg || '登录过期' });
  236. return;
  237. }
  238. // 将响应头信息附加到返回数据中
  239. const responseData = {
  240. data: res.data,
  241. };
  242. resolve(responseData);
  243. } else {
  244. reject(res);
  245. }
  246. },
  247. fail: (err) => {
  248. // 清除超时定时器
  249. if (timeoutTimer) {
  250. clearTimeout(timeoutTimer)
  251. }
  252. // 使用安全的toast显示方法
  253. loadingManager.safeShowToast({
  254. title: '网络请求失败',
  255. icon: 'none'
  256. })
  257. reject(err)
  258. },
  259. complete: () => {
  260. // 隐藏 loading
  261. if (shouldShowLoading) {
  262. loadingManager.hide()
  263. }
  264. // console.log('请求完成');
  265. }
  266. })
  267. })
  268. }
  269. }
  270. // 处理登录过期
  271. const handleLoginExpired = async () => {
  272. // 获取yhsbToken,有token就自动登录,没有就手动登录
  273. let userInfo = uni.getStorageSync('userInfo') || {};
  274. if(typeof userInfo === 'string'){
  275. userInfo = JSON.parse(userInfo);
  276. }
  277. let yhsbToken = userInfo.yhsbToken;
  278. console.log('🔒 处理登录过期 request.js:',userInfo);
  279. if (yhsbToken) {
  280. // 自动登录不需要wechatCode,直接用yhsbToken
  281. console.log('🔄 有token,跳转自动登录');
  282. uni.navigateTo({
  283. url: `/pages/common/webview?dest=autoLogin&title=自动登录&from=expired&yhsbToken=${yhsbToken}`
  284. });
  285. } else {
  286. // 手动登录需要获取wechatCode
  287. console.log('⚠️ 无token,获取微信授权码并跳转手动登录');
  288. try {
  289. // 获取微信授权码
  290. const loginRes = await uni.login({
  291. provider: 'weixin'
  292. });
  293. console.log('获取到微信授权码:', loginRes.code);
  294. uni.navigateTo({
  295. url: `/pages/common/webview?dest=login&title=登录&from=expired&wechatCode=${loginRes.code}`
  296. });
  297. } catch (error) {
  298. console.error('获取微信授权码失败:', error);
  299. // 降级处理:不传wechatCode
  300. uni.navigateTo({
  301. url: '/pages/common/webview?dest=login&title=登录&from=expired'
  302. });
  303. }
  304. }
  305. };
  306. // 添加一些便捷方法
  307. request.loadingManager = loadingManager
  308. // 静默请求(不显示 loading)
  309. request.silent = {
  310. get: (url, params = {}) => request.get(url, params, { loading: false }),
  311. post: (url, data = {}) => request.post(url, data, { loading: false }),
  312. put: (url, data = {}) => request.put(url, data, { loading: false }),
  313. delete: (url, data = {}) => request.delete(url, data, { loading: false })
  314. }
  315. // 带自定义 loading 文字的请求
  316. request.withLoading = (title) => ({
  317. get: (url, params = {}) => request.get(url, params, { loading: { title } }),
  318. post: (url, data = {}) => request.post(url, data, { loading: { title } }),
  319. put: (url, data = {}) => request.put(url, data, { loading: { title } }),
  320. delete: (url, data = {}) => request.delete(url, data, { loading: { title } })
  321. })
  322. export default request