2025-12-19 12:27:55 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 接口请求封装
|
|
|
|
|
|
* 统一处理请求拦截、响应拦截、错误处理、token管理等
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
// 基础URL配置(注意:末尾不要加斜杠)
|
2026-03-04 15:30:04 +00:00
|
|
|
|
// const BASE_URL = 'https://guangsh.manage.hschengtai.com'
|
|
|
|
|
|
const BASE_URL = 'http://192.168.5.134:48085'
|
2025-12-19 12:27:55 +00:00
|
|
|
|
// 是否正在刷新token(防止并发刷新)
|
|
|
|
|
|
let isRefreshing = false
|
|
|
|
|
|
// 等待刷新完成的请求队列
|
|
|
|
|
|
let refreshSubscribers = []
|
|
|
|
|
|
|
2026-01-14 10:55:42 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 检查当前是否在登录页面
|
|
|
|
|
|
* @returns {Boolean} 是否在登录页面
|
|
|
|
|
|
*/
|
|
|
|
|
|
function isInLoginPage() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const pages = getCurrentPages()
|
|
|
|
|
|
if (pages.length === 0) return false
|
|
|
|
|
|
const currentPage = pages[pages.length - 1]
|
|
|
|
|
|
const currentRoute = currentPage ? currentPage.route : ''
|
|
|
|
|
|
return currentRoute === 'pages/login/login'
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('检查登录页面状态失败:', error)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 检查是否刚刚从登录页面返回(页面栈中有登录页面)
|
|
|
|
|
|
* @returns {Boolean} 是否刚刚从登录页面返回
|
|
|
|
|
|
*/
|
|
|
|
|
|
function isJustBackFromLogin() {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const pages = getCurrentPages()
|
|
|
|
|
|
if (pages.length <= 1) return false
|
|
|
|
|
|
// 检查页面栈中是否有登录页面(除了当前页面)
|
|
|
|
|
|
const hasLoginPage = pages.some((page, index) => {
|
|
|
|
|
|
// 排除当前页面(最后一个)
|
|
|
|
|
|
if (index === pages.length - 1) return false
|
|
|
|
|
|
const route = page ? page.route : ''
|
|
|
|
|
|
return route === 'pages/login/login'
|
|
|
|
|
|
})
|
|
|
|
|
|
return hasLoginPage
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
console.error('检查登录页面返回状态失败:', error)
|
|
|
|
|
|
return false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 跳转到登录页面(如果不在登录页面才跳转)
|
|
|
|
|
|
* 如果已经在登录页面,则不进行跳转
|
|
|
|
|
|
* 如果多次被弹出,退出时直接回到根目录
|
|
|
|
|
|
*/
|
|
|
|
|
|
function navigateToLogin() {
|
|
|
|
|
|
// 如果已经在登录页面,不需要再次跳转
|
|
|
|
|
|
if (isInLoginPage()) {
|
|
|
|
|
|
console.log('已在登录页面,无需重复跳转')
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前页面栈
|
|
|
|
|
|
const pages = getCurrentPages()
|
|
|
|
|
|
|
|
|
|
|
|
// 如果页面栈中有多个页面,且包含登录页面,说明可能是多次弹出
|
|
|
|
|
|
// 使用 reLaunch 直接回到根目录(首页),然后跳转到登录页
|
|
|
|
|
|
if (pages.length > 1) {
|
|
|
|
|
|
// 检查页面栈中是否有登录页面
|
|
|
|
|
|
const hasLoginPage = pages.some(page => {
|
|
|
|
|
|
const route = page ? page.route : ''
|
|
|
|
|
|
return route === 'pages/login/login'
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
if (hasLoginPage) {
|
|
|
|
|
|
// 多次被弹出,直接回到根目录
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: '/pages/login/login'
|
|
|
|
|
|
})
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 正常情况,跳转到登录页面
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: '/pages/login/login',
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
// 如果 navigateTo 失败(可能因为页面栈已满),使用 reLaunch
|
|
|
|
|
|
console.warn('navigateTo 失败,使用 reLaunch:', err)
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: '/pages/login/login'
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 12:27:55 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* 刷新token
|
|
|
|
|
|
* @param {String} refreshToken 刷新令牌
|
|
|
|
|
|
* @returns {Promise} 返回刷新结果
|
|
|
|
|
|
*/
|
|
|
|
|
|
function refreshAccessToken(refreshToken) {
|
|
|
|
|
|
if (isRefreshing) {
|
|
|
|
|
|
// 如果正在刷新,返回一个Promise,等待刷新完成
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
refreshSubscribers.push({ resolve, reject })
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
isRefreshing = true
|
|
|
|
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
// 构建完整URL
|
|
|
|
|
|
const fullUrl = `${BASE_URL}/app-api/member/auth/refresh`
|
|
|
|
|
|
|
|
|
|
|
|
// 构建请求头
|
|
|
|
|
|
const requestHeader = {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'tenant-id': '1'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 发起刷新请求
|
|
|
|
|
|
uni.request({
|
|
|
|
|
|
url: fullUrl,
|
|
|
|
|
|
method: 'POST',
|
|
|
|
|
|
data: { refreshToken },
|
|
|
|
|
|
header: requestHeader,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
if (res.statusCode === 200 && res.data) {
|
|
|
|
|
|
// 处理响应数据
|
|
|
|
|
|
const responseData = res.data
|
|
|
|
|
|
if (responseData.code === 0 || responseData.code === 200) {
|
|
|
|
|
|
const data = responseData.data || responseData
|
|
|
|
|
|
|
|
|
|
|
|
// 保存新的token
|
|
|
|
|
|
const token = data?.accessToken || data?.token || data?.access_token
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
uni.setStorageSync('token', token)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存新的refreshToken(如果有)
|
|
|
|
|
|
const newRefreshToken = data?.refreshToken
|
|
|
|
|
|
if (newRefreshToken) {
|
|
|
|
|
|
uni.setStorageSync('refreshToken', newRefreshToken)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 保存新的过期时间
|
|
|
|
|
|
const expiresTime = data?.expiresTime
|
|
|
|
|
|
if (expiresTime) {
|
|
|
|
|
|
uni.setStorageSync('tokenExpiresTime', expiresTime)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 通知所有等待的请求
|
|
|
|
|
|
refreshSubscribers.forEach(subscriber => subscriber.resolve())
|
|
|
|
|
|
refreshSubscribers = []
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
|
|
|
|
|
|
resolve(data)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 刷新失败
|
|
|
|
|
|
const errorMsg = responseData.message || responseData.msg || '刷新token失败'
|
|
|
|
|
|
refreshSubscribers.forEach(subscriber => subscriber.reject(new Error(errorMsg)))
|
|
|
|
|
|
refreshSubscribers = []
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
reject(new Error(errorMsg))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 刷新失败
|
|
|
|
|
|
refreshSubscribers.forEach(subscriber => subscriber.reject(new Error('刷新token失败')))
|
|
|
|
|
|
refreshSubscribers = []
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
reject(new Error('刷新token失败'))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
// 刷新失败
|
|
|
|
|
|
refreshSubscribers.forEach(subscriber => subscriber.reject(err))
|
|
|
|
|
|
refreshSubscribers = []
|
|
|
|
|
|
isRefreshing = false
|
|
|
|
|
|
reject(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 统一请求方法
|
|
|
|
|
|
* @param {Object} options 请求配置
|
|
|
|
|
|
* @param {String} options.url 请求地址(相对路径,会自动拼接BASE_URL)
|
|
|
|
|
|
* @param {String} options.method 请求方法,默认GET
|
|
|
|
|
|
* @param {Object} options.data 请求参数
|
|
|
|
|
|
* @param {Object} options.header 请求头
|
|
|
|
|
|
* @param {Boolean} options.showLoading 是否显示loading,默认false
|
|
|
|
|
|
* @param {Boolean} options.needAuth 是否需要token认证,默认true
|
|
|
|
|
|
* @returns {Promise} 返回处理后的响应数据
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function request(options = {}) {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
const {
|
|
|
|
|
|
url,
|
|
|
|
|
|
method = 'GET',
|
|
|
|
|
|
data = {},
|
|
|
|
|
|
header = {},
|
|
|
|
|
|
showLoading = false,
|
|
|
|
|
|
needAuth = true
|
|
|
|
|
|
} = options
|
|
|
|
|
|
|
|
|
|
|
|
// 显示loading
|
|
|
|
|
|
if (showLoading) {
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: '加载中...',
|
|
|
|
|
|
mask: true
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 构建完整URL(处理BASE_URL末尾斜杠问题)
|
|
|
|
|
|
const fullUrl = url.startsWith('http') ? url : `${BASE_URL}${url.startsWith('/') ? url : '/' + url}`
|
|
|
|
|
|
|
2026-01-14 10:55:42 +00:00
|
|
|
|
// 构建请求头(先添加基础头,再添加传入的 header,最后添加 token,确保 token 不会被覆盖)
|
2025-12-19 12:27:55 +00:00
|
|
|
|
const requestHeader = {
|
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
|
'tenant-id': '1', // 统一添加租户ID
|
|
|
|
|
|
...header
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 10:55:42 +00:00
|
|
|
|
// 添加token(如果需要认证,必须在最后添加,确保不会被 header 参数覆盖)
|
2025-12-19 12:27:55 +00:00
|
|
|
|
if (needAuth) {
|
|
|
|
|
|
const token = uni.getStorageSync('token')
|
|
|
|
|
|
if (token) {
|
|
|
|
|
|
requestHeader['Authorization'] = `Bearer ${token}`
|
2026-01-14 10:55:42 +00:00
|
|
|
|
console.log('[API] 请求已添加 Authorization 头:', url)
|
|
|
|
|
|
console.log('[API] Token 长度:', token.length, 'Token 前10位:', token.substring(0, 10))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
console.error('[API] 需要认证但未找到 token,请求可能失败:', url)
|
|
|
|
|
|
console.error('[API] 当前存储的所有 key:', uni.getStorageInfoSync().keys)
|
|
|
|
|
|
// 即使没有 token,也尝试发起请求(让后端返回 401)
|
2025-12-19 12:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-01-14 10:55:42 +00:00
|
|
|
|
// 调试:打印请求信息
|
|
|
|
|
|
console.log('[API] 发起请求:', {
|
|
|
|
|
|
url: fullUrl,
|
|
|
|
|
|
method: method.toUpperCase(),
|
|
|
|
|
|
needAuth: needAuth,
|
|
|
|
|
|
hasAuthHeader: !!requestHeader['Authorization'],
|
|
|
|
|
|
headers: Object.keys(requestHeader)
|
|
|
|
|
|
})
|
|
|
|
|
|
|
2025-12-19 12:27:55 +00:00
|
|
|
|
// 发起请求
|
|
|
|
|
|
uni.request({
|
|
|
|
|
|
url: fullUrl,
|
|
|
|
|
|
method: method.toUpperCase(),
|
|
|
|
|
|
data: data,
|
|
|
|
|
|
header: requestHeader,
|
|
|
|
|
|
success: (res) => {
|
|
|
|
|
|
// 隐藏loading
|
|
|
|
|
|
if (showLoading) {
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 统一处理响应
|
|
|
|
|
|
if (res.statusCode === 200) {
|
|
|
|
|
|
// 根据实际后端返回的数据结构处理
|
|
|
|
|
|
// 如果后端返回格式为 { code: 200, data: {}, message: '' }
|
|
|
|
|
|
if (res.data && typeof res.data === 'object') {
|
|
|
|
|
|
// 如果后端有统一的code字段
|
|
|
|
|
|
if (res.data.code !== undefined) {
|
|
|
|
|
|
// 处理业务错误码401(账号未登录)
|
|
|
|
|
|
if (res.data.code === 401) {
|
2026-01-14 10:55:42 +00:00
|
|
|
|
// 如果已经在登录页面,不需要显示弹窗和跳转
|
|
|
|
|
|
if (isInLoginPage()) {
|
|
|
|
|
|
reject(new Error('未授权'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-12-19 12:27:55 +00:00
|
|
|
|
// 显示登录弹窗
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: res.data.msg || res.data.message || '账号未登录,请前往登录',
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
confirmText: '去登录',
|
|
|
|
|
|
success: (modalRes) => {
|
|
|
|
|
|
if (modalRes.confirm) {
|
|
|
|
|
|
// 清除本地存储的登录信息
|
|
|
|
|
|
uni.removeStorageSync('token')
|
|
|
|
|
|
uni.removeStorageSync('refreshToken')
|
|
|
|
|
|
uni.removeStorageSync('tokenExpiresTime')
|
|
|
|
|
|
uni.removeStorageSync('userId')
|
|
|
|
|
|
uni.removeStorageSync('userInfo')
|
|
|
|
|
|
// 跳转到登录页面
|
2026-01-14 10:55:42 +00:00
|
|
|
|
navigateToLogin()
|
2025-12-19 12:27:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(new Error('未授权'))
|
|
|
|
|
|
return
|
|
|
|
|
|
}
|
|
|
|
|
|
if (res.data.code === 200 || res.data.code === 0) {
|
|
|
|
|
|
resolve(res.data.data || res.data)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 业务错误
|
|
|
|
|
|
const errorMsg = res.data.message || res.data.msg || '请求失败'
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: errorMsg,
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
duration: 2000
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(new Error(errorMsg))
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 没有code字段,直接返回data
|
|
|
|
|
|
resolve(res.data)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
resolve(res.data)
|
|
|
|
|
|
}
|
|
|
|
|
|
} else if (res.statusCode === 401) {
|
2026-01-14 10:55:42 +00:00
|
|
|
|
// HTTP 401 状态码,直接显示登录弹窗
|
|
|
|
|
|
// 如果已经在登录页面,不需要显示弹窗和跳转
|
|
|
|
|
|
if (isInLoginPage()) {
|
2025-12-19 12:27:55 +00:00
|
|
|
|
reject(new Error('未授权'))
|
2026-01-14 10:55:42 +00:00
|
|
|
|
return
|
2025-12-19 12:27:55 +00:00
|
|
|
|
}
|
2026-01-14 10:55:42 +00:00
|
|
|
|
|
|
|
|
|
|
const errorMsg = res.data?.msg || res.data?.message || '账号未登录,请前往登录'
|
|
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: '提示',
|
|
|
|
|
|
content: errorMsg,
|
|
|
|
|
|
showCancel: false,
|
|
|
|
|
|
confirmText: '去登录',
|
|
|
|
|
|
success: (modalRes) => {
|
|
|
|
|
|
if (modalRes.confirm) {
|
|
|
|
|
|
// 清除token,跳转到登录页
|
|
|
|
|
|
uni.removeStorageSync('token')
|
|
|
|
|
|
uni.removeStorageSync('refreshToken')
|
|
|
|
|
|
uni.removeStorageSync('tokenExpiresTime')
|
|
|
|
|
|
uni.removeStorageSync('userId')
|
|
|
|
|
|
uni.removeStorageSync('userInfo')
|
|
|
|
|
|
navigateToLogin()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(new Error('未授权'))
|
2025-12-19 12:27:55 +00:00
|
|
|
|
} else if (res.statusCode >= 500) {
|
|
|
|
|
|
// 服务器错误
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: '服务器错误,请稍后重试',
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(new Error('服务器错误'))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 其他错误
|
|
|
|
|
|
const errorMsg = res.data?.message || res.data?.msg || `请求失败(${res.statusCode})`
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: errorMsg,
|
|
|
|
|
|
icon: 'none'
|
|
|
|
|
|
})
|
|
|
|
|
|
reject(new Error(errorMsg))
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
fail: (err) => {
|
|
|
|
|
|
// 隐藏loading
|
|
|
|
|
|
if (showLoading) {
|
|
|
|
|
|
uni.hideLoading()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 网络错误处理
|
|
|
|
|
|
let errorMsg = '网络请求失败'
|
|
|
|
|
|
if (err.errMsg) {
|
|
|
|
|
|
if (err.errMsg.includes('timeout')) {
|
|
|
|
|
|
errorMsg = '请求超时,请检查网络'
|
|
|
|
|
|
} else if (err.errMsg.includes('fail')) {
|
|
|
|
|
|
errorMsg = '网络连接失败,请检查网络设置'
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: errorMsg,
|
|
|
|
|
|
icon: 'none',
|
|
|
|
|
|
duration: 2000
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
reject(err)
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|