diff --git a/.env.development b/.env.development index 3836dc6b..ef44591b 100644 --- a/.env.development +++ b/.env.development @@ -1,4 +1,4 @@ #请求的环境 VITE_HTTP_ENV=DEV #请求地址 -VITE_HTTP_URL=http://192.168.100.43:8201 +VITE_HTTP_URL=https://test.aisuit.com.cn diff --git a/src/interface/business/demo.ts b/src/interface/business/demo.ts new file mode 100644 index 00000000..351913f3 --- /dev/null +++ b/src/interface/business/demo.ts @@ -0,0 +1,15 @@ +/** 数据字典 */ +export interface Dictionary { + /** 字典名字 */ + label: string; + /** 要素名字(一级指标) */ + charactorLabel: string; + /** 要素下的指标key(二级指标) */ + indicatorKey: string; + /** 要素下的指标名字(二级指标) */ + indicatorLabel: string; + /** 备注 */ + remark: string; + /** 指标公式 */ + formula: string; +} diff --git a/src/interface/business/index.ts b/src/interface/business/index.ts index 269586ee..a2c76b1c 100644 --- a/src/interface/business/index.ts +++ b/src/interface/business/index.ts @@ -1 +1,2 @@ export * from './auth'; +export * from './demo'; diff --git a/src/interface/common/api.ts b/src/interface/common/api.ts new file mode 100644 index 00000000..52e89481 --- /dev/null +++ b/src/interface/common/api.ts @@ -0,0 +1,15 @@ +/** 数据字典 */ +export interface ResponseDictionary { + /** 字典名字 */ + modelName: string; + /** 要素名字(一级指标) */ + modelCharactorName: string; + /** 要素下的指标key(二级指标) */ + modelIndicator: string; + /** 要素下的指标名字(二级指标) */ + modelIndicatorName: string; + /** 备注 */ + remarks: string; + /** 指标公式 */ + formula: string; +} diff --git a/src/interface/common/index.ts b/src/interface/common/index.ts index 5f7b5951..644cc464 100644 --- a/src/interface/common/index.ts +++ b/src/interface/common/index.ts @@ -1,2 +1,4 @@ export * from './system'; export * from './route'; +export * from './service'; +export * from './api'; diff --git a/src/interface/common/service.ts b/src/interface/common/service.ts new file mode 100644 index 00000000..85b06335 --- /dev/null +++ b/src/interface/common/service.ts @@ -0,0 +1,50 @@ +/** + * 请求服务的错误类型: + * - axios: axios错误:网络错误, 请求超时, 默认的兜底错误 + * - http: 请求成功,响应的状态码非200的错误 + * - backend: 请求成功,响应的状态码为200,由后端定义的业务错误 + */ +export type RequestServiceErrorType = 'axios' | 'http' | 'backend'; + +/** 请求服务的错误 */ +export interface RequestServiceError { + /** 请求服务的错误类型 */ + type: RequestServiceErrorType; + /** 错误码 */ + code: string | number; + /** 错误信息 */ + msg: string; +} + +/** 后端接口返回的类型结构 */ +export interface BackendServiceResult { + /** 状态码 */ + code: number; + /** 接口数据 */ + data: any; + /** 接口消息 */ + message: string; +} + +/** 自定义的请求成功结果 */ +export interface CustomSuccessRequestResult { + /** 请求错误 */ + error: null; + /** 请求数据 */ + data: ResponseData; + /** 网络状态 */ + networkStatus: boolean; +} + +/** 自定义的请求失败结果 */ +export interface CustomFailRequestResult { + /** 请求错误 */ + error: RequestServiceError; + /** 请求数据 */ + data: null; + /** 网络状态 */ + networkStatus: boolean; +} + +/** 自定义的请求结果 */ +export type CustomRequestResult = CustomSuccessRequestResult | CustomFailRequestResult; diff --git a/src/service/api/auth.ts b/src/service/api/auth.ts index 51a2184a..e69de29b 100644 --- a/src/service/api/auth.ts +++ b/src/service/api/auth.ts @@ -1,8 +0,0 @@ -import { request } from '../request'; - -/** - * 获取数据字典 - */ -export async function fetchDictionary(keyword: string) { - await request.post('/ehe/model/getByIndicator', { indiCatorName: keyword }); -} diff --git a/src/interface/common/request-error.ts b/src/service/api/common.ts similarity index 100% rename from src/interface/common/request-error.ts rename to src/service/api/common.ts diff --git a/src/service/api/demo.ts b/src/service/api/demo.ts new file mode 100644 index 00000000..6f32b180 --- /dev/null +++ b/src/service/api/demo.ts @@ -0,0 +1,27 @@ +import type { ResponseDictionary, Dictionary } from '@/interface'; +import { request, resultMiddleware } from '../request'; +import { fecthDictionaryMiddleware } from '../middleware'; + +// 接口示例 + +/** + * 获取数据字典(不加middleware处理) + * @param keyword - 关键词 + */ +export function fetchDictionary(keyword: string) { + return request.post('/emoss-entropy/ehe/model/getByIndicator', { + indiCatorName: keyword + }); +} + +/** + * 获取数据字典(加middleware处理) + * @param keyword - 关键词 + */ +export async function fetchDictionaryWithMiddleware(keyword: string) { + const res = await request.post('/emoss-entropy/ehe/model/getByIndicator', { + indiCatorName: keyword + }); + + return resultMiddleware(fecthDictionaryMiddleware, [res]); +} diff --git a/src/service/api/index.ts b/src/service/api/index.ts index 269586ee..689b5cbc 100644 --- a/src/service/api/index.ts +++ b/src/service/api/index.ts @@ -1 +1 @@ -export * from './auth'; +export * from './demo'; diff --git a/src/service/middleware/auth.ts b/src/service/middleware/auth.ts deleted file mode 100644 index c5b9d568..00000000 --- a/src/service/middleware/auth.ts +++ /dev/null @@ -1 +0,0 @@ -export function handleResponse() {} diff --git a/src/service/middleware/demo.ts b/src/service/middleware/demo.ts new file mode 100644 index 00000000..cf30278e --- /dev/null +++ b/src/service/middleware/demo.ts @@ -0,0 +1,23 @@ +import type { ResponseDictionary, Dictionary } from '@/interface'; + +export function fecthDictionaryMiddleware(data: ResponseDictionary[]): Dictionary[] { + return data.map(item => { + const { + modelName: label, + modelCharactorName: charactorLabel, + modelIndicator: indicatorKey, + modelIndicatorName: indicatorLabel, + remarks: remark, + formula + } = item; + + return { + label, + charactorLabel, + indicatorKey, + indicatorLabel, + remark, + formula + }; + }); +} diff --git a/src/service/middleware/index.ts b/src/service/middleware/index.ts index 269586ee..689b5cbc 100644 --- a/src/service/middleware/index.ts +++ b/src/service/middleware/index.ts @@ -1 +1 @@ -export * from './auth'; +export * from './demo'; diff --git a/src/service/request/axios/instance.ts b/src/service/request/axios/instance.ts index 03c44951..9f92921f 100644 --- a/src/service/request/axios/instance.ts +++ b/src/service/request/axios/instance.ts @@ -1,31 +1,19 @@ import axios from 'axios'; import type { AxiosRequestConfig, AxiosInstance, AxiosError, CancelTokenStatic } from 'axios'; import { getToken } from '@/utils'; -import { transformRequestData, handleAxiosError, handleResponseError } from '../helpers'; - -interface StatusConfig { - /** 表明请求状态的属性key */ - statusKey: string; - /** 请求信息的属性key */ - msgKey: string; - /** 成功状态的状态码 */ - successCode: string | number; -} +import type { BackendServiceResult } from '@/interface'; +import { transformRequestData, handleAxiosError, handleResponseError, handleBackendError } from '../helpers'; /** * 封装axios请求类 - * @author Soybean 2021-03-13 + * @author Soybean */ export default class CustomAxiosInstance { instance: AxiosInstance; - cancelToken: CancelTokenStatic; + private backendSuccessCode = 200; - private statusConfig: StatusConfig = { - statusKey: 'code', - msgKey: 'message', - successCode: 200 - }; + cancelToken: CancelTokenStatic; constructor(axiosConfig: AxiosRequestConfig) { this.instance = axios.create(axiosConfig); @@ -40,35 +28,34 @@ export default class CustomAxiosInstance { const handleConfig = { ...config }; if (handleConfig.headers) { // 数据转换 - handleConfig.data = await transformRequestData(handleConfig.data, handleConfig.headers['Content-Type']); + const contentType = handleConfig.headers['Content-Type']; + handleConfig.data = await transformRequestData(handleConfig.data, contentType); // 设置token handleConfig.headers.Authorization = getToken(); } return handleConfig; }, - (error: AxiosError) => { - handleAxiosError(error); + (axiosError: AxiosError) => { + const error = handleAxiosError(axiosError); return Promise.reject(error); } ); this.instance.interceptors.response.use( response => { - const { status, data } = response; - const { statusKey, msgKey, successCode } = this.statusConfig; + const { status } = response; if (status === 200 || status < 300 || status === 304) { - const responseData = data as any; - if (responseData[statusKey] === successCode) { - return Promise.resolve(responseData.data); + const backendServiceResult = response.data as BackendServiceResult; + if (backendServiceResult.code === this.backendSuccessCode) { + return Promise.resolve(backendServiceResult.data); } - window.$message?.error(responseData[msgKey]); - return Promise.reject(responseData[msgKey]); + const error = handleBackendError(backendServiceResult); + return Promise.reject(error); } - const error = { response }; - handleResponseError(response); + const error = handleResponseError(response); return Promise.reject(error); }, - (error: AxiosError) => { - handleAxiosError(error); + (axiosError: AxiosError) => { + const error = handleAxiosError(axiosError); return Promise.reject(error); } ); diff --git a/src/service/request/axios/request.ts b/src/service/request/axios/request.ts index 4bd84fac..c0df655c 100644 --- a/src/service/request/axios/request.ts +++ b/src/service/request/axios/request.ts @@ -1,12 +1,9 @@ import type { AxiosRequestConfig, AxiosInstance, AxiosResponse } from 'axios'; - -type ResponseSuccess = [null, any]; -type ResponseFail = [any, null]; +import type { RequestServiceError, CustomSuccessRequestResult, CustomFailRequestResult } from '@/interface'; /** * 封装各个请求方法及结果处理的类 - * @author Soybean 2021-03-15 - * @class Request + * @author Soybean */ export default class Request { instance: AxiosInstance; @@ -15,64 +12,51 @@ export default class Request { this.instance = instance; } - static successHandler(response: AxiosResponse) { - const result: ResponseSuccess = [null, response]; - return result; + static successHandler(response: AxiosResponse) { + const successResult: CustomSuccessRequestResult = { + data: response as unknown as ResponseData, + error: null, + networkStatus: window.navigator.onLine + }; + + return successResult; } - static failHandler(error: any) { - const result: ResponseFail = [error, null]; - return result; + static failHandler(error: RequestServiceError) { + const failResult: CustomFailRequestResult = { + data: null, + error, + networkStatus: window.navigator.onLine + }; + + return failResult; } - get(url: string, config?: AxiosRequestConfig) { - return this.instance.get(url, config).then(Request.successHandler).catch(Request.failHandler); + get(url: string, config?: AxiosRequestConfig) { + return this.instance + .get(url, config) + .then(res => Request.successHandler(res)) + .catch(Request.failHandler); } - post(url: string, data?: any, config?: AxiosRequestConfig) { - return this.instance.post(url, data, config).then(Request.successHandler).catch(Request.failHandler); + post(url: string, data?: any, config?: AxiosRequestConfig) { + return this.instance + .post(url, data, config) + .then(res => Request.successHandler(res)) + .catch(Request.failHandler); } - put(url: string, data?: any, config?: AxiosRequestConfig) { - return this.instance.put(url, data, config).then(Request.successHandler).catch(Request.failHandler); + put(url: string, data?: any, config?: AxiosRequestConfig) { + return this.instance + .put(url, data, config) + .then(res => Request.successHandler(res)) + .catch(Request.failHandler); } - delete(url: string, config?: AxiosRequestConfig) { - return this.instance.delete(url, config).then(Request.successHandler).catch(Request.failHandler); + delete(url: string, config?: AxiosRequestConfig) { + return this.instance + .delete(url, config) + .then(res => Request.successHandler(res)) + .catch(Request.failHandler); } } - -// import type { AxiosRequestConfig, AxiosInstance } from 'axios'; -// import { useBoolean } from '@/hooks'; - -// type RequestMethod = 'get' | 'post' | 'put' | 'delete'; - -// interface RequestParam { -// /** axios实例 */ -// instance: AxiosInstance; -// /** 请求地址 */ -// url: string; -// /** 请求方法 */ -// method?: RequestMethod; -// /** axios请求配置 */ -// axiosConfig?: AxiosRequestConfig; -// /** 请求结果的数据判断是否为空的函数 */ -// responseDataEmptyFunc?: (data: ResponseData) => boolean; -// /** 全局请求错误时是否弹出消息 */ -// showErrorMsg?: boolean; -// } - -// /** -// * 请求函数hooks -// * @param requestParam - 请求函数的参数 -// * @param url - 请求地址 -// * @param axiosConfig -// */ -// export default function useRequest(requestParam: RequestParam) { -// /** 网络状况 */ -// const { bool: networkStatus, setBool: setNetworkStatus } = useBoolean(window.navigator.onLine); -// /** 是否正在请求 */ -// const { bool: isFetching, setBool: setIsFetching } = useBoolean(); -// /** 响应的结果数据是否为空 */ -// const { bool: isEmpty, setBool: setIsEmpty } = useBoolean(); -// } diff --git a/src/service/request/config/index.ts b/src/service/request/config/index.ts index 385513e2..4acd04fb 100644 --- a/src/service/request/config/index.ts +++ b/src/service/request/config/index.ts @@ -1,12 +1,21 @@ /** 请求超时时间 */ export const REQUEST_TIMEOUT = 60 * 1000; -/** 默认的请求错误文本 */ +/** 错误信息的显示时间 */ +export const ERROR_MSG_DURATION = 3 * 1000; + +/** 兜底的请求错误code */ +export const DEFAULT_REQUEST_ERROR_CODE = 'DEFAULT'; +/** 兜底的请求错误文本 */ export const DEFAULT_REQUEST_ERROR_MSG = '请求错误~'; +/** 请求超时的错误code(为固定值:ECONNABORTED) */ +export const REQUEST_TIMEOUT_CODE = 'ECONNABORTED'; /** 请求超时的错误文本 */ export const REQUEST_TIMEOUT_MSG = '请求超时~'; +/** 网络不可用的code */ +export const NETWORK_ERROR_CODE = 'NETWORK_ERROR'; /** 网络不可用的错误文本 */ export const NETWORK_ERROR_MSG = '网络不可用~'; @@ -25,3 +34,6 @@ export const ERROR_STATUS = { 504: '504: 网关超时~', 505: '505: http版本不支持该请求~' }; + +/** 不弹出错误信息的code */ +export const NO_ERROR_MSG_CODE: (string | number)[] = []; diff --git a/src/service/request/helpers/error.ts b/src/service/request/helpers/error.ts index 899e103b..db4d8138 100644 --- a/src/service/request/helpers/error.ts +++ b/src/service/request/helpers/error.ts @@ -1,37 +1,45 @@ import type { AxiosError, AxiosResponse } from 'axios'; -import { DEFAULT_REQUEST_ERROR_MSG, ERROR_STATUS, NETWORK_ERROR_MSG } from '../config'; +import type { RequestServiceError, BackendServiceResult } from '@/interface'; +import { + DEFAULT_REQUEST_ERROR_CODE, + DEFAULT_REQUEST_ERROR_MSG, + NETWORK_ERROR_CODE, + NETWORK_ERROR_MSG, + REQUEST_TIMEOUT_CODE, + REQUEST_TIMEOUT_MSG, + ERROR_STATUS +} from '../config'; +import { showErrorMsg } from './msg'; type ErrorStatus = keyof typeof ERROR_STATUS; -/** - * 获取请求失败的错误 - * @param error - */ -export function getFailRequestErrorMsg(error: AxiosError) { - if (!window.navigator.onLine || error.message === 'Network Error') { - return NETWORK_ERROR_MSG; - } - if (error.code === 'ECONNABORTED') { - return error.message; - } - if (error.response) { - const errorCode: ErrorStatus = error.response.status as ErrorStatus; - const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG; - return msg; - } - return DEFAULT_REQUEST_ERROR_MSG; -} - /** * 处理请求失败的错误 * @param error - 错误 */ -export function handleAxiosError(error: AxiosError) { - const { $message: Message } = window; +export function handleAxiosError(axiosError: AxiosError) { + const error: RequestServiceError = { + type: 'axios', + code: DEFAULT_REQUEST_ERROR_CODE, + msg: DEFAULT_REQUEST_ERROR_MSG + }; - const msg = getFailRequestErrorMsg(error); + if (!window.navigator.onLine || axiosError.message === 'Network Error') { + // 网路错误 + Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG }); + } else if (axiosError.code === REQUEST_TIMEOUT_CODE && axiosError.message.includes('timeout')) { + /** 超时错误 */ + Object.assign(error, { code: REQUEST_TIMEOUT_CODE, msg: REQUEST_TIMEOUT_MSG }); + } else if (axiosError.response) { + // 请求不成功的错误 + const errorCode: ErrorStatus = axiosError.response.status as ErrorStatus; + const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG; + Object.assign(error, { code: errorCode, msg }); + } - Message?.error(msg); + showErrorMsg(error); + + return error; } /** @@ -39,10 +47,39 @@ export function handleAxiosError(error: AxiosError) { * @param response - 请求的响应 */ export function handleResponseError(response: AxiosResponse) { + const error: RequestServiceError = { + type: 'axios', + code: DEFAULT_REQUEST_ERROR_CODE, + msg: DEFAULT_REQUEST_ERROR_MSG + }; + if (!window.navigator.onLine) { - return NETWORK_ERROR_MSG; + // 网路错误 + Object.assign(error, { code: NETWORK_ERROR_CODE, msg: NETWORK_ERROR_MSG }); + } else { + // 请求成功的状态码非200的错误 + const errorCode: ErrorStatus = response.status as ErrorStatus; + const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG; + Object.assign(error, { type: 'backend', code: errorCode, msg }); } - const errorCode: ErrorStatus = response.status as ErrorStatus; - const msg = ERROR_STATUS[errorCode] || DEFAULT_REQUEST_ERROR_MSG; - return msg; + + showErrorMsg(error); + + return error; +} + +/** + * 处理后端返回的错误 + * @param backendResult - 后端接口的响应数据 + */ +export function handleBackendError(backendResult: BackendServiceResult) { + const error: RequestServiceError = { + type: 'backend', + code: backendResult.code, + msg: backendResult.message + }; + + showErrorMsg(error); + + return error; } diff --git a/src/service/request/helpers/handler.ts b/src/service/request/helpers/handler.ts index ee2c8bf9..5bae2e63 100644 --- a/src/service/request/helpers/handler.ts +++ b/src/service/request/helpers/handler.ts @@ -1,16 +1,26 @@ -// type ResultHandler = (...arg: any) => T; -// type SuccessRequest = { -// error: null; -// data: T; -// }; -// type FailRequest = { -// error: any; -// data: null; -// }; -// type RequestResult = SuccessRequest | FailRequest; -// /** -// * 对请求的结果数据进行格式化的处理 -// * @param handleFunc - 处理函数 -// * @param requests - 请求结果 -// */ -// export function handleResponse(resultHandler: ResultHandler, requests: RequestResult[]) {} +import { CustomRequestResult, CustomSuccessRequestResult, CustomFailRequestResult } from '@/interface'; + +type ResultHandler = (...arg: any) => T; +/** + * 对请求的结果数据进行格式化的处理 + * @param resultHandler - 处理函数 + * @param requests - 请求结果 + */ +export function resultMiddleware( + resultHandler: ResultHandler, + requests: CustomRequestResult[] +) { + const errorIndex = requests.findIndex(item => item.error !== null); + const hasError = errorIndex > -1; + const successResult: CustomSuccessRequestResult = { + data: resultHandler(...requests.map(item => item.data)), + error: null, + networkStatus: window.navigator.onLine + }; + const failResult: CustomFailRequestResult = { + data: null, + error: requests[errorIndex].error!, + networkStatus: window.navigator.onLine + }; + return hasError ? failResult : successResult; +} diff --git a/src/service/request/helpers/index.ts b/src/service/request/helpers/index.ts index 67293c80..788621af 100644 --- a/src/service/request/helpers/index.ts +++ b/src/service/request/helpers/index.ts @@ -1,2 +1,3 @@ export * from './transform'; export * from './error'; +export * from './handler'; diff --git a/src/service/request/helpers/msg.ts b/src/service/request/helpers/msg.ts new file mode 100644 index 00000000..547446b1 --- /dev/null +++ b/src/service/request/helpers/msg.ts @@ -0,0 +1,31 @@ +import type { RequestServiceError } from '@/interface'; +import { NO_ERROR_MSG_CODE, ERROR_MSG_DURATION } from '../config'; + +/** 错误消息栈,防止同一错误同时出现 */ +const errorMsgStack = new Map([]); + +function addErrorMsg(error: RequestServiceError) { + errorMsgStack.set(error.code, error.msg); +} +function removeErrorMsg(error: RequestServiceError) { + errorMsgStack.delete(error.code); +} +function hasErrorMsg(error: RequestServiceError) { + return errorMsgStack.has(error.code); +} + +/** + * 显示错误信息 + * @param error + */ +export function showErrorMsg(error: RequestServiceError) { + if (!NO_ERROR_MSG_CODE.includes(error.code)) { + if (!hasErrorMsg(error)) { + addErrorMsg(error); + window.$message?.error(error.msg, { duration: ERROR_MSG_DURATION }); + setTimeout(() => { + removeErrorMsg(error); + }, ERROR_MSG_DURATION); + } + } +} diff --git a/src/service/request/index.ts b/src/service/request/index.ts index e8e86cac..feebdeba 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -1,8 +1,9 @@ import { createRequest } from './axios'; -import { REQUEST_TIMEOUT, REQUEST_TIMEOUT_MSG } from './config'; +import { REQUEST_TIMEOUT } from './config'; export const request = createRequest({ baseURL: import.meta.env.VITE_HTTP_URL, - timeout: REQUEST_TIMEOUT, - timeoutErrorMessage: REQUEST_TIMEOUT_MSG + timeout: REQUEST_TIMEOUT }); + +export { resultMiddleware } from './helpers';