2025-12-03 18:58:36 +08:00
|
|
|
|
import config from './config.js'
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 网络请求封装
|
|
|
|
|
|
*/
|
|
|
|
|
|
class Request {
|
|
|
|
|
|
constructor() {
|
|
|
|
|
|
// 安全地获取配置
|
|
|
|
|
|
this.timeout = (config && config.REQUEST_TIMEOUT) ? config.REQUEST_TIMEOUT : 30000
|
|
|
|
|
|
// 重试配置
|
|
|
|
|
|
this.maxRetries = 2 // 最大重试次数
|
|
|
|
|
|
this.retryDelay = 1000 // 重试延迟(毫秒)
|
|
|
|
|
|
|
|
|
|
|
|
// 调试信息(生产环境关闭)
|
|
|
|
|
|
// if (process.env.NODE_ENV === 'development') {
|
|
|
|
|
|
// console.log('Request initialized')
|
|
|
|
|
|
// }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取当前baseURL(动态获取,支持运行时切换)
|
|
|
|
|
|
*/
|
|
|
|
|
|
getBaseURL() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 每次请求时动态获取服务器配置
|
|
|
|
|
|
let serverHost = 'localhost'
|
|
|
|
|
|
let serverPort = 8080
|
|
|
|
|
|
|
|
|
|
|
|
// 安全地获取服务器配置
|
|
|
|
|
|
if (config && typeof config.getServerConfig === 'function') {
|
|
|
|
|
|
const serverConfig = config.getServerConfig()
|
|
|
|
|
|
if (serverConfig) {
|
|
|
|
|
|
serverHost = serverConfig.serverHost || serverHost
|
|
|
|
|
|
serverPort = serverConfig.serverPort || serverPort
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果config.getServerConfig不可用,尝试直接从存储读取
|
|
|
|
|
|
try {
|
|
|
|
|
|
const storedHost = uni.getStorageSync('server_host')
|
|
|
|
|
|
const storedPort = uni.getStorageSync('server_port')
|
|
|
|
|
|
if (storedHost) serverHost = storedHost
|
|
|
|
|
|
if (storedPort) serverPort = parseInt(storedPort) || serverPort
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
console.warn('读取服务器配置失败,使用默认值:', e)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let baseURL = `http://${serverHost}:${serverPort}`
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef H5
|
|
|
|
|
|
// H5环境:如果访问的是localhost,使用Vite代理
|
|
|
|
|
|
if (typeof window !== 'undefined' && window.location) {
|
|
|
|
|
|
const hostname = window.location.hostname
|
2025-12-06 20:11:25 +08:00
|
|
|
|
if (hostname === 'localhost' || hostname === '192.168.1.8') {
|
2025-12-03 18:58:36 +08:00
|
|
|
|
// H5环境:使用后端服务器地址,不通过Vite代理
|
|
|
|
|
|
// 因为Vite代理只配置了/api和/ws,其他路径需要直接访问后端
|
|
|
|
|
|
return baseURL
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
// #ifdef APP-PLUS
|
|
|
|
|
|
// App环境:如果使用localhost,给出提示
|
2025-12-05 23:36:21 +08:00
|
|
|
|
if (serverHost === 'localhost' || serverHost === '127.0.0.1') {
|
2025-12-03 18:58:36 +08:00
|
|
|
|
console.error('❌ App环境无法使用localhost,请配置电脑的局域网IP地址!')
|
|
|
|
|
|
console.error('❌ 配置方法:uni.setStorageSync("server_host", "你的电脑IP")')
|
2025-12-06 20:11:25 +08:00
|
|
|
|
console.error('❌ 例如:uni.setStorageSync("server_host", "192.168.1.8")')
|
2025-12-03 18:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
// #endif
|
|
|
|
|
|
|
|
|
|
|
|
return baseURL
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('获取baseURL失败:', error)
|
|
|
|
|
|
// 返回默认值(本地开发服务器)
|
2025-12-06 20:11:25 +08:00
|
|
|
|
return 'http://192.168.1.8:30091'
|
2025-12-03 18:58:36 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 请求拦截器
|
|
|
|
|
|
*/
|
|
|
|
|
|
interceptRequest(requestConfig) {
|
|
|
|
|
|
// 检查是否需要token(注册接口等不需要token)
|
|
|
|
|
|
const isToken = requestConfig.header && requestConfig.header.isToken === false
|
|
|
|
|
|
|
|
|
|
|
|
// 添加token(RuoYi使用Authorization Bearer格式)
|
|
|
|
|
|
const token = uni.getStorageSync('token')
|
|
|
|
|
|
if (token && !isToken) {
|
|
|
|
|
|
requestConfig.header = requestConfig.header || {}
|
|
|
|
|
|
// RuoYi使用Authorization Bearer格式
|
|
|
|
|
|
requestConfig.header['Authorization'] = `Bearer ${token}`
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置请求头
|
|
|
|
|
|
requestConfig.header = requestConfig.header || {}
|
|
|
|
|
|
requestConfig.header['Content-Type'] = requestConfig.header['Content-Type'] || 'application/json'
|
|
|
|
|
|
|
|
|
|
|
|
// 删除isToken标记(不发送到服务器)
|
|
|
|
|
|
if (requestConfig.header.isToken !== undefined) {
|
|
|
|
|
|
delete requestConfig.header.isToken
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return requestConfig
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 响应拦截器
|
|
|
|
|
|
*/
|
|
|
|
|
|
interceptResponse(response) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const { statusCode, data } = response
|
|
|
|
|
|
|
|
|
|
|
|
// HTTP状态码检查
|
|
|
|
|
|
if (statusCode !== 200) {
|
|
|
|
|
|
reject(new Error(`请求失败: ${statusCode}`))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理响应数据(可能是字符串需要解析)
|
|
|
|
|
|
let responseData = data
|
|
|
|
|
|
if (typeof data === 'string') {
|
|
|
|
|
|
try {
|
|
|
|
|
|
responseData = JSON.parse(data)
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
reject(new Error('响应数据解析失败'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 业务状态码检查(RuoYi使用code字段)
|
|
|
|
|
|
if (responseData.code === 401) {
|
|
|
|
|
|
// Token过期,清除登录信息并跳转到登录页
|
|
|
|
|
|
uni.removeStorageSync('token')
|
|
|
|
|
|
uni.removeStorageSync('userInfo')
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: '/pages/login/login'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(new Error('登录已过期,请重新登录'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// RuoYi响应格式:{ code: 200, msg: "操作成功", data: {...} }
|
|
|
|
|
|
if (responseData.code !== 200) {
|
|
|
|
|
|
reject(new Error(responseData.msg || responseData.message || '请求失败'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 返回整个响应对象(包含code、msg、data等)
|
|
|
|
|
|
resolve(responseData)
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 判断错误是否可重试
|
|
|
|
|
|
*/
|
|
|
|
|
|
isRetryableError(error) {
|
|
|
|
|
|
if (!error || !error.errMsg) return false
|
|
|
|
|
|
|
|
|
|
|
|
const errMsg = error.errMsg.toLowerCase()
|
|
|
|
|
|
// 可重试的错误类型
|
|
|
|
|
|
const retryableErrors = [
|
|
|
|
|
|
'unexpected end of stream',
|
|
|
|
|
|
'abort',
|
|
|
|
|
|
'timeout',
|
|
|
|
|
|
'network',
|
|
|
|
|
|
'connection',
|
|
|
|
|
|
'fail'
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
return retryableErrors.some(keyword => errMsg.includes(keyword))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 通用请求方法(带重试机制)
|
|
|
|
|
|
*/
|
|
|
|
|
|
request(options, retryCount = 0) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 动态获取baseURL(支持运行时切换服务器地址)
|
|
|
|
|
|
const baseURL = this.getBaseURL()
|
|
|
|
|
|
|
|
|
|
|
|
// 处理URL:如果options.url已经是完整URL,则不拼接baseURL
|
|
|
|
|
|
let requestUrl = options.url
|
|
|
|
|
|
if (!requestUrl || typeof requestUrl !== 'string') {
|
|
|
|
|
|
reject(new Error('请求URL无效'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (!requestUrl.startsWith('http://') && !requestUrl.startsWith('https://')) {
|
|
|
|
|
|
requestUrl = baseURL + (requestUrl.startsWith('/') ? requestUrl : '/' + requestUrl)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 请求配置
|
|
|
|
|
|
const requestConfig = {
|
|
|
|
|
|
url: requestUrl,
|
|
|
|
|
|
method: options.method || 'GET',
|
|
|
|
|
|
data: options.data || {},
|
|
|
|
|
|
header: options.header || {},
|
|
|
|
|
|
timeout: this.timeout,
|
|
|
|
|
|
...options
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 覆盖url(确保使用处理后的URL)
|
|
|
|
|
|
requestConfig.url = requestUrl
|
|
|
|
|
|
|
|
|
|
|
|
// 请求拦截
|
|
|
|
|
|
const interceptedConfig = this.interceptRequest(requestConfig)
|
|
|
|
|
|
|
|
|
|
|
|
// 调试日志(生产环境关闭)
|
|
|
|
|
|
// if (process.env.NODE_ENV === 'development') {
|
|
|
|
|
|
// if (retryCount > 0) {
|
|
|
|
|
|
// console.log(`🔄 重试请求 (${retryCount}/${this.maxRetries}):`, requestUrl)
|
|
|
|
|
|
// } else {
|
|
|
|
|
|
// console.log('发送请求:', {
|
|
|
|
|
|
// url: requestUrl,
|
|
|
|
|
|
// method: requestConfig.method,
|
|
|
|
|
|
// data: requestConfig.data,
|
|
|
|
|
|
// header: interceptedConfig.header
|
|
|
|
|
|
// })
|
|
|
|
|
|
// }
|
|
|
|
|
|
// }
|
|
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
|
|
|
|
|
uni.request({
|
|
|
|
|
|
...interceptedConfig,
|
|
|
|
|
|
success: (response) => {
|
|
|
|
|
|
// 调试日志(生产环境关闭)
|
|
|
|
|
|
// if (process.env.NODE_ENV === 'development') {
|
|
|
|
|
|
// console.log('请求成功:', response)
|
|
|
|
|
|
// }
|
|
|
|
|
|
this.interceptResponse(response)
|
|
|
|
|
|
.then(resolve)
|
|
|
|
|
|
.catch(reject)
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (error) => {
|
|
|
|
|
|
// 调试日志
|
|
|
|
|
|
console.error('请求失败:', error)
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是否可重试
|
|
|
|
|
|
if (retryCount < this.maxRetries && this.isRetryableError(error)) {
|
|
|
|
|
|
console.log(`⚠️ 请求失败,将在 ${this.retryDelay / 1000} 秒后重试...`)
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
// 递归重试
|
|
|
|
|
|
this.request(options, retryCount + 1)
|
|
|
|
|
|
.then(resolve)
|
|
|
|
|
|
.catch(reject)
|
|
|
|
|
|
}, this.retryDelay)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 不可重试或已达到最大重试次数
|
|
|
|
|
|
let errorMessage = '网络请求失败'
|
|
|
|
|
|
if (error.errMsg) {
|
|
|
|
|
|
if (error.errMsg.includes('unexpected end of stream')) {
|
|
|
|
|
|
errorMessage = '连接中断,请检查网络或服务器状态'
|
|
|
|
|
|
} else if (error.errMsg.includes('timeout')) {
|
|
|
|
|
|
errorMessage = '请求超时,请检查网络连接'
|
|
|
|
|
|
} else if (error.errMsg.includes('abort')) {
|
|
|
|
|
|
errorMessage = '请求被中断,请重试'
|
|
|
|
|
|
} else {
|
|
|
|
|
|
errorMessage = error.errMsg
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: errorMessage,
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
duration: 3000
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('request() 方法执行失败:', error)
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* GET请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
get(url, params = {}, options = {}) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
return this.request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: 'GET',
|
|
|
|
|
|
data: params,
|
|
|
|
|
|
...options
|
|
|
|
|
|
})
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('request.get() 调用失败:', error)
|
|
|
|
|
|
return Promise.reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* POST请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
post(url, data = {}, options = {}) {
|
|
|
|
|
|
// 调试日志(生产环境关闭)
|
|
|
|
|
|
// if (process.env.NODE_ENV === 'development') {
|
|
|
|
|
|
// console.log('POST请求:', url, '数据:', data)
|
|
|
|
|
|
// }
|
|
|
|
|
|
return this.request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
data: data,
|
|
|
|
|
|
...options
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* PUT请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
put(url, data = {}, options = {}) {
|
|
|
|
|
|
return this.request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: 'PUT',
|
|
|
|
|
|
data,
|
|
|
|
|
|
...options
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* DELETE请求
|
|
|
|
|
|
*/
|
|
|
|
|
|
delete(url, data = {}, options = {}) {
|
|
|
|
|
|
return this.request({
|
|
|
|
|
|
url,
|
|
|
|
|
|
method: 'DELETE',
|
|
|
|
|
|
data,
|
|
|
|
|
|
...options
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 文件上传
|
|
|
|
|
|
*/
|
|
|
|
|
|
upload(url, filePath, formData = {}, options = {}) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const token = uni.getStorageSync('token')
|
|
|
|
|
|
// 动态获取baseURL(支持运行时切换服务器地址)
|
|
|
|
|
|
const baseURL = this.getBaseURL()
|
|
|
|
|
|
|
|
|
|
|
|
// 处理URL
|
|
|
|
|
|
let uploadUrl = url
|
|
|
|
|
|
if (!uploadUrl.startsWith('http://') && !uploadUrl.startsWith('https://')) {
|
|
|
|
|
|
uploadUrl = baseURL + (uploadUrl.startsWith('/') ? uploadUrl : '/' + uploadUrl)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uni.uploadFile({
|
|
|
|
|
|
url: uploadUrl,
|
|
|
|
|
|
filePath: filePath,
|
|
|
|
|
|
name: options.name || 'file',
|
|
|
|
|
|
formData: formData,
|
|
|
|
|
|
header: {
|
|
|
|
|
|
'Authorization': token ? `Bearer ${token}` : '' // RuoYi使用Authorization Bearer格式
|
|
|
|
|
|
},
|
|
|
|
|
|
success: (response) => {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const data = JSON.parse(response.data)
|
|
|
|
|
|
if (data.code === 200) {
|
|
|
|
|
|
resolve(data)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
reject(new Error(data.message || '上传失败'))
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (e) {
|
|
|
|
|
|
reject(new Error('响应解析失败'))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (error) => {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '上传失败',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(error)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建实例并导出
|
|
|
|
|
|
const request = new Request()
|
|
|
|
|
|
|
|
|
|
|
|
// 确保所有方法都正确定义(防止编译优化导致的问题)
|
|
|
|
|
|
if (typeof request.get !== 'function') {
|
|
|
|
|
|
console.error('❌ request.get 方法不可用!')
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof request.post !== 'function') {
|
|
|
|
|
|
console.error('❌ request.post 方法不可用!')
|
|
|
|
|
|
}
|
|
|
|
|
|
if (typeof request.request !== 'function') {
|
|
|
|
|
|
console.error('❌ request.request 方法不可用!')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export default request
|
|
|
|
|
|
|
|
|
|
|
|
|