Vue3 + Ts 封裝axios


封裝思路

index.ts
我們需要在src的根目錄下創建一個axios文件夾,其中創建一個index.ts文件,這個文件主要用來封裝axios的配置(實例化請求配置、請求攔截器、相應攔截器)及相應的方法(登錄跳轉、消息提示、錯誤處理等)

base.ts
這個文件主要用於項目擴展的情況下 不同模塊需要調用不同接口(請求的base地址 baseURL )而前期做的准備,便於后期的維護

request.ts
主要用於封裝基於axios配置的get/post/put/delete等使用方法。

api.ts
在后面的 main.ts 中引入該模塊,包括所有接口數據信息寫入該文件中。

index.ts

封裝如下。考慮到單一職責,index這塊只封裝axios

// index.ts
import axios, { AxiosRequestConfig, Method } from "axios";
import router from "@/router";
import store from "@/store";
import { message } from 'ant-design-vue'
import { storage } from "../storage/storage";
import { dataList } from "@/components/aspin/data";

/** 
 * 跳轉登錄頁
 * 攜帶當前頁面路由,以期在登錄頁面完成登錄后返回當前頁面
 */
const toLogin = () => {
  router.replace({
    name: 'LoginPage',
  });
}

/** 
 * 請求失敗后的錯誤統一處理 
 * @param {Number} status 請求失敗的狀態碼
 */
const errorHandle = (status: number, other: string) => {
  // 狀態碼判斷
  switch (status) {

    case 302: message.error('接口重定向了!');
      break;
    case 400:
      message.error("發出的請求有錯誤,服務器沒有進行新建或修改數據的操作==>" + status)
      break;
    // 401: 未登錄
    // 未登錄則跳轉登錄頁面,並攜帶當前頁面的路徑
    // 在登錄成功后返回當前頁面,這一步需要在登錄頁操作。                
    case 401: //重定向
      message.error("token:登錄失效==>" + status + ":" + store.state.Roles)
      storage.remove(store.state.Roles)
      storage.get(store.state.Roles)
      router.replace({
        path: '/Login',
      });
      break;
    // 403 token過期
    // 清除token並跳轉登錄頁
    case 403:
      message.error("登錄過期,用戶得到授權,但是訪問是被禁止的==>" + status)
      // store.commit('token', null);
      setTimeout(() => {
        router.replace({
          path: '/Login',
        });
      }, 1000);
      break;
    case 404:
      message.error("網絡請求不存在==>" + status)
      break;
    case 406:
      message.error("請求的格式不可得==>" + status)
      break;
    case 408: message.error(" 請求超時!")
      break;
    case 410:
      message.error("請求的資源被永久刪除,且不會再得到的==>" + status)
      break;
    case 422:
      message.error("當創建一個對象時,發生一個驗證錯誤==>" + status)
      break;
    case 500:
      message.error("服務器發生錯誤,請檢查服務器==>" + status)
      break;
    case 502:
      message.error("網關錯誤==>" + status)
      break;
    case 503:
      message.error("服務不可用,服務器暫時過載或維護==>" + status)
      break;
    case 504:
      message.error("網關超時==>" + status)
      break;
    default:
      message.error("其他錯誤錯誤==>" + status)
  }
}

// 定義接口
interface PendingType {
  url?: string;
  method?: Method;
  params: any;
  data: any;
  cancel: any;
}
// 取消重復請求
const pending: Array<PendingType> = [];
const CancelToken = axios.CancelToken;

// 移除重復請求
const removePending = (config: AxiosRequestConfig) => {
  for (const key in pending) {
    const item: number = +key;
    const list: PendingType = pending[key];
    // 當前請求在數組中存在時執行函數體
    if (list.url === config.url && list.method === config.method && JSON.stringify(list.params) === JSON.stringify(config.params) && JSON.stringify(list.data) === JSON.stringify(config.data)) {
      // 執行取消操作
      list.cancel('操作太頻繁,請稍后再試');
      // 從數組中移除記錄
      pending.splice(item, 1);
    }
  }
};

/* 實例化請求配置 */
const instance = axios.create({
  headers: {
    //php 的 post 傳輸請求頭一定要這個 不然報錯 接收不到值
    "Content-Type": "application/json;charset=UTF-8",
    "Access-Control-Allow-Origin-Type": '*'
  },
  // 請求時長
  timeout: 1000 * 30,
  // 請求的base地址 TODO:這塊以后根據不同的模塊調不同的api
  baseURL: process.env.VUE_APP_API_URL,
  //     ? "測試"
  //     : "正式",
  // 表示跨域請求時是否需要使用憑證
  withCredentials: false,
})

/** 
 * 請求攔截器 
 * 每次請求前,如果存在token則在請求頭中攜帶token 
 */
instance.interceptors.request.use(
  config => {

    removePending(config);
    config.cancelToken = new CancelToken((c) => {
      pending.push({ url: config.url, method: config.method, params: config.params, data: config.data, cancel: c });
    });
    // 登錄流程控制中,根據本地是否存在token判斷用戶的登錄情況        
    // 但是即使token存在,也有可能token是過期的,所以在每次的請求頭中攜帶token        
    // 后台根據攜帶的token判斷用戶的登錄情況,並返回給我們對應的狀態碼        
    // 而后我們可以在響應攔截器中,根據狀態碼進行一些統一的操作。        
    // const token = store.state.token;
    // localStorage.setItem('token', token);

    if (storage.get(store.state.Roles)) {
      store.state.Roles
      config.headers.Authorization = storage.get(store.state.Roles);
    }
    return config;
  },
  error => {
    message.error(error.data.error.message);
    return Promise.reject(error.data.error.message);
  }

)

// 響應攔截器
instance.interceptors.response.use(function (config) {

  dataList.show = true
  removePending(config.config);
  // 請求成功
  if (config.status === 200 || config.status === 204) {
    setTimeout(() => {
      dataList.show = false
    }, 400)
    return Promise.resolve(config);
  } else {
    return Promise.reject(config);
  }
  // 請求失敗
}, function (error) {

  const { response } = error;
  if (response) {
    errorHandle(response.status, response.data.message);

    // 超時重新請求
    const config = error.config;
    // 全局的請求次數,請求的間隙
    const [RETRY_COUNT, RETRY_DELAY] = [3, 1000];

    if (config && RETRY_COUNT) {
      // 設置用於跟蹤重試計數的變量
      config.__retryCount = config.__retryCount || 0;
      // 檢查是否已經把重試的總數用完
      if (config.__retryCount >= RETRY_COUNT) {
        return Promise.reject(response || { message: error.message });
      }
      // 增加重試計數
      config.__retryCount++;
      // 創造新的Promise來處理指數后退
      const backoff = new Promise<void>((resolve) => {
        setTimeout(() => {
          resolve();
        }, RETRY_DELAY || 1);
      });
      // instance重試請求的Promise
      return backoff.then(() => {
        return instance(config);
      });
    }

    return Promise.reject(response);
  } else {
    // 處理斷網的情況
    // eg:請求超時或斷網時,更新state的network狀態
    // network狀態在app.vue中控制着一個全局的斷網提示組件的顯示隱藏
    // 后續增加斷網情況下做的一些操作
    store.commit('networkState', false);
  }
}
)
// 只需要考慮單一職責,這塊只封裝axios
export default instance

base.ts

區分每個模塊的 baseUrl 方便后期維護管理

// base.ts
export class Base {
  /* 公共模塊 */
  static env = process.env.NODE_ENV === "development"
    ? "http://localhost:8087"
    : "https://produceCommon.com(生產線地址)"
}

也可以直接在index.ts中設置這樣就不需要base.ts

const instance = axios.create({
  // 請求的base地址 TODO:這塊以后根據不同的模塊調不同的api
  baseURL: process.env.VUE_APP_API_URL,
})

需要配置根目錄

.env.development

     NODE_ENV = 'development'
 	 # VUE_APP_API_URL = 'https://localhost:5001/'
    VUE_APP_API_URL = 'http://localhost:8087/'

.env.production

# 生產環境的請求接口
NODE_ENV = 'production'
VUE_APP_API_URL = 'http://129.xxxxx/'

request.ts

封裝axios的get、post方法,其余關於接口調用的方法也可寫入該文件中,便於管理。

// request.ts
import axios from "./index";
import qs from "qs";

export class Request {
  /**
   * get方法
   * @param {string} url 路徑
   * @param {object} params 參數
   */
  static get = (url: string, params?: any) => {
    return new Promise((resolve, reject) => {
      axios.get(url, { params: params }).then(res => {
        resolve(res);
      }).catch(err => {
        reject(err);
      })
    })
  }

  static post = (url: string, params?: any) => {
    return new Promise((resolve, reject) => {
      axios.post(url, qs.stringify(params)).then(res => {
        resolve(res);
      }).catch(err => {
        reject(err);
      })
    })
  }
}

api.ts

vue頁面需要使用的api接口

// 其中使用 install 的目的在於 ts在main.ts中 
// 不能通過Vue.prototype.$Api這個方式直接調用
//,在全局方法中會說到使用 插件的方式去掛載。
// api.ts
import { Base } from "./base";
import { Request } from "./request";

class api {
  /* api接口模塊 */
  public static article = {
     // 直接在index.ts中設置不需要Base模塊
    genre: () => Request.get('/api/SnArticle/GetCountAsync'),
     // 基於Base模塊封裝調用
    genres: () => Request.get(`${Base.env}/api/SnArticle/GetCountAsync`),
  }
}
export {
  api
}

index.vue

import { api } from '../../utils/api/api'
import { onMounted } from 'vue'
onMounted(async () => {
  await QueryAll()
  api.article.genre().then((res: any) => {
    console.log('genre' + res.data)
  })
  api.article.genres().then((res: any) => {
    console.log('genres' + res.data)
  })
})

參考

https://blog.csdn.net/qq_40031303/article/details/113920135


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM