request.js 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. import { API_BASE, TOKEN_KEY, REFRESH_TOKEN_KEY, USER_INFO_KEY } from '@/config/api'
  2. let clearingAuth = false
  3. function appendTimestampQuery(url) {
  4. const base = String(url || '')
  5. const ts = Date.now()
  6. if (!base) return `${base}?_t=${ts}`
  7. // 已有 _t 时覆盖为最新值,避免 query 反复堆叠
  8. if (/[?&]_t=/.test(base)) {
  9. return base.replace(/([?&]_t=)\d+/, `$1${ts}`)
  10. }
  11. return `${base}${base.includes('?') ? '&' : '?'}_t=${ts}`
  12. }
  13. function createUnauthorizedError(rawUrl = '') {
  14. const loginApiPattern = /\/mini\/auth\/login(?:$|\?)/
  15. const message = loginApiPattern.test(String(rawUrl))
  16. ? '用户名或密码错误,请重新输入'
  17. : '登录状态已失效,请重新登录'
  18. const error = new Error(message)
  19. error.name = 'UnauthorizedError'
  20. return error
  21. }
  22. async function onHttpUnauthorized() {
  23. if (clearingAuth) return
  24. clearingAuth = true
  25. // 先同步清理本地登录态,避免 store 动态导入失败时仍被识别为已登录
  26. uni.removeStorageSync(TOKEN_KEY)
  27. uni.removeStorageSync(REFRESH_TOKEN_KEY)
  28. uni.removeStorageSync(USER_INFO_KEY)
  29. try {
  30. const { useUserStore } = await import('@/stores/user')
  31. useUserStore().clearLoginInfo()
  32. } catch (error) {
  33. // ignore
  34. }
  35. const pages = typeof getCurrentPages === 'function' ? getCurrentPages() : []
  36. const currentRoute = pages.length ? pages[pages.length - 1].route : ''
  37. if (currentRoute === 'pages/login/login') {
  38. clearingAuth = false
  39. return
  40. }
  41. uni.showToast({ title: '登录已失效,请重新登录', icon: 'none' })
  42. uni.reLaunch({
  43. url: '/pages/login/login',
  44. fail: () => {
  45. // 某些端在请求回调中 reLaunch 可能失败,使用 redirectTo 兜底
  46. uni.redirectTo({ url: '/pages/login/login' })
  47. },
  48. complete: () => {
  49. setTimeout(() => {
  50. clearingAuth = false
  51. }, 800)
  52. }
  53. })
  54. }
  55. function isBizUnauthorized(data) {
  56. if (!data || typeof data !== 'object') return false
  57. return data.code === 401
  58. }
  59. export function request(options) {
  60. if (clearingAuth) {
  61. return Promise.reject(createUnauthorizedError(options?.url))
  62. }
  63. const rawToken = uni.getStorageSync(TOKEN_KEY)
  64. const token = typeof rawToken === 'string' ? rawToken.trim() : ''
  65. const rawUrl = options.url || ''
  66. const normalizedUrl = rawUrl.startsWith('http')
  67. ? rawUrl
  68. : `${API_BASE}${rawUrl.startsWith('/') ? rawUrl : `/${rawUrl}`}`
  69. const url = appendTimestampQuery(normalizedUrl)
  70. return new Promise((resolve, reject) => {
  71. uni.request({
  72. ...options,
  73. url,
  74. header: {
  75. 'Content-Type': 'application/json',
  76. ...(token ? { Authorization: `Bearer ${token}` } : {}),
  77. ...(options.header || {})
  78. },
  79. success: (res) => {
  80. if (res.statusCode === 401 || isBizUnauthorized(res.data)) {
  81. onHttpUnauthorized()
  82. reject(createUnauthorizedError(rawUrl))
  83. return
  84. }
  85. resolve(res)
  86. },
  87. fail: (err) => reject(err)
  88. })
  89. })
  90. }
  91. export function parseResponseJson(res) {
  92. const d = res.data
  93. if (d == null) return d
  94. if (typeof d === 'string') {
  95. try {
  96. return JSON.parse(d)
  97. } catch (error) {
  98. return d
  99. }
  100. }
  101. return d
  102. }