refactor(projects): 请求构造函数适配不同后端接口的数据结构
This commit is contained in:
parent
db75c91400
commit
4f9d544d43
@ -10,7 +10,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/getSmsCode',
|
||||
method: 'post',
|
||||
response: (): Service.BackendServiceResult<boolean> => {
|
||||
response: (): Service.MockServiceResult<boolean> => {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
@ -22,7 +22,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/loginByPwd',
|
||||
method: 'post',
|
||||
response: (): Service.BackendServiceResult<ApiAuth.Token> => {
|
||||
response: (): Service.MockServiceResult<ApiAuth.Token> => {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
@ -34,7 +34,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/loginByCode',
|
||||
method: 'post',
|
||||
response: (): Service.BackendServiceResult<ApiAuth.Token> => {
|
||||
response: (): Service.MockServiceResult<ApiAuth.Token> => {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
@ -46,7 +46,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/getUserInfo',
|
||||
method: 'get',
|
||||
response: (): Service.BackendServiceResult<ApiAuth.UserInfo> => {
|
||||
response: (): Service.MockServiceResult<ApiAuth.UserInfo> => {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
@ -62,7 +62,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/testToken',
|
||||
method: 'post',
|
||||
response: (option: any): Service.BackendServiceResult<true | null> => {
|
||||
response: (option: any): Service.MockServiceResult<true | null> => {
|
||||
if (option.headers?.authorization !== token.token) {
|
||||
return {
|
||||
code: 66666,
|
||||
@ -80,7 +80,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/updateToken',
|
||||
method: 'post',
|
||||
response: (): Service.BackendServiceResult<string> => {
|
||||
response: (): Service.MockServiceResult<string> => {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
|
@ -210,7 +210,7 @@ const apis: MockMethod[] = [
|
||||
{
|
||||
url: '/mock/getUserRoutes',
|
||||
method: 'post',
|
||||
response: (): Service.BackendServiceResult => {
|
||||
response: (): Service.MockServiceResult => {
|
||||
return {
|
||||
code: 200,
|
||||
message: 'ok',
|
||||
|
1
src/composables/events/index.ts
Normal file
1
src/composables/events/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
1
src/config/business/index.ts
Normal file
1
src/config/business/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
8
src/directives/index.ts
Normal file
8
src/directives/index.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import type { App } from 'vue';
|
||||
import setupNetworkDirective from './network';
|
||||
import setupLoginDirective from './login';
|
||||
|
||||
export function setupDirectives(app: App) {
|
||||
setupNetworkDirective(app);
|
||||
setupLoginDirective(app);
|
||||
}
|
27
src/directives/login.ts
Normal file
27
src/directives/login.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { App, Directive } from 'vue';
|
||||
import { useAuthStore } from '@/store';
|
||||
import { useRouterPush } from '@/composables';
|
||||
|
||||
export default function setupLoginDirective(app: App) {
|
||||
const auth = useAuthStore();
|
||||
const { toLogin } = useRouterPush(false);
|
||||
function listenerHandler(event: MouseEvent) {
|
||||
if (!auth.isLogin) {
|
||||
event.stopPropagation();
|
||||
toLogin();
|
||||
}
|
||||
}
|
||||
|
||||
const loginDirective: Directive<HTMLElement, boolean | undefined> = {
|
||||
mounted(el: HTMLElement, binding) {
|
||||
if (binding.value === false) return;
|
||||
el.addEventListener('click', listenerHandler, { capture: true });
|
||||
},
|
||||
unmounted(el: HTMLElement, binding) {
|
||||
if (binding.value === false) return;
|
||||
el.removeEventListener('click', listenerHandler);
|
||||
}
|
||||
};
|
||||
|
||||
app.directive('login', loginDirective);
|
||||
}
|
25
src/directives/network.ts
Normal file
25
src/directives/network.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import type { App, Directive } from 'vue';
|
||||
import { NETWORK_ERROR_MSG } from '@/config';
|
||||
|
||||
export default function setupNetworkDirective(app: App) {
|
||||
function listenerHandler(event: MouseEvent) {
|
||||
const hasNetwork = window.navigator.onLine;
|
||||
if (!hasNetwork) {
|
||||
window.$message?.error(NETWORK_ERROR_MSG);
|
||||
event.stopPropagation();
|
||||
}
|
||||
}
|
||||
|
||||
const networkDirective: Directive<HTMLElement, boolean | undefined> = {
|
||||
mounted(el: HTMLElement, binding) {
|
||||
if (binding.value === false) return;
|
||||
el.addEventListener('click', listenerHandler, { capture: true });
|
||||
},
|
||||
unmounted(el: HTMLElement, binding) {
|
||||
if (binding.value === false) return;
|
||||
el.removeEventListener('click', listenerHandler);
|
||||
}
|
||||
};
|
||||
|
||||
app.directive('network', networkDirective);
|
||||
}
|
1
src/enum/business/index.ts
Normal file
1
src/enum/business/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export {};
|
@ -2,6 +2,7 @@ import { createApp } from 'vue';
|
||||
import { setupAssets } from '@/plugins';
|
||||
import { setupRouter } from '@/router';
|
||||
import { setupStore } from '@/store';
|
||||
import { setupDirectives } from '@/directives';
|
||||
import App from './App.vue';
|
||||
|
||||
async function setupApp() {
|
||||
@ -13,6 +14,9 @@ async function setupApp() {
|
||||
// 挂载pinia状态
|
||||
setupStore(app);
|
||||
|
||||
// 挂载自定义vue指令
|
||||
setupDirectives(app);
|
||||
|
||||
// 挂载路由
|
||||
await setupRouter(app);
|
||||
|
||||
|
@ -1,4 +1,3 @@
|
||||
import setupAssets from './assets';
|
||||
import setupInitSvgLogo from './logo';
|
||||
|
||||
export { setupAssets, setupInitSvgLogo };
|
||||
export { setupAssets };
|
||||
|
@ -1,28 +0,0 @@
|
||||
/** 初始化加载效果的svg格式logo */
|
||||
export default function setupInitSvgLogo(id: string) {
|
||||
const svgStr = `<svg width="128px" height="128px" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
|
||||
y="0px" viewBox="0 0 158.9 158.9" style="enable-background:new 0 0 158.9 158.9;" xml:space="preserve">
|
||||
<path style="fill:none" d="M0,158.9C0,106.3,0,53.7,0,1.1C0,0.2,0.2,0,1.1,0c52.2,0,104.5,0,156.7,0c0.9,0,1.1,0.2,1.1,1.1
|
||||
c0,52.2,0,104.5,0,156.7c0,0.9-0.2,1.1-1.1,1.1C105.2,158.8,52.6,158.8,0,158.9z" />
|
||||
<path style="fill:currentColor" d="M81.3,55.9c-0.1-11.7-2.9-22.5-9.4-32.4c-1-1.5-2.1-2.9-2.5-4.7c-0.7-3.4,0.9-6.9,4-8.6c3-1.7,6.8-1.2,9.3,1.2
|
||||
c2.4,2.6,4.4,5.6,5.9,8.8c4.7,8.9,7.6,18.6,8.4,28.6c1,12.5-0.7,25-5.2,36.7c-0.9,2.5-1.9,4.9-3,7.3c-0.3,0.4-0.3,1,0,1.4
|
||||
c9.6,13.3,21.8,23,37.8,27.2c6.4,1.7,13.1,2.3,19.7,1.6c4.2-0.4,7.9,2.7,8.4,6.9c0.7,4.3-2.3,8.3-6.6,9c0,0,0,0-0.1,0
|
||||
c-7.7,0.9-15.5,0.5-23-1.3c-13.9-3.1-26.7-10-36.9-19.9c-4.4-4.2-8.4-8.8-11.9-13.7c-0.5-0.8-1.4-1.2-2.3-1.1
|
||||
c-9.5,0.7-18.8,3.3-27.4,7.6c-11.6,6-20.7,14.6-26.4,26.4c-0.7,1.9-2,3.5-3.7,4.7c-2.9,1.7-6.6,1.5-9.2-0.7c-2.8-2.2-3.8-6-2.4-9.3
|
||||
c2.2-5.2,5.1-10.1,8.7-14.5c12.2-15.4,28.2-24.6,47.3-28.6c4-0.8,8.1-1.4,12.2-1.6c0.5,0,1-0.3,1.2-0.8c3.3-7.1,5.5-14.6,6.5-22.3
|
||||
C81.1,61.2,81.3,58.6,81.3,55.9z" />
|
||||
<path style="fill:currentColor" d="M136.3,108.3c-3.8-0.5-7.6-1.4-11.1-2.9c-7.7-2.8-14.4-7.5-19.7-13.8c-2.9-3.3-2.5-8.4,0.8-11.3
|
||||
c1.4-1.2,3.1-1.9,4.9-1.9c2.5-0.1,5,1,6.5,2.9c4.9,5.6,11.6,9.4,18.9,10.8c1.5,0.2,3.1,0.6,4.5,1.2c3.2,1.8,4.8,5.6,3.8,9.2
|
||||
C144,106.1,140.8,108.4,136.3,108.3z" />
|
||||
<path style="fill:currentColor" d="M55.7,33.3c3,0.2,5.6,2.2,6.6,5c2.2,5.4,3.4,11.2,3.6,17c0.3,5.9-0.6,11.7-2.5,17.3c-2,5.8-8.2,7.8-12.9,4.2
|
||||
c-2.6-2.2-3.6-5.8-2.4-9c1.4-4,1.9-8.2,1.7-12.4c-0.2-3.8-1-7.5-2.4-11C45.3,38.9,49.2,33.3,55.7,33.3z" />
|
||||
<path style="fill:currentColor" d="M77.9,126.6c0,3.9-2.8,7.2-6.7,7.9c-7.8,1.5-14.8,5.9-19.7,12.2c-2.7,3.5-7.6,4.2-11.2,1.6
|
||||
c-3.6-2.6-4.3-7.6-1.7-11.2c0.1-0.1,0.2-0.3,0.3-0.4c4.1-5.2,9.3-9.6,15.1-12.8c4.4-2.5,9.1-4.2,14-5.1
|
||||
C73.3,117.7,77.9,121.3,77.9,126.6z" />
|
||||
</svg>
|
||||
`;
|
||||
const appEl = document.querySelector(id);
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = svgStr;
|
||||
appEl?.appendChild(div);
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import axios from 'axios';
|
||||
import type { AxiosRequestConfig, AxiosInstance, AxiosError, CancelTokenStatic } from 'axios';
|
||||
import type { AxiosRequestConfig, AxiosInstance, AxiosError } from 'axios';
|
||||
import { REQUEST_TIMEOUT, REFRESH_TOKEN_CODE } from '@/config';
|
||||
import {
|
||||
getToken,
|
||||
@ -18,17 +18,28 @@ import { refreshToken } from './helpers';
|
||||
export default class CustomAxiosInstance {
|
||||
instance: AxiosInstance;
|
||||
|
||||
private backendSuccessCode = 200;
|
||||
backendConfig: Service.BackendResultConfig;
|
||||
|
||||
cancelToken: CancelTokenStatic;
|
||||
|
||||
constructor(axiosConfig: AxiosRequestConfig) {
|
||||
/**
|
||||
*
|
||||
* @param axiosConfig - axios配置
|
||||
* @param backendSuccessCode - 后端业务上定义的成功请求的状态码
|
||||
*/
|
||||
constructor(
|
||||
axiosConfig: AxiosRequestConfig,
|
||||
backendConfig: Service.BackendResultConfig = {
|
||||
codeKey: 'code',
|
||||
dataKey: 'data',
|
||||
msgKey: 'message',
|
||||
successCode: 200
|
||||
}
|
||||
) {
|
||||
this.backendConfig = backendConfig;
|
||||
const defaultConfig: AxiosRequestConfig = {
|
||||
timeout: REQUEST_TIMEOUT
|
||||
};
|
||||
Object.assign(defaultConfig, axiosConfig);
|
||||
this.instance = axios.create(defaultConfig);
|
||||
this.cancelToken = axios.CancelToken;
|
||||
this.setInterceptor();
|
||||
}
|
||||
|
||||
@ -55,21 +66,22 @@ export default class CustomAxiosInstance {
|
||||
async response => {
|
||||
const { status } = response;
|
||||
if (status === 200 || status < 300 || status === 304) {
|
||||
const backend = response.data as Service.BackendServiceResult;
|
||||
const backend = response.data;
|
||||
const { codeKey, dataKey, successCode } = this.backendConfig;
|
||||
// 请求成功
|
||||
if (backend.code === this.backendSuccessCode) {
|
||||
return handleServiceResult(null, backend.data);
|
||||
if (backend[codeKey] === successCode) {
|
||||
return handleServiceResult(null, backend[dataKey]);
|
||||
}
|
||||
|
||||
// token失效, 刷新token
|
||||
if (REFRESH_TOKEN_CODE.includes(backend.code)) {
|
||||
if (REFRESH_TOKEN_CODE.includes(backend[codeKey])) {
|
||||
const config = await refreshToken(response.config);
|
||||
if (config) {
|
||||
return this.instance.request(config);
|
||||
}
|
||||
}
|
||||
|
||||
const error = handleBackendError(backend);
|
||||
const error = handleBackendError(backend, this.backendConfig);
|
||||
return handleServiceResult(error, null);
|
||||
}
|
||||
const error = handleResponseError(response);
|
||||
|
@ -16,9 +16,10 @@ interface RequestParam {
|
||||
/**
|
||||
* 创建请求
|
||||
* @param axiosConfig - axios配置
|
||||
* @param backendConfig - 后端接口字段配置
|
||||
*/
|
||||
export function createRequest(axiosConfig: AxiosRequestConfig) {
|
||||
const customInstance = new CustomAxiosInstance(axiosConfig);
|
||||
export function createRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {
|
||||
const customInstance = new CustomAxiosInstance(axiosConfig, backendConfig);
|
||||
|
||||
/**
|
||||
* 异步promise请求
|
||||
@ -98,9 +99,10 @@ type RequestResultHook<T = any> = {
|
||||
/**
|
||||
* 创建hooks请求
|
||||
* @param axiosConfig - axios配置
|
||||
* @param backendConfig - 后端接口字段配置
|
||||
*/
|
||||
export function createHookRequest(axiosConfig: AxiosRequestConfig) {
|
||||
const customInstance = new CustomAxiosInstance(axiosConfig);
|
||||
export function createHookRequest(axiosConfig: AxiosRequestConfig, backendConfig?: Service.BackendResultConfig) {
|
||||
const customInstance = new CustomAxiosInstance(axiosConfig, backendConfig);
|
||||
|
||||
/**
|
||||
* hooks请求
|
||||
|
28
src/typings/common/service.d.ts
vendored
28
src/typings/common/service.d.ts
vendored
@ -23,14 +23,16 @@ declare namespace Service {
|
||||
msg: string;
|
||||
}
|
||||
|
||||
/** 后端接口返回的数据的类型 */
|
||||
interface BackendServiceResult<T = any> {
|
||||
/** 状态码 */
|
||||
code: string | number;
|
||||
/** 接口数据 */
|
||||
data: T;
|
||||
/** 接口消息 */
|
||||
message: string;
|
||||
/** 后端接口返回的数据结构配置 */
|
||||
interface BackendResultConfig {
|
||||
/** 表示后端请求状态码的属性字段 */
|
||||
codeKey: string;
|
||||
/** 表示后端请求数据的属性字段 */
|
||||
dataKey: string;
|
||||
/** 表示后端消息的属性字段 */
|
||||
msgKey: string;
|
||||
/** 后端业务上定义的成功请求的状态 */
|
||||
successCode: number | string;
|
||||
}
|
||||
|
||||
/** 自定义的请求成功结果 */
|
||||
@ -51,4 +53,14 @@ declare namespace Service {
|
||||
|
||||
/** 自定义的请求结果 */
|
||||
type RequestResult<T = any> = SuccessResult<T> | FailedResult;
|
||||
|
||||
/** mock示例接口类型:后端接口返回的数据的类型 */
|
||||
interface MockServiceResult<T = any> {
|
||||
/** 状态码 */
|
||||
code: string | number;
|
||||
/** 接口数据 */
|
||||
data: T;
|
||||
/** 接口消息 */
|
||||
message: string;
|
||||
}
|
||||
}
|
||||
|
@ -87,11 +87,12 @@ export function handleResponseError(response: AxiosResponse) {
|
||||
* 处理后端返回的错误(业务错误)
|
||||
* @param backendResult - 后端接口的响应数据
|
||||
*/
|
||||
export function handleBackendError(backendResult: Service.BackendServiceResult) {
|
||||
export function handleBackendError(backendResult: Record<string, any>, config: Service.BackendResultConfig) {
|
||||
const { codeKey, msgKey } = config;
|
||||
const error: Service.RequestError = {
|
||||
type: 'backend',
|
||||
code: backendResult.code,
|
||||
msg: backendResult.message
|
||||
code: backendResult[codeKey],
|
||||
msg: backendResult[msgKey]
|
||||
};
|
||||
|
||||
showErrorMsg(error);
|
||||
|
Loading…
Reference in New Issue
Block a user