2026-04-20 06:23:11 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 请求拦截器封装
|
|
|
|
|
|
* 统一处理HTTP请求和响应
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
import { getBaseUrl, getTimeout, getStorageKey, statusCode } from '../config/index.js'
|
|
|
|
|
|
|
|
|
|
|
|
// 请求队列(用于取消重复请求)
|
|
|
|
|
|
const pendingRequests = new Map()
|
|
|
|
|
|
|
|
|
|
|
|
// 是否正在刷新token
|
|
|
|
|
|
let isRefreshing = false
|
|
|
|
|
|
// 等待刷新token的请求队列
|
|
|
|
|
|
let refreshSubscribers = []
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 生成请求唯一标识
|
|
|
|
|
|
*/
|
|
|
|
|
|
const generateRequestKey = (config) => {
|
|
|
|
|
|
const { url, method, data } = config
|
|
|
|
|
|
return `${method}_${url}_${JSON.stringify(data || {})}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 添加请求到队列
|
|
|
|
|
|
*/
|
|
|
|
|
|
const addPendingRequest = (config) => {
|
|
|
|
|
|
const key = generateRequestKey(config)
|
|
|
|
|
|
if (!pendingRequests.has(key)) {
|
|
|
|
|
|
pendingRequests.set(key, config)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 从队列移除请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
const removePendingRequest = (config) => {
|
|
|
|
|
|
const key = generateRequestKey(config)
|
|
|
|
|
|
pendingRequests.delete(key)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 取消重复请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
const cancelPendingRequest = (config) => {
|
|
|
|
|
|
const key = generateRequestKey(config)
|
|
|
|
|
|
if (pendingRequests.has(key)) {
|
|
|
|
|
|
return true
|
|
|
|
|
|
}
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取请求头
|
|
|
|
|
|
*/
|
|
|
|
|
|
const getHeaders = (needAuth = true) => {
|
|
|
|
|
|
const headers = {
|
|
|
|
|
|
'Content-Type': 'application/json'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (needAuth) {
|
|
|
|
|
|
const token = uni.getStorageSync(getStorageKey('token'))
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
headers['Authorization'] = `Bearer ${token}`
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return headers
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 处理响应错误
|
2026-04-22 06:47:04 +00:00
|
|
|
|
* 根据后端统一响应格式处理错误
|
|
|
|
|
|
* 后端格式: { code: 400/401/403/404/500, message: 'xxx', data: null }
|
2026-04-20 06:23:11 +00:00
|
|
|
|
*/
|
|
|
|
|
|
const handleResponseError = (response) => {
|
2026-04-22 06:47:04 +00:00
|
|
|
|
const { statusCode: httpCode, data } = response
|
2026-04-20 06:23:11 +00:00
|
|
|
|
|
|
|
|
|
|
// 优先使用后端返回的 message
|
2026-04-22 06:47:04 +00:00
|
|
|
|
const message = data?.message || '请求失败'
|
2026-04-20 06:23:11 +00:00
|
|
|
|
|
2026-04-22 06:47:04 +00:00
|
|
|
|
switch (httpCode) {
|
2026-04-20 06:23:11 +00:00
|
|
|
|
case 400:
|
|
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: message,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
case 401:
|
|
|
|
|
|
// Token过期,清除登录状态并跳转登录页
|
|
|
|
|
|
uni.removeStorageSync(getStorageKey('token'))
|
|
|
|
|
|
uni.removeStorageSync(getStorageKey('userInfo'))
|
|
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: message,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: '/pages/login/login'
|
|
|
|
|
|
})
|
|
|
|
|
|
}, 1500)
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
case 403:
|
|
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: message,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
case 404:
|
|
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: message,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
case 500:
|
|
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: message,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
break
|
|
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: message,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 统一请求方法
|
|
|
|
|
|
* @param {Object} options - 请求配置
|
|
|
|
|
|
* @param {boolean} needAuth - 是否需要认证
|
|
|
|
|
|
* @returns {Promise}
|
|
|
|
|
|
*/
|
|
|
|
|
|
const request = (options = {}, needAuth = true) => {
|
|
|
|
|
|
const baseUrl = getBaseUrl()
|
|
|
|
|
|
const timeout = getTimeout()
|
|
|
|
|
|
|
|
|
|
|
|
const config = {
|
|
|
|
|
|
url: options.url.startsWith('http') ? options.url : `${baseUrl}${options.url}`,
|
|
|
|
|
|
method: options.method || 'GET',
|
|
|
|
|
|
data: options.data || {},
|
|
|
|
|
|
header: { ...getHeaders(needAuth), ...options.header },
|
|
|
|
|
|
timeout: options.timeout || timeout
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查重复请求
|
|
|
|
|
|
if (cancelPendingRequest(config)) {
|
|
|
|
|
|
return Promise.reject(new Error('重复请求'))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 添加请求到队列
|
|
|
|
|
|
addPendingRequest(config)
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
uni.request({
|
|
|
|
|
|
...config,
|
|
|
|
|
|
success: (response) => {
|
2026-04-22 06:47:04 +00:00
|
|
|
|
const { statusCode: httpCode, data } = response
|
|
|
|
|
|
|
|
|
|
|
|
// HTTP 请求成功 (2xx)
|
|
|
|
|
|
if (httpCode >= 200 && httpCode < 300) {
|
|
|
|
|
|
// 根据后端统一响应格式处理
|
|
|
|
|
|
// 后端格式: { code: 200, message: 'xxx', data: xxx }
|
|
|
|
|
|
|
|
|
|
|
|
// 业务逻辑成功
|
|
|
|
|
|
if (data.code === 200) {
|
2026-04-20 06:23:11 +00:00
|
|
|
|
resolve(data)
|
|
|
|
|
|
} else {
|
2026-04-22 06:47:04 +00:00
|
|
|
|
// 业务逻辑失败(如参数错误等)
|
|
|
|
|
|
// 后端格式: { code: 400/403/404/500, message: 'xxx', data: null }
|
|
|
|
|
|
const errorMessage = data.message || '操作失败'
|
2026-04-20 06:23:11 +00:00
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: errorMessage,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(data)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
2026-04-22 06:47:04 +00:00
|
|
|
|
// HTTP 错误(4xx, 5xx)
|
2026-04-20 06:23:11 +00:00
|
|
|
|
handleResponseError(response)
|
|
|
|
|
|
reject(response)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (error) => {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '网络错误,请检查网络连接',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
},
|
|
|
|
|
|
complete: () => {
|
|
|
|
|
|
// 从队列移除请求
|
|
|
|
|
|
removePendingRequest(config)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GET请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const get = (url, params = {}, needAuth = true) => {
|
|
|
|
|
|
// 构建查询字符串
|
|
|
|
|
|
const queryString = Object.keys(params)
|
|
|
|
|
|
.map(key => `${encodeURIComponent(key)}=${encodeURIComponent(params[key])}`)
|
|
|
|
|
|
.join('&')
|
|
|
|
|
|
|
|
|
|
|
|
const fullUrl = queryString ? `${url}?${queryString}` : url
|
|
|
|
|
|
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url: fullUrl,
|
|
|
|
|
|
method: 'GET'
|
|
|
|
|
|
}, needAuth)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* POST请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const post = (url, data = {}, needAuth = true) => {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
data
|
|
|
|
|
|
}, needAuth)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* PUT请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const put = (url, data = {}, needAuth = true) => {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
data
|
|
|
|
|
|
}, needAuth)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* DELETE请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const del = (url, data = {}, needAuth = true) => {
|
|
|
|
|
|
return request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
data
|
|
|
|
|
|
}, needAuth)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 上传文件
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const upload = (url, filePath, formData = {}, needAuth = true) => {
|
|
|
|
|
|
const baseUrl = getBaseUrl()
|
|
|
|
|
|
const token = uni.getStorageSync(getStorageKey('token'))
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
|
url: `${baseUrl}${url}`,
|
|
|
|
|
|
filePath,
|
|
|
|
|
|
name: 'file',
|
|
|
|
|
|
formData,
|
|
|
|
|
|
header: needAuth && token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
|
|
|
|
success: (response) => {
|
|
|
|
|
|
const data = JSON.parse(response.data)
|
2026-04-22 06:47:04 +00:00
|
|
|
|
// 根据后端统一响应格式处理
|
2026-04-20 06:23:11 +00:00
|
|
|
|
if (data.code === 200) {
|
|
|
|
|
|
resolve(data)
|
|
|
|
|
|
} else {
|
2026-04-22 06:47:04 +00:00
|
|
|
|
const errorMessage = data.message || '上传失败'
|
2026-04-20 06:23:11 +00:00
|
|
|
|
uni.showToast({
|
2026-04-22 06:47:04 +00:00
|
|
|
|
title: errorMessage,
|
2026-04-20 06:23:11 +00:00
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(data)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (error) => {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '上传失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载文件
|
|
|
|
|
|
*/
|
|
|
|
|
|
export const download = (url, needAuth = true) => {
|
|
|
|
|
|
const baseUrl = getBaseUrl()
|
|
|
|
|
|
const token = uni.getStorageSync(getStorageKey('token'))
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
uni.downloadFile({
|
|
|
|
|
|
url: `${baseUrl}${url}`,
|
|
|
|
|
|
header: needAuth && token ? { 'Authorization': `Bearer ${token}` } : {},
|
|
|
|
|
|
success: (response) => {
|
|
|
|
|
|
if (response.statusCode === 200) {
|
|
|
|
|
|
resolve(response)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(response)
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (error) => {
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default {
|
|
|
|
|
|
request,
|
|
|
|
|
|
get,
|
|
|
|
|
|
post,
|
|
|
|
|
|
put,
|
|
|
|
|
|
delete: del,
|
|
|
|
|
|
upload,
|
|
|
|
|
|
download
|
|
|
|
|
|
}
|