axios接口封装

axios.ts

import axios from 'axios'
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
import Config, { ApiCode } from '@common/config'
import type { IResponse } from '@/api/services/type'
import router from '@/router'

const { baseURL, timeout } = Config
const $axios = axios.create({ baseURL, timeout })

// 请求拦截器
$axios.interceptors.request.use(async config => {
  const { headers = {} } = config
  const { hasToken = true } = headers as AxiosRequestConfig['headers'] & { hasToken: boolean }
  const token = localStorage.getItem(Config.token) || ''
  // 设置token
  if (hasToken) {
    if (token) {
      delete headers[Config.tokenKey]
      headers[Config.tokenKey] = `bearer ${token}`
    }
  }
  return config
})
// 返回体拦截器
$axios.interceptors.response.use(
  async response => {
    const { data = {} } = response as AxiosResponse<any> & IResponse<any>
    const { code, msg, RESPONSE, data: _data } = data
    // 检查 code 是否合法
    if (code === ApiCode.SUCCESS) return [null, _data === null ? undefined : _data, data]
    if (code === ApiCode.AUTH) {
      localStorage.removeItem(Config.token)
      window.$message.error(data.msg)
      router.push('/login')
      return [data, undefined, data]
    }
    if (msg) {
      window.$message.error(msg)
      data.showMessage = true
    } else if (RESPONSE) {
      window.$message.error(`接口请求异常,${JSON.stringify(RESPONSE)}`)
      data.showMessage = true
    }
    return [`error: ${data.msg}`, _data, data]
  },
  async error => {
    const { message } = error
    if (message === Config.requestCancel) return Promise.resolve([error, undefined, undefined])

    if (/Network\sError/.test(message)) console.error('请检查您的网络连接...')
    else if (/timeout/.test(message)) console.error('网络超时~')

    const data = error?.response?.data ?? error
    // Message.error('登录失效')
    // if (data.message === Code.authFail && !window.IS_DEV) window.location.href = Config.SSOUrl

    return Promise.resolve([data, data?.data, data])
  }
)

export default $axios

client.ts

import cConfig, { joinParams } from '@common/config'
import type { AxiosRequestConfig } from 'axios'
import axios from './axios'
import type { IDownloadFile, IResponse, IRequestParams as ReqParams, TData } from './type'

export class ApiClient {
  static FetchMap = new Map() // 防重复容器

  /**
   * @param url
   * @param params
   * @param config axios 的 config
   * @param client 配置自定义请求参数
   */
  protected request<T = any>(params: ReqParams): Promise<[any, T, IResponse<T>]> {
    const { url, params: data, config, client = {} } = params
    const axiosConfig = config ?? {}
    axiosConfig.headers = {
      ...(axiosConfig.headers ?? {}),
      ...client
    } as AxiosRequestConfig['headers']
    const method = params.method ?? (data === undefined ? 'GET' : 'POST')

    return this.createAxios({ url, data, method, ...axiosConfig })
  }

  private async createAxios(config: AxiosRequestConfig) {
    const { noRepeat = true } = config.headers ?? {}
    const requestKey = joinParams(config)

    // if (/(undefined|null)/.test(config.url)) {
    //   console.warn(`接口 ${config.url} 存在 undefined 或 null 参数,请查看是否正确`)
    // }

    if (noRepeat) {
      //  防止重复请求逻辑
      delete config.headers?.noRepeat
      if (ApiClient.FetchMap.has(requestKey)) {
        console.warn(`接口重复,已取消接口 ${config.url} 的重复请求, 已返回相同请求数据`)
        return await ApiClient.FetchMap.get(requestKey) // 接口请求中,直接返回在请求的 Promise【解决被取消接口没数据返回问题】
      }

      const axiosRequest = axios({ ...config })
      ApiClient.FetchMap.set(requestKey, axiosRequest)

      const res = await axiosRequest
      noRepeat && ApiClient.FetchMap.delete(requestKey)
      return res
    }
    return await axios({ ...config })
  }

  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  protected downloadFile({ params, method = 'GET', fileName, url }: IDownloadFile) {
    return new Promise((resolve, reject) => {
      const req = new XMLHttpRequest()
      req.open(method, /^https?:\/\//.test(url) ? url : `${cConfig.baseURL}${url}`, true)
      req.responseType = 'blob'
      req.setRequestHeader('Content-Type', 'application/json')
      const token = localStorage.getItem(cConfig.token) || ''
      req.setRequestHeader(cConfig.tokenKey, `bearer ${token}`)
      // console.log(req)

      req.onload = () => {
        // console.log('----------')
        // const { response } = req
        // const { type } = response
        // console.log({ response })
        // TODO 下载文件下载待完成
        // if (cConfig.excelKey.has(type)) {
        //   resolve({ data: 'succees', code: '' })
        //   loadFileData(response, `${fileName}${getFileSuffix(type)}`)
        // } else if (type === 'text/plain') {

        // }
        let name = ''
        // 名称从ResponseHeader取Content-disposition
        const fileInfo = req.getResponseHeader('Content-disposition')
        if (!fileInfo)
          name = fileName ?? '未知名字'
        else
          name = decodeURI(fileInfo.split('=')[1])

        try {
          const { response } = req
          const elink = document.createElement('a')
          elink.download = name
          elink.style.display = 'none'
          elink.href = window.URL.createObjectURL(response)
          document.body.appendChild(elink)
          elink.click()
          URL.revokeObjectURL(elink.href) // 释放URL 对象
          document.body.removeChild(elink)
          resolve({ data: 'succees', code: '' })
        } catch (error) {
          reject(error)
        }
      }
      req.onerror = error => reject(error)
      req.send(JSON.stringify(params || {}))
    })
  }

  /**
   * @param params
   * @description 拼接参数
   */
  protected serialize(params: TData): string {
    return Object.entries(params)
      .map(([key, value]) => (value !== undefined && value != null && value !== '' ? `${key}=${value}` : undefined))
      .filter(Boolean)
      .join('&')
  }
}

export default new ApiClient()

type.d.ts

import type { AxiosRequestConfig } from 'axios'

export type TData = Record<string, any>

export interface IResponseList<T> {
  count: number
  page: number
  list: T[]
}

export interface IClient {
  /**
   * 是否可以开启多次重复请求,默认: true
   * 说明:
   * 默认情况下,当某个请求发出,在该请求没完成的情况下,再发起同样的请求,后发的请求会被取消
   * 如果 noRepeat 配置为 false 则关闭防重复请求功能
   * 该配置用于 分包上传文件 等接口
   */
  noRepeat?: boolean

  /**
   * 是否携带 token,默认: true
   */
  hasToken?: boolean
}

export interface IResponse<T> {
  data: T
  code: string
  msg?: string
}

export interface IRequestParams {
  url: string
  params?: TData
  client?: IClient
  config?: AxiosRequestConfig
  method?: AxiosRequestConfig['method']
}

export interface IDownloadFile {
  url: string
  params?: TData
  method?: IRequestParams['method']
  fileName?: string
}

export interface PageInfo {
  page: number
  pageSize: number
}

services->axios->client
使用

import { ApiClient } from './services/client'

class LoginApi extends ApiClient {
  // 品牌方用户账号密码登录验证
  loginByAccountAndPassword<T>(params: LoginType.LoginByAccountAndPassword) {
    return this.request<T>({ url: '/tianyin-service-user/brand/user/loginByAccountAndPassword', params })
  } 
}
export default new LoginApi()
async function loginByAccountAndPassword(params: LoginType.LoginByAccountAndPassword) {
  const [err, data] = await $api.LoginApi.loginByAccountAndPassword<UserToken>(params)
  if (err) return
  token(data.token)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值