OfficeSystem/utils/request/index.js
WindowBird 8f202e50a1 401重新登入和403权限警告
网络状态检测代码优化
2025-11-27 10:32:15 +08:00

178 lines
6.3 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.

import { useUserStore } from '@/store/user'
/**
* 初始化请求拦截器
* 使用 Pinia store 管理用户状态和 token
*/
let isRedirectingToLogin = false
/**
* 检测网络状态
* @returns {Promise<Object>} 返回网络信息如果无网络则reject
* @throws {Object} 网络不可用或检测失败时抛出错误
*/
async function checkNetworkStatus() {
try {
const networkInfo = await new Promise((resolve, reject) => {
uni.getNetworkType({
success: resolve,
fail: reject
})
})
// 如果没有网络连接,抛出错误
if (networkInfo.networkType === 'none') {
uni.$uv.toast('网络连接不可用,请检查网络设置')
return Promise.reject({
errMsg: '网络连接不可用',
networkType: 'none'
})
}
return networkInfo
} catch (error) {
console.error('获取网络状态失败:', error)
uni.$uv.toast('无法检测网络状态,请检查网络连接')
return Promise.reject({
errMsg: '无法检测网络状态',
error
})
}
}
/**
* 处理认证错误
* @param {number} statusCode - HTTP状态码
* @param {number} code - 响应数据中的code可选用于响应拦截器
*/
function handleAuthError(statusCode, code = null) {
// 使用响应数据中的code如果提供否则使用statusCode
const errorCode = code !== null ? code : statusCode
// 401: 未授权,需要重新登录
if (errorCode === 401) {
const userStore = useUserStore()
userStore.logout()
// 防止重复跳转
if (!isRedirectingToLogin) {
isRedirectingToLogin = true
uni.$uv.toast('登录已过期,请重新登录')
setTimeout(() => {
uni.reLaunch({ url: '/pages/login/index' })
isRedirectingToLogin = false
}, 200)
}
}
// 403: 无权限,只提示,不跳转
else if (errorCode === 403) {
uni.$uv.toast('没有权限')
}
}
export const Request = () => {
// 初始化请求配置
uni.$uv.http.setConfig((config) => {
/* config 为默认全局配置*/
config.baseURL = 'http://192.168.1.2:4001'; /* 根域名 */
config.baseURL = 'https://pm.ccttiot.com/prod-api'; /* 根域名 */
return config
})
// 请求拦截
uni.$uv.http.interceptors.request.use(async (config) => {
// 检测网络状态
try {
await checkNetworkStatus()
} catch (error) {
// 网络不可用,阻止请求
return Promise.reject(error)
}
// 初始化请求拦截器时会执行此方法此时data为undefined赋予默认{}
config.data = config.data || {}
// 根据custom参数中配置的是否需要token添加对应的请求头
if(config?.custom?.auth) {
// 使用 Pinia store 获取 token
const userStore = useUserStore()
// 直接使用 state.tokenPinia getter 可以直接访问)
const token = userStore.token
if (token) {
// 使用标准的 Authorization header添加 Bearer 前缀
config.header.Authorization = `Bearer ${token}`
} else {
console.warn('请求拦截器token 为空,无法添加认证头')
}
}
return config
}, config => {
return Promise.reject(config)
})
// 响应拦截
uni.$uv.http.interceptors.response.use((response) => { /* 对响应成功做点什么 可使用async await 做异步操作*/
const data = response.data
// 自定义参数
const custom = response.config?.custom
// 处理认证错误401/403
// 401需要跳转登录403只提示权限不足
if (data.code === 401 || data.code === 403) {
handleAuthError(null, data.code)
}
if (data.code !== 200) {
// 如果没有显式定义custom的toast参数为false的话默认对报错进行toast弹出提示
if (custom?.toast !== false) {
uni.$uv.toast(data.message || data.msg || '请求失败')
}
// 如果需要catch返回则进行reject
if (custom?.catch) {
return Promise.reject(data)
} else {
// 修复内存泄漏返回resolved Promise而不是pending Promise
// 返回null表示请求失败调用方可以通过 if (!response) 检查来处理
// 这样既避免了内存泄漏,又保持了向后兼容性
return Promise.resolve(null)
}
}
// 如果响应数据有 data 字段,返回 data 字段的值
// 如果没有 data 字段,返回去除 code 和 msg 后的数据对象
if (data.data !== undefined) {
return data.data
} else {
// 创建一个新对象,排除 code 和 msg 字段
const { code, msg, message, ...rest } = data
return Object.keys(rest).length > 0 ? rest : data
}
}, async (response) => {
// 对响应错误做点什么 statusCode !== 200
// 网络错误或服务器错误处理
// 检测是否是网络错误
if (!response.statusCode || response.statusCode === 0) {
// 可能是网络连接问题,再次检测网络状态
try {
await checkNetworkStatus()
} catch (error) {
// 网络检测失败,直接返回错误
return Promise.reject(error)
}
// 网络正常但请求失败,提示并返回响应错误
uni.$uv.toast('网络连接异常,请稍后重试')
return Promise.reject(response)
}
// 处理认证错误401/403
// 401需要跳转登录403只提示权限不足
if (response.statusCode === 401 || response.statusCode === 403) {
handleAuthError(response.statusCode)
}
return Promise.reject(response)
})
}