zoukankan      html  css  js  c++  java
  • 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/ 
    版权所有,欢迎转载,转载请注明原文作者及出处。

  • 相关阅读:
    算法----递归
    函数调用栈、任务队列、事件轮询、宏任务、微任务
    苹果浏览器和ios中,时间字符串转换问题
    npm 命令行基本操作
    一些积累(做阿里笔试题)……
    CSS reset
    一些正则表达式的实例,供参考使用
    不同浏览器获取不同高与宽的方法
    盒子模型
    CSS中的字体样式和文本样式
  • 原文地址:https://www.cnblogs.com/xifengxiaoma/p/9535399.html
Copyright © 2011-2022 走看看