consumer-app/api/index.js

398 lines
11 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/**
* 接口请求封装
* 统一处理请求拦截、响应拦截、错误处理、token管理等
*/
// 基础URL配置注意末尾不要加斜杠
const BASE_URL = 'https://guangsh.manage.hschengtai.com'
// const BASE_URL = 'http://192.168.0.97:48085'
// const BASE_URL = 'http://192.168.5.135:48085'
// 是否正在刷新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: true,
cancelText: '取消',
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: true,
cancelText: '取消',
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()
}
// 网络错误处理(真机/预览时域名未配置会报 domain 相关错误)
let errorMsg = '网络请求失败'
if (err.errMsg) {
const em = err.errMsg
if (em.includes('timeout')) {
errorMsg = '请求超时,请检查网络'
} else if (em.includes('domain') || em.includes('url not in')) {
errorMsg = '请在小程序后台配置服务器域名'
} else if (em.includes('fail')) {
errorMsg = '网络连接失败,请检查网络设置'
}
}
uni.showToast({
title: errorMsg,
icon: 'none',
duration: 2000
})
reject(err)
}
})
})
}