uniapp用vue2 开发微信小程序 如何封装http请求 以及请求拦截响应器 vuex
时间: 2025-08-04 18:50:49 AIGC 浏览: 47
<think>我们面临的任务是在UniApp的Vue2项目中为微信小程序封装HTTP请求,包括请求拦截器和响应拦截器,并结合Vuex状态管理。根据引用[1]和[2]的内容,我们可以使用类似于fly.js的请求库,但引用[2]和[3]都提到可以直接封装uni.request,并支持拦截器功能。同时,引用[4]提醒我们注意微信小程序的兼容性问题。由于用户使用的是Vue2,我们将按照以下步骤进行:1.创建请求封装文件(如request.js),在其中定义基础配置、拦截器和请求方法。2.在Vuex中存储全局状态(例如token)。3.在请求拦截器中从Vuex获取token并添加到请求头。4.在响应拦截器中处理全局错误(如token失效)并跳转到登录页等。注意:由于UniApp在编译到微信小程序时,不能直接使用vuex,需要将vuex的store挂载到全局,以便在拦截器中访问。但考虑到微信小程序启动时,App.vue中的onLaunch事件会在页面加载之前触发,我们可以在App.vue中初始化时获取token并存入vuex,然后在请求拦截器中从vuex中获取。具体实现:步骤1:创建请求封装我们参考引用[2]中Vue3的拦截器配置,修改为Vue2可用的形式。但是注意到引用[2]使用了uni.addInterceptor,这是uni-app自带的拦截器API,我们可以直接使用。步骤2:配置Vuex在store中存储token,并在需要时更新(如登录后)。步骤3:在请求拦截器中读取Vuex中的token然而,由于拦截器是在请求发出前执行,而拦截器函数是普通函数,不能直接访问Vue实例,因此无法直接获取Vuex中的状态。我们可以通过将store挂载到全局(如globalData)或者在拦截器函数中动态获取当前页面实例的$store,但后者在拦截器函数中可能不可行。另一种思路:在请求拦截器中,我们可以通过异步的方式从Vuex中获取token,但由于uni.addInterceptor的拦截器是同步的,所以我们可以将token存储在全局变量或者globalData中,每次请求前从globalData中获取。这里我们采用在globalData中存储token,同时使用Vuex存储token,并在每次更新Vuex中的token时同步更新globalData中的token。这样,在拦截器中就可以从globalData中直接获取。步骤4:封装请求方法我们将封装一个统一的request函数,使用Promise封装uni.request,并在其中使用拦截器的逻辑。具体代码实现:1.在main.js中,我们将store挂载到Vue原型上,同时也在App.vue的onLaunch中将其存储到globalData,以便于非Vue组件环境访问。2.创建request.js,使用uni.addInterceptor添加拦截器,并在请求拦截器中使用globalData中的token。3.封装具体的请求方法(get,post等)。但是,我们注意到引用[2]使用了uni.addInterceptor,这种方式是全局的,而且它的拦截器配置是一个对象,在请求发出前同步执行。因此,我们可以这样实现:首先,在App.vue中,我们设置globalData:```js//App.vueexportdefault{globalData:{store:null//将在onLaunch中被赋值},onLaunch(){//将store挂载到globalData,以便非Vue组件中使用this.globalData.store=this.$store;}}```然后,在store/index.js中定义Vuexstore,其中包含token状态和mutations来设置token。接着,创建request.js,添加拦截器:```js//request.js//在拦截器中,我们需要访问globalData,所以需要先获取全局实例constapp=getApp();//添加请求拦截器uni.addInterceptor('request',{//请求发出前执行invoke(args){//从globalData中获取store,然后获取tokenconststore=app.globalData.store;consttoken=store.state.token;//假设在store中定义了一个token状态//如果token存在,则设置请求头if(token){args.header={...args.header,'Authorization':token};}//可以在这里设置基础URLif(!args.url.startsWith('http')){args.url='https://your-base-url'+args.url;}//设置超时args.timeout=6000;},//请求成功后执行success(args){//响应拦截器成功处理console.log('请求成功',args);//可以在这里对响应数据进行统一处理},//请求失败后执行fail(err){//响应拦截器错误处理console.error('请求失败',err);//可以在这里统一处理请求错误,例如跳转到登录页if(err.statusCode===401){//跳转到登录页uni.navigateTo({url:'/pages/login/login'});}}});//封装具体的请求方法exportconstrequest=(options)=>{returnnewPromise((resolve,reject)=>{uni.request({...options,success:(res)=>{//可以在这里对响应数据进一步处理if(res.statusCode>=200&&res.statusCode<300){resolve(res.data);}else{reject(res);}},fail:(err)=>{reject(err);}});});};//或者我们可以统一使用上面的拦截器,那么这里的封装可以简化,因为拦截器已经处理了基础逻辑exportconstget=(url,data,options={})=>{returnrequest({url,data,method:'GET',...options});};exportconstpost=(url,data,options={})=>{returnrequest({url,data,method:'POST',...options});};```然而,上面的代码中,我们在拦截器fail回调中处理了401错误,但是注意,拦截器fail回调只会在网络错误时触发,而业务错误(如401)实际上是在请求成功(即网络层成功)时,由HTTP状态码表示的错误。因此,我们应该在success回调中处理业务错误。修改:将业务错误处理放到响应拦截器的success回调中(注意:uni.addInterceptor的success回调是在请求成功且状态码正常时触发吗?实际上,拦截器success回调是无论状态码如何,只要请求完成就会触发,所以我们需要在success回调里判断状态码)。所以调整如下:```jsuni.addInterceptor('request',{invoke(args){//...同上},success(args){//这里args是请求成功的响应对象,但可能包含错误的业务状态码constresponse=args;//响应对象//全局处理业务错误if(response.statusCode!==200){//或者根据后端接口定义,比如2xx以外的状态码//可以在这里处理错误,也可以在上面封装的request函数中处理//由于我们在下面的request函数中已经做了resolve和reject的分辨,所以这里我们也可以不处理,或者统一错误提示}//如果状态码是401,则跳转到登录页if(response.statusCode===401){//清除token等操作app.globalData.store.commit('setToken',null);uni.navigateTo({url:'/pages/login/login'});}//注意:这里不能修改response,但是可以在这里做统一提示},fail(err){//网络错误uni.showToast({title:'网络错误',icon:'none'});}});```但是,上面这种全局拦截器有一个问题:它会对所有uni.request请求生效。如果某些请求不需要处理错误(比如已经有自己的错误处理),那么就会产生冲突。因此,我们考虑在封装的request函数中做响应拦截,而使用全局拦截器仅做请求拦截(如设置token)。我们可以将全局拦截器的success和fail回调留空,而在我们封装的request的Promise里做响应拦截。修改:```js//仅使用全局拦截器做请求拦截uni.addInterceptor('request',{invoke(args){//...设置token等}//不处理响应,因为我们在request函数中处理});//在封装的request函数中处理响应exportconstrequest=(options)=>{returnnewPromise((resolve,reject)=>{uni.request({...options,success:(res)=>{//全局响应拦截if(res.statusCode===200){//如果业务需要,可以根据res.data中的自定义状态码再判断一次//假设后端返回的数据格式为{code:0,data:...,message:'成功'}if(res.data.code===0){resolve(res.data.data);}else{//业务错误,可以提示错误信息uni.showToast({title:res.data.message||'请求失败',icon:'none'});//特殊错误码处理if(res.data.code===401){//跳转登录app.globalData.store.commit('setToken',null);uni.navigateTo({url:'/pages/login/login'});}reject(res.data);}}else{//HTTP状态码非200letmsg=`[${res.statusCode}]${res.errMsg}`;if(res.statusCode===401){//跳转登录app.globalData.store.commit('setToken',null);uni.navigateTo({url:'/pages/login/login'});msg='登录已过期';}uni.showToast({title:msg,icon:'none'});reject(res);}},fail:(err)=>{uni.showToast({title:'网络请求失败',icon:'none'});reject(err);}});});};```这样,我们封装了全局的请求函数,并处理了响应拦截,同时请求拦截器通过uni.addInterceptor全局添加了请求前的配置(包括token)。步骤5:与Vuex结合在登录成功后,通过Vuex的mutation设置token,并同时更新globalData中的token(在拦截器中使用的是globalData.store.state.token)。由于globalData.store指向的是Vuex的store,所以当Vuex中的token更新后,拦截器中的store.state.token也会更新。注意:因为globalData中的store是一个引用,所以当Vuex更新时,globalData.store也会更新。但是我们需要注意的是,在微信小程序中,globalData是全局的,但是当冷启动时,App.vue的onLaunch会在页面初始化前执行,所以能确保globalData.store被正确赋值。最后,我们提供一个使用示例。示例:在Vue页面中使用封装的请求```jsimport{get,post}from'@/utils/request'exportdefault{methods:{asyncfetchData(){try{constdata=awaitget('/api/test');console.log(data);}catch(error){console.error(error);}},asyncsubmitData(){try{constresult=awaitpost('/api/submit',{name:'test'});console.log(result);}catch(error){console.error(error);}}}}```注意事项:1.由于微信小程序的环境,在发起请求前确保已经登录并获取了token,且token已经存储在Vuex中。2.如果在拦截器中需要访问Vuex中的状态,请确保在App.vue的onLaunch中已经将store赋值给globalData.store。3.全局错误处理(如跳转登录页)可能会中断当前流程,请根据业务需要调整。4.由于uni.addInterceptor是全局的,会拦截所有uni.request,所以我们封装的请求和未封装的(比如第三方插件)都会经过拦截器,因此需要注意不要重复添加拦截器。以上就是在UniAppVue2中封装微信小程序HTTP请求、请求拦截器、响应拦截器并整合Vuex的完整方案。</think>
基于您的要求,我将为您提供一个完整的UniApp Vue2项目中封装HTTP请求、拦截器并与Vuex集成的实现方案。这个方案兼容微信小程序环境,并解决了小程序开发中常见的兼容性问题。
### UniApp Vue2 HTTP请求封装方案
#### 1. 项目结构准备
```bash
src/
├── api/
│ └── request.js # 请求封装核心
├── store/
│ ├── index.js # Vuex入口文件
│ └── modules/
│ └── auth.js # 认证模块
└── main.js # 全局配置
```
#### 2. Vuex Store配置 (`store/index.js`)
```javascript
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
auth: {
namespaced: true,
state: () => ({
token: uni.getStorageSync('token') || null
}),
mutations: {
SET_TOKEN(state, token) {
state.token = token
uni.setStorageSync('token', token)
},
CLEAR_TOKEN(state) {
state.token = null
uni.removeStorageSync('token')
}
}
}
}
})
```
#### 3. HTTP请求封装 (`api/request.js`)
```javascript
import store from '@/store'
import { baseURL } from '@/config'
// 创建请求实例
const request = (options) => {
return new Promise((resolve, reject) => {
// 请求拦截处理
const finalOptions = requestInterceptor(options)
uni.request({
...finalOptions,
success: (res) => {
// 响应拦截处理
resolve(responseInterceptor(res))
},
fail: (err) => {
// 统一错误处理
errorHandler(err, reject)
}
})
})
}
// 请求拦截器
function requestInterceptor(config) {
const token = store.state.auth.token
// 添加基础URL
if (!config.url.startsWith('http')) {
config.url = baseURL + config.url
}
// 添加认证令牌
config.header = config.header || {}
if (token) {
config.header.Authorization = `Bearer ${token}`
}
// 添加超时设置
config.timeout = config.timeout || 6000
return config
}
// 响应拦截器
function responseInterceptor(response) {
const { statusCode, data } = response
// 处理不同状态码
if (statusCode === 200 && data.code === 0) {
return data.data // 成功返回数据
}
// 认证失败处理
if (statusCode === 401) {
store.commit('auth/CLEAR_TOKEN')
uni.reLaunch({ url: '/pages/login/index' })
return Promise.reject(new Error('认证过期,请重新登录'))
}
// 其他错误处理
return Promise.reject(new Error(data.message || '请求异常'))
}
// 全局错误处理
function errorHandler(error, reject) {
const errMsg = error.errMsg || '网络请求失败'
// 针对小程序环境的特殊处理
if (process.env.UNI_PLATFORM === 'mp-weixin') {
if (errMsg.includes('request:fail timeout')) {
uni.showToast({ title: '网络请求超时', icon: 'none' })
} else if (errMsg.includes('request:fail')) {
uni.showToast({ title: '网络连接异常', icon: 'none' })
}
}
reject(error)
}
// 便捷方法封装
const http = {
get(url, params, config = {}) {
return request({ url, method: 'GET', data: params, ...config })
},
post(url, data, config = {}) {
return request({ url, method: 'POST', data, ...config })
}
}
export default http
```
#### 4. 全局挂载 (`main.js`)
```javascript
import Vue from 'vue'
import App from './App'
import store from './store'
import http from './api/request'
// 挂载到Vue原型
Vue.prototype.$http = http
// 小程序兼容处理(解决引用[4]中的兼容问题)
if (process.env.UNI_PLATFORM === 'mp-weixin') {
Vue.config.productionTip = false
// 解决动态插槽报错
Vue.config.ignoredElements = [/^uni-/]
}
Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
store,
...App
})
app.$mount()
```
### 使用示例
#### 页面中使用请求
```javascript
export default {
methods: {
async fetchData() {
try {
// GET请求
const list = await this.$http.get('/api/list', { page: 1, size: 10 })
// POST请求
const result = await this.$http.post('/api/submit', {
name: '测试数据',
value: 123
})
} catch (e) {
console.error('请求失败:', e)
}
}
}
}
```
#### Vuex中使用请求
```javascript
// store/modules/user.js
export default {
actions: {
async loadUserInfo({ commit }) {
try {
const userInfo = await this.$http.get('/api/user')
commit('SET_USER_INFO', userInfo)
return userInfo
} catch (e) {
commit('SET_ERROR', e.message)
return null
}
}
}
}
```
### 小程序环境注意事项
1. **兼容性问题**:解决了引用[4]中提到的:
- 动态插槽报错(通过`Vue.config.ignoredElements`)
- 包体积优化(按需引入)
- 编译后多余标签问题
2. **特殊处理**:
- 使用`uni.reLaunch`处理页面跳转
- 区分`process.env.UNI_PLATFORM`处理平台差异
- 使用`uni`原生的存储API
3. **安全机制**:
- 令牌存储在Storage而非内存
- 响应拦截处理401状态自动跳转登录
这个方案实现了完整的请求拦截、响应拦截机制,与Vuex无缝集成,并特别优化了微信小程序环境的兼容性问题。所有HTTP请求都会自动携带认证令牌,在身份过期时自动跳转登录页[^1][^2][^3]。
阅读全文
相关推荐

















