Vue企业级优雅实战06-框架开发02-封装基于Axios请求

预览本文的实现效果:

# gitee
git clone git@gitee.com:cloudyly/dscloudy-admin-single.git
# github
git clone git@github.com:cloudyly/dscloudy-admin-single.git

git checkout 04_Axios

本文主要内容:使用 Axios 进行网络请求,并进行二次封装。

Git 本地仓库切换新分支:

git checkout -b 04_Axios

确认分支:

git branch

1 数据格式

1.1 RESTful 简介

无论是传统的 JSP、Freemarker,还是当下主流的前后端分离模式,几乎所有的应用都有前端和后端两个部分。前端设备除了 PC 浏览器,还有移动端 App、公众号程序、小程序等,前后端之间进行通信就需要有一种统一的机制。 RESTful API 是目前比较成熟的一套 API 设计理论。

REST 全称是 Representational State Transfer 表述性状态转换。按照我最浅层次的理解,RESTful API 就是一种前后端通信 API 的风格,所谓 “表述性状态转换”,最核心包括两个内容:

  • 表述性:描述资源,即针对什么资源;
  • 状态转换:对资源进行什么操作,如对资源的增删改查,就是资源的状态改变。

传统的 API,通常只使用 GET、POST 请求,在 RESTful 中,会充分利用各种请求。以用户的增删改查为例:

添加用户

  • 传统 API: [POST] /addUser
  • RESTful API: [POST] /users

根据 ID 删除用户

  • 传统 API: [POST] /deleteUserById?id=xxx
  • RESTful API:[DELETE] /users/xxx

根据 ID 修改用户

  • 传统 API:[POST] /updateIUserById?id=xxx
  • RESTful API:[PUT] /users/xxx

根据 ID 查询用户

  • 传统 API:[GET] /getUserById?id=xxx
  • RESTful API:[GET] /users/xxx

查看用户列表

  • 传统 API:[GET] /getUserList
  • RESTful API:[GET] /users

RESTful API 充分利用 HTTP 请求的特性,如请求方式、状态码等信息。

1.2 响应参数格式

本系统后台接口尽量按照 RESTful 方式进行开发。常规增删改查与上面示例的 RESTful API 风格一致,只有特殊的操作才会有差异。

关于请求的响应,充分使用 HTTP 的状态码,无论是 HTTP 请求成功与否、还是业务成功与否,都统一汇聚在 HTTP 的状态码中。如果 HTTP 状态码返回 200,表示请求成功并且业务成功,此时响应体中只返回业务数据。如果业务发生异常,则 HTTP 状态码返回 500,响应体返回错误具体信息:

{
	"code": "XXXXXX",
	"msg": "xxxxxxx"
}

以根据 ID 查询用户为例:

  • ID 为空 或 根据 ID 未查询到用户,HTTP 状态码返回 500,响应体返回格式为 { "code": "BS0001", "msg": "未查询到用户" }
  • 查询到用户,HTTP 状态码返回 200,请求体中直接返回用户 JSON。

特别注意:

本系统不像市面上部分设计那样,无论成功与否,都返回 code、msg、data 等一堆参数,然后将业务数据放在 data 中。本系统只要 HTTP 状态码为 200,响应体中只有业务数据,没有多余的字段。其他信息在请求头或响应头中设置。

2 封装 Loading

由于网络请求是一个异步耗时操作,在请求过程中,为了优化用户体验,建议显示 loading 效果,告知用户系统不是没有反应。我使用 element-ui 中提供的 Loading 组件,并对其进行二次封装。

src/common 目录存放通用的 JS 文件(高大上的方式来说,就是存放咱自己开发的类库),loading 效果属于通用 JS,故在该目录下创建 loading.js 文件,用来控制 loading。

src/common/loading.js

import { Loading } from 'element-ui'
import i18n from '@/i18n'

/**
 * Loading 组件的实例对象
 */
let loadingInstance = null

/**
 * 当前需要显示 loading 的请求数
 */
let loadingRequestCount = 0

/*
 * 内部方法:开始显示 loading 框
 */
const beginLoading = () => {
  loadingInstance = Loading.service({
    lock: true,
    text: i18n.t('app.loadingText'),
    background: 'rgba(0, 0, 0, 0.1)',
    target: document.querySelector('#app'),
    customClass: '.ds-loading'
  })
}

/*
 * 内部方法:结束显示 loading 框
 */
const endLoading = () => {
  if (loadingInstance) {
    loadingInstance.close()
  }
}

/**
 * 显示 loading 框
 */
export const showLoading = () => {
  if (loadingRequestCount === 0) {
    beginLoading()
  }
  loadingRequestCount++
}

/**
 * 关闭 loading 框
 */
export const closeLoading = () => {
  if (loadingRequestCount <= 0) {
    return
  }
  loadingRequestCount--
  if (loadingRequestCount === 0) {
    endLoading()
  }
}

由于还没有创建主界面,这里将 loading 框挂在 #app 上。另外,上面使用到了 app.loadingText 语言文件,需要分别在 src/i18n/ 目录下的 en.jszh.js 中定义,这里将接下来要用到的网络请求的提示也配置进去:

  app: {
    appName: '微服务微前端基础框架',
    loadingText: '加载中,请稍候...',
    requestSendError: '请求失败,请刷新页面后重试',
    requestTimeoutError: '请求超时,请稍后重试'
  },
  app: {
    appName: 'Micro Service Micro Front Platform',
    loadingText: 'Loading ...',
    requestSendError: 'Request Error, Please refresh page and try again',
    requestTimeoutError: 'Request Timeout, please try it latter'
  },

3 封装 Axios

3.1 安装依赖

npm install --save axios

3.2 定义 axios.js

网络请求也是属于通用 JS,故在该目录下创建 axios.js 文件。该文件主要做五件事:

1、 axios 的设置,如超时时间等;
2、 请求的通用拦截器,如显示 loading,请求头设置 token 等;
3、 响应的通用拦截器,如异常(业务 & 请求)时的错误提示、响应参数的解析等
4、 创建各种请求(get、post、put、delete)

src/common/axios.js

import axios from 'axios'
import { Message } from 'element-ui'
import systemConfig from '@/config'
import * as loading from './loading'
import i18n from '@/i18n'

// 创建 axios 对象
const axiosInstance = axios.create({
  timeout: systemConfig.timeout
})

// 显示错误信息
const showError = (msg) => Message.error({
  message: msg,
  type: 'error',
  duration: 3 * 1000
})

// 请求拦截器
axiosInstance.interceptors.request.use(
  config => {
    // 后面需要设置 token
    loading.showLoading()
    return config
  },
  error => {
    console.error(error)
    loading.closeLoading()
    showError(`${i18n.t('app.sendRequestError')}`)
    return Promise.reject(error)
  }
)

// 响应拦截器
axiosInstance.interceptors.response.use(
  response => {
    loading.closeLoading()
    // HTTP 状态码为 200 表示成功,其他情况均为失败
    if (response.status === 200) {
      return Promise.resolve(response.data)
    }
    const respData = response.data
    Message.error(`${respData.msg}(${respData.code})`)
    return Promise.reject(response.data)
  },
  error => {
    console.error(error)
    loading.closeLoading()
    const { code, message } = error
    if (code === 'ECONNABORTED' || message === 'Network Error') { // 请求超时
      showError(`${i18n.t('app.requestTimeoutError')}`)
      return Promise.reject(error)
    }
    if (error.response) {
      if (error.response.status === 401) {
        // 针对无权限的处理
      } else {
        showError(`${error.response.data.msg}(${error.response.data.code})`)
      }
    }
    return Promise.reject(error)
  }
)

export const createApi = (url, method, data) => {
  const config = {}
  if (method === 'get') {
    config.params = data
  } else {
    config.data = data || {}
  }
  return axiosInstance({
    url,
    method,
    ...config
  })
}

export const get = (url, data) => createApi(url, 'get', data)
export const post = (url, data) => createApi(url, 'post', data)
export const put = (url, data) => createApi(url, 'put', data)
export const del = (url, data) => createApi(url, 'delete', data)

上面导出了 createApi、get、post、put、del 方法,其他地方在使用时,只需要引入这个文件即可:

import { get } from '@/common/axios'

还有一些内容该文件还没有定义,将会在后面的开发中完善进去,主要包括:

1、请求无权限时的处理(界面提示及页面跳转);
2、文件上传下载封装
3、form 表单方式进行提交

4 登录请求

4.1 定义 URL

src/config/urls.js 中定义请求路径:core.login

const urls = {
  /**
   * 核心模块
   */
  core: {
    login: `${config.host.core}/login`, // 登录
    test: `${config.host.core}/test`
  },

  ... // 其他内容省略
}

export default urls

4.2 创建 API

所有发送请求的代码都放在各个 module 的 api 目录中,在 src/modules/core/api 中创建文件 core-api.js:

import { post } from '@/common/axios'
import urls from '@/config/urls'

/**
 * 登录
 */
export const login = param => post(urls.core.login, param)

4.3 调用 API

上一篇文章已经开发了登录界面 src/modules/core/pages/login.vue,修改点击登录按钮的事件,在表单校验成功后调用接口(后面还需要改造,在 vuex 中发送请求并管理返回的数据)

    async onLoginBtnClick () {
      const valid = await this.$refs.form.validate()
      if (!valid) {
        return
      }
      // 表单校验通过,提交数据
      const param = { ...this.loginForm }
      const result = await coreApi.login(param)
      console.log(result)
    }

在浏览器中点击登录按钮进行测试,会提示 Network Error。现在暂时没有提供正确的登录接口地址,主要原因是咱们要做一套 mock 数据,这样便可以不依赖服务器进行开发。下一篇就是如何优雅的设计 mock。

login.vue 界面做了一小个改动,在验证码输入框上加了键盘回车事件 @keyup.enter.native="onLoginBtnClick",用户输入验证码后回车,就相当于点击了登录按钮:

<el-input v-model="loginForm.validCode" :placeholder="$t('core.login.validCodePlaceHolder')"
                        @keyup.enter.native="onLoginBtnClick">

提交代码:

git add .
git cz
[框架开发] Axios封装

合并到 master 分支:

git checkout master
git merge 04_Axios

将本地分支分别全部推送到 Gitee 和 GitHub

git push --all gitee_origin
git push --all github_origin

更多内容请关注我的个人公众号,留言可加我个人微信或交流问题

程序员搞艺术

©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页