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


    封装 axios 模块

    封装背景

    使用axios发起一个请求是比较简单的事情,但是axios没有进行封装复用,项目越来越大,会引起越来越多的代码冗余,让代码变得越来越难维护。所以我们在这里先对 axios 进行二次封装,使项目中各个组件能够复用请求,让代码变得更容易维护。

    封装要点

    • 统一 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 封装成插件,按插件方式引入。

    config.js

    复制代码
    export default {
      method: 'get',
      // 基础url前缀
      baseURL: 'http://localhost:8080/',
      // 请求头信息
      headers: {
        'Content-Type': 'application/json;charset=UTF-8'
      },
      // 参数
      data: {},
      // 设置超时时间
      timeout: 10000,
      // 携带凭证
      withCredentials: true,
      // 返回数据类型
      responseType: 'json'
    }
    复制代码

    axios.js

    复制代码
    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) {
      return new Promise((resolve, reject) => {
        const instance = axios.create({
          baseURL: config.baseURL,
          headers: {},
          transformResponse: [function (data) {
          }]
        })
    
        // 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
          },
    
          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后的字符串)
            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

    复制代码
    // 导入所有接口
    import apis from './interface'
    
    const install = Vue => {
        if (install.installed)
            return;
    
        install.installed = true;
    
        Object.defineProperties(Vue.prototype, {
            // 注意,此处挂载在 Vue 原型的 $api 对象上
            $api: {
                get() {
                    return apis
                }
            }
        })
    }
    
    export default install
    复制代码

    安装 js-cookie

    上面 axios.js 中,会用到 Cookie 获取 token,所以需要把相关依赖安装一下。

    执行以下命令,安装依赖包。

    yarn add js-cookie

    代码实例

    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。

    启动测试

    浏览器访问:http://localhost:8080/#/login,显示登录界面。

    点击登录按钮,首先弹出框,显示返回的 token 信息。

    点击确定关掉弹出框后,跳转到主页。点击用户、菜单按钮,接口调用正常。

    封装 mock 模块

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

    文件结构

    在 mock 目录下新建一个 index.js ,创建 modules 目录并在里面创建三个模块 *.js 文件。

    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) {
          ((res) => {
            if (res.isOpen !== false) {
              Mock.mock(new RegExp(res.url), res.type, (opts) => {
                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
              })
            }
          })(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'
            // 其他数据
          }
        }
      }
    }
    复制代码

    user.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
            // 其他数据
          }
        }
      }
    }
    复制代码

    修改引入

    Login.vue

    Home.vue

    启动测试

    浏览器访问:http://localhost:8080/#/,按照先前流程走一遍,没有问题。

    源码下载

    后端:https://gitee.com/liuge1988/kitty

    前端:https://gitee.com/liuge1988/kitty-ui.git


    作者:朝雨忆轻尘
    出处:https://www.cnblogs.com/xifengxiaoma/ 
    版权所有,欢迎转载,转载请注明原文作者及出处。

     
  • 相关阅读:
    使用 awk 命令统计文本
    Mysql基础及系统函数(分享)
    存储过程 :字段按逗号拆分并插入到关联表
    Spring Security SavedRequestAwareAuthenticationSuccessHandler类
    自制Springboot Starter
    vue3 input中回车生成标签
    NOMURA Programming Contest 2022(AtCoder Beginner Contest 253)
    Educational Codeforces Round 129 (Rated for Div. 2)
    Panasonic Programming Contest 2022(AtCoder Beginner Contest 251)
    2022 Google Kick Start Round C
  • 原文地址:https://www.cnblogs.com/zzd9999/p/11382655.html
Copyright © 2020-2023  润新知