西藏巴青项目

request.js 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. import { joinApiUrl } from '@/config'
  2. import { getToken } from '@/utils/auth'
  3. import errorCode from '@/utils/errorCode'
  4. import { useUserStore } from '@/store/user'
  5. /** 是否正在处理 401 跳转,避免重复提示 */
  6. export const isRelogin = { show: false }
  7. const SESSION_EXPIRED_MSG = '登录过期,即将登录'
  8. const LOGIN_PAGE_URL = '/pages/login/index'
  9. const LOGIN_REDIRECT_DELAY_MS = 400
  10. /** 与 ruoyi-ui tansParams 一致:null / undefined / 空字符串不参与序列化 */
  11. function isEmptyParam(value) {
  12. return value === null || value === undefined || value === ''
  13. }
  14. /**
  15. * 剔除空值形参(GET 查询用)
  16. */
  17. export function sanitizeParams(params) {
  18. if (params == null || typeof params !== 'object') {
  19. return params
  20. }
  21. if (Array.isArray(params)) {
  22. return params
  23. }
  24. const result = {}
  25. Object.keys(params).forEach((key) => {
  26. const value = params[key]
  27. if (isEmptyParam(value)) {
  28. return
  29. }
  30. if (typeof value === 'object' && !Array.isArray(value)) {
  31. const nested = sanitizeParams(value)
  32. if (nested && Object.keys(nested).length > 0) {
  33. result[key] = nested
  34. }
  35. return
  36. }
  37. result[key] = value
  38. })
  39. return result
  40. }
  41. function appendQuery(url, params) {
  42. const parts = []
  43. const build = (obj, prefix) => {
  44. Object.keys(obj).forEach((key) => {
  45. const value = obj[key]
  46. const name = prefix ? `${prefix}[${key}]` : key
  47. if (value !== null && value !== '' && typeof value !== 'undefined') {
  48. if (typeof value === 'object' && !Array.isArray(value)) {
  49. build(value, name)
  50. } else {
  51. parts.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`)
  52. }
  53. }
  54. })
  55. }
  56. build(params, '')
  57. if (!parts.length) {
  58. return url
  59. }
  60. const qs = parts.join('&')
  61. return url + (url.indexOf('?') >= 0 ? '&' : '?') + qs
  62. }
  63. /**
  64. * 封装 uni.request,响应格式与 ruoyi-ui axios 拦截器一致
  65. */
  66. export function request(options = {}) {
  67. const header = { ...(options.header || {}) }
  68. const skipToken = header.isToken === false
  69. delete header.isToken
  70. delete header.repeatSubmit
  71. // 登录等接口不得携带过期 Token,否则部分环境下会误返回 401
  72. if (skipToken) {
  73. delete header.Authorization
  74. } else if (getToken()) {
  75. header.Authorization = 'Bearer ' + getToken()
  76. }
  77. const method = (options.method || 'GET').toUpperCase()
  78. let url = (options.url || '').startsWith('http')
  79. ? options.url
  80. : joinApiUrl(options.url)
  81. let requestData = options.data !== undefined ? options.data : options.params
  82. if (method === 'GET' && requestData && typeof requestData === 'object' && !Array.isArray(requestData)) {
  83. const cleaned = sanitizeParams(requestData)
  84. const keys = Object.keys(cleaned || {})
  85. if (keys.length) {
  86. url = appendQuery(url, cleaned)
  87. }
  88. requestData = undefined
  89. }
  90. return new Promise((resolve, reject) => {
  91. uni.request({
  92. url,
  93. method,
  94. data: requestData,
  95. header: {
  96. 'Content-Type': 'application/json;charset=utf-8',
  97. ...header
  98. },
  99. timeout: options.timeout || 10000,
  100. success: (res) => {
  101. const httpStatus = res.statusCode || 200
  102. const data = parseResponseData(res.data)
  103. const bizCode = data.code !== undefined && data.code !== null ? Number(data.code) : null
  104. const code = bizCode !== null && !Number.isNaN(bizCode) ? bizCode : httpStatus >= 200 && httpStatus < 300 ? 200 : httpStatus
  105. const msg = resolveErrorMessage(data, code)
  106. if (code === 401) {
  107. // 登录、验证码等未带 Token 的接口:仍展示后端 msg
  108. if (skipToken) {
  109. showErrorToast(msg)
  110. reject(new Error(msg))
  111. } else {
  112. handle401SessionExpired()
  113. reject(new Error(SESSION_EXPIRED_MSG))
  114. }
  115. return
  116. }
  117. if (code !== 200) {
  118. if (!options.silent) {
  119. showErrorToast(msg)
  120. }
  121. reject(new Error(msg || 'error'))
  122. return
  123. }
  124. resolve(data)
  125. },
  126. fail: (err) => {
  127. let message = err.errMsg || '网络异常'
  128. if (message.includes('timeout')) {
  129. message = '系统接口请求超时'
  130. } else if (message.includes('fail')) {
  131. message = '后端接口连接异常'
  132. }
  133. showErrorToast(message)
  134. reject(err)
  135. }
  136. })
  137. })
  138. }
  139. /** 解析响应体(uni 有时返回字符串) */
  140. function parseResponseData(raw) {
  141. if (raw == null || raw === '') {
  142. return {}
  143. }
  144. if (typeof raw === 'object') {
  145. return raw
  146. }
  147. if (typeof raw === 'string') {
  148. try {
  149. return JSON.parse(raw)
  150. } catch (e) {
  151. return { msg: raw }
  152. }
  153. }
  154. return {}
  155. }
  156. /**
  157. * 错误文案:优先后端 msg,其次 errorCode 映射,最后默认
  158. */
  159. function resolveErrorMessage(data, code) {
  160. const serverMsg = data && data.msg != null ? String(data.msg).trim() : ''
  161. if (serverMsg) {
  162. return serverMsg
  163. }
  164. const key = String(code)
  165. if (errorCode[key]) {
  166. return errorCode[key]
  167. }
  168. return errorCode.default || '请求失败'
  169. }
  170. function showErrorToast(message) {
  171. const title = (message && String(message).trim()) || errorCode.default || '请求失败'
  172. uni.showToast({
  173. title: title.length > 40 ? title.slice(0, 40) + '…' : title,
  174. icon: 'none',
  175. duration: 3000
  176. })
  177. }
  178. function handle401SessionExpired() {
  179. if (isRelogin.show) {
  180. return
  181. }
  182. isRelogin.show = true
  183. uni.showToast({
  184. title: SESSION_EXPIRED_MSG,
  185. icon: 'none',
  186. duration: 2000
  187. })
  188. setTimeout(() => {
  189. const userStore = useUserStore()
  190. userStore.fedLogOut().finally(() => {
  191. isRelogin.show = false
  192. uni.reLaunch({ url: LOGIN_PAGE_URL })
  193. })
  194. }, LOGIN_REDIRECT_DELAY_MS)
  195. }
  196. export default request