diff --git a/Yi.Vben5.Vue3/apps/web-antd/.env b/Yi.Vben5.Vue3/apps/web-antd/.env index 1850fc64..d3518403 100644 --- a/Yi.Vben5.Vue3/apps/web-antd/.env +++ b/Yi.Vben5.Vue3/apps/web-antd/.env @@ -6,3 +6,6 @@ VITE_APP_NAMESPACE=vben-web-antd # 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密 VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key + +# API响应风格: auto(自动检测) | furion | abp +VITE_GLOB_API_STYLE=auto diff --git a/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts b/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts index 159cc9f7..b224a810 100644 --- a/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts +++ b/Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts @@ -27,11 +27,29 @@ import { } from '#/utils/encryption/crypto'; import * as encryptUtil from '#/utils/encryption/jsencrypt'; -const { apiURL, clientId, enableEncrypt, demoMode } = useAppConfig( +const { apiURL, clientId, enableEncrypt, demoMode, apiStyle } = useAppConfig( import.meta.env, import.meta.env.PROD, ); +/** 判断是否为 Furion 风格响应 */ +function isFurionResponse(data: any): boolean { + return ( + data != null && + typeof data === 'object' && + 'statusCode' in data && + 'succeeded' in data + ); +} + +/** 判断当前响应是否应按 ABP 风格处理 */ +function shouldUseAbpStyle(data: any): boolean { + if (apiStyle === 'abp') return true; + if (apiStyle === 'furion') return false; + // auto: 不是 Furion 结构就按 ABP 处理 + return !isFurionResponse(data); +} + /** * 是否已经处在登出过程中了 一个标志位 * 主要是防止一个页面会请求多个api 都401 会导致登出执行多次 @@ -180,6 +198,47 @@ function createRequestClient(baseURL: string) { if (error?.__isDemoModeError || error?.name === 'DemoModeException') { return; } + + const status = error?.response?.status; + const responseData = error?.response?.data; + const abpError = responseData?.error; + + // 判断是否为 ABP 风格错误 + const isAbp = + apiStyle === 'abp' || + (apiStyle === 'auto' && + abpError && + typeof abpError.message === 'string'); + + if (isAbp) { + // ABP 401/403 需要触发登出 + if ((status === 401 || status === 403) && !isLogoutProcessing) { + isLogoutProcessing = true; + const _msg = abpError?.message || msg; + const userStore = useAuthStore(); + userStore.logout().finally(() => { + message.error(_msg); + isLogoutProcessing = false; + }); + return; + } + + // ABP 其他错误:提取 error.message,拼接 validationErrors + let errorMsg = abpError?.message || msg; + if (abpError?.validationErrors?.length) { + const details = abpError.validationErrors + .map((e: any) => e.message) + .filter(Boolean) + .join('; '); + if (details) { + errorMsg = `${errorMsg}: ${details}`; + } + } + message.error(errorMsg); + return; + } + + // Furion 风格 / 非 ABP:保持原有行为 message.error(msg); }, ); @@ -234,6 +293,20 @@ function createRequestClient(baseURL: string) { throw new Error($t('http.apiRequestFailed')); } + // ABP 风格:HTTP 200 即成功,直接返回数据 + if (shouldUseAbpStyle(axiosResponseData)) { + if (response.config.successMessageMode === 'modal') { + Modal.success({ + content: $t('http.operationSuccess'), + title: $t('http.successTip'), + }); + } else if (response.config.successMessageMode === 'message') { + message.success($t('http.operationSuccess')); + } + return axiosResponseData; + } + + // Furion 风格响应处理 console.log('axiosResponseData', axiosResponseData); // 适配后端数据结构: { statusCode, data, succeeded, errors, extras, timestamp } const { statusCode, data, succeeded, errors, extras, timestamp } = diff --git a/Yi.Vben5.Vue3/packages/effects/hooks/src/use-app-config.ts b/Yi.Vben5.Vue3/packages/effects/hooks/src/use-app-config.ts index 83bacbd6..37df2c66 100644 --- a/Yi.Vben5.Vue3/packages/effects/hooks/src/use-app-config.ts +++ b/Yi.Vben5.Vue3/packages/effects/hooks/src/use-app-config.ts @@ -23,6 +23,7 @@ export function useAppConfig( VITE_GLOB_RSA_PUBLIC_KEY, VITE_GLOB_SSE_ENABLE, VITE_GLOB_DEMO_MODE, + VITE_GLOB_API_STYLE, } = config; return { @@ -39,5 +40,9 @@ export function useAppConfig( sseEnable: VITE_GLOB_SSE_ENABLE === 'false', // 是否开启演示模式 demoMode: VITE_GLOB_DEMO_MODE === 'true', + // API响应风格 + apiStyle: (['abp', 'furion'].includes(VITE_GLOB_API_STYLE) + ? VITE_GLOB_API_STYLE + : 'auto') as 'abp' | 'auto' | 'furion', }; } diff --git a/Yi.Vben5.Vue3/packages/types/global.d.ts b/Yi.Vben5.Vue3/packages/types/global.d.ts index 1a7baed1..bd74fa99 100644 --- a/Yi.Vben5.Vue3/packages/types/global.d.ts +++ b/Yi.Vben5.Vue3/packages/types/global.d.ts @@ -22,6 +22,8 @@ export interface VbenAdminProAppConfigRaw { VITE_GLOB_SSE_ENABLE: string; // 是否开启演示模式(只读模式,禁止修改操作) 注意从配置文件获取的类型为string VITE_GLOB_DEMO_MODE: string; + // API响应风格: auto(自动检测) | furion | abp + VITE_GLOB_API_STYLE: string; } export interface ApplicationConfig { @@ -39,6 +41,8 @@ export interface ApplicationConfig { sseEnable: boolean; // 是否开启演示模式(只读模式,禁止修改操作) demoMode: boolean; + // API响应风格 + apiStyle: 'abp' | 'auto' | 'furion'; } declare global {