Vue + Element UI 实现权限管理系统 前端篇(三):工具模块封装

封装 axios 模块

封装背景

使用 axios 发起一个请求是比较简单的事情,但是axios没有进行封装复用,项目越大的话会造成代码冗(rǒng )余,维护会越来越难。

所以在此二次封装,是项目中各个组件能够复用,让代码更容易维护。

封装要点

  • 统一 url 配置
  • 统一 api 请求
  • request(请求)拦截器,例如:带上token等,设置请求头
  • response(响应)拦截器,例如:统一错误处理,页面重定向等
  • 根据需要,结合 Vuex 做全局的 loading 动画,或者错误处理
  • 将 axios 封装成 Vue 插件使用

文件结构

在 src 下新建一个 http 文件夹,用来存放 http 交互 api 代码。

config.js: axios 默认配置,包含基础路径等信息。

axios.js: 二次封装 axios 模板,包含拦截器等信息。

interface.js: 请求接口汇总模块,汇聚模块 API。

index.js: 将 axios 封装成插件,按插件方式引入。

安装 js-cookie

在axios.js中会用 Cookie 获取 token

npm i js-cookie --save-dev

config.js

// axios 默认配置,包含基础路径等消息。
export default {
    method: 'get',
    // 基础 url 前缀
    baseURL: 'http://localhost:8080/',
    // 请求头信息
    headers: {
        'Content-Type': 'application/json;charset=UTF-8'
    },
    // 参数
    data: {},
    // 设置超时时间
    timeout: 1000,
    // 携带凭证,是否允许跨域属性
    withCredentials: true,
    // 返回数据类型
    responseType: 'json'
}

axios.js

// 二次封装 axios 模块,包含拦截器等信息
import axios from 'axios';
import config from './config';
import qs from 'qs';
import Cookies from 'js-cookie';
import router from '@/router';

// 使用vuex做全局loading时使用
// import store from '@/store'

export default function $axios(options) {   // 模板默认导出一个 Promise 实例
    return new Promise ((resolve, reject) => {
        const instance = axios.create({     // axios.create() 是实例化
            baseURL: config.baseURL,
            headers: {},
            transformResponse: [function (data) {   // transformResponse 在传递给 then/catch 前,允许修改响应数据,用的不多?
            }]
        });
        
        // request 拦截器
        instance.interceptors.request.use((config) => {
            let token = Cookies.get('token');
            // 1.请求开始的时候可以结合 vuex 开启全屏 loading 动画
            // console.log(store.state.loading)
            // console.log('准备发送请求...')
            
            // 2.带上 token
            if (token) {
                config.headers.accessToken = token
            } else {
                // 重定向到登入页面
                router.push('/login')
            };

            // 3.根据请求方法,序列化转来的参数,根据后端需求是否序列化
            if (config.method === 'post') {
                if (config.data.__proto__ === FormData.prototype
                    || config.url.endsWith('path')
                    || config.url.endsWith('mark')
                    || config.url.endsWith('patchs')
                ) {

                } else {
                    config.data = qs.stringify(config.data)
                }
            } else {
                return config
            }
        },
        error => {
            // 请求错误时
            console.log('request:', error);
            // 1.判断请求超时
            if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
                console.log('timeout请求超时')
                // return service.request(originalRequest);  // 再重复请求一次
            };
            // 2.需要重定向到错误页面
            const errorInfo = error.response;
            console.log(errorInfo);
            if (errorInfo) {
                error = errorInfo.data  // 页面那边 catch 的时候就能拿到详细的错误信息,看最下边的Promise.reject
                const errorStatus = errorInfo.status;   // 404 403 500 ...
                router.push({
                    path: `/error/${errorStatus}`
                })
            } else {
                return Promise.reject(error);   // 在调用的那边可以拿到(catch)你想返回的错误信息
            }
        }
        );

        // response 拦截器
        instance.interceptors.response.use(
            (response) => {
                let data;
                // IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
                // 在此处我以为说 IE9 需要,现在不需要了,就直接 let data = response.data 导致各种undefined
                if (response.data == undefined) {
                  data = JSON.parse(response.request.responseText)
                } else {
                  data = response.data
                }

                // 根据返回的 code 值来做不同处理
                switch (data.rc) {  
                    case 1:
                        console.log(data.desc)
                        break;
                    case 0:
                        store.commit('changeState')
                        // console.log('登入成功')
                    default:
                }
                // 若不是正确的返回code,且已经登入,就抛出错误
                // const err = new Error(data.desc)
                // err.data = data
                // err.response = response
                // throw err
                return data
        },
        (err) => {
            if (err && err.response) {
                switch (err.response.status) {
                    case 400:
                        err.message = '请求错误'
                        break;
                    case 401:
                        err.message = '未授权,请登入'
                        break;
                    case 403:
                        err.message = '拒绝访问'
                        break;
                    case 404:
                        err.message = `请求地址出错:${err.response.config.url}`
                        break;
                    case 408:
                        err.message = '请求超时'
                        break;
                    case 500:
                        err.message = '服务器内部错误'
                        break;
                    case 501:
                        err.message = '服务未实现'
                        break;
                    case 502:
                        err.message = '网关错误'
                        break;
                    case 503:
                        err.message = '服务不可用'
                        break;
                    case 504:
                        err.message = '网关超时'
                        break;
                    case 505:
                        err.message = 'HTTP版本不受支持'
                        break;
                    default:
                }
            } else {
                console.error(err)
                return Promise.reject(err)  // 返回接口返回的错误信息
            }
        }
        );

        // 请求处理
        instance(options).then(res => {
            resolve(res);
            return false;
        }).catch(error => {
            reject(error);
        })

    })
}

interface.js

// 二次封装 axios 模块,包含拦截器等信息
import axios from 'axios';
import config from './config';
import qs from 'qs';
import Cookies from 'js-cookie';
import router from '@/router';

// 使用vuex做全局loading时使用
// import store from '@/store'

export default function $axios(options) {   // 模板默认导出一个 Promise 实例
    return new Promise ((resolve, reject) => {
        const instance = axios.create({     // axios.create() 是实例化
            baseURL: config.baseURL,
            headers: {},
            transformResponse: [function (data) {   // transformResponse 在传递给 then/catch 前,允许修改响应数据,用的不多?
            }]
        });
        
        // request 拦截器
        instance.interceptors.request.use((config) => {
            let token = Cookies.get('token');
            // 1.请求开始的时候可以结合 vuex 开启全屏 loading 动画
            // console.log(store.state.loading)
            // console.log('准备发送请求...')
            
            // 2.带上 token
            if (token) {
                config.headers.accessToken = token
            } else {
                // 重定向到登入页面
                router.push('/login')
            };

            // 3.根据请求方法,序列化转来的参数,根据后端需求是否序列化
            if (config.method === 'post') {
                if (config.data.__proto__ === FormData.prototype
                    || config.url.endsWith('path')
                    || config.url.endsWith('mark')
                    || config.url.endsWith('patchs')
                ) {

                } else {
                    config.data = qs.stringify(config.data)
                }
            } else {
                return config
            }
        },
        error => {
            // 请求错误时
            console.log('request:', error);
            // 1.判断请求超时
            if (error.code === 'ECONNABORTED' && error.message.indexOf('timeout') !== -1) {
                console.log('timeout请求超时')
                // return service.request(originalRequest);  // 再重复请求一次
            };
            // 2.需要重定向到错误页面
            const errorInfo = error.response;
            console.log(errorInfo);
            if (errorInfo) {
                error = errorInfo.data  // 页面那边 catch 的时候就能拿到详细的错误信息,看最下边的Promise.reject
                const errorStatus = errorInfo.status;   // 404 403 500 ...
                router.push({
                    path: `/error/${errorStatus}`
                })
            } else {
                return Promise.reject(error);   // 在调用的那边可以拿到(catch)你想返回的错误信息
            }
        }
        );

        // response 拦截器
        instance.interceptors.response.use(
            (response) => {
                let data;
                // IE9时response.data是undefined,因此需要使用response.request.responseText(Stringify后的字符串)
                // 在此处我以为说 IE9 需要,现在不需要了,就直接 let data = response.data 导致各种undefined
                if (response.data == undefined) {
                  data = JSON.parse(response.request.responseText)
                } else {
                  data = response.data
                }

                // 根据返回的 code 值来做不同处理
                switch (data.rc) {  
                    case 1:
                        console.log(data.desc)
                        break;
                    case 0:
                        store.commit('changeState')
                        // console.log('登入成功')
                    default:
                }
                // 若不是正确的返回code,且已经登入,就抛出错误
                // const err = new Error(data.desc)
                // err.data = data
                // err.response = response
                // throw err
                return data
        },
        (err) => {
            if (err && err.response) {
                switch (err.response.status) {
                    case 400:
                        err.message = '请求错误'
                        break;
                    case 401:
                        err.message = '未授权,请登入'
                        break;
                    case 403:
                        err.message = '拒绝访问'
                        break;
                    case 404:
                        err.message = `请求地址出错:${err.response.config.url}`
                        break;
                    case 408:
                        err.message = '请求超时'
                        break;
                    case 500:
                        err.message = '服务器内部错误'
                        break;
                    case 501:
                        err.message = '服务未实现'
                        break;
                    case 502:
                        err.message = '网关错误'
                        break;
                    case 503:
                        err.message = '服务不可用'
                        break;
                    case 504:
                        err.message = '网关超时'
                        break;
                    case 505:
                        err.message = 'HTTP版本不受支持'
                        break;
                    default:
                }
            } else {
                console.error(err)
                return Promise.reject(err)  // 返回接口返回的错误信息
            }
        }
        );

        // 请求处理
        instance(options).then(res => {
            resolve(res);
            return false;
        }).catch(error => {
            reject(error);
        })

    })
}

interface.js

import axios from './axios';

/*
 * 将所有接口统一起来方便维护
 * 如果项目很大可以将 url 独立成文件,接口分成不同的模块
 */

// 单独导出
export const login = () => {
    return axios({
        url: '/login',
        method: 'get'
    })
};

export const getUser = () => {
    return axios({
        url: '/user',
        method: 'get'
    })
}

export const getMenu = data => {
    return axios({
        url: '/menu',
        method: 'post',
        data
    })
}

// 默认全部导出
export default {
    login,
    getUser,
    getMenu
}

index.js

import axios from './axios';

/*
 * 将所有接口统一起来方便维护
 * 如果项目很大可以将 url 独立成文件,接口分成不同的模块
 */

// 单独导出
export const login = () => {
    return axios({
        url: '/login',
        method: 'get'
    })
};

export const getUser = () => {
    return axios({
        url: '/user',
        method: 'get'
    })
}

export const getMenu = data => {
    return axios({
        url: '/menu',
        method: 'post',
        data
    })
}

// 默认全部导出
export default {
    login,
    getUser,
    getMenu
}

代码实例

1.引入插件

在main.js 中以 vue 插件的形式引入 封装后的axios,这样在其它地方就可以通过 this.$api 调用相关接口了。

2.编写接口

在 interface.js 中添加 login 接口。

 3.调用接口

在登入界面 Login.vue 中,添加一个登入按钮,点击处理函数通过 axios 调用 login 接口返回数据。

成果返回后,将 token 放入 Cookie 并转跳到主页。

<template>
    <div class="page">
        <h2>Login Page</h2>
        <el-button type="primary" @click="login()">登入</el-button>
    </div>
</template>

<script>
    import mock from '@/mock/api.js';
    import Cookies from 'js-cookie';
    import router from '@/router'

    export default {
        name: 'Login',
        methods: {
            login() {
                this.$api.login().then(function(res) {
                    alert(res.data.token)
                    Cookies.set('token', res.data.token)    // 放置 token 到 Cookie
                    router.push('/')    // 登入成功,转跳到主页
                }).catch(function(res) {
                    alert(res);
                });
            }
        }
    }
</script>

4.mock 接口

在mock.js 中添加一个 login 接口进行拦截, 返回一个token

Mock.mock('http://localhost:8080/login', {  
    data: {                    // 之前的数据现在也得用 data{}包起来
        'token': '4344323121398',
        'rc': 1
        // 其它数据
    }
});

启动测试


封装 mock 模块

为了统一可以统一管理和集中控制数据模拟接口,我们对 mock 模块进行了封装,可以方便的定制模拟接口的统一开关和个体开关。

猜你喜欢

转载自www.cnblogs.com/CZheng7/p/13394578.html