/** 表单校验错误(key 对应 vue-i18n 文案) */ export class FormValidationError extends Error { readonly key: string; constructor(key: string) { super(key); this.name = 'FormValidationError'; this.key = key; } } export function resolveFormError(e: unknown, t: (key: string) => string): string { if (e instanceof FormValidationError) return t(e.key); if (e instanceof Error && e.message.startsWith('err.')) return t(e.message); return t('msg.form_invalid'); } /** Nest 默认 4xx 响应里 error 常为 "Bad Request" 等泛化文案,真实原因在 message */ const GENERIC_HTTP_ERRORS = new Set([ 'Bad Request', 'Unauthorized', 'Forbidden', 'Not Found', 'Conflict', 'Unprocessable Entity', 'Internal Server Error', ]); /** 从 API 错误响应提取可读文案(兼容 GlobalExceptionFilter 与 Nest 默认格式) */ export function resolveApiError( err: unknown, t: (key: string) => string, fallbackKey = 'msg.save_failed', ): string { const data = (err as { response?: { data?: { error?: string | string[]; message?: string | string[] } } }) ?.response?.data; const msgRaw = data?.message; const errRaw = data?.error; const message = Array.isArray(msgRaw) ? msgRaw.join(';') : msgRaw; const error = Array.isArray(errRaw) ? errRaw.join(';') : errRaw; if (typeof message === 'string' && message.trim()) { if (!error || GENERIC_HTTP_ERRORS.has(error)) return message; } if (typeof error === 'string' && error.trim() && !GENERIC_HTTP_ERRORS.has(error)) return error; return t(fallbackKey); }