From 738a8459c9d61c5708f51b6d0c3cc674792050c1 Mon Sep 17 00:00:00 2001 From: fawney19 Date: Fri, 12 Dec 2025 16:14:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(frontend):=20=E6=96=B0=E5=A2=9E=20API=20?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E7=B1=BB=E5=9E=8B=E5=AE=9A=E4=B9=89=EF=BC=8C?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=94=99=E8=AF=AF=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 api-error.ts 定义标准错误类型和工具函数 - 重构 error.ts 和 errorParser.ts 使用新类型 - 更新 api client 的类型定义 --- frontend/src/api/admin.ts | 2 +- frontend/src/api/client.ts | 2 +- frontend/src/types/api-error.ts | 83 +++++++++++++++++++++++++++++++ frontend/src/utils/error.ts | 27 ++++------ frontend/src/utils/errorParser.ts | 17 ++++--- 5 files changed, 107 insertions(+), 24 deletions(-) create mode 100644 frontend/src/types/api-error.ts diff --git a/frontend/src/api/admin.ts b/frontend/src/api/admin.ts index 8d0da28..648e71d 100644 --- a/frontend/src/api/admin.ts +++ b/frontend/src/api/admin.ts @@ -59,7 +59,7 @@ export const adminApi = { is_active?: boolean }): Promise { const response = await apiClient.get('/api/admin/api-keys', { - params: params + params }) return response.data }, diff --git a/frontend/src/api/client.ts b/frontend/src/api/client.ts index 4464601..5c0984b 100644 --- a/frontend/src/api/client.ts +++ b/frontend/src/api/client.ts @@ -204,7 +204,7 @@ class ApiClient { await this.refreshPromise originalRequest.headers.Authorization = `Bearer ${this.getToken()}` return this.client.request(originalRequest) - } catch (refreshError) { + } catch { return Promise.reject(error) } } diff --git a/frontend/src/types/api-error.ts b/frontend/src/types/api-error.ts new file mode 100644 index 0000000..08664bf --- /dev/null +++ b/frontend/src/types/api-error.ts @@ -0,0 +1,83 @@ +/** + * API 错误类型定义 + * 用于替代各处的 any 类型,提供类型安全的错误处理 + */ + +import type { AxiosError } from 'axios' + +/** + * 后端标准错误响应格式 + */ +export interface ApiErrorResponse { + error?: { + type?: string + message?: string + } + detail?: string + message?: string +} + +/** + * API 错误类型 + * 封装 Axios 错误,提供类型安全的错误处理 + */ +export interface ApiError extends AxiosError { + response?: AxiosError['response'] & { + data?: ApiErrorResponse + } +} + +/** + * 类型守卫:检查是否为 API 错误 + */ +export function isApiError(error: unknown): error is ApiError { + return ( + typeof error === 'object' && + error !== null && + 'response' in error + ) +} + +/** + * 从错误对象中安全提取错误消息 + */ +export function getErrorMessage(error: unknown, defaultMessage = '操作失败'): string { + if (!error) return defaultMessage + + if (isApiError(error)) { + // 优先从标准错误格式提取 + if (error.response?.data?.error?.message) { + return error.response.data.error.message + } + // FastAPI 标准 detail 字段 + if (error.response?.data?.detail) { + return error.response.data.detail + } + // 通用 message 字段 + if (error.response?.data?.message) { + return error.response.data.message + } + } + + // Error 实例 + if (error instanceof Error) { + return error.message + } + + // 字符串错误 + if (typeof error === 'string') { + return error + } + + return defaultMessage +} + +/** + * 从错误对象中安全提取 HTTP 状态码 + */ +export function getErrorStatus(error: unknown): number | undefined { + if (isApiError(error)) { + return error.response?.status + } + return undefined +} diff --git a/frontend/src/utils/error.ts b/frontend/src/utils/error.ts index ffd37b5..7118300 100644 --- a/frontend/src/utils/error.ts +++ b/frontend/src/utils/error.ts @@ -2,19 +2,10 @@ * 从后端响应中提取错误消息 * 后端统一返回格式: {"error": {"type": "...", "message": "..."}} */ -export function extractErrorMessage(error: any, defaultMessage = '操作失败'): string { - // 优先从响应中提取错误消息 - if (error.response?.data?.error?.message) { - return error.response.data.error.message - } +import { type ApiError, isApiError, getErrorMessage as _getErrorMessage } from '@/types/api-error' - // 如果是网络错误或其他异常 - if (error.message) { - return error.message - } - - // 返回默认消息 - return defaultMessage +export function extractErrorMessage(error: unknown, defaultMessage = '操作失败'): string { + return _getErrorMessage(error, defaultMessage) } /** @@ -38,8 +29,8 @@ export type ErrorType = typeof ErrorType[keyof typeof ErrorType] /** * 从后端响应中提取错误类型 */ -export function extractErrorType(error: any): ErrorType | null { - if (error.response?.data?.error?.type) { +export function extractErrorType(error: unknown): ErrorType | null { + if (isApiError(error) && error.response?.data?.error?.type) { return error.response.data.error.type as ErrorType } return null @@ -48,6 +39,10 @@ export function extractErrorType(error: any): ErrorType | null { /** * 检查是否为特定类型的错误 */ -export function isErrorType(error: any, type: ErrorType): boolean { +export function isErrorType(error: unknown, type: ErrorType): boolean { return extractErrorType(error) === type -} \ No newline at end of file +} + +// 重新导出类型 +export type { ApiError } +export { isApiError } diff --git a/frontend/src/utils/errorParser.ts b/frontend/src/utils/errorParser.ts index 09049c1..9c23188 100644 --- a/frontend/src/utils/errorParser.ts +++ b/frontend/src/utils/errorParser.ts @@ -2,6 +2,8 @@ * 解析 API 错误响应,提取友好的错误信息 */ +import { isApiError } from '@/types/api-error' + /** * Pydantic 验证错误项 */ @@ -9,7 +11,7 @@ interface ValidationError { loc: (string | number)[] msg: string type: string - ctx?: Record + ctx?: Record } /** @@ -138,11 +140,14 @@ function formatValidationError(error: ValidationError): string { * @param defaultMessage 默认错误信息 * @returns 格式化的错误信息 */ -export function parseApiError(err: any, defaultMessage: string = '操作失败'): string { +export function parseApiError(err: unknown, defaultMessage: string = '操作失败'): string { if (!err) return defaultMessage // 处理网络错误 - if (!err.response) { + if (!isApiError(err) || !err.response) { + if (err instanceof Error) { + return err.message || defaultMessage + } return '无法连接到服务器,请检查网络连接' } @@ -169,8 +174,8 @@ export function parseApiError(err: any, defaultMessage: string = '操作失败') // 3. 处理对象错误 if (typeof detail === 'object') { // 可能是自定义错误对象 - if (detail.message) { - return detail.message + if ((detail as Record).message) { + return String((detail as Record).message) } // 尝试 JSON 序列化 try { @@ -186,7 +191,7 @@ export function parseApiError(err: any, defaultMessage: string = '操作失败') /** * 解析并提取第一个错误信息(用于简短提示) */ -export function parseApiErrorShort(err: any, defaultMessage: string = '操作失败'): string { +export function parseApiErrorShort(err: unknown, defaultMessage: string = '操作失败'): string { const fullError = parseApiError(err, defaultMessage) // 如果有多行错误,只取第一行