diff --git a/package.json b/package.json index 7328392..9eb17d0 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@types/mockjs": "1.0.10", "@types/redux-thunk": "2.1.0", "ahooks": "3.7.9", + "axios": "1.6.7", "clsx": "2.1.0", "install": "0.13.0", "mockjs": "1.1.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc180cb..136e44a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -50,6 +50,9 @@ dependencies: ahooks: specifier: 3.7.9 version: 3.7.9(react@18.2.0) + axios: + specifier: 1.6.7 + version: 1.6.7 clsx: specifier: 2.1.0 version: 2.1.0 @@ -4711,7 +4714,6 @@ packages: { integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==, } - dev: true /at-least-node@1.0.0: resolution: @@ -4756,6 +4758,19 @@ packages: engines: { node: '>=4' } dev: true + /axios@1.6.7: + resolution: + { + integrity: sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==, + } + dependencies: + follow-redirects: 1.15.5 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.2.1: resolution: { @@ -5384,7 +5399,6 @@ packages: engines: { node: '>= 0.8' } dependencies: delayed-stream: 1.0.0 - dev: true /commander@11.1.0: resolution: @@ -5854,7 +5868,6 @@ packages: integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==, } engines: { node: '>=0.4.0' } - dev: true /dequal@2.0.3: resolution: @@ -7193,6 +7206,19 @@ packages: } dev: true + /follow-redirects@1.15.5: + resolution: + { + integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==, + } + engines: { node: '>=4.0' } + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: { @@ -7212,7 +7238,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /fraction.js@4.2.0: resolution: @@ -9536,7 +9561,6 @@ packages: integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==, } engines: { node: '>= 0.6' } - dev: true /mime-types@2.1.35: resolution: @@ -9546,7 +9570,6 @@ packages: engines: { node: '>= 0.6' } dependencies: mime-db: 1.52.0 - dev: true /mimic-fn@2.1.0: resolution: @@ -10371,6 +10394,13 @@ packages: object-assign: 4.1.1 react-is: 16.13.1 + /proxy-from-env@1.1.0: + resolution: + { + integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==, + } + dev: false + /psl@1.9.0: resolution: { diff --git a/src/content/Content.tsx b/src/content/Content.tsx index b284d63..8f25aa5 100644 --- a/src/content/Content.tsx +++ b/src/content/Content.tsx @@ -1,8 +1,32 @@ -import React, { ReactElement } from 'react'; +import React, { ReactElement, useEffect } from 'react'; +import Snackbar, { SnackbarOrigin } from '@mui/material/Snackbar'; + +import http, { ResultData } from '../service/request'; +import useGlobalStore from '../store/useGlobalStore'; import PersistentDrawerRight from './drawer'; const Content = (): ReactElement => { + const { message, messageType, messageOpen, closeMessage } = useGlobalStore((state) => ({ + ...state, + })); + console.log(message); + + useEffect(() => { + http.get('/api/share/trades', { + trader: '123', + subject: '123', + }); + // fetch('https://test-xfans-api.d.buidlerdao.xyz/api/share/trades') + // .then((response) => response.json()) + // .then((json) => { + // console.log(json); + // }) + // .catch((error) => { + // console.log(error); + // }); + }, []); + return (
{ }} > +
); }; diff --git a/src/service/checkStatus.ts b/src/service/checkStatus.ts new file mode 100644 index 0000000..91dfee3 --- /dev/null +++ b/src/service/checkStatus.ts @@ -0,0 +1,69 @@ +import useGlobalStore from '../store/useGlobalStore'; +// import { message } from "antd"; + +/** + * @description: 校验网络请求状态码 + * @param {Number} status + * @return void + */ +export const checkStatus = (status: number) => { + switch (status) { + case 400: + useGlobalStore.setState({ + message: '请求失败!请您稍后重试', + messageType: 'error', + messageOpen: true, + }); + break; + case 401: + useGlobalStore.setState({ token: '' }); + useGlobalStore.setState({ + message: '登录失效!请您重新登录', + messageType: 'error', + messageOpen: true, + }); + break; + case 403: + useGlobalStore.setState({ + message: '当前账号无权限访问!', + messageType: 'error', + messageOpen: true, + }); + break; + case 404: + useGlobalStore.setState({ + message: '你所访问的资源不存在!', + messageType: 'error', + messageOpen: true, + }); + break; + case 405: + useGlobalStore.setState({ + message: '请求方式错误!请您稍后重试', + messageType: 'error', + messageOpen: true, + }); + break; + case 408: + useGlobalStore.setState({ + message: '请求超时!请您稍后重试', + messageType: 'error', + messageOpen: true, + }); + break; + case 500: + useGlobalStore.setState({ message: '服务异常!', messageType: 'error', messageOpen: true }); + break; + case 502: + useGlobalStore.setState({ message: '网关错误!', messageType: 'error', messageOpen: true }); + break; + case 503: + useGlobalStore.setState({ message: '服务不可用!', messageType: 'error', messageOpen: true }); + break; + case 504: + useGlobalStore.setState({ message: '网关超时!', messageType: 'error', messageOpen: true }); + break; + default: + useGlobalStore.setState({ message: '请求失败!', messageType: 'error', messageOpen: true }); + } +}; diff --git a/src/service/request.ts b/src/service/request.ts new file mode 100644 index 0000000..31845df --- /dev/null +++ b/src/service/request.ts @@ -0,0 +1,153 @@ +import axios, { + AxiosError, + AxiosInstance, + AxiosRequestConfig, + AxiosResponse, + InternalAxiosRequestConfig, +} from 'axios'; + +import useGlobalStore from '../store/useGlobalStore'; + +import { checkStatus } from './checkStatus'; + +// 请求响应参数(不包含data) +export interface Result { + code: string | number; + message: string; +} + +// 请求响应参数(包含data) +export interface ResultData extends Result { + data: T; +} + +export enum ResultEnum { + SUCCESS = 200, + ERROR = 500, + OVERDUE = 401, + TIMEOUT = 30000, + TYPE = 'success', +} + +const config = { + // 默认地址请求地址,可在 .env.** 文件中修改 + baseURL: 'https://test-xfans-api.d.buidlerdao.xyz', + // 设置超时时间 + timeout: ResultEnum.TIMEOUT as number, + // 跨域时候允许携带凭证 + withCredentials: true, +}; + +let showMsg = true; + +class RequestHttp { + service: AxiosInstance; + public constructor(config: AxiosRequestConfig>) { + // instantiation + this.service = axios.create(config); + + /** + * @description 请求拦截器 + * 客户端发送请求 -> [请求拦截器] -> 服务器 + * token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中 + */ + this.service.interceptors.request.use( + (config: InternalAxiosRequestConfig) => { + const token = + useGlobalStore.getState().token ?? JSON.parse(localStorage.getItem('token')!).state.token; + if (config.headers && typeof config.headers.set === 'function') { + config.headers.set('Authorization', 'Bearer ' + token); + } + return config; + }, + (error: AxiosError) => { + return Promise.reject(error); + } + ); + + /** + * @description 响应拦截器 + * 服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息 + */ + this.service.interceptors.response.use( + (response: AxiosResponse) => { + const { data } = response; + + // 登陆失效 + if (data.code == ResultEnum.OVERDUE) { + useGlobalStore.setState({ + token: '', + message: data.message, + messageType: 'error', + messageOpen: true, + }); + return Promise.reject(data); + } + // 全局错误信息拦截(防止下载文件的时候返回数据流,没有 code 直接报错) + if (data.code && data.code !== ResultEnum.SUCCESS) { + useGlobalStore.setState({ + message: data.message, + messageType: 'error', + messageOpen: true, + }); + return Promise.reject(data); + } + // 成功请求(在页面上除非特殊情况,否则不用处理失败逻辑) + return data; + }, + async (error: AxiosError) => { + const { response } = error; + + if (showMsg) { + // 请求超时 && 网络错误单独判断,没有 response + if (error.message.indexOf('timeout') !== -1) { + useGlobalStore.setState({ + message: '请求超时!请您稍后重试', + messageType: 'error', + messageOpen: true, + }); + } + if (error.message.indexOf('Network Error') !== -1) { + useGlobalStore.setState({ + message: '网络错误!请您稍后重试', + messageType: 'error', + messageOpen: true, + }); + } + // 根据服务器响应的错误状态码,做不同的处理 + if (response) { + checkStatus(response.status); + } + showMsg = false; + setTimeout(() => { + showMsg = true; + }, 3000); + } + // 服务器结果都没有返回(可能服务器错误可能客户端断网),断网处理:可以跳转到断网页面 + if (!window.navigator.onLine) window.location.replace('/500'); + return Promise.reject(error); + } + ); + } + + /** + * @description 常用请求方法封装 + */ + get(url: string, params?: P, _object = {}): Promise> { + return this.service.get(url, { params, ..._object }); + } + post(url: string, params?: object | string, _object = {}): Promise { + return this.service.post(url, params, _object); + } + put(url: string, params?: object, _object = {}): Promise> { + return this.service.put(url, params, _object); + } + delete(url: string, params?: any, _object = {}): Promise> { + return this.service.delete(url, { params, ..._object }); + } + download(url: string, params?: object, _object = {}): Promise { + return this.service.post(url, params, { ..._object, responseType: 'blob' }); + } +} + +export default new RequestHttp(config); diff --git a/src/store/useGlobalStore.ts b/src/store/useGlobalStore.ts new file mode 100644 index 0000000..ecd7c4d --- /dev/null +++ b/src/store/useGlobalStore.ts @@ -0,0 +1,25 @@ +import { create } from 'zustand'; + +interface GlobalStoreProps { + token: string; + message: string; + messageType: string; + messageOpen: boolean; + closeMessage: () => void; +} + +const useGlobalStore = create((set) => ({ + token: '', + message: '', + messageType: '', + messageOpen: false, + closeMessage() { + set({ + messageOpen: false, + message: '', + messageType: '', + }); + }, +})); + +export default useGlobalStore;