uniapp - ajax 请求篇

本篇文章适合人群:vue和小程序技术框架人群
从本篇文章可以获得:如何从项目全局思考,封装更加灵活多变的 ajax 方法

请求封装

http目录结构

1|-http
2   |-- lib
3        |-- http.js
4        |-- util.js
5   |--index.js

http.js

 1import { deepCopy, merge } from "./util.js"
2
3
4function Http(options{
5    this.init(options);
6}
7
8Http.prototype.init = function (options{
9    // 默认配置
10    var defaultOptions = {
11        header: { 'Content-Type''application/json;charset=UTF-8' },
12        timeout6000,
13        dataType'json',
14        responseType'text',
15        baseUrl'Config.baseURL',
16        data: {},
17        isCheckTokentrue,//是否校验token
18        showLoading: true,//请求是否显示loading
19        loadingText: '加载中',//请求loading文字
20    };
21    // 实例配置   合并 defaultOptions 和 options
22    this.instanceConfig = merge(defaultOptions, options);
23    // 拦截器
24    this.interceptors = {
25        requestnull,//请求拦截器
26        response: null,// 响应拦截器
27    };
28};
29Http.prototype.request = function(userOptions){
30    return new Promise((resolve,reject)=>{
31        let globalConfig = deepCopy(this.instanceConfig)
32        let finallyConfig = merge(globalConfig, userOptions)
33        //是否校验token
34        if(finallyConfig.isCheckToken){
35            let token = uni.getStorageSync('token')
36            if (token) {
37                finallyConfig.header['Authorization'] = `bearer ${token}`
38            } else {
39                reject('token不存在')
40            }
41        }
42        //是否显示请求loading
43        if (finallyConfig.showLoading) {
44            uni.showLoading({ mask: finallyConfig.mask || truetitle: finallyConfig.loadText || '加载中' })
45        }
46        //构造请求参数
47        let requestParam = {
48            url: finallyConfig.baseUrl + finallyConfig.url,
49            data: finallyConfig.data,
50            header: finallyConfig.header,
51            method: finallyConfig.method,
52            timeout: finallyConfig.timeout,
53        }
54        //请求参数拦截
55        if (this.interceptors.request) {
56            requestParam = this.interceptors.request(requestParam, finallyConfig)
57        }
58        requestParam.success = (res)=>{
59            //请求响应拦截
60            if (this.interceptors.response) {
61                this.interceptors.response(res,finallyConfig)
62            }
63            let code = res.data.code
64            if( code === 'success'){
65                resolve(res.data.data)
66            }else{
67                reject(res)
68            }
69        }
70        requestParam.fail = (err)=>{
71            if (this.interceptors.response) {
72                this.interceptors.response(err,finallyConfig)
73            }
74            reject(err)
75        }
76        requestParam.complete = function(res){
77            if (finallyConfig.showLoading) uni.hideLoading()
78        }
79        uni.request(requestParam)
80    })
81}
82Http.prototype.get = function (url, userOptions{
83    return this.request({ method'GET',url, ...userOptions });
84};
85Http.prototype.post = function (url, userOptions{
86    console.log(url, userOptions)
87    return this.request({method'POST',url, ...userOptions });
88};
89export default Http

util.js

 1/**
2 * 对象 深拷贝 deepCopy
3 * @param {Object} p 被拷贝对象
4 * @param {Object} c 拷贝到的对象
5 * @return {Object} 拷贝后的对象
6 */

7
8export const deepCopy = (p,c)=>{
9    var c = c || {}
10    for(var i in p){
11        if(Array.isArray(p[i]) || Object.prototype.toString.call(p[i]).slice(8,-1) === 'Object'){
12            deepCopy(p[i],(c[i]=Array.isArray(p[i])?[]:{}))
13        }else{
14            c[i] = p[i]
15        }
16    }
17    return c
18}
19
20/**
21 * 对象 合并 merge
22 * @param {Object} defaultOptions 默认基对象
23 * @param {Object} userOptions 被合并对象
24 * @return {Object} 合并后的对象
25 */

26export const merge = (defaultOptions,userOptions)=>{
27    for (var i in userOptions) {
28        defaultOptions[i] = defaultOptions[i] && defaultOptions[i].toString() === "[object Object]" ? merge(defaultOptions[i], userOptions[i]) : defaultOptions[i] = userOptions[i]
29    }
30    return defaultOptions
31}

index.js

1import Http from "./lib/http-request.js"
2
3export default Http

如何使用

在项目中创建一个api文件夹,目录结构如下:

1|-api
2   |-- config.js
3   |-- http.js
4   |-- api.js
5   |-- util.js

api.js 编写所有的请求api,依赖http.js
http.js创建http的实例,并配置参数

config.js (全局配置)

该配置文件包含:开发环境 baseUrl 配置;生成环境 baseUrl 配置;以及其它自定义配置

 1//开发环境 baseUrl
2let devBaseUrl = 'xxxxxxx'
3
4//生成环境 baseUrl
5let proBaseUrl = 'xxxxxxx'
6
7// 不是生成环境就是测试环境
8let isProduction = false
9
10export default {
11    sysId'1000000000000000001',//特殊的配置 系统id (自定义配置 好管理)
12    baseURL:isProduction ? proBaseUrl : devBaseUrl ,
13    timeout10000,//全局请求 超时时间配置参数,
14}

该配置项的超时时间 timeout < http实例配置超时时间 timeout < http实例请求配置超时时间 timeout
其它配置也可以按这样类似配置,优先级也是和timeout一样

http.js

 1// 导入 Http 构造类
2import Http from '@/http/index.js'
3
4// 导入 Config 全局配置
5import Config from './config.js'
6
7// 导入 工具函数
8import { tip } from './util.js'
9
10// 实例化 http 类,并配置实例默认参数
11const http = new Http({
12    // 实例配置的超时时间,优先级 高于 全局配置里的 timeout, 低于 http实例请求的配置的 timeout
13    timeout: Config.timeout || 6000
14    // 请求显示loading 状态 ,优先级低于 http请求时的配置的 showLoading,默认 true 
15    showLoading: true,
16    // 实例请求的 请求基地址
17    baseUrl: Config.baseURL,
18    // 是否验证token 默认 true,每个请求都要验证,优先级低于 http实例请求的配置参数 isCheckToken
19    isCheckToken: true,
20})
21
22// 请求拦截器 配置
23http.interceptors.request = function(requestParam,globalConfig){
24    console.log(`【拦截器-请求-地址】--URL:${requestParam.url}`)
25    console.log(`【拦截器-请求-参数】`,requestParam)
26    console.log(`【拦截器-请求-全局配置对象】`,globalConfig)
27    //有这样的需要:有些请求你不需要验证token,除了在请求时配置  isCheckToken:fals外
28    //你还可以在请求时传入自定义参数,用以标识这个请求,在此处,再去检测你的自定义参数
29    //在此处,你可以根据你请求传入的自定义配置,个人建议你的自定义配置放到 一个对象 custom身上
30    //在此处,你根据你的自定义参数来加工你的请求参数,然后返回
31    return requestParam
32}
33
34// 请求拦截器 配置
35http.interceptors.response = function (res, globalConfig{
36    console.log(`【拦截器-响应-地址】--URL:${globalConfig.url}`)
37    console.log(`【拦截器-响应-数据】`, res)
38    console.log(`【拦截器-响应-全局配置对象】`, globalConfig)
39
40    //  获取 http 状态码,响应的数据 data,错误信息 errMsg 
41    let { statusCode, data, errMsg } = res
42
43    //  获取请求的 url 地址
44    let url = globalConfig.url
45    console.log(statusCode, data, errMsg)
46
47    // ****感觉可以干的事情很多******
48
49    switch (statusCode) {
50
51        case 200:
52            if (data.success) {
53                // 服务端 返回的成功
54            }else{
55                // 服务端 返回的失败
56            }
57            break;
58        case 403:
59            tip('权限错误' ,{
60                reLaunch:{
61                    url'../login/index' 
62                }
63            })
64            break;
65        case 404:
66            tip('请求的页面不存在')
67            break;
68        case 500:
69            tip('服务器错误')
70            break;
71        case 502:
72            tip('网关错误')
73            break;
74        default:
75            tip("未知错误")
76    }
77}
78// 导出 http 实例
79export default http

api.js

 1// 导入 http 实例
2import http from "./http.js"
3
4// 开始快乐的写 api 接口吧
5
6// 举一个 手机号授权登录的api (wxlogin )
7
8// 获取微信login的code
9export const getUserWxloginCode = () => {
10    return new Promise((resolve, reject) => {
11        wx.login({
12            success(res) {
13                if (res.code) {
14                    resolve(res.code)
15                } else {
16                    console.log('wxLogin without code:', err)
17                    resolve(null)
18                }
19            },
20            fail(err) {
21                console.log('wxLogin fail:', err)
22                resolve(null)
23            }
24        })
25    })
26}
27
28//微信手机号码登录
29export const wxlogin = async (encryptedData,iv,vm) => {
30    let code = await getUserWxloginCode()
31    return http.post('/sys/login/weChat',{
32        isCheckToken:false,
33        tipMessage:true,
34        data:{
35            code,
36            encryptedData,
37            iv,
38        }
39    })
40
41}

util.js

 1/**
2 * 对象 深拷贝 deepCopy
3 * @param {Object} p 被拷贝对象
4 * @param {Object} c 拷贝到的对象
5 * @return {Object} 拷贝后的对象
6 */

7
8export const deepCopy = (p,c)=>{
9    var c = c || {}
10    for(var i in p){
11        if(Array.isArray(p[i]) || Object.prototype.toString.call(p[i]).slice(8,-1) === 'Object'){
12            deepCopy(p[i],(c[i]=Array.isArray(p[i])?[]:{}))
13        }else{
14            c[i] = p[i]
15        }
16    }
17    return c
18}
19
20/**
21 * URL 参数序列化
22 * @param { String } baseURL :url地址
23 * @param { Object } params  :参数对象
24 * @return 参数序列化后的字符串
25 */

26 export const ParamSerialize = (baseURL,params)=>{
27    return Object.keys(params).reduce((pre,cur)=> pre += ((pre===baseURL? '?':'&')+`${cur}=${params[cur]}`),baseURL)
28}
29
30/**
31 * 基于uni的组件封装的 提示 Tip
32 * @param { String } content:提示内容
33 * @param { Object } opt:配置对象
34 * @return void
35 */

36 export const tip= (content,opt)=>{
37
38    uni.showToast(Object.assign({title:content,mask:true,icon:'none',duration:opt && opt.delay?opt.delay : 2000},opt))
39    if(opt){
40        let isNeed =  Object.keys(opt).some(key=>['navigateTo','redirectTo','reLaunch','switchTab','navigateBack'].includes(key))
41        if(isNeed){
42            setTimeout(()=>{
43
44                if(opt.navigateTo){
45                    uni.navigateTo(opt.navigateTo);
46                }else if(opt.redirectTo){
47                    uni.redirectTo(opt.redirectTo);
48                }else if(opt.reLaunch){
49                    uni.reLaunch(opt.reLaunch);
50                }else if(opt.switchTab){
51                    uni.switchTab(opt.switchTab);
52                }else if(opt.navigateBack){
53                    uni.navigateBack(opt.navigateTo)
54                }
55            },opt.delay || 2000)
56        }
57    }   
58}

篇幅有限,只列举了部分个人的工具函数

全局挂载

为了使用方便,我们在 main.js 全局挂载

 1import Vue from 'vue'
2
3import App from './App'
4
5
6import * as api from "@/api/api.js"
7
8import { tip } from "@/api/util.js"
9
10
11
12Vue.config.productionTip = false
13
14App.mpType = 'app'
15
16// 全局挂载 请求api 
17Vue.prototype.api = api
18
19// 全局挂载 提示函数 tip
20Vue.prototype.tip = tip 
21
22
23// 全局参数对象 参考 uni-app 全局变量的几种实现方式 https://ask.dcloud.net.cn/article/35021
24// Vue.prototype.globalData = {}
25
26const app = new Vue({
27    ...App
28})
29
30app.$mount()

组件里使用

 1<template>
2   <view>
3       <button 
4           class="phone-button" 
5           open-type="getPhoneNumber" 
6          @getphonenumber="usePhoneToLogin">

7          获取手机号码登录
8       </button>
9   </view>
10</template>
11
12<script>
13export default {
14     data() {
15
16     },
17     methods:{
18        usePhoneToLogin(e){
19
20            if(!e.detail.encryptedData){
21                console.log(e,'用户拒绝授权手机号码 -- 跳转到账号密码登录页面')
22                uni.reLaunch({url: '../login/index'})
23
24            }else{
25                console.log(e,'用户同意授权手机号码并登录')
26                let encryptedData = e.detail.encryptedData
27                let iv = e.detail.iv
28                this.api.wxlogin(encryptedData,iv).then(res=>{
29                    uni.setStorageSync('token', res.access_token)
30                    this.globalData.userInfo = res.user_info
31                    uni.switchTab({url: '../index/index'})
32                }).catch(err=>{
33                    uni.reLaunch({url: '../login/index})
34                })
35            }
36
37        }
38     }
39}
40
</script>
41<style lang="scss" scoped></style>

结语

知道的越多,知道的越少!