/** * 公共请求方法封装(用户端小程序) * 统一处理:地址拼接、token 注入、响应解包、异常提示、登录失效跳转 */ import config from '@/config/index.js' // 登录失效提示开关:防止多个并发请求同时弹窗 let isReloginTipShowing = false // ======================== 登录令牌读写 ======================== /** 读取本地缓存的 token */ export function getToken() { return uni.getStorageSync(config.tokenKey) || '' } /** 写入 token 到本地缓存 */ export function setToken(token) { uni.setStorageSync(config.tokenKey, token) } /** 清除本地登录态 */ export function clearToken() { uni.removeStorageSync(config.tokenKey) } /** * 页面登录态守卫:无 token 时重定向至登录页 * 供各页面 onShow 调用,避免登录态校验逻辑重复散落 * @returns {Boolean} 已登录返回 true,未登录(已触发跳转)返回 false */ export function ensureLogin() { if (getToken()) return true uni.reLaunch({ url: config.loginPage }) return false } // ======================== 核心请求方法 ======================== /** * 统一请求方法 * @param {Object} options 请求配置 * @param {String} options.url 接口路径(不含根地址;传完整 http 地址则直接使用) * @param {String} [options.method] 请求方法,默认 GET * @param {Object} [options.data] 请求参数 * @param {Object} [options.header] 自定义请求头 * @param {Boolean} [options.auth] 是否需要携带 token,默认 true * @param {Boolean} [options.loading] 是否显示加载中提示,默认 false * @param {String} [options.loadingText] 加载提示文案 * @returns {Promise} 解析后的响应体 { code, msg, data } */ export function request(options = {}) { const { url = '', method = 'GET', data = {}, header = {}, auth = true, loading = false, loadingText = '加载中...' } = options // 拼接完整请求地址 const fullUrl = /^https?:\/\//i.test(url) ? url : config.baseUrl + url // 组装请求头 const finalHeader = { 'Content-Type': 'application/json;charset=utf-8', ...header } // 鉴权接口自动注入 token const token = getToken() if (auth && token) { finalHeader.Authorization = 'Bearer ' + token } if (loading) { uni.showLoading({ title: loadingText, mask: true }) } return new Promise((resolve, reject) => { uni.request({ url: fullUrl, method: method.toUpperCase(), data, header: finalHeader, timeout: config.timeout, success: (res) => { const { statusCode, data: body } = res // 非 HTTP 200 视为网络异常 if (statusCode !== 200) { reject(new Error('网络请求异常:' + statusCode)) return } // 登录失效:清理登录态并跳转登录页 if (body && body.code === config.unauthorizedCode) { _handleUnauthorized() reject(new Error(body.msg || '登录状态已过期')) return } // 业务成功:返回完整响应体,调用方按需取 data if (body && body.code === config.successCode) { resolve(body) return } // 业务失败:统一抛出错误信息 reject(new Error((body && body.msg) || '请求失败')) }, fail: (err) => { reject(new Error((err && err.errMsg) || '网络连接失败')) }, complete: () => { if (loading) { uni.hideLoading() } } }) }) } /** * 处理登录态失效:清缓存 + 提示 + 延时跳转登录页 */ function _handleUnauthorized() { if (isReloginTipShowing) return isReloginTipShowing = true clearToken() uni.showToast({ title: '登录状态已过期,请重新登录', icon: 'none' }) setTimeout(() => { isReloginTipShowing = false uni.reLaunch({ url: config.loginPage }) }, 1500) } // ======================== 文件上传方法 ======================== /** * 统一文件上传方法(基于 uni.uploadFile) * 与 request 保持一致的令牌注入、响应解包与登录失效处理,供图片/文件上传使用 * @param {String} url 上传接口路径(不含根地址;传完整 http 地址则直接使用) * @param {String} filePath 本地文件路径(如 chooseImage 返回的 tempFilePath) * @param {Object} [options] 可选配置 * @param {String} [options.name] 表单文件字段名,默认 file * @param {Object} [options.formData] 附加表单参数 * @param {Object} [options.header] 自定义请求头 * @param {Boolean} [options.auth] 是否需要携带 token,默认 true * @param {Boolean} [options.loading] 是否显示上传中提示,默认 true * @param {String} [options.loadingText] 上传提示文案 * @returns {Promise} 解析后的响应体 { code, msg, data } */ export function uploadFile(url = '', filePath = '', options = {}) { const { name = 'file', formData = {}, header = {}, auth = true, loading = true, loadingText = '上传中...' } = options // 拼接完整请求地址 const fullUrl = /^https?:\/\//i.test(url) ? url : config.baseUrl + url // 组装请求头并注入令牌(multipart 边界由底层自动生成,此处不设 Content-Type) const finalHeader = { ...header } const token = getToken() if (auth && token) { finalHeader.Authorization = 'Bearer ' + token } if (loading) { uni.showLoading({ title: loadingText, mask: true }) } return new Promise((resolve, reject) => { uni.uploadFile({ url: fullUrl, filePath, name, formData, header: finalHeader, timeout: config.timeout, success: (res) => { const { statusCode } = res // 非 HTTP 200 视为网络异常 if (statusCode !== 200) { reject(new Error('网络请求异常:' + statusCode)) return } // uploadFile 返回的 data 为字符串,需解析为 JSON let body try { body = JSON.parse(res.data) } catch (e) { reject(new Error('上传响应解析失败')) return } // 登录失效:清理登录态并跳转登录页 if (body && body.code === config.unauthorizedCode) { _handleUnauthorized() reject(new Error(body.msg || '登录状态已过期')) return } // 业务成功:返回完整响应体,调用方按需取 data if (body && body.code === config.successCode) { resolve(body) return } // 业务失败:统一抛出错误信息 reject(new Error((body && body.msg) || '上传失败')) }, fail: (err) => { reject(new Error((err && err.errMsg) || '上传失败')) }, complete: () => { if (loading) { uni.hideLoading() } } }) }) } // ======================== 快捷方法 ======================== /** GET 请求 */ export const get = (url, data = {}, options = {}) => request({ url, method: 'GET', data, ...options }) /** POST 请求 */ export const post = (url, data = {}, options = {}) => request({ url, method: 'POST', data, ...options }) /** PUT 请求 */ export const put = (url, data = {}, options = {}) => request({ url, method: 'PUT', data, ...options }) /** DELETE 请求 */ export const del = (url, data = {}, options = {}) => request({ url, method: 'DELETE', data, ...options }) export default request