
前言在 uni-app 跨端项目开发中官方原生uni.requestAPI 虽然开箱即用但绝大多数开发者直接在页面中裸写请求会导致项目后期出现大量问题代码冗余杂乱、接口难以统一管理、Token 携带混乱、无统一超时控制、弱网体验差、报错提示不统一、按钮重复请求、登录态失效重复跳转等一系列工程化问题。尤其在同时兼容小程序、H5、App的多端项目中原生请求写法极易出现各端表现不一致、异常处理漏洞多、维护成本极高的情况。为解决以上痛点本文从零实现一套企业级、高可用、可直接上线的 uni-app 网络请求封装。涵盖多环境配置、请求拦截、响应拦截、全局超时、防重复请求、Loading 防抖、分级错误处理、登录态锁、静默请求、开发日志等全套能力。全文代码完整、注释详细、多场景示例丰富复制即可直接投入项目使用非常适合日常开发与课程作业、技术博客创作。一、原生 uni.request 的开发痛点如果项目不做请求封装长期裸写原生请求会存在以下致命问题代码极度冗余每个页面重复写 Loading、Token、异常判断、请求头配置。环境无法统一管理开发、测试、生产域名分散上线改代码极易出错。无超时控制弱网、断网场景下请求一直挂起页面长期卡顿。错误处理混乱网络错误、超时、404、500、登录失效提示杂乱不统一。重复请求问题严重快速点击按钮会重复提交接口导致数据错乱。登录态体验差Token 过期后多个接口同时报错造成多次弹窗、多次跳转登录页。交互不灵活无法适配下拉刷新、轮询、埋点等特殊业务场景。二、本次封装整体设计思路本次封装遵循高可用、易扩展、多端统一、工程化原则实现企业级网络层架构多环境域名统一配置一键切换开发/测试/生产环境统一请求拦截自动携带 Token、统一请求头、防重复请求全局超时统一管控单独捕获超时异常统一响应拦截分层处理 HTTP 状态码、后端业务码精细化分级错误处理超时、断网、401、404、500、业务错误区分提示登录态锁机制防止 Token 失效重复跳转登录页Loading 防抖优化解决加载框闪烁问题支持静默请求、关闭加载框适配全部业务场景开发环境日志打印方便调试排错三、完整封装代码实现按照 uni-app 规范在项目根目录utils文件夹下新建request.js文件这是请求核心封装文件所有网络能力均在此实现。3.1 核心请求工具类 utils/request.jsjavascript运行// 一、环境与全局配置 // 环境切换dev 开发 / test 测试 / prod 生产 const ENV dev // 多环境接口域名管理 const baseUrlMap { dev: https://dev-api.xxx.com, // 开发环境接口 test: https://test-api.xxx.com, // 测试环境接口 prod: https://prod-api.xxx.com // 生产环境接口 } const baseUrl baseUrlMap[ENV] // 全局统一请求超时时间8000ms const REQUEST_TIMEOUT 8000 // 存储进行中的请求标识用于拦截重复请求 const pendingRequest new Set() // 登录状态锁防止Token失效后重复跳转登录页 let isToLogin false // 二、通用工具函数 /** * 生成请求唯一标识地址请求方式参数用于防重复请求 * param {Object} config 请求配置 * returns {String} 唯一标识字符串 */ function generateReqKey(config) { const { url, method, data } config return ${url}-${method}-${JSON.stringify(data || {})} } /** * Loading 防抖关闭避免加载框一闪而过 */ let loadingTimer null function hideLoading() { if (loadingTimer) clearTimeout(loadingTimer) loadingTimer setTimeout(() { uni.hideLoading() }, 200) } // 三、核心请求主函数 /** * 统一请求入口 * param {Object} config 请求配置 */ function request(config) { // 解构配置项设置默认值 const { url, data {}, method GET, header {}, loading true, // 是否展示加载框 silent false // 是否静默请求不弹出错误提示 } config // 生成请求唯一标识 const reqKey generateReqKey(config) // 拦截短时间内重复发起的相同请求 if (pendingRequest.has(reqKey)) { return Promise.reject(检测到重复请求已拦截) } pendingRequest.add(reqKey) // 拼接完整接口地址 const fullUrl baseUrl url // 初始化默认请求头 const defaultHeader { Content-Type: application/json;charsetUTF-8 } // 统一挂载登录凭证 Token const token uni.getStorageSync(token) if (token) { defaultHeader.Authorization Bearer ${token} } // 合并自定义请求头 const mergeHeader { ...defaultHeader, ...header } // 展示全局加载框添加遮罩防止点击穿透 if (loading) { uni.showLoading({ title: 请求中..., mask: true }) } // 返回 Promise 对象支持 async/await 语法 return new Promise((resolve, reject) { uni.request({ url: fullUrl, data, method, header: mergeHeader, timeout: REQUEST_TIMEOUT, // 网络层面请求成功状态码 2xx success: (res) { // 移除请求标识 pendingRequest.delete(reqKey) hideLoading() const { statusCode, data: resData } res // 开发环境打印日志方便调试 if (ENV dev) { console.log(【${method}】${fullUrl} 响应数据, resData) } // 1. 处理 HTTP 状态码 if (statusCode 200 || statusCode 300) { if (!silent) { switch (statusCode) { case 401: // 未授权/Token 过期 if (!isToLogin) { isToLogin true uni.showToast({ title: 登录已失效请重新登录, icon: none }) uni.removeStorageSync(token) // 延迟跳转保证提示框展示完成 setTimeout(() { uni.reLaunch({ url: /pages/login/login }) isToLogin false }, 1000) } break case 404: uni.showToast({ title: 接口地址不存在, icon: none }) break case 500: uni.showToast({ title: 服务器内部错误, icon: none }) break default: uni.showToast({ title: 请求错误 ${statusCode}, icon: none }) } } reject(resData) return } // 2. 处理后端自定义业务状态码约定 code200 为业务正常 const { code, data, msg } resData if (code 200) { resolve(data) } else { if (!silent) { uni.showToast({ title: msg || 业务请求失败, icon: none }) } reject(resData) } }, // 网络层面请求失败断网、超时、跨域等 fail: (err) { pendingRequest.delete(reqKey) hideLoading() const errMsg err.errMsg || // 开发环境打印错误日志 if (ENV dev) { console.error(【${method}】${fullUrl} 请求失败, err) } if (!silent) { if (errMsg.includes(timeout)) { uni.showToast({ title: 请求超时请检查网络, icon: none }) } else { uni.showToast({ title: 网络连接异常请稍后重试, icon: none }) } } reject(err) } }) } } // 四、导出常用请求方法 export default { get(url, data, options {}) { return request({ url, data, method: GET, ...options }) }, post(url, data, options {}) { return request({ url, data, method: POST, ...options }) }, put(url, data, options {}) { return request({ url, data, method: PUT, ...options }) }, delete(url, data, options {}) { return request({ url, data, method: DELETE, ...options }) } }四、完整核心代码实现request.js在项目utils目录下新建request.js以下为完整可运行源码可直接复制使用。// 1. 全局环境配置 const ENV dev // dev开发 / test测试 / prod生产const baseUrlMap {dev: https://dev-api.xxx.com,test: https://test-api.xxx.com,prod: https://prod-api.xxx.com}const baseUrl baseUrlMap[ENV]// 全局统一超时时间 8秒const REQUEST_TIMEOUT 8000// 防重复请求队列const pendingRequest new Set()// 登录锁防止多次跳转登录页let isToLogin false// 2. 工具函数 /*** 生成请求唯一标识用于拦截重复请求*/function generateReqKey(config) {const { url, method, data } configreturn ${url}-${method}-${JSON.stringify(data || {})}}/*** Loading防抖关闭防止闪烁*/let loadingTimer nullfunction hideLoading() {if (loadingTimer) clearTimeout(loadingTimer)loadingTimer setTimeout(() {uni.hideLoading()}, 200)}// 3. 核心请求方法 function request(config) {const {url,data {},method GET,header {},loading true,silent false} config// 拦截重复请求const reqKey generateReqKey(config)if (pendingRequest.has(reqKey)) {return Promise.reject(重复请求已拦截)}pendingRequest.add(reqKey)// 拼接接口地址const fullUrl baseUrl url// 默认请求头const defaultHeader {Content-Type: application/json;charsetUTF-8}// 自动携带Tokenconst token uni.getStorageSync(token)if (token) {defaultHeader.Authorization Bearer ${token}}// 合并自定义请求头const mergeHeader { ...defaultHeader, ...header }// 开启加载框if (loading) {uni.showLoading({title: 请求中...,mask: true})}return new Promise((resolve, reject) {uni.request({url: fullUrl,data,method,header: mergeHeader,timeout: REQUEST_TIMEOUT,// 网络请求成功success: (res) {pendingRequest.delete(reqKey)hideLoading()const { statusCode, data: resData } res// 开发环境打印日志if (ENV dev) {console.log(【${method}】${fullUrl}, resData)}// HTTP状态码错误处理if (statusCode 200 || statusCode 300) {if (!silent) {switch (statusCode) {case 401:if (!isToLogin) {isToLogin trueuni.showToast({ title: 登录已失效请重新登录, icon: none })uni.removeStorageSync(token)setTimeout(() {uni.reLaunch({ url: /pages/login/login })isToLogin false}, 1000)}breakcase 404:uni.showToast({ title: 接口地址不存在, icon: none })breakcase 500:uni.showToast({ title: 服务器内部错误, icon: none })breakdefault:uni.showToast({ title: 请求错误${statusCode}, icon: none })}}reject(resData)return}// 业务状态码处理const { code, data, msg } resDataif (code 200) {resolve(data)} else {if (!silent) {uni.showToast({ title: msg || 请求失败, icon: none })}reject(resData)}},// 网络请求失败fail: (err) {pendingRequest.delete(reqKey)hideLoading()const errMsg err.errMsg || if (ENV dev) {console.error(【${method}】${fullUrl} 请求失败, err)}if (!silent) {if (errMsg.includes(timeout)) {uni.showToast({ title: 请求超时请检查网络, icon: none })} else {uni.showToast({ title: 网络异常请稍后重试, icon: none })}}reject(err)}})})}// 4. 导出常用请求方法 export default {get(url, data, options {}) {return request({ url, data, method: GET, ...options })},post(url, data, options {}) {return request({ url, data, method: POST, ...options })},put(url, data, options {}) {return request({ url, data, method: PUT, ...options })},delete(url, data, options {}) {return request({ url, data, method: DELETE, ...options })}}五、全局挂载main.js将请求方法全局挂载所有页面无需重复引入直接使用。import Vue from vueimport App from ./Appimport request from ./utils/request.jsVue.prototype.$http requestconst app new Vue({...App})app.$mount()六、进阶接口模块化管理1.接口模块化拆分进阶规范api 目录管理接口项目规模变大后不建议在页面中直接写接口地址推荐按业务模块拆分接口文件。在根目录新建api文件夹拆分不同业务接口示例 1api/user.js 用户相关接口javascript运行import request from ../utils/request.js // 用户登录 export function login(data) { return request.post(/user/login, data) } // 获取用户信息 export function getUserInfo() { return request.get(/user/info) } // 退出登录 export function logout() { return request.post(/user/logout, {}, { loading: false }) }示例 2api/article.js 文章相关接口javascript运行import request from ../utils/request.js // 获取文章列表 export function getArticleList(data) { return request.get(/article/list, data) } // 发布文章 export function publishArticle(data) { return request.post(/article/add, data) }2. 全局挂载main.js将请求方法全局挂载到 Vue 原型全项目页面、组件可直接调用javascript运行import Vue from vue import App from ./App // 引入请求工具 import request from ./utils/request.js // 全局挂载 Vue.prototype.$http request const app new Vue({ ...App }) app.$mount()3.api/user.js 用户模块接口大型项目建议新建api文件夹按业务拆分接口方便统一维护。import request from ../utils/request.js// 用户登录export function login(data) {return request.post(/user/login, data)}// 获取用户信息export function getUserInfo() {return request.get(/user/info)}七、多场景实战调用示例6.1 基础请求默认带 Loadingasync getList() {try {const res await this.$http.get(/article/list, { page: 1, size: 10 })console.log(列表数据, res)} catch (err) {console.log(请求失败, err)}}6.2 关闭 Loading下拉刷新场景async onPullDownRefresh() {const res await this.$http.get(/article/list, { page: 1 }, { loading: false })this.list resuni.stopPullDownRefresh()}6.3 静默请求埋点、后台轮询无需弹窗、无需加载框适合无痕上报场景。async reportLog() {await this.$http.post(/log/report, { name: 页面浏览 }, {loading: false,silent: true})}结合不同业务场景提供基础调用、进阶配置、模块化接口调用三类示例覆盖日常开发绝大多数场景。6.4 基础用法常规带加载框请求适用于页面初始化、普通表单提交等场景默认展示加载框、弹出错误提示。vuetemplate view button clickgetArticleData获取文章列表/button /view /template script export default { methods: { async getArticleData() { try { // 发起GET请求传递分页参数 const res await this.$http.get(/article/list, { page: 1, pageSize: 10 }) console.log(文章列表数据, res) } catch (err) { console.log(请求异常, err) } } } } /script6.5 进阶用法 1关闭加载框下拉刷新 / 局部刷新列表下拉刷新、上拉加载更多时无需重复展示加载框配置loading: false。javascript运行async onPullDownRefresh() { try { // 关闭加载动画 const res await this.$http.get(/article/list, { page: 1 }, { loading: false }) this.articleList res } catch (e) { console.log(刷新失败, e) } finally { uni.stopPullDownRefresh() // 停止下拉刷新动画 } }6.6 进阶用法 2静默请求埋点 / 后台轮询页面埋点、心跳检测、后台轮询等场景即使请求失败也不需要弹窗提示配置silent: true。javascript运行// 页面加载完成后上报浏览记录 onShow() { this.reportBrowseLog() }, methods: { async reportBrowseLog() { // 静默请求不显示加载框、不弹出错误提示 await this.$http.post(/log/browse, { pageName: 首页, time: new Date().getTime() }, { loading: false, silent: true }) } }6.7 进阶用法 3模块化接口调用推荐大型项目使用引入api模块中封装好的接口代码解耦便于统一维护接口地址。vuescript // 引入模块接口 import { login } from /api/user.js export default { data() { return { form: { username: , password: } } }, methods: { // 登录提交 async submitLogin() { try { const res await login(this.form) uni.setStorageSync(token, res.token) uni.switchTab({ url: /pages/index/index }) } catch (err) { console.log(登录失败, err) } } } } /script6.8 进阶用法 4自定义请求头部分特殊接口需要额外追加请求头字段直接传入header配置即可。javascript运行async getSpecialData() { const res await this.$http.get(/api/special, {}, { header: { Custom-Field: 123456 // 自定义请求头 } }) }八、本封装核心优化亮点多环境一键切换适配开发、测试、生产上线无需改代码防重复请求机制彻底解决按钮连击、重复提交问题统一请求拦截自动携带 Token权限管理统一规范全局超时拦截解决弱网卡死、请求无限挂起问题分级错误处理网络、超时、服务端、业务错误精准提示登录锁防重复跳转彻底解决 Token 过期多次弹窗 bugLoading 防抖优化界面体验更加流畅高级多场景适配支持常规请求、刷新、轮询、埋点全部场景优化亮点详解1. 多环境灵活切换通过ENV变量区分开发、测试、生产三套域名项目打包上线无需大面积修改代码适配迭代、上线流程。2. 防重复请求机制通过接口地址 请求方式 请求参数生成唯一标识短时间内相同请求直接拦截避免按钮连点、重复提交造成接口压力。3. 登录态防重复跳转新增登录锁isToLoginToken 失效时仅执行一次登录跳转彻底解决连续弹窗、多层路由叠加问题。4. Loading 防抖优化使用定时器防抖关闭加载框避免接口响应过快时 Loading 一闪而过提升视觉体验同时增加mask遮罩防止加载期间页面点击穿透。5. 分级错误处理分层区分网络错误、请求超时、HTTP 状态码404/401/500、后端业务码不同错误对应不同提示文案逻辑清晰。6. 灵活配置项支持loading、silent、自定义header配置完美适配列表刷新、轮询、埋点、表单提交等不同业务场景。7. 开发环境日志仅在开发环境打印完整请求与响应日志线上环境关闭日志兼顾调试便利与性能安全。8. 结构解耦工具方法、配置项、核心请求、请求方法分层编写同时支持接口模块化拆分代码结构清晰后续扩展加密、加签、请求取消都非常方便。九、扩展拓展建议项目进阶请求参数加密在请求拦截中对data参数做 AES/RSA 加密保障接口数据安全。请求取消结合AbortController实现页面卸载时取消进行中的请求减少无效网络请求。自动重试机制针对网络偶发失败增加有限次数自动重试逻辑。全局请求计数统计当前请求数量实现 “所有请求完成后再关闭全局加载” 的场景。拦截器拆分参考 Axios 思想单独拆分请求拦截器、响应拦截器进一步解耦代码。十、总结本文完成了一套企业级、全端适配、高稳定性的 uni-app 网络请求封装。相比原生请求本次封装解决了代码冗余、环境混乱、体验差、BUG 多等核心问题实现了统一拦截、统一超时、统一错误处理、防重复请求、登录态管控的完整工程化能力。搭配接口模块化管理方案后项目结构更加规范后期接口迭代、环境切换、问题排查效率大幅提升。同时多种调用方式可以适配不同业务场景兼顾新手上手与大型项目架构要求。整套代码兼容 uni-app 小程序、H5、App 全端可直接落地到实际项目中是 uni-app 工程化开发的标准实践。整套代码注释完善、可直接运行、适配小程序/H5/App 全平台是 uni-app 项目开发的最佳工程化实践适合学习、毕设、课程作业以及商业项目直接使用。觉得有用欢迎收藏有问题可在评论区交流