zoukankan      html  css  js  c++  java
  • 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)
                    }
                }
                return config       // if() {} 大括号里面的执行完,继续执行大括号后面的时候不加 else {}
            },
            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}`
                    })
                }
                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:
                    }
                }
                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

    // 将 axios 封装成插件, 挂载到Vue上,然后就可以按插件方式引入
    
    // 导入所有接口
    import apis from './interface';
    
    const install = Vue => {        // install:安装
        if (install.installed) {
            return;
        } else {
            install.installed = true;
        };
    
        Object.defineProperties(Vue.prototype, {
            // 此处挂载在 Vue 原型的 $api 对象上
            $api: {
                get() {
                    return apis;
                }
            }
        })
    }
    
    export default install;

    代码实例

    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/mock.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 模块进行了封装,可以方便的定制模拟接口的统一开关和个体开关。

    文件结构

    index.js: 模拟接口模块聚合文件

    login.js: 登入相关接口模拟

    user.js: 用户相关接口模拟

    menu.js: 菜单相关接口模拟

    index.js

    // 聚合文件,总
    import Mock from 'mockjs';
    import * as login from './modules/login';
    import * as user from './modules/user';
    import * as menu from './modules/menu';
    
    // 1.关闭/开启[业务模块]拦截,通过调用fnCreate方法[isOpen参数]设置。
    // 2.关闭/开启[业务模块中的某个请求]拦截,通过函数返回对象中的[isOpen属性]设置。
    fnCreate(login, true);
    fnCreate(user, true);
    fnCreate(menu, true);
    
    /**
     * 创建mock模拟数据
     * @param {*} mod 模块
     * @param {*} isOpen 是否开启?
     */
    function fnCreate (mod, isOpen = true) {
        if (isOpen) {
            for (var key in mod) {      // key = "login" key才是模块里面的函数, mod = Module {__esModule: true, Symbol(Symbol.toStringTag): "Module"} 这是一个模块。
                // key 似乎跟 res 是一样的? 但是好像不关联。这个 key 关联的是 mod[key] 里面的。
                // 只将 上方的 key 改成 res 会提示 已声明但未取值。
                ((res) => {
                        /** res?
                         *data: {msg: "success", code: 0, data: {…}}
                         *type: "get"
                         *url: "http://localhost:8080/login"
                         *__proto__: Object
                        */
                    if (res.isOpen !== false) {
                        // 格式:Mock.mock( rurl, rtype, function( options ) )
                        //options:指向本次请求的 Ajax 选项集,含有 url、type 和 body 三个属性,参见 XMLHttpRequest 规范。
                        // url用正则写,这样get请求传参时,也能拦截数据了
                        Mock.mock(new RegExp(res.url), res.type, (opts) => {    // opts = {url: "http://localhost:8080/login", type: "GET", body: null, data: null}
                            opts['data'] = opts.body ? JSON.parse(opts.body) : null;
                            delete opts.body;
                            console.log('
    ');
                            console.log('%cmock拦截, 请求: ', 'color:blue', opts);
                            console.log('%cmock拦截, 响应: ', 'color:blue', res.data);
                            return res.data; // 这是在 对应模块中获取的 data
                        })
                    }
                }) (mod[key] () || {})
            }
        }
    }

    login.js

    // 登入接口
    export function login () {
        return {
            // isOpen: false,
            url: 'http://localhost:8080/login',
            type: 'get',
            data: {
                'msg': 'success',
                'code': 0,
                'data': {
                    'token': '4344323121398'
                    // 其它数据
                }
            }
        }
    }

    menu.js

    // 获取用户信息
    export function getUser () {
        return {
            // isOpen: false,
            url: 'http://localhost:8080/user',
            type: 'get',
            data: {
                'msg': 'success',
                'code': 0,
                'data': {
                    'id': '@increment',
                    'name': '@name',
                    'email': '@email',
                    'age|10-20': 12
                    // 其它数据
                }
            }
        }
    }

    menu.js

    // 获取菜单信息
    export function getMenu () {
        return {
            // isOpen: false,
            url: 'http://localhost:8080/menu',
            type: 'get',
            data: {
                'msg': 'success',
                'code': 0,
                'data': {
                    'id': '@increment',
                    'name': 'menu',
                    'order|10-20': 12
                    // 其它数据
                }
            }
        }
    }

    然后将之前的 mock 引入的地方都改成引入 '@/mock/index.js'

     

  • 相关阅读:
    实验 3:Mininet 实验——测量路径的损耗率
    软件工程——第一次作业:自我介绍
    实验 2:Mininet 实验——拓扑的命令脚本生成
    实验1:Mininet源码安装和可视化拓扑工具
    软工作业第一次
    031802417 林宇杰
    导航控制器
    Xcode 简易计算器 (修改版)
    WSY的博客向导
    2021年8月11日训练笔记
  • 原文地址:https://www.cnblogs.com/CZheng7/p/13394578.html
Copyright © 2011-2022 走看看