request.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /**
  2. * 微信小程序统一请求封装
  3. * 参考 ruoyi-admin/src/utils/request.ts 结构
  4. */
  5. // -------------------- 配置 --------------------
  6. const BASE_URL = 'http://localhost:8080'
  7. const TIMEOUT = 30000
  8. const TOKEN_KEY = 'token'
  9. // 防止 401 弹窗重复弹出
  10. let isReloginPending = false
  11. // -------------------- 工具 --------------------
  12. function getToken() {
  13. return uni.getStorageSync(TOKEN_KEY) || ''
  14. }
  15. /**
  16. * 将对象序列化为 query string
  17. */
  18. function toQueryString(params) {
  19. return Object.entries(params)
  20. .filter(([, v]) => v !== undefined && v !== null)
  21. .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
  22. .join('&')
  23. }
  24. // -------------------- 响应处理 --------------------
  25. /**
  26. * 处理业务响应码,统一返回 { data, total, rows }(按实际字段存在)
  27. */
  28. function handleResponse(responseData) {
  29. return new Promise((resolve, reject) => {
  30. const code = responseData.code
  31. const msg = responseData.msg || '系统异常'
  32. if (code === 200) {
  33. const result = {}
  34. if (responseData.data !== undefined) result.data = responseData.data
  35. if (responseData.total !== undefined) result.total = responseData.total
  36. if (responseData.rows !== undefined) result.rows = responseData.rows
  37. resolve(result)
  38. } else if (code === 401) {
  39. if (!isReloginPending) {
  40. isReloginPending = true
  41. uni.removeStorageSync(TOKEN_KEY)
  42. uni.showModal({
  43. title: '登录已过期',
  44. content: '登录状态已过期,请重新登录',
  45. showCancel: false,
  46. confirmText: '重新登录',
  47. confirmColor: '#C1001C',
  48. success: () => {
  49. isReloginPending = false
  50. const pages = getCurrentPages()
  51. const currentRoute = pages.length ? '/' + pages[pages.length - 1].route : ''
  52. uni.reLaunch({
  53. url: '/pages/login/index' + (currentRoute
  54. ? '?redirect=' + encodeURIComponent(currentRoute)
  55. : '')
  56. })
  57. }
  58. })
  59. }
  60. reject(new Error('无效的会话,或者会话已过期,请重新登录。'))
  61. } else if (code === 500) {
  62. uni.showToast({ title: msg, icon: 'none', duration: 2500 })
  63. reject(new Error(msg))
  64. } else {
  65. uni.showToast({ title: msg, icon: 'none', duration: 2500 })
  66. reject(new Error(msg))
  67. }
  68. })
  69. }
  70. // -------------------- 核心请求 --------------------
  71. /**
  72. * 统一请求方法
  73. * @param {object} options
  74. * @param {string} options.url 接口路径,如 '/system/user/list'
  75. * @param {string} [options.method] 请求方法,默认 'GET'
  76. * @param {object} [options.data] 请求体(POST / PUT)
  77. * @param {object} [options.params] URL 查询参数(GET 也可用此字段)
  78. * @param {object} [options.header] 额外请求头
  79. * @param {boolean} [options.isToken] 是否携带 Token,默认 true
  80. * @returns {Promise<{data?, total?, rows?}>}
  81. */
  82. function request({ url, method = 'GET', data = {}, params = {}, header = {}, isToken = true } = {}) {
  83. return new Promise((resolve, reject) => {
  84. const upperMethod = method.toUpperCase()
  85. // 拼接 query string
  86. let finalUrl = BASE_URL + url
  87. if (Object.keys(params).length) {
  88. finalUrl += '?' + toQueryString(params)
  89. }
  90. // 组装请求头
  91. const requestHeader = {
  92. 'Content-Type': 'application/json',
  93. ...header
  94. }
  95. if (isToken) {
  96. const token = getToken()
  97. if (token) {
  98. requestHeader['Authorization'] = 'Bearer ' + token
  99. }
  100. }
  101. uni.request({
  102. url: finalUrl,
  103. method: upperMethod,
  104. data: upperMethod === 'GET' ? undefined : data,
  105. header: requestHeader,
  106. timeout: TIMEOUT,
  107. success: (res) => {
  108. const { statusCode, data: responseData } = res
  109. // HTTP 层异常
  110. if (statusCode !== 200) {
  111. const httpMsg = `系统接口 ${statusCode} 异常`
  112. uni.showToast({ title: httpMsg, icon: 'none' })
  113. return reject(new Error(httpMsg))
  114. }
  115. // 业务层处理
  116. handleResponse(responseData).then(resolve).catch(reject)
  117. },
  118. fail: (err) => {
  119. let message = '请求失败,请检查网络'
  120. if (err.errMsg) {
  121. if (err.errMsg.includes('timeout')) {
  122. message = '系统接口请求超时'
  123. } else if (err.errMsg.toLowerCase().includes('network') || err.errMsg.includes('连接')) {
  124. message = '后端接口连接异常'
  125. }
  126. }
  127. uni.showToast({ title: message, icon: 'none' })
  128. reject(new Error(message))
  129. }
  130. })
  131. })
  132. }
  133. // -------------------- 下载文件 --------------------
  134. /**
  135. * 下载文件并自动打开
  136. * @param {string} url 接口路径
  137. * @param {object} [params] URL 查询参数
  138. * @param {string} [fileName] 保存文件名(仅做备注,小程序由系统决定)
  139. * @returns {Promise<string>} 临时文件路径
  140. */
  141. function download(url, params = {}, fileName = '') {
  142. return new Promise((resolve, reject) => {
  143. let finalUrl = BASE_URL + url
  144. if (Object.keys(params).length) {
  145. finalUrl += '?' + toQueryString(params)
  146. }
  147. const header = {}
  148. const token = getToken()
  149. if (token) {
  150. header['Authorization'] = 'Bearer ' + token
  151. }
  152. uni.showLoading({ title: '正在下载...', mask: true })
  153. uni.downloadFile({
  154. url: finalUrl,
  155. header,
  156. success: (res) => {
  157. uni.hideLoading()
  158. if (res.statusCode === 200) {
  159. uni.openDocument({
  160. filePath: res.tempFilePath,
  161. showMenu: true,
  162. success: () => {
  163. resolve(res.tempFilePath)
  164. },
  165. fail: () => {
  166. uni.showToast({ title: '文件打开失败', icon: 'none' })
  167. reject(new Error('文件打开失败'))
  168. }
  169. })
  170. } else {
  171. uni.showToast({ title: '下载失败', icon: 'none' })
  172. reject(new Error('下载失败'))
  173. }
  174. },
  175. fail: () => {
  176. uni.hideLoading()
  177. uni.showToast({ title: '下载文件出现错误,请联系管理员', icon: 'none' })
  178. reject(new Error('下载文件出现错误,请联系管理员'))
  179. }
  180. })
  181. })
  182. }
  183. // -------------------- 导出 --------------------
  184. export { request, download }
  185. export default request