import { joinApiUrl } from '@/config' import { getToken } from '@/utils/auth' import errorCode from '@/utils/errorCode' import { useUserStore } from '@/store/user' /** 是否正在处理 401 跳转,避免重复提示 */ export const isRelogin = { show: false } const SESSION_EXPIRED_MSG = '登录过期,即将登录' const LOGIN_PAGE_URL = '/pages/login/index' const LOGIN_REDIRECT_DELAY_MS = 400 /** 与 ruoyi-ui tansParams 一致:null / undefined / 空字符串不参与序列化 */ function isEmptyParam(value) { return value === null || value === undefined || value === '' } /** * 剔除空值形参(GET 查询用) */ export function sanitizeParams(params) { if (params == null || typeof params !== 'object') { return params } if (Array.isArray(params)) { return params } const result = {} Object.keys(params).forEach((key) => { const value = params[key] if (isEmptyParam(value)) { return } if (typeof value === 'object' && !Array.isArray(value)) { const nested = sanitizeParams(value) if (nested && Object.keys(nested).length > 0) { result[key] = nested } return } result[key] = value }) return result } function appendQuery(url, params) { const parts = [] const build = (obj, prefix) => { Object.keys(obj).forEach((key) => { const value = obj[key] const name = prefix ? `${prefix}[${key}]` : key if (value !== null && value !== '' && typeof value !== 'undefined') { if (typeof value === 'object' && !Array.isArray(value)) { build(value, name) } else { parts.push(`${encodeURIComponent(name)}=${encodeURIComponent(value)}`) } } }) } build(params, '') if (!parts.length) { return url } const qs = parts.join('&') return url + (url.indexOf('?') >= 0 ? '&' : '?') + qs } /** * 封装 uni.request,响应格式与 ruoyi-ui axios 拦截器一致 */ export function request(options = {}) { const header = { ...(options.header || {}) } const skipToken = header.isToken === false delete header.isToken delete header.repeatSubmit // 登录等接口不得携带过期 Token,否则部分环境下会误返回 401 if (skipToken) { delete header.Authorization } else if (getToken()) { header.Authorization = 'Bearer ' + getToken() } const method = (options.method || 'GET').toUpperCase() let url = (options.url || '').startsWith('http') ? options.url : joinApiUrl(options.url) let requestData = options.data !== undefined ? options.data : options.params if (method === 'GET' && requestData && typeof requestData === 'object' && !Array.isArray(requestData)) { const cleaned = sanitizeParams(requestData) const keys = Object.keys(cleaned || {}) if (keys.length) { url = appendQuery(url, cleaned) } requestData = undefined } return new Promise((resolve, reject) => { uni.request({ url, method, data: requestData, header: { 'Content-Type': 'application/json;charset=utf-8', ...header }, timeout: options.timeout || 10000, success: (res) => { const httpStatus = res.statusCode || 200 const data = parseResponseData(res.data) const bizCode = data.code !== undefined && data.code !== null ? Number(data.code) : null const code = bizCode !== null && !Number.isNaN(bizCode) ? bizCode : httpStatus >= 200 && httpStatus < 300 ? 200 : httpStatus const msg = resolveErrorMessage(data, code) if (code === 401) { // 登录、验证码等未带 Token 的接口:仍展示后端 msg if (skipToken) { showErrorToast(msg) reject(new Error(msg)) } else { handle401SessionExpired() reject(new Error(SESSION_EXPIRED_MSG)) } return } if (code !== 200) { if (!options.silent) { showErrorToast(msg) } reject(new Error(msg || 'error')) return } resolve(data) }, fail: (err) => { let message = err.errMsg || '网络异常' if (message.includes('timeout')) { message = '系统接口请求超时' } else if (message.includes('fail')) { message = '后端接口连接异常' } showErrorToast(message) reject(err) } }) }) } /** 解析响应体(uni 有时返回字符串) */ function parseResponseData(raw) { if (raw == null || raw === '') { return {} } if (typeof raw === 'object') { return raw } if (typeof raw === 'string') { try { return JSON.parse(raw) } catch (e) { return { msg: raw } } } return {} } /** * 错误文案:优先后端 msg,其次 errorCode 映射,最后默认 */ function resolveErrorMessage(data, code) { const serverMsg = data && data.msg != null ? String(data.msg).trim() : '' if (serverMsg) { return serverMsg } const key = String(code) if (errorCode[key]) { return errorCode[key] } return errorCode.default || '请求失败' } function showErrorToast(message) { const title = (message && String(message).trim()) || errorCode.default || '请求失败' uni.showToast({ title: title.length > 40 ? title.slice(0, 40) + '…' : title, icon: 'none', duration: 3000 }) } function handle401SessionExpired() { if (isRelogin.show) { return } isRelogin.show = true uni.showToast({ title: SESSION_EXPIRED_MSG, icon: 'none', duration: 2000 }) setTimeout(() => { const userStore = useUserStore() userStore.fedLogOut().finally(() => { isRelogin.show = false uni.reLaunch({ url: LOGIN_PAGE_URL }) }) }, LOGIN_REDIRECT_DELAY_MS) } export default request