/** * 接口请求封装 * 统一处理请求拦截、响应拦截、错误处理、token管理等 */ // 基础URL配置(注意:末尾不要加斜杠) const BASE_URL = 'https://guangsh.manage.hschengtai.com' // 是否正在刷新token(防止并发刷新) let isRefreshing = false // 等待刷新完成的请求队列 let refreshSubscribers = [] /** * 检查当前是否在登录页面 * @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' }) } }) } /** * 刷新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}` // 构建请求头(先添加基础头,再添加传入的 header,最后添加 token,确保 token 不会被覆盖) const requestHeader = { 'Content-Type': 'application/json', 'tenant-id': '1', // 统一添加租户ID ...header } // 添加token(如果需要认证,必须在最后添加,确保不会被 header 参数覆盖) if (needAuth) { const token = uni.getStorageSync('token') if (token) { requestHeader['Authorization'] = `Bearer ${token}` 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) } } // 调试:打印请求信息 console.log('[API] 发起请求:', { url: fullUrl, method: method.toUpperCase(), needAuth: needAuth, hasAuthHeader: !!requestHeader['Authorization'], headers: Object.keys(requestHeader) }) // 发起请求 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) { // 如果已经在登录页面,不需要显示弹窗和跳转 if (isInLoginPage()) { reject(new Error('未授权')) return } // 显示登录弹窗 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') // 跳转到登录页面 navigateToLogin() } } }) 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) { // HTTP 401 状态码,直接显示登录弹窗 // 如果已经在登录页面,不需要显示弹窗和跳转 if (isInLoginPage()) { reject(new Error('未授权')) return } 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('未授权')) } 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) } }) }) }