Files
martial-mini/utils/request.js
2025-12-12 17:29:38 +08:00

385 lines
9.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* HTTP请求封装
* 基于uni.request进行二次封装
*/
import config from '@/config/api.config.js'
class Request {
constructor() {
this.baseURL = config.baseURL
this.timeout = config.timeout
this.isRedirecting = false // 防止重复跳转登录页
}
/**
* 核心请求方法
* @param {Object} options 请求配置
* @returns {Promise}
*/
request(options) {
// 显示loading
if (options.loading !== false) {
uni.showLoading({
title: options.loadingText || '加载中...',
mask: true
})
}
// 打印请求信息仅POST请求
if (options.method === 'POST') {
console.log('=== HTTP POST 请求 ===')
console.log('URL:', this.baseURL + options.url)
console.log('Method:', options.method)
console.log('请求数据:', options.data)
console.log('请求头:', this.getHeaders(options.header))
}
return new Promise((resolve, reject) => {
uni.request({
url: this.baseURL + options.url,
method: options.method || 'GET',
data: options.data || {},
header: this.getHeaders(options.header),
timeout: options.timeout || this.timeout,
success: (res) => {
// 隐藏loading
if (options.loading !== false) {
uni.hideLoading()
}
console.log('=== HTTP 响应 ===')
console.log('状态码:', res.statusCode)
console.log('响应数据:', res.data)
// 处理响应
this.handleResponse(res, resolve, reject)
},
fail: (err) => {
// 隐藏loading
if (options.loading !== false) {
uni.hideLoading()
}
console.log('=== HTTP 请求失败 ===')
console.log('错误信息:', err)
// 处理错误
this.handleError(err, reject)
}
})
})
}
/**
* 获取请求头
* @param {Object} customHeader 自定义请求头
* @returns {Object}
*/
getHeaders(customHeader = {}) {
// 获取token - 使用access_token作为key
const token = uni.getStorageSync('access_token') || ''
return {
'Content-Type': 'application/json',
'Blade-Auth': token ? `bearer ${token}` : '',
'Tenant-Id': '000000',
...customHeader
}
}
/**
* 处理响应数据
* @param {Object} res 响应对象
* @param {Function} resolve Promise resolve
* @param {Function} reject Promise reject
*/
handleResponse(res, resolve, reject) {
const data = res.data
// 判断HTTP状态码 - 特别处理401
if (res.statusCode === 401) {
this.handleTokenExpired()
reject({
statusCode: 401,
code: 401,
message: '未登录或登录已过期'
})
return
}
// 其他HTTP错误状态码
if (res.statusCode < 200 || (res.statusCode >= 300 && res.statusCode !== 304)) {
this.showError('网络请求失败')
reject({
statusCode: res.statusCode,
code: res.statusCode,
message: '网络请求失败'
})
return
}
// 特殊处理OAuth2 token接口直接返回数据有access_token字段
if (data.access_token) {
resolve(data)
return
}
// 判断业务状态码
if (data.code === 200 || data.success === true) {
// 请求成功,返回数据
resolve(data.data)
} else {
// 业务错误处理
const errorMsg = data.msg || data.message || data.error_description || '请求失败'
// 特殊错误码处理
if (data.code === 401 || data.code === 403) {
// token过期或无权限
this.handleTokenExpired()
}
this.showError(errorMsg)
reject({
statusCode: res.statusCode,
code: data.code,
message: errorMsg,
data: data.data
})
}
}
/**
* 处理请求错误
* @param {Object} err 错误对象
* @param {Function} reject Promise reject
*/
handleError(err, reject) {
console.error('请求失败:', err)
console.error('请求URL:', this.baseURL)
let message = '网络请求失败'
if (err.errMsg) {
if (err.errMsg.includes('timeout')) {
message = '请求超时,请检查网络'
} else if (err.errMsg.includes('fail')) {
message = '网络连接失败,请检查服务器地址'
}
}
this.showError(message)
reject({
code: undefined,
message: '请求失败',
data: undefined,
error: err,
url: this.baseURL
})
}
/**
* 显示错误提示
* @param {String} message 错误信息
*/
showError(message) {
uni.showToast({
title: message,
icon: 'none',
duration: 2000
})
}
/**
* 处理token过期
*/
handleTokenExpired() {
console.log('=== handleTokenExpired 被调用 ===')
console.log('isRedirecting:', this.isRedirecting)
// 防止重复跳转
if (this.isRedirecting) {
console.log('已经在跳转中,跳过')
return
}
this.isRedirecting = true
console.log('开始清除认证信息')
// 清除所有认证信息
uni.removeStorageSync('access_token')
uni.removeStorageSync('refresh_token')
uni.removeStorageSync('userInfo')
console.log('认证信息已清除')
// 提示用户
console.log('显示Toast提示')
uni.showToast({
title: '登录已过期,请重新登录',
icon: 'none',
duration: 1500
})
// 立即跳转不等待Toast
console.log('准备跳转到登录页')
// 获取当前页面路径
const pages = getCurrentPages()
console.log('当前页面栈:', pages)
const currentPage = pages[pages.length - 1]
const currentRoute = currentPage ? currentPage.route : ''
console.log('当前页面路由:', currentRoute)
// 如果当前不在登录页,才跳转
if (currentRoute !== 'pages/login/login') {
console.log('开始执行跳转...')
// 使用 setTimeout 确保在下一个事件循环执行
setTimeout(() => {
this.isRedirecting = false
uni.reLaunch({
url: '/pages/login/login',
success: () => {
console.log('✅ reLaunch 跳转成功')
},
fail: (err) => {
console.error('❌ reLaunch 失败:', err)
// 如果reLaunch失败尝试使用redirectTo
uni.redirectTo({
url: '/pages/login/login',
success: () => {
console.log('✅ redirectTo 跳转成功')
},
fail: (err2) => {
console.error('❌ redirectTo 也失败:', err2)
// 最后尝试navigateTo
uni.navigateTo({
url: '/pages/login/login',
success: () => {
console.log('✅ navigateTo 跳转成功')
},
fail: (err3) => {
console.error('❌ navigateTo 也失败:', err3)
console.error('所有跳转方式都失败了!')
}
})
}
})
}
})
}, 100)
} else {
console.log('当前已在登录页,不需要跳转')
this.isRedirecting = false
}
}
/**
* GET请求
* @param {String} url 请求地址
* @param {Object} data 请求参数
* @param {Object} options 额外配置
* @returns {Promise}
*/
get(url, data = {}, options = {}) {
return this.request({
url,
method: 'GET',
data,
...options
})
}
/**
* POST请求
* @param {String} url 请求地址
* @param {Object|String} data 请求参数(可以是对象或字符串)
* @param {Object} options 额外配置
* @returns {Promise}
*/
post(url, data = {}, options = {}) {
return this.request({
url,
method: 'POST',
data,
...options
})
}
/**
* PUT请求
* @param {String} url 请求<E8AFB7><E6B182>
* @param {Object} data 请求参数
* @param {Object} options 额外配置
* @returns {Promise}
*/
put(url, data = {}, options = {}) {
return this.request({
url,
method: 'PUT',
data,
...options
})
}
/**
* DELETE请求
* @param {String} url 请求地址
* @param {Object} data 请求参数
* @param {Object} options 额外配置
* @returns {Promise}
*/
delete(url, data = {}, options = {}) {
return this.request({
url,
method: 'DELETE',
data,
...options
})
}
/**
* 文件上传
* @param {String} url 上传地址
* @param {String} filePath 文件路径
* @param {Object} formData 额外数据
* @returns {Promise}
*/
upload(url, filePath, formData = {}) {
uni.showLoading({
title: '上传中...',
mask: true
})
return new Promise((resolve, reject) => {
uni.uploadFile({
url: this.baseURL + url,
filePath,
name: 'file',
formData,
header: this.getHeaders(),
success: (res) => {
uni.hideLoading()
const data = JSON.parse(res.data)
if (data.code === 200) {
resolve(data.data)
} else {
this.showError(data.msg || '上传失败')
reject(data)
}
},
fail: (err) => {
uni.hideLoading()
this.showError('上传失败')
reject(err)
}
})
})
}
}
// 导出实例
export default new Request()