From d00cdcf12262f7c5574e77ad86e0ab986e828c9e Mon Sep 17 00:00:00 2001 From: dubai Date: Thu, 12 Feb 2026 00:43:52 +0800 Subject: [PATCH] =?UTF-8?q?feat(api):=20=E5=85=BC=E5=AE=B9=E5=A4=9A?= =?UTF-8?q?=E7=A7=8D=E5=90=8E=E7=AB=AFAPI=E5=93=8D=E5=BA=94=E9=A3=8E?= =?UTF-8?q?=E6=A0=BC=20-=20=E5=9C=A8=E7=8E=AF=E5=A2=83=E5=8F=98=E9=87=8F?= =?UTF-8?q?=E4=B8=AD=E6=96=B0=E5=A2=9EVITE=5FGLOB=5FAPI=5FSTYLE=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E9=A1=B9=E4=BB=A5=E6=94=AF=E6=8C=81auto=E3=80=81furio?= =?UTF-8?q?n=E5=92=8Cabp=E4=B8=89=E7=A7=8D=E5=93=8D=E5=BA=94=E9=A3=8E?= =?UTF-8?q?=E6=A0=BC=20-=20=E5=9C=A8=E5=85=A8=E5=B1=80=E7=B1=BB=E5=9E=8B?= =?UTF-8?q?=E5=AE=9A=E4=B9=89=E4=B8=AD=E6=B7=BB=E5=8A=A0apiStyle=E5=AD=97?= =?UTF-8?q?=E6=AE=B5=EF=BC=8C=E6=94=AF=E6=8C=81=E5=AF=B9=E5=BA=94=E5=93=8D?= =?UTF-8?q?=E5=BA=94=E9=A3=8E=E6=A0=BC=E7=B1=BB=E5=9E=8B=E7=BA=A6=E6=9D=9F?= =?UTF-8?q?=20-=20request.ts=E4=B8=AD=E5=AE=9E=E7=8E=B0Furion=E9=A3=8E?= =?UTF-8?q?=E6=A0=BC=E5=93=8D=E5=BA=94=E5=88=A4=E6=96=AD=E5=92=8C=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E5=88=87=E6=8D=A2ABP=E9=A3=8E=E6=A0=BC=E5=A4=84?= =?UTF-8?q?=E7=90=86=E9=80=BB=E8=BE=91=20-=20=E5=A2=9E=E5=8A=A0ABP?= =?UTF-8?q?=E9=A3=8E=E6=A0=BC=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86=EF=BC=8C?= =?UTF-8?q?=E5=8C=85=E5=90=AB401/403=E8=87=AA=E5=8A=A8=E7=99=BB=E5=87=BA?= =?UTF-8?q?=E5=8F=8A=E9=AA=8C=E8=AF=81=E9=94=99=E8=AF=AF=E6=8F=90=E7=A4=BA?= =?UTF-8?q?=20-=20ABP=E9=A3=8E=E6=A0=BC=E4=B8=8B=E6=94=AF=E6=8C=81HTTP=202?= =?UTF-8?q?00=E7=9B=B4=E6=8E=A5=E6=88=90=E5=8A=9F=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=95=B0=E6=8D=AE=E5=92=8C=E4=B8=8D=E5=90=8C=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E6=A8=A1=E5=BC=8F=20-=20use-app-config.ts?= =?UTF-8?q?=E4=B8=AD=E6=B7=BB=E5=8A=A0apiStyle=E9=85=8D=E7=BD=AE=E8=AF=BB?= =?UTF-8?q?=E5=8F=96=E5=8F=8A=E9=BB=98=E8=AE=A4=E5=80=BC=E9=80=BB=E8=BE=91?= =?UTF-8?q?=20-=20=E6=B3=A8=E9=87=8A=E6=8E=89YiAbpWebModule=E4=B8=ADFurion?= =?UTF-8?q?=E7=BB=9F=E4=B8=80=E5=93=8D=E5=BA=94API=E7=9A=84=E5=90=AF?= =?UTF-8?q?=E7=94=A8=E4=BB=A3=E7=A0=81=EF=BC=8C=E6=94=B9=E4=B8=BA=E9=BB=98?= =?UTF-8?q?=E8=AE=A4=E4=BD=BF=E7=94=A8ABP=E9=A3=8E=E6=A0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Yi.Vben5.Vue3/apps/web-antd/.env | 3 + .../apps/web-antd/src/api/request.ts | 75 ++++++++++++++++++- .../effects/hooks/src/use-app-config.ts | 5 ++ Yi.Vben5.Vue3/packages/types/global.d.ts | 4 + 4 files changed, 86 insertions(+), 1 deletion(-) 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 {