/** * 微信小程序统一请求封装 * 参考 ruoyi-admin/src/utils/request.ts 结构 */ // -------------------- 配置 -------------------- const BASE_URL = 'http://localhost:8080' const TIMEOUT = 30000 const TOKEN_KEY = 'token' // 防止 401 弹窗重复弹出 let isReloginPending = false // -------------------- 工具 -------------------- function getToken() { return uni.getStorageSync(TOKEN_KEY) || '' } /** * 将对象序列化为 query string */ function toQueryString(params) { return Object.entries(params) .filter(([, v]) => v !== undefined && v !== null) .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`) .join('&') } // -------------------- 响应处理 -------------------- /** * 处理业务响应码,统一返回 { data, total, rows }(按实际字段存在) */ function handleResponse(responseData) { return new Promise((resolve, reject) => { const code = responseData.code const msg = responseData.msg || '系统异常' if (code === 200) { const result = {} if (responseData.data !== undefined) result.data = responseData.data if (responseData.total !== undefined) result.total = responseData.total if (responseData.rows !== undefined) result.rows = responseData.rows resolve(result) } else if (code === 401) { if (!isReloginPending) { isReloginPending = true uni.removeStorageSync(TOKEN_KEY) uni.showModal({ title: '登录已过期', content: '登录状态已过期,请重新登录', showCancel: false, confirmText: '重新登录', confirmColor: '#C1001C', success: () => { isReloginPending = false const pages = getCurrentPages() const currentRoute = pages.length ? '/' + pages[pages.length - 1].route : '' uni.reLaunch({ url: '/pages/login/index' + (currentRoute ? '?redirect=' + encodeURIComponent(currentRoute) : '') }) } }) } reject(new Error('无效的会话,或者会话已过期,请重新登录。')) } else if (code === 500) { uni.showToast({ title: msg, icon: 'none', duration: 2500 }) reject(new Error(msg)) } else { uni.showToast({ title: msg, icon: 'none', duration: 2500 }) reject(new Error(msg)) } }) } // -------------------- 核心请求 -------------------- /** * 统一请求方法 * @param {object} options * @param {string} options.url 接口路径,如 '/system/user/list' * @param {string} [options.method] 请求方法,默认 'GET' * @param {object} [options.data] 请求体(POST / PUT) * @param {object} [options.params] URL 查询参数(GET 也可用此字段) * @param {object} [options.header] 额外请求头 * @param {boolean} [options.isToken] 是否携带 Token,默认 true * @returns {Promise<{data?, total?, rows?}>} */ function request({ url, method = 'GET', data = {}, params = {}, header = {}, isToken = true } = {}) { return new Promise((resolve, reject) => { const upperMethod = method.toUpperCase() // 拼接 query string let finalUrl = BASE_URL + url if (Object.keys(params).length) { finalUrl += '?' + toQueryString(params) } // 组装请求头 const requestHeader = { 'Content-Type': 'application/json', ...header } if (isToken) { const token = getToken() if (token) { requestHeader['Authorization'] = 'Bearer ' + token } } uni.request({ url: finalUrl, method: upperMethod, data: upperMethod === 'GET' ? undefined : data, header: requestHeader, timeout: TIMEOUT, success: (res) => { const { statusCode, data: responseData } = res // HTTP 层异常 if (statusCode !== 200) { const httpMsg = `系统接口 ${statusCode} 异常` uni.showToast({ title: httpMsg, icon: 'none' }) return reject(new Error(httpMsg)) } // 业务层处理 handleResponse(responseData).then(resolve).catch(reject) }, fail: (err) => { let message = '请求失败,请检查网络' if (err.errMsg) { if (err.errMsg.includes('timeout')) { message = '系统接口请求超时' } else if (err.errMsg.toLowerCase().includes('network') || err.errMsg.includes('连接')) { message = '后端接口连接异常' } } uni.showToast({ title: message, icon: 'none' }) reject(new Error(message)) } }) }) } // -------------------- 下载文件 -------------------- /** * 下载文件并自动打开 * @param {string} url 接口路径 * @param {object} [params] URL 查询参数 * @param {string} [fileName] 保存文件名(仅做备注,小程序由系统决定) * @returns {Promise} 临时文件路径 */ function download(url, params = {}, fileName = '') { return new Promise((resolve, reject) => { let finalUrl = BASE_URL + url if (Object.keys(params).length) { finalUrl += '?' + toQueryString(params) } const header = {} const token = getToken() if (token) { header['Authorization'] = 'Bearer ' + token } uni.showLoading({ title: '正在下载...', mask: true }) uni.downloadFile({ url: finalUrl, header, success: (res) => { uni.hideLoading() if (res.statusCode === 200) { uni.openDocument({ filePath: res.tempFilePath, showMenu: true, success: () => { resolve(res.tempFilePath) }, fail: () => { uni.showToast({ title: '文件打开失败', icon: 'none' }) reject(new Error('文件打开失败')) } }) } else { uni.showToast({ title: '下载失败', icon: 'none' }) reject(new Error('下载失败')) } }, fail: () => { uni.hideLoading() uni.showToast({ title: '下载文件出现错误,请联系管理员', icon: 'none' }) reject(new Error('下载文件出现错误,请联系管理员')) } }) }) } // -------------------- 导出 -------------------- export { request, download } export default request