Compare commits

..

6 Commits

21 changed files with 184 additions and 233 deletions

View File

@ -13,11 +13,12 @@ import type {
ResponseType ResponseType
} from './type'; } from './type';
function createCommonRequest<ResponseData = any>( function createCommonRequest<
axiosConfig?: CreateAxiosDefaults, ResponseData,
options?: Partial<RequestOption<ResponseData>> ApiData = ResponseData,
) { State extends Record<string, unknown> = Record<string, unknown>
const opts = createDefaultOptions<ResponseData>(options); >(axiosConfig?: CreateAxiosDefaults, options?: Partial<RequestOption<ResponseData, ApiData, State>>) {
const opts = createDefaultOptions<ResponseData, ApiData, State>(options);
const axiosConf = createAxiosConfig(axiosConfig); const axiosConf = createAxiosConfig(axiosConfig);
const instance = axios.create(axiosConf); const instance = axios.create(axiosConf);
@ -80,14 +81,6 @@ function createCommonRequest<ResponseData = any>(
} }
); );
function cancelRequest(requestId: string) {
const abortController = abortControllerMap.get(requestId);
if (abortController) {
abortController.abort();
abortControllerMap.delete(requestId);
}
}
function cancelAllRequest() { function cancelAllRequest() {
abortControllerMap.forEach(abortController => { abortControllerMap.forEach(abortController => {
abortController.abort(); abortController.abort();
@ -98,7 +91,6 @@ function createCommonRequest<ResponseData = any>(
return { return {
instance, instance,
opts, opts,
cancelRequest,
cancelAllRequest cancelAllRequest
}; };
} }
@ -109,15 +101,16 @@ function createCommonRequest<ResponseData = any>(
* @param axiosConfig axios config * @param axiosConfig axios config
* @param options request options * @param options request options
*/ */
export function createRequest<ResponseData = any, State = Record<string, unknown>>( export function createRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
axiosConfig?: CreateAxiosDefaults, axiosConfig?: CreateAxiosDefaults,
options?: Partial<RequestOption<ResponseData>> options?: Partial<RequestOption<ResponseData, ApiData, State>>
) { ) {
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options); const { instance, opts, cancelAllRequest } = createCommonRequest<ResponseData, ApiData, State>(axiosConfig, options);
const request: RequestInstance<State> = async function request<T = any, R extends ResponseType = 'json'>( const request: RequestInstance<ApiData, State> = async function request<
config: CustomAxiosRequestConfig T extends ApiData = ApiData,
) { R extends ResponseType = 'json'
>(config: CustomAxiosRequestConfig) {
const response: AxiosResponse<ResponseData> = await instance(config); const response: AxiosResponse<ResponseData> = await instance(config);
const responseType = response.config?.responseType || 'json'; const responseType = response.config?.responseType || 'json';
@ -127,9 +120,8 @@ export function createRequest<ResponseData = any, State = Record<string, unknown
} }
return response.data as MappedType<R, T>; return response.data as MappedType<R, T>;
} as RequestInstance<State>; } as RequestInstance<ApiData, State>;
request.cancelRequest = cancelRequest;
request.cancelAllRequest = cancelAllRequest; request.cancelAllRequest = cancelAllRequest;
request.state = {} as State; request.state = {} as State;
@ -144,14 +136,14 @@ export function createRequest<ResponseData = any, State = Record<string, unknown
* @param axiosConfig axios config * @param axiosConfig axios config
* @param options request options * @param options request options
*/ */
export function createFlatRequest<ResponseData = any, State = Record<string, unknown>>( export function createFlatRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
axiosConfig?: CreateAxiosDefaults, axiosConfig?: CreateAxiosDefaults,
options?: Partial<RequestOption<ResponseData>> options?: Partial<RequestOption<ResponseData, ApiData, State>>
) { ) {
const { instance, opts, cancelRequest, cancelAllRequest } = createCommonRequest<ResponseData>(axiosConfig, options); const { instance, opts, cancelAllRequest } = createCommonRequest<ResponseData, ApiData, State>(axiosConfig, options);
const flatRequest: FlatRequestInstance<State, ResponseData> = async function flatRequest< const flatRequest: FlatRequestInstance<ResponseData, ApiData, State> = async function flatRequest<
T = any, T extends ApiData = ApiData,
R extends ResponseType = 'json' R extends ResponseType = 'json'
>(config: CustomAxiosRequestConfig) { >(config: CustomAxiosRequestConfig) {
try { try {
@ -160,20 +152,21 @@ export function createFlatRequest<ResponseData = any, State = Record<string, unk
const responseType = response.config?.responseType || 'json'; const responseType = response.config?.responseType || 'json';
if (responseType === 'json') { if (responseType === 'json') {
const data = opts.transformBackendResponse(response); const data = await opts.transformBackendResponse(response);
return { data, error: null, response }; return { data, error: null, response };
} }
return { data: response.data as MappedType<R, T>, error: null }; return { data: response.data as MappedType<R, T>, error: null, response };
} catch (error) { } catch (error) {
return { data: null, error, response: (error as AxiosError<ResponseData>).response }; return { data: null, error, response: (error as AxiosError<ResponseData>).response };
} }
} as FlatRequestInstance<State, ResponseData>; } as FlatRequestInstance<ResponseData, ApiData, State>;
flatRequest.cancelRequest = cancelRequest;
flatRequest.cancelAllRequest = cancelAllRequest; flatRequest.cancelAllRequest = cancelAllRequest;
flatRequest.state = {} as State; flatRequest.state = {
...opts.defaultState
} as State;
return flatRequest; return flatRequest;
} }

View File

@ -4,12 +4,16 @@ import { stringify } from 'qs';
import { isHttpSuccess } from './shared'; import { isHttpSuccess } from './shared';
import type { RequestOption } from './type'; import type { RequestOption } from './type';
export function createDefaultOptions<ResponseData = any>(options?: Partial<RequestOption<ResponseData>>) { export function createDefaultOptions<
const opts: RequestOption<ResponseData> = { ResponseData,
ApiData = ResponseData,
State extends Record<string, unknown> = Record<string, unknown>
>(options?: Partial<RequestOption<ResponseData, ApiData, State>>) {
const opts: RequestOption<ResponseData, ApiData, State> = {
onRequest: async config => config, onRequest: async config => config,
isBackendSuccess: _response => true, isBackendSuccess: _response => true,
onBackendFail: async () => {}, onBackendFail: async () => {},
transformBackendResponse: async response => response.data, transformBackendResponse: async response => response.data as unknown as ApiData,
onError: async () => {} onError: async () => {}
}; };

View File

@ -8,7 +8,23 @@ export type ContentType =
| 'application/x-www-form-urlencoded' | 'application/x-www-form-urlencoded'
| 'application/octet-stream'; | 'application/octet-stream';
export interface RequestOption<ResponseData = any> { export type ResponseTransform<Input = any, Output = any> = (input: Input) => Output | Promise<Output>;
export interface RequestOption<
ResponseData,
ApiData = ResponseData,
State extends Record<string, unknown> = Record<string, unknown>
> {
/**
* The default state
*/
defaultState?: State;
/**
* transform the response data to the api data
*
* @param response Axios response
*/
transformBackendResponse: ResponseTransform<AxiosResponse<ResponseData>, ApiData>;
/** /**
* The hook before request * The hook before request
* *
@ -35,12 +51,6 @@ export interface RequestOption<ResponseData = any> {
response: AxiosResponse<ResponseData>, response: AxiosResponse<ResponseData>,
instance: AxiosInstance instance: AxiosInstance
) => Promise<AxiosResponse | null> | Promise<void>; ) => Promise<AxiosResponse | null> | 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 * The hook to handle error
* *
@ -68,15 +78,7 @@ export type CustomAxiosRequestConfig<R extends ResponseType = 'json'> = Omit<Axi
responseType?: R; responseType?: R;
}; };
export interface RequestInstanceCommon<T> { export interface RequestInstanceCommon<State extends Record<string, unknown>> {
/**
* cancel the request by request id
*
* if the request provide abort controller sign from config, it will not collect in the abort controller map
*
* @param requestId
*/
cancelRequest: (requestId: string) => void;
/** /**
* cancel all request * cancel all request
* *
@ -84,32 +86,35 @@ export interface RequestInstanceCommon<T> {
*/ */
cancelAllRequest: () => void; cancelAllRequest: () => void;
/** you can set custom state in the request instance */ /** you can set custom state in the request instance */
state: T; state: State;
} }
/** The request instance */ /** The request instance */
export interface RequestInstance<S = Record<string, unknown>> extends RequestInstanceCommon<S> { export interface RequestInstance<ApiData, State extends Record<string, unknown>> extends RequestInstanceCommon<State> {
<T = any, R extends ResponseType = 'json'>(config: CustomAxiosRequestConfig<R>): Promise<MappedType<R, T>>; <T extends ApiData = ApiData, R extends ResponseType = 'json'>(
config: CustomAxiosRequestConfig<R>
): Promise<MappedType<R, T>>;
} }
export type FlatResponseSuccessData<T = any, ResponseData = any> = { export type FlatResponseSuccessData<ResponseData, ApiData> = {
data: T; data: ApiData;
error: null; error: null;
response: AxiosResponse<ResponseData>; response: AxiosResponse<ResponseData>;
}; };
export type FlatResponseFailData<ResponseData = any> = { export type FlatResponseFailData<ResponseData> = {
data: null; data: null;
error: AxiosError<ResponseData>; error: AxiosError<ResponseData>;
response: AxiosResponse<ResponseData>; response: AxiosResponse<ResponseData>;
}; };
export type FlatResponseData<T = any, ResponseData = any> = export type FlatResponseData<ResponseData, ApiData> =
| FlatResponseSuccessData<T, ResponseData> | FlatResponseSuccessData<ResponseData, ApiData>
| FlatResponseFailData<ResponseData>; | FlatResponseFailData<ResponseData>;
export interface FlatRequestInstance<S = Record<string, unknown>, ResponseData = any> extends RequestInstanceCommon<S> { export interface FlatRequestInstance<ResponseData, ApiData, State extends Record<string, unknown>>
<T = any, R extends ResponseType = 'json'>( extends RequestInstanceCommon<State> {
<T extends ApiData = ApiData, R extends ResponseType = 'json'>(
config: CustomAxiosRequestConfig<R> config: CustomAxiosRequestConfig<R>
): Promise<FlatResponseData<MappedType<R, T>, ResponseData>>; ): Promise<FlatResponseData<ResponseData, MappedType<R, T>>>;
} }

View File

@ -1,5 +1,4 @@
import { inject, provide } from 'vue'; import { inject, provide } from 'vue';
import type { InjectionKey } from 'vue';
/** /**
* Use context * Use context
@ -12,7 +11,7 @@ import type { InjectionKey } from 'vue';
* import { ref } from 'vue'; * import { ref } from 'vue';
* import { useContext } from '@sa/hooks'; * import { useContext } from '@sa/hooks';
* *
* export const { setupStore, useStore } = useContext('demo', () => { * export const [provideDemoContext, useDemoContext] = useContext('demo', () => {
* const count = ref(0); * const count = ref(0);
* *
* function increment() { * function increment() {
@ -35,10 +34,10 @@ import type { InjectionKey } from 'vue';
* <div>A</div> * <div>A</div>
* </template> * </template>
* <script setup lang="ts"> * <script setup lang="ts">
* import { setupStore } from './context'; * import { provideDemoContext } from './context';
* *
* setupStore(); * provideDemoContext();
* // const { increment } = setupStore(); // also can control the store in the parent component * // const { increment } = provideDemoContext(); // also can control the store in the parent component
* </script> * </script>
* ``` // B.vue * ``` // B.vue
* ```vue * ```vue
@ -46,9 +45,9 @@ import type { InjectionKey } from 'vue';
* <div>B</div> * <div>B</div>
* </template> * </template>
* <script setup lang="ts"> * <script setup lang="ts">
* import { useStore } from './context'; * import { useDemoContext } from './context';
* *
* const { count, increment } = useStore(); * const { count, increment } = useDemoContext();
* </script> * </script>
* ```; * ```;
* *
@ -57,40 +56,41 @@ import type { InjectionKey } from 'vue';
* @param contextName Context name * @param contextName Context name
* @param fn Context function * @param fn Context function
*/ */
export default function useContext<T extends (...args: any[]) => any>(contextName: string, fn: T) { export default function useContext<Arguments extends Array<any>, T>(
type Context = ReturnType<T>; contextName: string,
composable: (...args: Arguments) => T
) {
const key = Symbol(contextName);
const { useProvide, useInject: useStore } = createContext<Context>(contextName); /**
* Injects the context value.
*
* @param consumerName - The name of the component that is consuming the context. If provided, the component must be
* used within the context provider.
* @param defaultValue - The default value to return if the context is not provided.
* @returns The context value.
*/
const useInject = <N extends string | null | undefined = undefined>(
consumerName?: N,
defaultValue?: T
): N extends null | undefined ? T | null : T => {
const value = inject(key, defaultValue);
function setupStore(...args: Parameters<T>) { if (consumerName && !value) {
const context: Context = fn(...args); throw new Error(`\`${consumerName}\` must be used within \`${contextName}\``);
return useProvide(context); }
}
return { // @ts-expect-error - we want to return null if the value is undefined or null
/** Setup store in the parent component */ return value || null;
setupStore,
/** Use store in the child component */
useStore
}; };
}
/** Create context */ const useProvide = (...args: Arguments) => {
function createContext<T>(contextName: string) { const value = composable(...args);
const injectKey: InjectionKey<T> = Symbol(contextName);
function useProvide(context: T) { provide(key, value);
provide(injectKey, context);
return context; return value;
}
function useInject() {
return inject(injectKey) as T;
}
return {
useProvide,
useInject
}; };
return [useProvide, useInject] as const;
} }

View File

@ -6,31 +6,31 @@ import type {
CreateAxiosDefaults, CreateAxiosDefaults,
CustomAxiosRequestConfig, CustomAxiosRequestConfig,
MappedType, MappedType,
RequestInstanceCommon,
RequestOption, RequestOption,
ResponseType ResponseType
} from '@sa/axios'; } from '@sa/axios';
import useLoading from './use-loading'; import useLoading from './use-loading';
export type HookRequestInstanceResponseSuccessData<T = any> = { export type HookRequestInstanceResponseSuccessData<ApiData> = {
data: Ref<T>; data: Ref<ApiData>;
error: Ref<null>; error: Ref<null>;
}; };
export type HookRequestInstanceResponseFailData<ResponseData = any> = { export type HookRequestInstanceResponseFailData<ResponseData> = {
data: Ref<null>; data: Ref<null>;
error: Ref<AxiosError<ResponseData>>; error: Ref<AxiosError<ResponseData>>;
}; };
export type HookRequestInstanceResponseData<T = any, ResponseData = any> = { export type HookRequestInstanceResponseData<ResponseData, ApiData> = {
loading: Ref<boolean>; loading: Ref<boolean>;
} & (HookRequestInstanceResponseSuccessData<T> | HookRequestInstanceResponseFailData<ResponseData>); } & (HookRequestInstanceResponseSuccessData<ApiData> | HookRequestInstanceResponseFailData<ResponseData>);
export interface HookRequestInstance<ResponseData = any> { export interface HookRequestInstance<ResponseData, ApiData, State extends Record<string, unknown>>
<T = any, R extends ResponseType = 'json'>( extends RequestInstanceCommon<State> {
<T extends ApiData = ApiData, R extends ResponseType = 'json'>(
config: CustomAxiosRequestConfig config: CustomAxiosRequestConfig
): HookRequestInstanceResponseData<MappedType<R, T>, ResponseData>; ): HookRequestInstanceResponseData<ResponseData, MappedType<R, T>>;
cancelRequest: (requestId: string) => void;
cancelAllRequest: () => void;
} }
/** /**
@ -39,25 +39,26 @@ export interface HookRequestInstance<ResponseData = any> {
* @param axiosConfig * @param axiosConfig
* @param options * @param options
*/ */
export default function createHookRequest<ResponseData = any>( export default function createHookRequest<ResponseData, ApiData, State extends Record<string, unknown>>(
axiosConfig?: CreateAxiosDefaults, axiosConfig?: CreateAxiosDefaults,
options?: Partial<RequestOption<ResponseData>> options?: Partial<RequestOption<ResponseData, ApiData, State>>
) { ) {
const request = createFlatRequest<ResponseData>(axiosConfig, options); const request = createFlatRequest<ResponseData, ApiData, State>(axiosConfig, options);
const hookRequest: HookRequestInstance<ResponseData> = function hookRequest<T = any, R extends ResponseType = 'json'>( const hookRequest: HookRequestInstance<ResponseData, ApiData, State> = function hookRequest<
config: CustomAxiosRequestConfig T extends ApiData = ApiData,
) { R extends ResponseType = 'json'
>(config: CustomAxiosRequestConfig) {
const { loading, startLoading, endLoading } = useLoading(); const { loading, startLoading, endLoading } = useLoading();
const data = ref<MappedType<R, T> | null>(null) as Ref<MappedType<R, T>>; const data = ref(null) as Ref<MappedType<R, T>>;
const error = ref<AxiosError<ResponseData> | null>(null) as Ref<AxiosError<ResponseData> | null>; const error = ref(null) as Ref<AxiosError<ResponseData> | null>;
startLoading(); startLoading();
request(config).then(res => { request(config).then(res => {
if (res.data) { if (res.data) {
data.value = res.data; data.value = res.data as MappedType<R, T>;
} else { } else {
error.value = res.error; error.value = res.error;
} }
@ -70,9 +71,8 @@ export default function createHookRequest<ResponseData = any>(
data, data,
error error
}; };
} as HookRequestInstance<ResponseData>; } as HookRequestInstance<ResponseData, ApiData, State>;
hookRequest.cancelRequest = request.cancelRequest;
hookRequest.cancelAllRequest = request.cancelAllRequest; hookRequest.cancelAllRequest = request.cancelAllRequest;
return hookRequest; return hookRequest;

View File

@ -1,15 +0,0 @@
{
"name": "@sa/fetch",
"version": "1.3.15",
"exports": {
".": "./src/index.ts"
},
"typesVersions": {
"*": {
"*": ["./src/*"]
}
},
"dependencies": {
"ofetch": "1.4.1"
}
}

View File

@ -1,10 +0,0 @@
import { ofetch } from 'ofetch';
import type { FetchOptions } from 'ofetch';
export function createRequest(options: FetchOptions) {
const request = ofetch.create(options);
return request;
}
export default createRequest;

View File

@ -1,20 +0,0 @@
{
"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"]
}

View File

@ -1,4 +1,4 @@
import { computed, effectScope, nextTick, onScopeDispose, ref, watch } from 'vue'; import { computed, effectScope, nextTick, onScopeDispose, shallowRef, watch } from 'vue';
import { useElementSize } from '@vueuse/core'; import { useElementSize } from '@vueuse/core';
import * as echarts from 'echarts/core'; import * as echarts from 'echarts/core';
import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts'; import { BarChart, GaugeChart, LineChart, PictorialBarChart, PieChart, RadarChart, ScatterChart } from 'echarts/charts';
@ -86,11 +86,11 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const darkMode = computed(() => themeStore.darkMode); const darkMode = computed(() => themeStore.darkMode);
const domRef = ref<HTMLElement | null>(null); const domRef = shallowRef<HTMLElement | null>(null);
const initialSize = { width: 0, height: 0 }; const initialSize = { width: 0, height: 0 };
const { width, height } = useElementSize(domRef, initialSize); const { width, height } = useElementSize(domRef, initialSize);
let chart: echarts.ECharts | null = null; const chart = shallowRef<echarts.ECharts | null>(null);
const chartOptions: T = optionsFactory(); const chartOptions: T = optionsFactory();
const { const {
@ -111,15 +111,6 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
onDestroy onDestroy
} = hooks; } = hooks;
/**
* whether can render chart
*
* when domRef is ready and initialSize is valid
*/
function canRender() {
return domRef.value && initialSize.width > 0 && initialSize.height > 0;
}
/** is chart rendered */ /** is chart rendered */
function isRendered() { function isRendered() {
return Boolean(domRef.value && chart); return Boolean(domRef.value && chart);
@ -138,52 +129,52 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
Object.assign(chartOptions, updatedOpts); Object.assign(chartOptions, updatedOpts);
if (isRendered()) { if (isRendered()) {
chart?.clear(); chart.value?.clear();
} }
chart?.setOption({ ...updatedOpts, backgroundColor: 'transparent' }); chart.value?.setOption({ ...updatedOpts, backgroundColor: 'transparent' });
await onUpdated?.(chart!); await onUpdated?.(chart.value!);
} }
function setOptions(options: T) { function setOptions(options: T) {
chart?.setOption(options); chart.value?.setOption(options);
} }
/** render chart */ /** render chart */
async function render() { async function render() {
if (!isRendered()) { if (isRendered()) return;
const chartTheme = darkMode.value ? 'dark' : 'light';
await nextTick(); const chartTheme = darkMode.value ? 'dark' : 'light';
chart = echarts.init(domRef.value, chartTheme); await nextTick();
chart.setOption({ ...chartOptions, backgroundColor: 'transparent' }); chart.value = echarts.init(domRef.value, chartTheme);
await onRender?.(chart); chart.value?.setOption({ ...chartOptions, backgroundColor: 'transparent' });
}
await onRender?.(chart.value!);
} }
/** resize chart */ /** resize chart */
function resize() { function resize() {
chart?.resize(); chart.value?.resize();
} }
/** destroy chart */ /** destroy chart */
async function destroy() { async function destroy() {
if (!chart) return; if (!chart.value) return;
await onDestroy?.(chart); await onDestroy?.(chart.value);
chart?.dispose(); chart.value?.dispose();
chart = null; chart.value = null;
} }
/** change chart theme */ /** change chart theme */
async function changeTheme() { async function changeTheme() {
await destroy(); await destroy();
await render(); await render();
await onUpdated?.(chart!); await onUpdated?.(chart.value!);
} }
/** /**
@ -196,13 +187,6 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
initialSize.width = w; initialSize.width = w;
initialSize.height = h; initialSize.height = h;
// size is abnormal, destroy chart
if (!canRender()) {
await destroy();
return;
}
// resize chart // resize chart
if (isRendered()) { if (isRendered()) {
resize(); resize();
@ -211,15 +195,17 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
// render chart // render chart
await render(); await render();
if (chart) { await onUpdated?.(chart.value!);
await onUpdated?.(chart);
}
} }
scope.run(() => { scope.run(() => {
watch([width, height], ([newWidth, newHeight]) => { watch(
renderChartBySize(newWidth, newHeight); [width, height],
}); ([newWidth, newHeight]) => {
renderChartBySize(newWidth, newHeight);
},
{ flush: 'post' }
);
watch(darkMode, () => { watch(darkMode, () => {
changeTheme(); changeTheme();
@ -233,6 +219,7 @@ export function useEcharts<T extends ECOption>(optionsFactory: () => T, hooks: C
return { return {
domRef, domRef,
chart,
updateOptions, updateOptions,
setOptions setOptions
}; };

View File

@ -43,7 +43,7 @@ export function useTable<A extends NaiveUI.TableApiFn>(config: NaiveUI.NaiveTabl
// Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors. // Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors.
const pageSize = size <= 0 ? 10 : size; const pageSize = size <= 0 ? 10 : size;
const recordsWithIndex = records.map((item, index) => { const recordsWithIndex = records.map((item: GetTableData<A>, index: number) => {
return { return {
...item, ...item,
index: (current - 1) * pageSize + index + 1 index: (current - 1) * pageSize + index + 1

View File

@ -10,7 +10,7 @@ import GlobalTab from '../modules/global-tab/index.vue';
import GlobalContent from '../modules/global-content/index.vue'; import GlobalContent from '../modules/global-content/index.vue';
import GlobalFooter from '../modules/global-footer/index.vue'; import GlobalFooter from '../modules/global-footer/index.vue';
import ThemeDrawer from '../modules/theme-drawer/index.vue'; import ThemeDrawer from '../modules/theme-drawer/index.vue';
import { setupMixMenuContext } from '../context'; import { provideMixMenuContext } from '../modules/global-menu/context';
defineOptions({ defineOptions({
name: 'BaseLayout' name: 'BaseLayout'
@ -18,7 +18,7 @@ defineOptions({
const appStore = useAppStore(); const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = setupMixMenuContext(); const { childLevelMenus, isActiveFirstLevelMenuHasChildren } = provideMixMenuContext();
const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue')); const GlobalMenu = defineAsyncComponent(() => import('../modules/global-menu/index.vue'));

View File

@ -5,7 +5,7 @@ import type { RouteKey } from '@elegant-router/types';
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
export const { setupStore: setupMixMenuContext, useStore: useMixMenuContext } = useContext('mix-menu', useMixMenu); export const [provideMixMenuContext, useMixMenuContext] = useContext('MixMenu', useMixMenu);
function useMixMenu() { function useMixMenu() {
const route = useRoute(); const route = useRoute();

View File

@ -2,7 +2,7 @@
import { GLOBAL_HEADER_MENU_ID } from '@/constants/app'; import { GLOBAL_HEADER_MENU_ID } from '@/constants/app';
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { useMenu } from '../../../context'; import { useMenu } from '../context';
defineOptions({ defineOptions({
name: 'HorizontalMenu' name: 'HorizontalMenu'

View File

@ -7,7 +7,7 @@ import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { useMenu, useMixMenuContext } from '../../../context'; import { useMenu, useMixMenuContext } from '../context';
defineOptions({ defineOptions({
name: 'TopHybridHeaderFirst' name: 'TopHybridHeaderFirst'
@ -18,7 +18,8 @@ const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const routeStore = useRouteStore(); const routeStore = useRouteStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush(); const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } = useMixMenuContext(); const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } =
useMixMenuContext('TopHybridHeaderFirst');
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
const expandedKeys = ref<string[]>([]); const expandedKeys = ref<string[]>([]);

View File

@ -4,7 +4,7 @@ import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import FirstLevelMenu from '../components/first-level-menu.vue'; import FirstLevelMenu from '../components/first-level-menu.vue';
import { useMenu, useMixMenuContext } from '../../../context'; import { useMenu, useMixMenuContext } from '../context';
defineOptions({ defineOptions({
name: 'TopHybridSidebarFirst' name: 'TopHybridSidebarFirst'
@ -13,7 +13,8 @@ defineOptions({
const appStore = useAppStore(); const appStore = useAppStore();
const themeStore = useThemeStore(); const themeStore = useThemeStore();
const { routerPushByKeyWithMetaQuery } = useRouterPush(); const { routerPushByKeyWithMetaQuery } = useRouterPush();
const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } = useMixMenuContext(); const { firstLevelMenus, secondLevelMenus, activeFirstLevelMenuKey, handleSelectFirstLevelMenu } =
useMixMenuContext('TopHybridSidebarFirst');
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
</script> </script>

View File

@ -9,7 +9,7 @@ import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { useMenu, useMixMenuContext } from '../../../context'; import { useMenu, useMixMenuContext } from '../context';
import FirstLevelMenu from '../components/first-level-menu.vue'; import FirstLevelMenu from '../components/first-level-menu.vue';
import GlobalLogo from '../../global-logo/index.vue'; import GlobalLogo from '../../global-logo/index.vue';
@ -34,7 +34,7 @@ const {
handleSelectSecondLevelMenu, handleSelectSecondLevelMenu,
getActiveSecondLevelMenuKey, getActiveSecondLevelMenuKey,
childLevelMenus childLevelMenus
} = useMixMenuContext(); } = useMixMenuContext('VerticalHybridHeaderFirst');
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted); const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);

View File

@ -7,7 +7,7 @@ import { useAppStore } from '@/store/modules/app';
import { useThemeStore } from '@/store/modules/theme'; import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { useMenu } from '../../../context'; import { useMenu } from '../context';
defineOptions({ defineOptions({
name: 'VerticalMenu' name: 'VerticalMenu'

View File

@ -10,7 +10,7 @@ import { useThemeStore } from '@/store/modules/theme';
import { useRouteStore } from '@/store/modules/route'; import { useRouteStore } from '@/store/modules/route';
import { useRouterPush } from '@/hooks/common/router'; import { useRouterPush } from '@/hooks/common/router';
import { $t } from '@/locales'; import { $t } from '@/locales';
import { useMenu, useMixMenuContext } from '../../../context'; import { useMenu, useMixMenuContext } from '../context';
import FirstLevelMenu from '../components/first-level-menu.vue'; import FirstLevelMenu from '../components/first-level-menu.vue';
import GlobalLogo from '../../global-logo/index.vue'; import GlobalLogo from '../../global-logo/index.vue';
@ -31,7 +31,7 @@ const {
isActiveFirstLevelMenuHasChildren, isActiveFirstLevelMenuHasChildren,
getActiveFirstLevelMenuKey, getActiveFirstLevelMenuKey,
handleSelectFirstLevelMenu handleSelectFirstLevelMenu
} = useMixMenuContext(); } = useMixMenuContext('VerticalMixMenu');
const { selectedKey } = useMenu(); const { selectedKey } = useMenu();
const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted); const inverted = computed(() => !themeStore.darkMode && themeStore.sider.inverted);

View File

@ -10,7 +10,7 @@ import type { RequestInstanceState } from './type';
const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
export const request = createFlatRequest<App.Service.Response, RequestInstanceState>( export const request = createFlatRequest(
{ {
baseURL, baseURL,
headers: { headers: {
@ -18,6 +18,13 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
} }
}, },
{ {
defaultState: {
errMsgStack: [],
refreshTokenPromise: null
} as RequestInstanceState,
transformBackendResponse(response: AxiosResponse<App.Service.Response<any>>) {
return response.data.data;
},
async onRequest(config) { async onRequest(config) {
const Authorization = getAuthorization(); const Authorization = getAuthorization();
Object.assign(config.headers, { Authorization }); Object.assign(config.headers, { Authorization });
@ -91,9 +98,6 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
return null; return null;
}, },
transformBackendResponse(response) {
return response.data.data;
},
onError(error) { onError(error) {
// when the request is fail, you can show error message // when the request is fail, you can show error message
@ -123,11 +127,14 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
} }
); );
export const demoRequest = createRequest<App.Service.DemoResponse>( export const demoRequest = createRequest(
{ {
baseURL: otherBaseURL.demo baseURL: otherBaseURL.demo
}, },
{ {
transformBackendResponse(response: AxiosResponse<App.Service.DemoResponse>) {
return response.data.result;
},
async onRequest(config) { async onRequest(config) {
const { headers } = config; const { headers } = config;
@ -147,9 +154,6 @@ export const demoRequest = createRequest<App.Service.DemoResponse>(
// when the backend response code is not "200", it means the request is fail // when the backend response code is not "200", it means the request is fail
// for example: the token is expired, refresh token and retry request // for example: the token is expired, refresh token and retry request
}, },
transformBackendResponse(response) {
return response.data.result;
},
onError(error) { onError(error) {
// when the request is fail, you can show error message // when the request is fail, you can show error message

View File

@ -28,14 +28,14 @@ async function handleRefreshToken() {
} }
export async function handleExpiredRequest(state: RequestInstanceState) { export async function handleExpiredRequest(state: RequestInstanceState) {
if (!state.refreshTokenFn) { if (!state.refreshTokenPromise) {
state.refreshTokenFn = handleRefreshToken(); state.refreshTokenPromise = handleRefreshToken();
} }
const success = await state.refreshTokenFn; const success = await state.refreshTokenPromise;
setTimeout(() => { setTimeout(() => {
state.refreshTokenFn = null; state.refreshTokenPromise = null;
}, 1000); }, 1000);
return success; return success;

View File

@ -1,6 +1,7 @@
export interface RequestInstanceState { export interface RequestInstanceState {
/** whether the request is refreshing token */ /** the promise of refreshing token */
refreshTokenFn: Promise<boolean> | null; refreshTokenPromise: Promise<boolean> | null;
/** the request error message stack */ /** the request error message stack */
errMsgStack: string[]; errMsgStack: string[];
[key: string]: unknown;
} }