import { DEFAULT_BASE_URL, DEFAULT_TIMEOUT, SUCCESS_CODE } from '@/constants/request' import { clearAuth, getToken, isAuthEnabled, redirectToLogin } from '@/utils/auth' import { showToast } from '@/utils/toast' let baseURL = DEFAULT_BASE_URL function buildUrl(url = '') { if (!url) { return baseURL } if (/^https?:\/\//.test(url)) { return url } if (url.startsWith('/')) { return `${baseURL}${url}` } return `${baseURL}/${url}` } /** * 构造请求头。 * @param {Record} customHeader 自定义请求头 * @param {boolean} needAuth 是否需要携带 token * @param {boolean} withJson 是否设置 JSON Content-Type * @returns {Record} */ function buildHeaders(customHeader = {}, needAuth = true, withJson = true) { const headers = { ...(withJson ? { 'Content-Type': 'application/json' } : {}), ...customHeader } if (needAuth && isAuthEnabled()) { const token = getToken() if (token) { headers.Authorization = `Bearer ${token}` } } return headers } function handleUnauthorized() { if (!isAuthEnabled()) { return } clearAuth() redirectToLogin() } /** * 统一解析后端响应结构。 * 约定:`code === 0` 为成功,最终返回 `data` 字段。 */ function resolveResponseData(responseData) { const code = typeof responseData?.code === 'number' ? responseData.code : SUCCESS_CODE if (code !== SUCCESS_CODE) { const error = new Error(responseData?.message || '业务处理失败') error.code = code throw error } return responseData?.data !== undefined ? responseData.data : responseData } /** * 通用请求方法。 * @param {object} options uni.request 参数扩展 * @returns {Promise} */ function request(options = {}) { const { url, method = 'GET', data, header, timeout = DEFAULT_TIMEOUT, needAuth = true, showErrorToast = true, showToast: showToastLegacy } = options const shouldShowErrorToast = typeof showToastLegacy === 'boolean' ? showToastLegacy : showErrorToast return new Promise((resolve, reject) => { uni.request({ url: buildUrl(url), method: String(method).toUpperCase(), data, timeout, header: buildHeaders(header, needAuth, true), success: (res) => { const { statusCode = 0, data: responseData = {} } = res if (statusCode === 401) { handleUnauthorized() reject(new Error('登录状态已失效')) return } if (statusCode < 200 || statusCode >= 300) { const message = responseData?.message || `网络错误(${statusCode})` if (shouldShowErrorToast) { showToast(message) } reject(new Error(message)) return } try { const finalData = resolveResponseData(responseData) resolve(finalData) } catch (error) { if (error.code === 401) { handleUnauthorized() } if (shouldShowErrorToast) { showToast(error.message) } reject(error) } }, fail: (error) => { if (shouldShowErrorToast) { showToast('网络异常,请稍后重试') } reject(error) } }) }) } request.get = (url, params = {}, config = {}) => { return request({ url, method: 'GET', data: params, ...config }) } request.post = (url, data = {}, config = {}) => { return request({ url, method: 'POST', data, ...config }) } request.put = (url, data = {}, config = {}) => { return request({ url, method: 'PUT', data, ...config }) } request.delete = (url, data = {}, config = {}) => { return request({ url, method: 'DELETE', data, ...config }) } request.upload = (url, filePath, name = 'file', formData = {}, config = {}) => { const { header = {}, needAuth = true, showErrorToast = true, showToast: showToastLegacy } = config const shouldShowErrorToast = typeof showToastLegacy === 'boolean' ? showToastLegacy : showErrorToast return new Promise((resolve, reject) => { uni.uploadFile({ url: buildUrl(url), filePath, name, formData, header: buildHeaders(header, needAuth, false), success: (res) => { if (res.statusCode === 401) { handleUnauthorized() reject(new Error('登录状态已失效')) return } if (res.statusCode < 200 || res.statusCode >= 300) { const message = `上传失败(${res.statusCode})` if (shouldShowErrorToast) { showToast(message) } reject(new Error(message)) return } let responseData = {} try { responseData = JSON.parse(res.data || '{}') } catch (error) { if (shouldShowErrorToast) { showToast('上传返回数据格式错误') } reject(error) return } try { const finalData = resolveResponseData(responseData) resolve(finalData) } catch (error) { if (error.code === 401) { handleUnauthorized() } if (shouldShowErrorToast) { showToast(error.message) } reject(error) } }, fail: (error) => { if (shouldShowErrorToast) { showToast('上传失败,请稍后重试') } reject(error) } }) }) } /** * 动态设置 API 基础地址。 */ request.setBaseUrl = (url) => { if (!url || typeof url !== 'string') { return } baseURL = url.replace(/\/$/, '') } request.getBaseUrl = () => baseURL export default request