Vue项目中全局统一处理

141 阅读1分钟

全局错误处理

// src/utils/errorHandler.ts

import { ElMessage } from 'element-plus';

const errorType = {
  vue: '捕获组件错误',
  error: '捕获javascrpit错误',
  unhandledrejection: '捕获javascrpit异步错误',
};
// 错误捕获处理函数
const errorHandler = (type, error) => {
  if (!error) return;
  console.error(`%c ${errorType[type]}`, 'background-color: red;');
  console.error(error);
  ElMessage.error((error && (error.msg || error.message)) || '未知错误');
};

// 全局组件异常处理
export const vueErrorHandler = {
  install: Vue => {
    Vue.config.errorHandler = error => {
      errorHandler('vue', error);
    };
  },
};

// js错误和资源加载错误处理
window.addEventListener('error', function (event) {
  errorHandler('error', event.error);
});

// js异步错误处理
window.addEventListener('unhandledrejection', function (event) {
  errorHandler('unhandledrejection', event.reason);
});

全局axios接口返回错误统一处理

// src/utils/request.ts

import axios from 'axios';
import store from '@/store/index';
// import qs from 'qs';
import router from '../router/index';
import { AXIOS_BASE_URL } from '@/constant/constant.js';
export const request = axios.create({
  baseURL: AXIOS_BASE_URL,
  timeout: 1000 * 10 * 3,
  withCredentials: false,
});

request.interceptors.request.use(
  config => {
    const user = store.state.app.user;
    if (user && user.F_token) {
      config.headers['F_token'] = user.F_token;
    }
    // if (config.method === 'get') {
    //   config.paramsSerializer = function (params) {
    //     return qs.stringify(params, { arrayFormat: 'repeat' });
    //   };
    // }
    return config;
  },
  error => {
    return Promise.reject(error);
  },
);

request.interceptors.response.use(
  response => {
    if (response.config.responseType === 'blob') {
      return processResponse(response);
    }
    // 接口返回不标准,需要特殊处理
    if (!response.data.code && !response.data.result) {
      const data = {};
      data.code = 200;
      data.msg = null;
      if (
        Array.isArray(response.data) ||
        response.data == null ||
        typeof response.data !== 'object' ||
        !response.data.data
      ) {
        data.data = response.data;
      } else {
        Object.assign(data, response.data);
      }

      response.data = data;
    }
    return processResponse(response);
  },
  error => {
    if (error.response && error.response.status === 401) {
      router.push('/login');
      // return Promise.reject({ msg: 'token失效,请重新登录' });
      return;
    }
    return Promise.reject(error);
  },
);

// 并发限制 + 请求重试
const default_retryTimes = 0; // 重连次数
const default_retryDelay = 1000 * 3;
const MAX_COUNT = 5;
const requestQueue = [];
let activeCount = 0;

// 并发限制 + 请求重试
function processQueue() {
  if (activeCount < MAX_COUNT && requestQueue.length) {
    const { config, retryTimes, retryDelay, resolve, reject } = requestQueue.shift();
    activeCount++;
    request(config)
      .then(response => {
        resolve(response);
      })
      .catch(error => {
        if (retryTimes === 0) {
          reject(error);
        } else {
          setTimeout(() => {
            requestQueue.push({
              config,
              retryTimes: retryTimes - 1,
              retryDelay,
              resolve,
              reject,
            });
          }, retryDelay);
        }
      })
      .finally(() => {
        activeCount--;
        processQueue();
      });
  }
}

// 并发限制 + 请求重试
function requestHandler(config, retryTimes = default_retryTimes, retryDelay = default_retryDelay) {
  return new Promise((resolve, reject) => {
    requestQueue.push({ config, retryTimes, retryDelay, resolve, reject });
    processQueue();
  });
}

// 接口返回错误统一处理
function processResponse(response) {
  if (response.data.code !== 200 && !['success', 'warning'].includes(response.data.result)) {
    // 下载文件 给予通过
    if (response.config.responseType === 'blob') {
      return response;
    }
    if (response.data && typeof response.data !== 'object') {
      Promise.reject({ msg: '接口返回JSON数据格式错误' });
    }
    // 登录token失效
    if (response.data.code === 401) {
      router.push('/login');
      // return Promise.reject({ msg: 'token失效,请重新登录' });
      return;
    }
    // 网络异常处理
    if (!navigator.onLine) {
      Promise.reject({ msg: '当前网络不可用,需检查你的网络设置' });
    }
    // 后端返回错误
    // Promise.reject(response.data);
    return Promise.reject(response.data);
  }
  return response.data;
}

request.get = (url, options) => {
  const opt = {
    ...options,
    url,
    method: 'get',
  };
  return requestHandler(opt);
};

request.post = (url, data, options) => {
  const opt = {
    ...options,
    url,
    data,
    method: 'post',
  };
  return requestHandler(opt);
};

request.delete = (url, id, options) => {
  const opt = {
    ...options,
    url: url + '/' + id,
    method: 'delete',
  };
  return requestHandler(opt);
};

request.put = (url, data, options) => {
  const opt = {
    ...options,
    url,
    data,
    method: 'put',
  };
  return requestHandler(opt);
};

request.patch = (url, data, options) => {
  const opt = {
    ...options,
    url,
    data,
    method: 'patch',
  };
  return requestHandler(opt);
};

request.downFile = (url, option, fileName) => {
  const options = {
    ...option,
    responseType: 'blob',
  };
  return request.get(url, options).then(res => {
    const disposition = res.headers['content-disposition'];
    if (!fileName) {
      const match = disposition.match(/filename=([^;]+)/i);
      if (match && match[1]) {
        fileName = match[1].trim().replace(/^"|"$/g, '');
      }
    }
    const blob = new Blob([res.data]);
    const url = window.URL.createObjectURL(blob);
    const a = document.createElement('a');
    a.href = url;
    if (fileName) {
      a.setAttribute('download', fileName);
    } else {
      a.setAttribute('download', '');
    }
    a.click();
  });
};

  • axios.d.ts
// /src/axios.d.ts
import axios from 'axios';

export interface CustomAxiosResponse {
  code: number;
  data?: any;
  msg?: string;
  message?: string;
}

// export interface Post {
//   (url: string, data: object): Promise<AxiosResponse<CustomAxiosResponse>>;
// }

declare module 'axios' {
  export interface AxiosRequestConfig {
    loading?: object | boolean;
  }

  export interface AxiosInstance {
    get(url: string, config?: AxiosRequestConfig): Promise<CustomAxiosResponse>;
    post(url: string, data: any, config?: AxiosRequestConfig): Promise<CustomAxiosResponse>;
    downFile(url: string, config?: AxiosRequestConfig, fileName: string): void;
  }
}

全局loading处理

// src\utils\loading.ts

import { ElLoading } from 'element-plus';
import { isObject } from './util';
class Loading {
  private loadingNum: number;
  private load;
  private options: object = {
    fullscreen: true,
  };
  constructor() {
    this.loadingNum = 0;
  }
  start(options: object | boolean) {
    if (this.loadingNum == 0) {
      if (isObject(options)) {
        this.options = options as object;
      }
      this.load = ElLoading.service(this.options);
    }
    this.loadingNum++;
  }
  end() {
    this.loadingNum--;
    if (this.loadingNum <= 0) {
      this.load.close();
    }
  }
}

export const load = new Loading();