feat(projects): @sa/axios: createRequest, createFlatRequest, createHookRequest
This commit is contained in:
parent
fbf4cc430d
commit
bac1632457
@ -9,7 +9,8 @@ export default defineConfig(
|
||||
{
|
||||
ignores: ['index', 'App', '[id]']
|
||||
}
|
||||
]
|
||||
],
|
||||
'no-empty-function': 'off'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -25,10 +25,10 @@
|
||||
"dependencies": {
|
||||
"@better-scroll/core": "2.5.1",
|
||||
"@iconify/vue": "4.1.1",
|
||||
"@sa/axios": "workspace:*",
|
||||
"@sa/color-palette": "workspace:*",
|
||||
"@sa/hooks": "workspace:*",
|
||||
"@sa/materials": "workspace:*",
|
||||
"@sa/request": "workspace:*",
|
||||
"@sa/utils": "workspace:*",
|
||||
"@vueuse/core": "10.7.2",
|
||||
"clipboard": "2.0.11",
|
||||
|
17
packages/axios/package.json
Normal file
17
packages/axios/package.json
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "@sa/axios",
|
||||
"version": "1.0.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/utils": "workspace:*",
|
||||
"axios": "1.6.5",
|
||||
"axios-retry": "^4.0.0"
|
||||
}
|
||||
}
|
5
packages/axios/src/constant.ts
Normal file
5
packages/axios/src/constant.ts
Normal file
@ -0,0 +1,5 @@
|
||||
/** request id key */
|
||||
export const REQUEST_ID_KEY = 'X-Request-Id';
|
||||
|
||||
/** the backend error code key */
|
||||
export const BACKEND_ERROR_CODE = 'BACKEND_ERROR';
|
176
packages/axios/src/index.ts
Normal file
176
packages/axios/src/index.ts
Normal file
@ -0,0 +1,176 @@
|
||||
import axios, { AxiosError } from 'axios';
|
||||
import type { AxiosResponse, CancelTokenSource, CreateAxiosDefaults, InternalAxiosRequestConfig } from 'axios';
|
||||
import axiosRetry from 'axios-retry';
|
||||
import { nanoid } from '@sa/utils';
|
||||
import { createAxiosConfig, createDefaultOptions, createRetryOptions } from './options';
|
||||
import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant';
|
||||
import type {
|
||||
CustomAxiosRequestConfig,
|
||||
FlatRequestInstance,
|
||||
MappedType,
|
||||
RequestInstance,
|
||||
RequestOption,
|
||||
ResponseType
|
||||
} from './type';
|
||||
|
||||
function createCommonRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const opts = createDefaultOptions<ResponseData>(options);
|
||||
|
||||
const axiosConf = createAxiosConfig(axiosConfig);
|
||||
const instance = axios.create(axiosConf);
|
||||
|
||||
const cancelTokenSourceMap = new Map<string, CancelTokenSource>();
|
||||
|
||||
// config axios retry
|
||||
const retryOptions = createRetryOptions(axiosConf);
|
||||
axiosRetry(instance, retryOptions);
|
||||
|
||||
instance.interceptors.request.use(conf => {
|
||||
const config: InternalAxiosRequestConfig = { ...conf };
|
||||
|
||||
// set request id
|
||||
const requestId = nanoid();
|
||||
config.headers.set(REQUEST_ID_KEY, requestId);
|
||||
|
||||
// config cancel token
|
||||
const cancelTokenSource = axios.CancelToken.source();
|
||||
config.cancelToken = cancelTokenSource.token;
|
||||
cancelTokenSourceMap.set(requestId, cancelTokenSource);
|
||||
|
||||
// handle config by hook
|
||||
const handledConfig = opts.onRequest?.(config) || config;
|
||||
|
||||
return handledConfig;
|
||||
});
|
||||
|
||||
instance.interceptors.response.use(
|
||||
async response => {
|
||||
if (opts.isBackendSuccess(response)) {
|
||||
return Promise.resolve(response);
|
||||
}
|
||||
|
||||
const fail = await opts.onBackendFail(response, instance);
|
||||
if (fail) {
|
||||
return fail;
|
||||
}
|
||||
|
||||
const backendError = new AxiosError<ResponseData>(
|
||||
'the backend request error',
|
||||
BACKEND_ERROR_CODE,
|
||||
response.config,
|
||||
response,
|
||||
response.request
|
||||
);
|
||||
|
||||
await opts.onError(backendError);
|
||||
|
||||
return Promise.reject(backendError);
|
||||
},
|
||||
async (error: AxiosError<ResponseData>) => {
|
||||
await opts.onError(error);
|
||||
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
function cancelRequest(requestId: string) {
|
||||
const cancelTokenSource = cancelTokenSourceMap.get(requestId);
|
||||
if (cancelTokenSource) {
|
||||
cancelTokenSource.cancel();
|
||||
cancelTokenSourceMap.delete(requestId);
|
||||
}
|
||||
}
|
||||
|
||||
function cancelAllRequest() {
|
||||
cancelTokenSourceMap.forEach(cancelTokenSource => {
|
||||
cancelTokenSource.cancel();
|
||||
});
|
||||
cancelTokenSourceMap.clear();
|
||||
}
|
||||
|
||||
return {
|
||||
instance,
|
||||
opts,
|
||||
cancelRequest,
|
||||
cancelAllRequest
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* create a request instance
|
||||
*
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const request: RequestInstance = async function request<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||
|
||||
const responseType = response.config?.responseType || 'json';
|
||||
|
||||
if (responseType === 'json') {
|
||||
return opts.transformBackendResponse(response);
|
||||
}
|
||||
|
||||
return response.data as MappedType<R, T>;
|
||||
} as RequestInstance;
|
||||
|
||||
request.cancelRequest = cancelRequest;
|
||||
request.cancelAllRequest = cancelAllRequest;
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a flat request instance
|
||||
*
|
||||
* The response data is a flat object: { data: any, error: AxiosError }
|
||||
*
|
||||
* @param axiosConfig axios config
|
||||
* @param options request options
|
||||
*/
|
||||
export function createFlatRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const flatRequest: FlatRequestInstance = async function flatRequest<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
try {
|
||||
const response: AxiosResponse<ResponseData> = await instance(config);
|
||||
|
||||
const responseType = response.config?.responseType || 'json';
|
||||
|
||||
if (responseType === 'json') {
|
||||
const data = opts.transformBackendResponse(response);
|
||||
|
||||
return { data, error: null };
|
||||
}
|
||||
|
||||
return { data: response.data as MappedType<R, T>, error: null };
|
||||
} catch (error) {
|
||||
return { data: null, error };
|
||||
}
|
||||
} as FlatRequestInstance;
|
||||
|
||||
flatRequest.cancelRequest = cancelRequest;
|
||||
flatRequest.cancelAllRequest = cancelAllRequest;
|
||||
|
||||
return flatRequest;
|
||||
}
|
||||
|
||||
export { BACKEND_ERROR_CODE, REQUEST_ID_KEY };
|
||||
export type * from './type';
|
||||
export type { CreateAxiosDefaults, AxiosError };
|
44
packages/axios/src/options.ts
Normal file
44
packages/axios/src/options.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import type { CreateAxiosDefaults } from 'axios';
|
||||
import type { IAxiosRetryConfig } from 'axios-retry';
|
||||
import { isHttpSuccess } from './shared';
|
||||
import type { RequestOption } from './type';
|
||||
|
||||
export function createDefaultOptions<ResponseData = any>(options?: Partial<RequestOption<ResponseData>>) {
|
||||
const opts: RequestOption<ResponseData> = {
|
||||
onRequest: async config => config,
|
||||
isBackendSuccess: _response => true,
|
||||
onBackendFail: async () => {},
|
||||
transformBackendResponse: async response => response.data,
|
||||
onError: async () => {}
|
||||
};
|
||||
|
||||
Object.assign(opts, options);
|
||||
|
||||
return opts;
|
||||
}
|
||||
|
||||
export function createRetryOptions(config?: Partial<CreateAxiosDefaults>) {
|
||||
const retryConfig: IAxiosRetryConfig = {
|
||||
retries: 3
|
||||
};
|
||||
|
||||
Object.assign(retryConfig, config);
|
||||
|
||||
return retryConfig;
|
||||
}
|
||||
|
||||
export function createAxiosConfig(config?: Partial<CreateAxiosDefaults>) {
|
||||
const TEN_SECONDS = 10 * 1000;
|
||||
|
||||
const axiosConfig: CreateAxiosDefaults = {
|
||||
timeout: TEN_SECONDS,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
validateStatus: isHttpSuccess
|
||||
};
|
||||
|
||||
Object.assign(axiosConfig, config);
|
||||
|
||||
return axiosConfig;
|
||||
}
|
28
packages/axios/src/shared.ts
Normal file
28
packages/axios/src/shared.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import type { AxiosHeaderValue, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
export function getContentType(config: InternalAxiosRequestConfig) {
|
||||
const contentType: AxiosHeaderValue = config.headers?.['Content-Type'] || 'application/json';
|
||||
|
||||
return contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* check if http status is success
|
||||
*
|
||||
* @param status
|
||||
*/
|
||||
export function isHttpSuccess(status: number) {
|
||||
const isSuccessCode = status >= 200 && status < 300;
|
||||
return isSuccessCode || status === 304;
|
||||
}
|
||||
|
||||
/**
|
||||
* is response json
|
||||
*
|
||||
* @param response axios response
|
||||
*/
|
||||
export function isResponseJson(response: AxiosResponse) {
|
||||
const { responseType } = response.config;
|
||||
|
||||
return responseType === 'json' || responseType === undefined;
|
||||
}
|
97
packages/axios/src/type.ts
Normal file
97
packages/axios/src/type.ts
Normal file
@ -0,0 +1,97 @@
|
||||
import type { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios';
|
||||
|
||||
export type ContentType =
|
||||
| 'text/html'
|
||||
| 'text/plain'
|
||||
| 'multipart/form-data'
|
||||
| 'application/json'
|
||||
| 'application/x-www-form-urlencoded'
|
||||
| 'application/octet-stream';
|
||||
|
||||
export interface RequestOption<ResponseData = any> {
|
||||
/**
|
||||
* The hook before request
|
||||
*
|
||||
* For example: You can add header token in this hook
|
||||
*
|
||||
* @param config Axios config
|
||||
*/
|
||||
onRequest: (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig | Promise<InternalAxiosRequestConfig>;
|
||||
/**
|
||||
* The hook to check backend response is success or not
|
||||
*
|
||||
* @param response Axios response
|
||||
*/
|
||||
isBackendSuccess: (response: AxiosResponse<ResponseData>) => boolean;
|
||||
/**
|
||||
* The hook after backend request fail
|
||||
*
|
||||
* For example: You can handle the expired token in this hook
|
||||
*
|
||||
* @param response Axios response
|
||||
* @param instance Axios instance
|
||||
* @returns
|
||||
*/
|
||||
onBackendFail: (
|
||||
response: AxiosResponse<ResponseData>,
|
||||
instance: AxiosInstance
|
||||
) => Promise<AxiosResponse> | Promise<void>;
|
||||
/**
|
||||
* transform backend response when the responseType is json
|
||||
*
|
||||
* @param response Axios response
|
||||
*/
|
||||
transformBackendResponse(response: AxiosResponse<ResponseData>): any | Promise<any>;
|
||||
/**
|
||||
* The hook to handle error
|
||||
*
|
||||
* For example: You can show error message in this hook
|
||||
*
|
||||
* @param error
|
||||
*/
|
||||
onError: (error: AxiosError<ResponseData>) => void | Promise<void>;
|
||||
}
|
||||
|
||||
interface ResponseMap {
|
||||
blob: Blob;
|
||||
text: string;
|
||||
arrayBuffer: ArrayBuffer;
|
||||
stream: ReadableStream<Uint8Array>;
|
||||
document: Document;
|
||||
}
|
||||
export type ResponseType = keyof ResponseMap | 'json';
|
||||
|
||||
export type MappedType<R extends ResponseType, JsonType = any> = R extends keyof ResponseMap
|
||||
? ResponseMap[R]
|
||||
: JsonType;
|
||||
|
||||
export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<AxiosRequestConfig, 'responseType'> & {
|
||||
responseType?: R;
|
||||
};
|
||||
|
||||
/** The request instance */
|
||||
export interface RequestInstance {
|
||||
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>;
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
}
|
||||
|
||||
export type FlatResponseSuccessData<T = any> = {
|
||||
data: T;
|
||||
error: null;
|
||||
};
|
||||
|
||||
export type FlatResponseFailData<T = any> = {
|
||||
data: null;
|
||||
error: AxiosError<T>;
|
||||
};
|
||||
|
||||
export type FlatResponseData<T = any> = FlatResponseSuccessData<T> | FlatResponseFailData<T>;
|
||||
|
||||
export interface FlatRequestInstance {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig<R>
|
||||
): Promise<FlatResponseData<MappedType<R, T>>>;
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
}
|
@ -8,5 +8,8 @@
|
||||
"*": {
|
||||
"*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@sa/axios": "workspace:*"
|
||||
}
|
||||
}
|
||||
|
79
packages/hooks/src/use-request.ts
Normal file
79
packages/hooks/src/use-request.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { ref } from 'vue';
|
||||
import type { Ref } from 'vue';
|
||||
import { createFlatRequest } from '@sa/axios';
|
||||
import type {
|
||||
AxiosError,
|
||||
CreateAxiosDefaults,
|
||||
CustomAxiosRequestConfig,
|
||||
MappedType,
|
||||
RequestOption,
|
||||
ResponseType
|
||||
} from '@sa/axios';
|
||||
import useLoading from './use-loading';
|
||||
|
||||
export type HookRequestInstanceResponseSuccessData<T = any> = {
|
||||
data: Ref<T>;
|
||||
error: Ref<null>;
|
||||
};
|
||||
|
||||
export type HookRequestInstanceResponseFailData<T = any> = {
|
||||
data: Ref<null>;
|
||||
error: Ref<AxiosError<T>>;
|
||||
};
|
||||
|
||||
export type HookRequestInstanceResponseData<T = any> = {
|
||||
loading: Ref<boolean>;
|
||||
} & (HookRequestInstanceResponseSuccessData<T> | HookRequestInstanceResponseFailData<T>);
|
||||
|
||||
export interface HookRequestInstance {
|
||||
<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
): HookRequestInstanceResponseData<MappedType<R, T>>;
|
||||
cancelRequest: (requestId: string) => void;
|
||||
cancelAllRequest: () => void;
|
||||
}
|
||||
|
||||
/**
|
||||
* create a hook request instance
|
||||
*
|
||||
* @param axiosConfig
|
||||
* @param options
|
||||
*/
|
||||
export default function createHookRequest<ResponseData = any>(
|
||||
axiosConfig?: CreateAxiosDefaults,
|
||||
options?: Partial<RequestOption<ResponseData>>
|
||||
) {
|
||||
const request = createFlatRequest<ResponseData>(axiosConfig, options);
|
||||
|
||||
const hookRequest: HookRequestInstance = function hookRequest<T = any, R extends ResponseType = 'json'>(
|
||||
config: CustomAxiosRequestConfig
|
||||
) {
|
||||
const { loading, startLoading, endLoading } = useLoading();
|
||||
|
||||
const data = ref<MappedType<R, T> | null>(null);
|
||||
const error = ref<AxiosError<MappedType<R, T>> | null>(null);
|
||||
|
||||
startLoading();
|
||||
|
||||
request(config).then(res => {
|
||||
if (res.data) {
|
||||
data.value = res.data;
|
||||
} else {
|
||||
error.value = res.error;
|
||||
}
|
||||
|
||||
endLoading();
|
||||
});
|
||||
|
||||
return {
|
||||
loading,
|
||||
data,
|
||||
error
|
||||
};
|
||||
} as HookRequestInstance;
|
||||
|
||||
hookRequest.cancelRequest = request.cancelRequest;
|
||||
hookRequest.cancelAllRequest = request.cancelAllRequest;
|
||||
|
||||
return hookRequest;
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "@sa/request",
|
||||
"name": "@sa/fetch",
|
||||
"version": "1.0.0",
|
||||
"exports": {
|
||||
".": "./src/index.ts"
|
||||
@ -10,7 +10,6 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.6.5",
|
||||
"ofetch": "1.3.3"
|
||||
}
|
||||
}
|
@ -1,10 +1,10 @@
|
||||
import { ofetch } from 'ofetch';
|
||||
import type { FetchOptions } from 'ofetch';
|
||||
|
||||
export function createOfetch(options: FetchOptions) {
|
||||
export function createRequest(options: FetchOptions) {
|
||||
const request = ofetch.create(options);
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
export default createOfetch;
|
||||
export default createRequest;
|
20
packages/ofetch/tsconfig.json
Normal file
20
packages/ofetch/tsconfig.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"jsx": "preserve",
|
||||
"lib": ["DOM", "ESNext"],
|
||||
"baseUrl": ".",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"resolveJsonModule": true,
|
||||
"types": ["node"],
|
||||
"strict": true,
|
||||
"strictNullChecks": true,
|
||||
"noUnusedLocals": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
@ -1,10 +0,0 @@
|
||||
import axios from 'axios';
|
||||
import type { CreateAxiosDefaults } from 'axios';
|
||||
|
||||
export function createAxios(config?: CreateAxiosDefaults) {
|
||||
const instance = axios.create(config);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
export default createAxios;
|
@ -1,4 +0,0 @@
|
||||
import { createAxios } from './axios';
|
||||
import { createOfetch } from './ofetch';
|
||||
|
||||
export { createAxios, createOfetch };
|
@ -12,7 +12,8 @@
|
||||
"dependencies": {
|
||||
"colord": "2.9.3",
|
||||
"crypto-js": "4.2.0",
|
||||
"localforage": "1.10.0"
|
||||
"localforage": "1.10.0",
|
||||
"nanoid": "5.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "4.2.1"
|
||||
|
@ -1,3 +1,4 @@
|
||||
export * from './color';
|
||||
export * from './crypto';
|
||||
export * from './storage';
|
||||
export * from './nanoid';
|
||||
|
3
packages/utils/src/nanoid.ts
Normal file
3
packages/utils/src/nanoid.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { nanoid } from 'nanoid';
|
||||
|
||||
export { nanoid };
|
@ -14,6 +14,9 @@ importers:
|
||||
'@iconify/vue':
|
||||
specifier: 4.1.1
|
||||
version: 4.1.1(vue@3.4.14)
|
||||
'@sa/axios':
|
||||
specifier: workspace:*
|
||||
version: link:packages/axios
|
||||
'@sa/color-palette':
|
||||
specifier: workspace:*
|
||||
version: link:packages/color-palette
|
||||
@ -23,9 +26,6 @@ importers:
|
||||
'@sa/materials':
|
||||
specifier: workspace:*
|
||||
version: link:packages/materials
|
||||
'@sa/request':
|
||||
specifier: workspace:*
|
||||
version: link:packages/request
|
||||
'@sa/utils':
|
||||
specifier: workspace:*
|
||||
version: link:packages/utils
|
||||
@ -160,6 +160,18 @@ importers:
|
||||
specifier: 1.8.27
|
||||
version: 1.8.27(typescript@5.3.3)
|
||||
|
||||
packages/axios:
|
||||
dependencies:
|
||||
'@sa/utils':
|
||||
specifier: workspace:*
|
||||
version: link:../utils
|
||||
axios:
|
||||
specifier: 1.6.5
|
||||
version: 1.6.5
|
||||
axios-retry:
|
||||
specifier: ^4.0.0
|
||||
version: 4.0.0(axios@1.6.5)
|
||||
|
||||
packages/color-palette:
|
||||
dependencies:
|
||||
colord:
|
||||
@ -172,7 +184,11 @@ importers:
|
||||
specifier: 1.0.0-rc.36
|
||||
version: 1.0.0-rc.36(@algolia/client-search@4.22.1)(@types/node@20.11.2)(nprogress@0.2.0)(postcss@5.2.18)(sass@1.69.7)(search-insights@2.13.0)(typescript@5.3.3)
|
||||
|
||||
packages/hooks: {}
|
||||
packages/hooks:
|
||||
dependencies:
|
||||
'@sa/axios':
|
||||
specifier: workspace:*
|
||||
version: link:../axios
|
||||
|
||||
packages/materials:
|
||||
dependencies:
|
||||
@ -190,11 +206,8 @@ importers:
|
||||
specifier: 0.8.1
|
||||
version: 0.8.1
|
||||
|
||||
packages/request:
|
||||
packages/ofetch:
|
||||
dependencies:
|
||||
axios:
|
||||
specifier: 1.6.5
|
||||
version: 1.6.5
|
||||
ofetch:
|
||||
specifier: 1.3.3
|
||||
version: 1.3.3
|
||||
@ -236,6 +249,9 @@ importers:
|
||||
localforage:
|
||||
specifier: 1.10.0
|
||||
version: 1.10.0
|
||||
nanoid:
|
||||
specifier: 5.0.4
|
||||
version: 5.0.4
|
||||
devDependencies:
|
||||
'@types/crypto-js':
|
||||
specifier: 4.2.1
|
||||
@ -2564,6 +2580,15 @@ packages:
|
||||
engines: {node: '>= 0.4'}
|
||||
dev: true
|
||||
|
||||
/axios-retry@4.0.0(axios@1.6.5):
|
||||
resolution: {integrity: sha512-F6P4HVGITD/v4z9Lw2mIA24IabTajvpDZmKa6zq/gGwn57wN5j1P3uWrAV0+diqnW6kTM2fTqmWNfgYWGmMuiA==}
|
||||
peerDependencies:
|
||||
axios: 0.x || 1.x
|
||||
dependencies:
|
||||
axios: 1.6.5
|
||||
is-retry-allowed: 2.2.0
|
||||
dev: false
|
||||
|
||||
/axios@1.6.5:
|
||||
resolution: {integrity: sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==}
|
||||
dependencies:
|
||||
@ -5096,6 +5121,11 @@ packages:
|
||||
has-tostringtag: 1.0.0
|
||||
dev: true
|
||||
|
||||
/is-retry-allowed@2.2.0:
|
||||
resolution: {integrity: sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==}
|
||||
engines: {node: '>=10'}
|
||||
dev: false
|
||||
|
||||
/is-shared-array-buffer@1.0.2:
|
||||
resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==}
|
||||
dependencies:
|
||||
@ -6079,6 +6109,12 @@ packages:
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
/nanoid@5.0.4:
|
||||
resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==}
|
||||
engines: {node: ^18 || >=20}
|
||||
hasBin: true
|
||||
dev: false
|
||||
|
||||
/nanomatch@1.2.13:
|
||||
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
@ -7,9 +7,10 @@ import { request } from '../request';
|
||||
* @param password Password
|
||||
*/
|
||||
export function fetchLogin(userName: string, password: string) {
|
||||
return request<App.Service.Response<Api.Auth.LoginToken>>('/auth/login', {
|
||||
return request<Api.Auth.LoginToken>({
|
||||
url: '/auth/login',
|
||||
method: 'post',
|
||||
body: {
|
||||
data: {
|
||||
userName,
|
||||
password
|
||||
}
|
||||
@ -18,7 +19,7 @@ export function fetchLogin(userName: string, password: string) {
|
||||
|
||||
/** Get user info */
|
||||
export function fetchGetUserInfo() {
|
||||
return request<App.Service.Response<Api.Auth.UserInfo>>('/auth/getUserInfo');
|
||||
return request<Api.Auth.UserInfo>({ url: '/auth/getUserInfo' });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -27,10 +28,22 @@ export function fetchGetUserInfo() {
|
||||
* @param refreshToken Refresh token
|
||||
*/
|
||||
export function fetchRefreshToken(refreshToken: string) {
|
||||
return request<App.Service.Response<Api.Auth.LoginToken>>('/auth/refreshToken', {
|
||||
return request<Api.Auth.LoginToken>({
|
||||
url: '/auth/refreshToken',
|
||||
method: 'post',
|
||||
body: {
|
||||
data: {
|
||||
refreshToken
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchDebug() {
|
||||
return request<string>({
|
||||
url: '/debug-post',
|
||||
method: 'post',
|
||||
headers: { 'content-type': 'application/x-www-form-urlencoded' },
|
||||
data: {
|
||||
a: '1'
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import { request } from '../request';
|
||||
* @param example Whether to use example data, default: 0
|
||||
*/
|
||||
export function fetchGetUserRoutes(example: '0' | '1' = '0') {
|
||||
return request<App.Service.Response<Api.Route.UserRoute>>('/route/getUserRoutes', { params: { example } });
|
||||
return request<Api.Route.UserRoute>({ url: '/route/getUserRoutes', params: { example } });
|
||||
}
|
||||
|
||||
/**
|
||||
@ -16,5 +16,5 @@ export function fetchGetUserRoutes(example: '0' | '1' = '0') {
|
||||
* @param example Whether to use example data, default: 0
|
||||
*/
|
||||
export function fetchIsRouteExist(routeName: string, example: '0' | '1' = '0') {
|
||||
return request<App.Service.Response<boolean>>('/route/isRouteExist', { params: { routeName, example } });
|
||||
return request<boolean>({ url: '/route/isRouteExist', params: { routeName, example } });
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { createOfetch as createRequest } from '@sa/request';
|
||||
import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios';
|
||||
import { localStg } from '@/utils/storage';
|
||||
import { createProxyPattern, createServiceConfig } from '~/env.config';
|
||||
|
||||
@ -6,20 +6,89 @@ const { baseURL, otherBaseURL } = createServiceConfig(import.meta.env);
|
||||
|
||||
const isHttpProxy = import.meta.env.VITE_HTTP_PROXY === 'Y';
|
||||
|
||||
export const request = createRequest({
|
||||
baseURL: isHttpProxy ? createProxyPattern() : baseURL,
|
||||
headers: {
|
||||
apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2'
|
||||
export const request = createFlatRequest<App.Service.Response>(
|
||||
{
|
||||
baseURL: isHttpProxy ? createProxyPattern() : baseURL,
|
||||
headers: {
|
||||
apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2'
|
||||
}
|
||||
},
|
||||
onRequest({ options }) {
|
||||
if (options.headers) {
|
||||
{
|
||||
async onRequest(config) {
|
||||
const { headers } = config;
|
||||
|
||||
// set token
|
||||
const token = localStg.get('token');
|
||||
const Authorization = token ? `Bearer ${token}` : null;
|
||||
Object.assign(headers, { Authorization });
|
||||
|
||||
const Authorization = token ? `Bearer ${token}` : '';
|
||||
return config;
|
||||
},
|
||||
isBackendSuccess(response) {
|
||||
// when the backend response code is "0000", it means the request is success
|
||||
// you can change this logic by yourself
|
||||
return response.data.code === '0000';
|
||||
},
|
||||
async onBackendFail(_response) {
|
||||
// when the backend response code is not 200, it means the request is fail
|
||||
// for example: the token is expired, refetch token and retry request
|
||||
},
|
||||
transformBackendResponse(response) {
|
||||
return response.data.data;
|
||||
},
|
||||
onError(error) {
|
||||
// when the request is fail, you can show error message
|
||||
|
||||
Object.assign(options.headers, { Authorization });
|
||||
let message = error.message;
|
||||
|
||||
// show backend error message
|
||||
if (error.code === BACKEND_ERROR_CODE) {
|
||||
message = error.request?.data.msg || message;
|
||||
}
|
||||
|
||||
window.$message?.error(message);
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
export const demoRequest = createRequest({ baseURL: isHttpProxy ? createProxyPattern('demo') : otherBaseURL.demo });
|
||||
export const demoRequest = createRequest<App.Service.DemoResponse>(
|
||||
{
|
||||
baseURL: isHttpProxy ? createProxyPattern('demo') : otherBaseURL.demo
|
||||
},
|
||||
{
|
||||
async onRequest(config) {
|
||||
const { headers } = config;
|
||||
|
||||
// set token
|
||||
const token = localStg.get('token');
|
||||
const Authorization = token ? `Bearer ${token}` : null;
|
||||
Object.assign(headers, { Authorization });
|
||||
|
||||
return config;
|
||||
},
|
||||
isBackendSuccess(response) {
|
||||
// when the backend response code is 200, it means the request is success
|
||||
// you can change this logic by yourself
|
||||
return response.data.status === '200';
|
||||
},
|
||||
async onBackendFail(_response) {
|
||||
// when the backend response code is not 200, it means the request is fail
|
||||
// for example: the token is expired, refetch token and retry request
|
||||
},
|
||||
transformBackendResponse(response) {
|
||||
return response.data.result;
|
||||
},
|
||||
onError(error) {
|
||||
// when the request is fail, you can show error message
|
||||
|
||||
let message = error.message;
|
||||
|
||||
// show backend error message
|
||||
if (error.code === BACKEND_ERROR_CODE) {
|
||||
message = error.request?.data.message || message;
|
||||
}
|
||||
|
||||
window.$message?.error(message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -45,27 +45,28 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
async function login(userName: string, password: string) {
|
||||
startLoading();
|
||||
|
||||
try {
|
||||
const { data: loginToken } = await fetchLogin(userName, password);
|
||||
const { data: loginToken, error } = await fetchLogin(userName, password);
|
||||
|
||||
await loginByToken(loginToken);
|
||||
if (!error) {
|
||||
const pass = await loginByToken(loginToken);
|
||||
|
||||
await routeStore.initAuthRoute();
|
||||
if (pass) {
|
||||
await routeStore.initAuthRoute();
|
||||
|
||||
await redirectFromLogin();
|
||||
await redirectFromLogin();
|
||||
|
||||
if (routeStore.isInitAuthRoute) {
|
||||
window.$notification?.success({
|
||||
title: $t('page.login.common.loginSuccess'),
|
||||
content: $t('page.login.common.welcomeBack', { userName: userInfo.userName }),
|
||||
duration: 4500
|
||||
});
|
||||
if (routeStore.isInitAuthRoute) {
|
||||
window.$notification?.success({
|
||||
title: $t('page.login.common.loginSuccess'),
|
||||
content: $t('page.login.common.welcomeBack', { userName: userInfo.userName })
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
} else {
|
||||
resetStore();
|
||||
} finally {
|
||||
endLoading();
|
||||
}
|
||||
|
||||
endLoading();
|
||||
}
|
||||
|
||||
async function loginByToken(loginToken: Api.Auth.LoginToken) {
|
||||
@ -73,14 +74,20 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
|
||||
localStg.set('token', loginToken.token);
|
||||
localStg.set('refreshToken', loginToken.refreshToken);
|
||||
|
||||
const { data: info } = await fetchGetUserInfo();
|
||||
const { data: info, error } = await fetchGetUserInfo();
|
||||
|
||||
// 2. store user info
|
||||
localStg.set('userInfo', info);
|
||||
if (!error) {
|
||||
// 2. store user info
|
||||
localStg.set('userInfo', info);
|
||||
|
||||
// 3. update auth route
|
||||
token.value = loginToken.token;
|
||||
Object.assign(userInfo, info);
|
||||
// 3. update auth route
|
||||
token.value = loginToken.token;
|
||||
Object.assign(userInfo, info);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -167,17 +167,19 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
|
||||
|
||||
/** Init dynamic auth route */
|
||||
async function initDynamicAuthRoute() {
|
||||
const {
|
||||
data: { routes, home }
|
||||
} = await fetchGetUserRoutes();
|
||||
const { data, error } = await fetchGetUserRoutes();
|
||||
|
||||
handleAuthRoutes(routes);
|
||||
if (!error) {
|
||||
const { routes, home } = data;
|
||||
|
||||
setRouteHome(home);
|
||||
handleAuthRoutes(routes);
|
||||
|
||||
handleUpdateRootRouteRedirect(home);
|
||||
setRouteHome(home);
|
||||
|
||||
setIsInitAuthRoute(true);
|
||||
handleUpdateRootRouteRedirect(home);
|
||||
|
||||
setIsInitAuthRoute(true);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
12
src/typings/app.d.ts
vendored
12
src/typings/app.d.ts
vendored
@ -426,9 +426,19 @@ declare namespace App {
|
||||
/** The backend service response code */
|
||||
code: string;
|
||||
/** The backend service response message */
|
||||
message: string;
|
||||
msg: string;
|
||||
/** The backend service response data */
|
||||
data: T;
|
||||
};
|
||||
|
||||
/** The demo backend service response data */
|
||||
type DemoResponse<T = unknown> = {
|
||||
/** The backend service response code */
|
||||
status: string;
|
||||
/** The backend service response message */
|
||||
message: string;
|
||||
/** The backend service response data */
|
||||
result: T;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user