diff --git a/package.json b/package.json index a420e305..1dd0ab37 100644 --- a/package.json +++ b/package.json @@ -52,13 +52,11 @@ "@better-scroll/core": "2.5.1", "@iconify/vue": "4.1.2", "@sa/alova": "workspace:*", - "@sa/axios": "workspace:*", "@sa/color": "workspace:*", "@sa/hooks": "workspace:*", "@sa/materials": "workspace:*", "@sa/utils": "workspace:*", "@vueuse/core": "11.0.3", - "alova": "^3.0.16", "clipboard": "2.0.11", "dayjs": "1.11.13", "dhtmlx-gantt": "8.0.10", diff --git a/packages/alova/package.json b/packages/alova/package.json index b46df2fd..6cbdf975 100644 --- a/packages/alova/package.json +++ b/packages/alova/package.json @@ -12,6 +12,6 @@ }, "dependencies": { "@sa/utils": "workspace:*", - "alova": "^3.0.16" + "alova": "^3.0.19" } } diff --git a/packages/alova/src/constant.ts b/packages/alova/src/constant.ts index d6c5a338..be5c43ca 100644 --- a/packages/alova/src/constant.ts +++ b/packages/alova/src/constant.ts @@ -1,5 +1,2 @@ -/** request id key */ -export const REQUEST_ID_KEY = 'X-Request-Id'; - /** the backend error code key */ export const BACKEND_ERROR_CODE = 'BACKEND_ERROR'; diff --git a/packages/alova/src/index.ts b/packages/alova/src/index.ts index 92dce465..8d871492 100644 --- a/packages/alova/src/index.ts +++ b/packages/alova/src/index.ts @@ -1,86 +1,76 @@ +import type { AlovaDefaultCacheAdapter, AlovaGenerics, AlovaGlobalCacheAdapter, AlovaRequestAdapter } from 'alova'; import { createAlova } from 'alova'; import VueHook from 'alova/vue'; +import type { FetchRequestInit } from 'alova/fetch'; import adapterFetch from 'alova/fetch'; import { createServerTokenAuthentication } from 'alova/client'; -import { BACKEND_ERROR_CODE, REQUEST_ID_KEY } from './constant'; -import { isJSON } from './shared'; +import { BACKEND_ERROR_CODE } from './constant'; import type { CustomAlovaConfig, RequestOptions } from './type'; -export const createAlovaRequest = (customConfig: CustomAlovaConfig, options: RequestOptions) => { - const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication({ +export const createAlovaRequest = < + RequestConfig = FetchRequestInit, + ResponseType = Response, + ResponseHeader = Headers, + L1Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter, + L2Cache extends AlovaGlobalCacheAdapter = AlovaDefaultCacheAdapter +>( + customConfig: CustomAlovaConfig< + AlovaGenerics + >, + options: RequestOptions> +) => { + const { tokenRefresher } = options; + const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication< + typeof VueHook, + AlovaRequestAdapter + >({ refreshTokenOnSuccess: { - isExpired: async response => { - const contentType = response.headers.get('Content-Type'); - if (isJSON(contentType ?? '')) { - const resp = response.clone(); - const data = await resp.json(); - const responseCode = String(data.code); - if (customConfig.expiredTokenCodes.includes(responseCode)) { - return true; - } - } - return false; - }, - handler: async () => { - if (options.refreshTokenHandler) { - await options.refreshTokenHandler(); - } - } + isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false, + handler: async (response, method) => tokenRefresher?.handler(response, method) }, refreshTokenOnError: { - isExpired: async response => { - const contentType = response.headers.get('Content-Type'); - if (isJSON(contentType ?? '')) { - const resp = response.clone(); - const data = await resp.json(); - const responseCode = String(data.code); - if (customConfig.expiredTokenCodes.includes(responseCode)) { - return true; - } - } - return false; - }, - handler: async () => { - if (options.refreshTokenHandler) { - await options.refreshTokenHandler(); - } - } + isExpired: (response, method) => tokenRefresher?.isExpired(response, method) || false, + handler: async (response, method) => tokenRefresher?.handler(response, method) } }); const instance = createAlova({ ...customConfig, timeout: customConfig.timeout ?? 10 * 1000, - requestAdapter: customConfig.requestAdapter ?? adapterFetch(), + requestAdapter: (customConfig.requestAdapter as any) ?? adapterFetch(), statesHook: VueHook, - beforeRequest: onAuthRequired(options.onRequest), + beforeRequest: onAuthRequired(options.onRequest as any), responded: onResponseRefreshToken({ - onSuccess: async resp => { + onSuccess: async (response, method) => { // check if http status is success - if (resp.ok || resp.status === 304) { - if ( - !isJSON(resp.headers.get('Content-Type') ?? '') || - (options.isBackendSuccess && (await options.isBackendSuccess(resp))) - ) { - return options.transformBackendResponse ? await options.transformBackendResponse(resp) : resp; - } - if (options.onBackendFail) { - const fail = await options.onBackendFail(resp); - if (fail) { - return fail; - } + let error: any = null; + let transformedData: any = null; + try { + if (await options.isBackendSuccess(response)) { + transformedData = await options.transformBackendResponse(response); + } else { + error = new Error('the backend request error'); + error.code = BACKEND_ERROR_CODE; } + } catch (err) { + error = err; } - throw new Error(resp.statusText); + + if (error) { + await options.onError?.(error, response, method); + throw error; + } + + return transformedData; }, onComplete: options.onComplete, - onError: options.onError + onError: (error, method) => options.onError?.(error, null, method) }) }); return instance; }; -export { BACKEND_ERROR_CODE, REQUEST_ID_KEY }; +export { BACKEND_ERROR_CODE }; export type * from './type'; export type * from 'alova'; diff --git a/packages/alova/src/shared.ts b/packages/alova/src/shared.ts deleted file mode 100644 index 70e628ef..00000000 --- a/packages/alova/src/shared.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function isJSON(contentType: string) { - return contentType.includes('application/json'); -} diff --git a/packages/alova/src/type.ts b/packages/alova/src/type.ts index 335b8804..f7b5593a 100644 --- a/packages/alova/src/type.ts +++ b/packages/alova/src/type.ts @@ -1,18 +1,9 @@ -import type { - AlovaGenerics, - AlovaOptions, - AlovaRequestAdapter, - Method, - ResponseCompleteHandler, - ResponseErrorHandler -} from 'alova'; +import type { AlovaGenerics, AlovaOptions, AlovaRequestAdapter, Method, ResponseCompleteHandler } from 'alova'; export type CustomAlovaConfig = Omit< AlovaOptions, 'statesHook' | 'beforeRequest' | 'responded' | 'requestAdapter' > & { - /** expired token codes */ - expiredTokenCodes: string[]; /** request adapter. all request of alova will be sent by it. */ requestAdapter?: AlovaRequestAdapter; }; @@ -25,26 +16,23 @@ export interface RequestOptions { * * @param method alova Method Instance */ - onRequest?: (method: Method) => void | Promise; + onRequest?: AlovaOptions['beforeRequest']; /** * The hook to check backend response is success or not * - * @param response Axios response + * @param response alova response */ - isBackendSuccess?: (response: Response) => Promise; + isBackendSuccess: (response: AG['Response']) => Promise; - /** The hook to refresh token */ - refreshTokenHandler?: () => Promise; - /** - * 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 - */ - onBackendFail?: (response: Response) => Promise | Promise; + /** The config to refresh token */ + tokenRefresher?: { + /** detect the token is expired */ + isExpired(response: AG['Response'], Method: Method): Promise | boolean; + /** refhresh token handler */ + handler(response: AG['Response'], Method: Method): Promise; + }; + /** The hook after backend request complete */ onComplete?: ResponseCompleteHandler; /** @@ -54,11 +42,11 @@ export interface RequestOptions { * * @param error */ - onError?: ResponseErrorHandler; + onError?: (error: any, response: AG['Response'] | null, methodInstance: Method) => any | Promise; /** * transform backend response when the responseType is json * - * @param response Axios response + * @param response alova response */ - transformBackendResponse?: (response: AG['Response']) => any | Promise; + transformBackendResponse: (response: AG['Response']) => any; } diff --git a/packages/hooks/src/use-table.ts b/packages/hooks/src/use-table.ts index 24ac40e9..56862e3d 100644 --- a/packages/hooks/src/use-table.ts +++ b/packages/hooks/src/use-table.ts @@ -17,14 +17,7 @@ export type TableColumnCheck = { export type TableDataWithIndex = T & { index: number }; -export type TransformedData = { - data: TableDataWithIndex[]; - pageNum: number; - pageSize: number; - total: number; -}; - -export type Transformer = (response: Response) => TransformedData; +export type Transformer = (response: Response) => TableDataWithIndex[]; export type TableConfig = { /** api function to get table data */ @@ -32,7 +25,7 @@ export type TableConfig = { /** api params */ apiParams?: Parameters[0]; /** transform api response to table data */ - transformer: Transformer>>; + transformer: Transformer['send']>>>; /** columns factory */ columns: () => C[]; /** @@ -47,12 +40,6 @@ export type TableConfig = { * @param columns */ getColumns: (columns: C[], checks: TableColumnCheck[]) => C[]; - /** - * callback when response fetched - * - * @param transformed transformed data - */ - onFetched?: (transformed: TransformedData) => MaybePromise; /** * whether to get data immediately * @@ -74,17 +61,17 @@ export default function useHookTable(config: TableConfig< const columns = computed(() => getColumns(allColumns.value, columnChecks.value)); - const states = usePagination( - (page, pageSize) => apiFn({ ...formatSearchParams(searchParams), page, size: pageSize }), + const states = usePagination extends Method ? AG : never, ReturnType>( + (page, size) => apiFn({ ...formatSearchParams(searchParams), page, size }) as any, { immediate, data: transformer, - total: res => res.data.total + total: res => res.total } - ).onSuccess(event => { - setEmpty(event.data.length === 0); + ).onSuccess(({ data }) => { + setEmpty(data.length === 0); }); - delete states.uploading; + Reflect.deleteProperty(states, 'uploading'); function reloadColumns() { allColumns.value = config.columns(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4d768be1..c8f48c0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,9 +23,6 @@ importers: '@sa/alova': specifier: workspace:* version: link:packages/alova - '@sa/axios': - specifier: workspace:* - version: link:packages/axios '@sa/color': specifier: workspace:* version: link:packages/color @@ -41,9 +38,6 @@ importers: '@vueuse/core': specifier: 11.0.3 version: 11.0.3(vue@3.5.3(typescript@5.5.4)) - alova: - specifier: ^3.0.16 - version: 3.0.16 clipboard: specifier: 2.0.11 version: 2.0.11 @@ -223,8 +217,8 @@ importers: specifier: workspace:* version: link:../utils alova: - specifier: ^3.0.16 - version: 3.0.16 + specifier: ^3.0.19 + version: 3.0.19 packages/axios: dependencies: @@ -1601,8 +1595,8 @@ packages: resolution: {integrity: sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==} engines: {node: '>=0.10.0'} - alova@3.0.16: - resolution: {integrity: sha512-iCMi/MFAyEU9ukt2Hy/htshT2gpDTa5L4b9IZzrtu/O1Oevj6zES/uC49hNEBuZWyBBky3M6tvynurMYusfFYA==} + alova@3.0.19: + resolution: {integrity: sha512-G8YEuGn06vwg/B8mvyfRfMtxq8S8t88TwdAPlncyvUKOG4Hz1rKc4aH++QF9H+9coCVTKQzDbE4pWrgl3I0kBw==} engines: {node: '>= 18.0.0'} amdefine@1.0.1: @@ -6469,7 +6463,7 @@ snapshots: longest: 1.0.1 repeat-string: 1.6.1 - alova@3.0.16: + alova@3.0.19: dependencies: '@alova/shared': 1.0.5 rate-limiter-flexible: 5.0.3 diff --git a/src/hooks/common/table.ts b/src/hooks/common/table.ts index 207a0dee..665d7620 100644 --- a/src/hooks/common/table.ts +++ b/src/hooks/common/table.ts @@ -7,10 +7,10 @@ import { useAppStore } from '@/store/modules/app'; import { $t } from '@/locales'; type TableData = NaiveUI.TableData; -type GetTableData = NaiveUI.GetTableData; +type GetTableData = NaiveUI.GetTableData; type TableColumn = NaiveUI.TableColumn; -export function useTable(config: NaiveUI.NaiveTableConfig) { +export function useTable(config: NaiveUI.NaiveTableConfig) { const scope = effectScope(); const appStore = useAppStore(); @@ -21,8 +21,7 @@ export function useTable(config: NaiveUI.NaiveTabl const SELECTION_KEY = '__selection__'; const EXPAND_KEY = '__expand__'; - - const { reloadColumns, page, pageSize, total, ...rest } = useHookTable< + const { reloadColumns, page, pageSize, total, getData, update, ...rest } = useHookTable< A, GetTableData, TableColumn>> @@ -31,17 +30,15 @@ export function useTable(config: NaiveUI.NaiveTabl apiParams, columns: config.columns, transformer: res => { - const { records = [], current = 1, size = 10 } = res.data || {}; + const { records = [], current = 1, size = 10 } = res || {}; // Ensure that the size is greater than 0, If it is less than 0, it will cause paging calculation errors. const pageSizeValue = size <= 0 ? 10 : size; - return records.map((item, index) => { - return { - ...item, - index: (current - 1) * pageSizeValue + index + 1 - }; - }); + return records.map((item, index) => ({ + ...item, + index: (current - 1) * pageSizeValue + index + 1 + })); }, getColumnChecks: cols => { const checks: NaiveUI.TableColumnCheck[] = []; @@ -129,16 +126,16 @@ export function useTable(config: NaiveUI.NaiveTabl return p; }); - function updatePagination(update: Partial) { - (['page', 'pageSize', 'itemCount'] as const).forEach(key => { - update[key] && - ({ - page, - pageSize, - itemCount: total - }[key].value = update[key]); + function updatePagination(updateProps: Partial) { + const innerPageStates = ['page', 'pageSize', 'itemCount'] as const; + innerPageStates.forEach(key => { + if (updateProps[key]) { + update({ + [key]: updateProps[key] + }); + } }); - Object.assign(paginationBase, update); + Object.assign(paginationBase, updateProps); } /** @@ -148,6 +145,7 @@ export function useTable(config: NaiveUI.NaiveTabl */ async function getDataByPage(pageNum: number = 1) { page.value = pageNum; + return getData(); } scope.run(() => { @@ -165,6 +163,7 @@ export function useTable(config: NaiveUI.NaiveTabl return { ...rest, + getData, reloadColumns, pagination, mobilePagination, @@ -173,7 +172,7 @@ export function useTable(config: NaiveUI.NaiveTabl }; } -export function useTableOperate(data: Ref, getData: () => Promise) { +export function useTableOperate(data: Ref, getData: () => Promise | void) { const { bool: drawerVisible, setTrue: openDrawer, setFalse: closeDrawer } = useBoolean(); const operateType = ref('add'); diff --git a/src/service/api/auth.ts b/src/service/api/auth.ts index a27059cf..5e1ba708 100644 --- a/src/service/api/auth.ts +++ b/src/service/api/auth.ts @@ -39,5 +39,8 @@ export function fetchRefreshToken(refreshToken: string) { * @param msg error message */ export function fetchCustomBackendError(code: string, msg: string) { - return alova.Get('/auth/error', { params: { code, msg } }); + return alova.Get('/auth/error', { + params: { code, msg }, + shareRequest: false + }); } diff --git a/src/service/request/index.ts b/src/service/request/index.ts index d6f2bc1f..80409ad8 100644 --- a/src/service/request/index.ts +++ b/src/service/request/index.ts @@ -1,175 +1,19 @@ -import type { AxiosResponse } from 'axios'; -import { BACKEND_ERROR_CODE, createFlatRequest, createRequest } from '@sa/axios'; import { createAlovaRequest } from '@sa/alova'; import { useAuthStore } from '@/store/modules/auth'; import { $t } from '@/locales'; -import { localStg } from '@/utils/storage'; import { getServiceBaseURL } from '@/utils/service'; -import { getAuthorization, handleExpiredRequest, handleRefreshToken, showErrorMsg } from './shared'; +import { getAuthorization, handleRefreshToken, showErrorMsg } from './shared'; import type { RequestInstanceState } from './type'; const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y'; -const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); - -export const request = createFlatRequest( - { - baseURL, - headers: { - apifoxToken: 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2' - } - }, - { - async onRequest(config) { - const Authorization = getAuthorization(); - Object.assign(config.headers, { Authorization }); - - return config; - }, - isBackendSuccess(response) { - // when the backend response code is "0000"(default), it means the request is success - // to change this logic by yourself, you can modify the `VITE_SERVICE_SUCCESS_CODE` in `.env` file - return String(response.data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE; - }, - async onBackendFail(response, instance) { - const authStore = useAuthStore(); - const responseCode = String(response.data.code); - - function handleLogout() { - authStore.resetStore(); - } - - function logoutAndCleanup() { - handleLogout(); - window.removeEventListener('beforeunload', handleLogout); - - request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== response.data.msg); - } - - // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page - const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; - if (logoutCodes.includes(responseCode)) { - handleLogout(); - return null; - } - - // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal - const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; - if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) { - request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg]; - - // prevent the user from refreshing the page - window.addEventListener('beforeunload', handleLogout); - - window.$dialog?.error({ - title: $t('common.error'), - content: response.data.msg, - positiveText: $t('common.confirm'), - maskClosable: false, - closeOnEsc: false, - onPositiveClick() { - logoutAndCleanup(); - }, - onClose() { - logoutAndCleanup(); - } - }); - - return null; - } - - // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token - // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes` - const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; - if (expiredTokenCodes.includes(responseCode)) { - const success = await handleExpiredRequest(request.state); - if (success) { - const Authorization = getAuthorization(); - Object.assign(response.config.headers, { Authorization }); - - return instance.request(response.config) as Promise; - } - } - - return null; - }, - transformBackendResponse(response) { - return response.data.data; - }, - onError(error) { - // when the request is fail, you can show error message - - let message = error.message; - let backendErrorCode = ''; - - // get backend error message and code - if (error.code === BACKEND_ERROR_CODE) { - message = error.response?.data?.msg || message; - backendErrorCode = String(error.response?.data?.code || ''); - } - - // the error message is displayed in the modal - const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; - if (modalLogoutCodes.includes(backendErrorCode)) { - return; - } - - // when the token is expired, refresh token and retry request, so no need to show error message - const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; - if (expiredTokenCodes.includes(backendErrorCode)) { - return; - } - - showErrorMsg(request.state, message); - } - } -); - -export const demoRequest = createRequest( - { - baseURL: 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, refresh 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.response?.data?.message || message; - } - - window.$message?.error(message); - } - } -); +const { baseURL } = getServiceBaseURL(import.meta.env, isHttpProxy); +const state: RequestInstanceState = { + errMsgStack: [] +}; export const alova = createAlovaRequest( { - baseURL, - expiredTokenCodes: import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [] + baseURL }, { onRequest({ config }) { @@ -177,8 +21,15 @@ export const alova = createAlovaRequest( config.headers.Authorization = Authorization; config.headers.apifoxToken = 'XL299LiMEDZ0H5h3A29PxwQXdMJqWyY2'; }, - async refreshTokenHandler() { - await handleRefreshToken(); + tokenRefresher: { + async isExpired(response) { + const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; + const { code } = await response.clone().json(); + return expiredTokenCodes.includes(String(code)); + }, + async handler() { + await handleRefreshToken(); + } }, async isBackendSuccess(response) { // when the backend response code is "0000"(default), it means the request is success @@ -187,42 +38,49 @@ export const alova = createAlovaRequest( const data = await resp.json(); return String(data.code) === import.meta.env.VITE_SERVICE_SUCCESS_CODE; }, - async onBackendFail(response) { + async transformBackendResponse(response) { + return (await response.clone().json()).data; + }, + async onError(error, response) { const authStore = useAuthStore(); - const resp = response.clone(); - const data = await resp.json(); - const responseCode = String(data.code); + let message = error.message; + let responseCode = ''; + if (response) { + const data = await response?.clone().json(); + message = data.msg; + responseCode = String(data.code); + } function handleLogout() { + showErrorMsg(state, message); authStore.resetStore(); } function logoutAndCleanup() { handleLogout(); window.removeEventListener('beforeunload', handleLogout); - - request.state.errMsgStack = request.state.errMsgStack.filter(msg => msg !== data.msg); + state.errMsgStack = state.errMsgStack.filter(msg => msg !== message); } // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || []; if (logoutCodes.includes(responseCode)) { handleLogout(); - return null; + throw error; } // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; - if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(data.msg)) { - request.state.errMsgStack = [...(request.state.errMsgStack || []), data.msg]; + if (modalLogoutCodes.includes(responseCode) && !state.errMsgStack?.includes(message)) { + state.errMsgStack = [...(state.errMsgStack || []), message]; // prevent the user from refreshing the page window.addEventListener('beforeunload', handleLogout); window.$dialog?.error({ title: $t('common.error'), - content: data.msg, + content: message, positiveText: $t('common.confirm'), maskClosable: false, closeOnEsc: false, @@ -233,39 +91,10 @@ export const alova = createAlovaRequest( logoutAndCleanup(); } }); - - return null; + throw error; } - return null; - }, - transformBackendResponse(response) { - return response.json(); - }, - onError(error) { - // when the request is fail, you can show error message - - let message = error.message; - let backendErrorCode = ''; - - // get backend error message and code - if (error.code === BACKEND_ERROR_CODE) { - message = error.response?.data?.msg || message; - backendErrorCode = String(error.response?.data?.code || ''); - } - - // the error message is displayed in the modal - const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || []; - if (modalLogoutCodes.includes(backendErrorCode)) { - return; - } - - // when the token is expired, refresh token and retry request, so no need to show error message - const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || []; - if (expiredTokenCodes.includes(backendErrorCode)) { - return; - } - - showErrorMsg(request.state, message); + showErrorMsg(state, message); + throw error; } } ); diff --git a/src/service/request/shared.ts b/src/service/request/shared.ts index ef9238ac..8d3cf38f 100644 --- a/src/service/request/shared.ts +++ b/src/service/request/shared.ts @@ -15,30 +15,19 @@ export async function handleRefreshToken() { const { resetStore } = useAuthStore(); const rToken = localStg.get('refreshToken') || ''; - const { error, data } = await fetchRefreshToken(rToken); - if (!error) { + const refreshTokenMethod = fetchRefreshToken(rToken); + + // set the refreshToken role, so that the request will not be intercepted + refreshTokenMethod.meta.authRole = 'refreshToken'; + + try { + const data = await refreshTokenMethod; localStg.set('token', data.token); localStg.set('refreshToken', data.refreshToken); - return true; + } catch (error) { + resetStore(); + throw error; } - - resetStore(); - - return false; -} - -export async function handleExpiredRequest(state: RequestInstanceState) { - if (!state.refreshTokenFn) { - state.refreshTokenFn = handleRefreshToken(); - } - - const success = await state.refreshTokenFn; - - setTimeout(() => { - state.refreshTokenFn = null; - }, 1000); - - return success; } export function showErrorMsg(state: RequestInstanceState, message: string) { diff --git a/src/service/request/type.ts b/src/service/request/type.ts index 4e4a2f71..5f5ce5c8 100644 --- a/src/service/request/type.ts +++ b/src/service/request/type.ts @@ -1,6 +1,4 @@ export interface RequestInstanceState { - /** whether the request is refreshing token */ - refreshTokenFn: Promise | null; /** the request error message stack */ errMsgStack: string[]; } diff --git a/src/store/modules/auth/index.ts b/src/store/modules/auth/index.ts index 8fcdebed..9bd421db 100644 --- a/src/store/modules/auth/index.ts +++ b/src/store/modules/auth/index.ts @@ -63,9 +63,8 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => { async function login(userName: string, password: string, redirect = true) { startLoading(); - const { data: loginToken, error } = await fetchLogin(userName, password); - - if (!error) { + try { + const loginToken = await fetchLogin(userName, password); const pass = await loginByToken(loginToken); if (pass) { @@ -81,7 +80,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => { }); } } - } else { + } catch (err) { resetStore(); } @@ -106,15 +105,13 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => { } async function getUserInfo() { - const { data: info, error } = await fetchGetUserInfo(); - - if (!error) { + try { + const info = await fetchGetUserInfo(); // update store Object.assign(userInfo, info); return true; - } - + } catch (error) {} return false; } diff --git a/src/typings/components.d.ts b/src/typings/components.d.ts index 09f7cdd1..dbedf91c 100644 --- a/src/typings/components.d.ts +++ b/src/typings/components.d.ts @@ -87,6 +87,7 @@ declare module 'vue' { NSelect: typeof import('naive-ui')['NSelect'] NSkeleton: typeof import('naive-ui')['NSkeleton'] NSpace: typeof import('naive-ui')['NSpace'] + NSpin: typeof import('naive-ui')['NSpin'] NStatistic: typeof import('naive-ui')['NStatistic'] NSwitch: typeof import('naive-ui')['NSwitch'] NTab: typeof import('naive-ui')['NTab'] diff --git a/src/typings/naive-ui.d.ts b/src/typings/naive-ui.d.ts index d9ffb778..ace99331 100644 --- a/src/typings/naive-ui.d.ts +++ b/src/typings/naive-ui.d.ts @@ -9,7 +9,6 @@ declare namespace NaiveUI { type PaginationProps = import('naive-ui').PaginationProps; type TableColumnCheck = import('@sa/hooks').TableColumnCheck; type TableDataWithIndex = import('@sa/hooks').TableDataWithIndex; - type FlatResponseData = import('@sa/axios').FlatResponseData; /** * the custom column key @@ -26,9 +25,9 @@ declare namespace NaiveUI { type TableColumn = TableColumnWithKey | DataTableSelectionColumn | DataTableExpandColumn; - type TableApiFn = ( + type TableAlovaApiFn = ( params: R - ) => Promise>>; + ) => import('@sa/alova').Method>>; /** * the type of table operation @@ -38,9 +37,9 @@ declare namespace NaiveUI { */ type TableOperateType = 'add' | 'edit'; - type GetTableData = A extends TableApiFn ? T : never; + type GetTableData = A extends TableAlovaApiFn ? T : never; - type NaiveTableConfig = Pick< + type NaiveTableConfig = Pick< import('@sa/hooks').TableConfig, TableColumn>>>, 'apiFn' | 'apiParams' | 'columns' | 'immediate' > & { diff --git a/src/views/manage/menu/index.vue b/src/views/manage/menu/index.vue index 907fb98c..7d7ff072 100644 --- a/src/views/manage/menu/index.vue +++ b/src/views/manage/menu/index.vue @@ -3,7 +3,7 @@ import { ref } from 'vue'; import type { Ref } from 'vue'; import { NButton, NPopconfirm, NTag } from 'naive-ui'; import { useBoolean } from '@sa/hooks'; -import { fetchGetAllPages, fetchGetMenuList } from '@/service/api'; +import { fetchGetMenuList } from '@/service/api'; import { useAppStore } from '@/store/modules/app'; import { useTable, useTableOperate } from '@/hooks/common/table'; import { $t } from '@/locales'; @@ -18,7 +18,7 @@ const { bool: visible, setTrue: openModal } = useBoolean(); const wrapperRef = ref(null); -const { columns, columnChecks, data, loading, pagination, getData, getDataByPage } = useTable({ +const { columns, columnChecks, data, loading, pagination, refresh, reload, getDataByPage } = useTable({ apiFn: fetchGetMenuList, columns: () => [ { @@ -170,7 +170,7 @@ const { columns, columnChecks, data, loading, pagination, getData, getDataByPage ] }); -const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, getData); +const { checkedRowKeys, onBatchDeleted, onDeleted } = useTableOperate(data, reload); const operateType = ref('add'); @@ -210,20 +210,6 @@ function handleAddChildMenu(item: Api.SystemManage.Menu) { openModal(); } - -const allPages = ref([]); - -async function getAllPages() { - const { data: pages } = await fetchGetAllPages(); - allPages.value = pages || []; -} - -function init() { - getAllPages(); -} - -// init -init(); diff --git a/src/views/manage/menu/modules/menu-operate-modal.vue b/src/views/manage/menu/modules/menu-operate-modal.vue index 271fad44..141ab8a2 100644 --- a/src/views/manage/menu/modules/menu-operate-modal.vue +++ b/src/views/manage/menu/modules/menu-operate-modal.vue @@ -1,12 +1,13 @@ @@ -148,6 +148,7 @@ watch(visible, () => {