// 错误码定义 // 格式:E + 模块 (2 位) + 序号 (3 位) export const ErrorCodes = { // ============ 通用错误 (00-09) ============ E00000: { code: 'E00000', message: '未知错误', httpStatus: 0 }, E00001: { code: 'E00001', message: '网络连接失败,请检查网络', httpStatus: 0 }, E00002: { code: 'E00002', message: '服务器响应超时', httpStatus: 0 }, E00003: { code: 'E00003', message: '服务器内部错误', httpStatus: 500 }, // ============ 认证错误 (10-19) ============ E00010: { code: 'E00010', message: '未登录或登录已过期', httpStatus: 401 }, E00011: { code: 'E00011', message: '用户名或密码错误', httpStatus: 401 }, E00012: { code: 'E00012', message: '验证码错误', httpStatus: 400 }, E00013: { code: 'E00013', message: '账号已被禁用', httpStatus: 403 }, E00014: { code: 'E00014', message: '无权访问此资源', httpStatus: 403 }, E00015: { code: 'E00015', message: '令牌无效或已过期', httpStatus: 401 }, // ============ 登录注册 (20-29) ============ E00020: { code: 'E00020', message: '请输入用户名和密码', httpStatus: 400 }, E00021: { code: 'E00021', message: '用户名至少 3 个字符', httpStatus: 400 }, E00022: { code: 'E00022', message: '密码至少 6 个字符', httpStatus: 400 }, E00023: { code: 'E00023', message: '用户名已存在', httpStatus: 400 }, E00024: { code: 'E00024', message: '邮箱已被注册', httpStatus: 400 }, E00025: { code: 'E00025', message: '邮箱格式不正确', httpStatus: 400 }, E00026: { code: 'E00026', message: '手机号格式不正确', httpStatus: 400 }, // ============ 藏品管理 (30-39) ============ E00030: { code: 'E00030', message: '藏品名称不能为空', httpStatus: 400 }, E00031: { code: 'E00031', message: '藏品名称至少 2 个字符', httpStatus: 400 }, E00032: { code: 'E00032', message: '藏品分类不能为空', httpStatus: 400 }, E00033: { code: 'E00033', message: '藏品不存在', httpStatus: 404 }, E00034: { code: 'E00034', message: '禁止重复:此冠字号已存在', httpStatus: 400 }, E00035: { code: 'E00035', message: '成本价格必须>=0', httpStatus: 400 }, E00036: { code: 'E00036', message: '目标价格必须>=0', httpStatus: 400 }, E00037: { code: 'E00037', message: '发行年份必须是 4 位数字', httpStatus: 400 }, E00038: { code: 'E00038', message: '图片格式不正确', httpStatus: 400 }, E00039: { code: 'E00039', message: '图片大小不能超过 10MB', httpStatus: 400 }, // ============ OCR 识别 (40-49) ============ E00040: { code: 'E00040', message: '请选择图片文件', httpStatus: 400 }, E00041: { code: 'E00041', message: '图片尺寸太小,无法识别', httpStatus: 400 }, E00042: { code: 'E00042', message: 'OCR 识别失败,请重试', httpStatus: 500 }, E00043: { code: 'E00043', message: 'OCR 服务暂时不可用', httpStatus: 503 }, E00044: { code: 'E00044', message: '无法识别图片内容', httpStatus: 400 }, // ============ 用户管理 (50-59) ============ E00050: { code: 'E00050', message: '仅管理员可访问', httpStatus: 403 }, E00051: { code: 'E00051', message: '用户不存在', httpStatus: 404 }, E00052: { code: 'E00052', message: '不能删除自己', httpStatus: 400 }, E00053: { code: 'E00053', message: '不能修改自己的角色', httpStatus: 403 }, // ============ 文件上传 (60-69) ============ E00060: { code: 'E00060', message: '文件太大', httpStatus: 400 }, E00061: { code: 'E00061', message: '不支持的文件格式', httpStatus: 400 }, E00062: { code: 'E00062', message: '上传失败', httpStatus: 500 }, } // 根据 HTTP 状态码和错误信息匹配错误码 export function matchErrorCode(status, errorMessage = '') { // 首先尝试精确匹配错误信息 for (const code in ErrorCodes) { const error = ErrorCodes[code] if (error.httpStatus === status) { // 检查错误信息是否包含关键词 const msg = errorMessage.toLowerCase() if (status === 401) { if (msg.includes('password') || msg.includes('密码')) return ErrorCodes.E00011 if (msg.includes('token') || msg.includes('令牌')) return ErrorCodes.E00015 return ErrorCodes.E00010 } if (status === 403) { if (msg.includes('admin') || msg.includes('管理员')) return ErrorCodes.E00050 return ErrorCodes.E00014 } if (status === 404) { if (msg.includes('user')) return ErrorCodes.E00051 if (msg.includes('collection') || msg.includes('藏品')) return ErrorCodes.E00033 } if (status === 400) { if (msg.includes('captcha') || msg.includes('验证码')) return ErrorCodes.E00012 if (msg.includes('username') || msg.includes('用户名')) return ErrorCodes.E00023 if (msg.includes('email') || msg.includes('邮箱')) return ErrorCodes.E00024 if (msg.includes('重复') || msg.includes('duplicate')) return ErrorCodes.E00034 if (msg.includes('价格') || msg.includes('price')) { if (msg.includes('greater') || msg.includes('>=')) return ErrorCodes.E00035 } if (msg.includes('year') || msg.includes('年份')) return ErrorCodes.E00037 if (msg.includes('image') || msg.includes('图片')) return ErrorCodes.E00038 } if (status === 422) { // 验证错误 if (msg.includes('cost_price') || msg.includes('成本')) return ErrorCodes.E00035 if (msg.includes('target_price') || msg.includes('目标价格')) return ErrorCodes.E00036 if (msg.includes('issue_year') || msg.includes('年份')) return ErrorCodes.E00037 if (msg.includes('name') || msg.includes('名称')) return ErrorCodes.E00031 if (msg.includes('category') || msg.includes('分类')) return ErrorCodes.E00032 } if (status === 500) { if (msg.includes('ocr') || msg.includes('识别')) return ErrorCodes.E00042 } } } // 默认错误码 if (status === 0) return ErrorCodes.E00001 if (status >= 500) return ErrorCodes.E00003 return ErrorCodes.E00000 } // 格式化错误信息 export function formatError(error) { if (error.code && typeof error.code === 'string' && error.code.startsWith('E')) { // 已经是错误码格式 const errorCode = ErrorCodes[error.code] if (errorCode) { return `${errorCode.code}: ${errorCode.message}` } } // 匹配错误码 const status = error.status || error.httpStatus || 0 const message = error.message || error.detail || error.error || '' const errorCode = matchErrorCode(status, message) return `${errorCode.code}: ${errorCode.message}` } // 显示错误 Toast export function showErrorToast(error, duration = 3000) { const errorMessage = formatError(error) // 创建 Toast 元素 const toast = document.createElement('div') toast.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(239, 68, 68, 0.95); color: white; padding: 12px 24px; border-radius: 8px; font-size: 14px; z-index: 9999; box-shadow: 0 4px 12px rgba(0,0,0,0.3); animation: slideDown 0.3s ease; ` toast.textContent = errorMessage document.body.appendChild(toast) // 3 秒后移除 setTimeout(() => { toast.style.animation = 'slideUp 0.3s ease' setTimeout(() => toast.remove(), 300) }, duration) } export default { ErrorCodes, matchErrorCode, formatError, showErrorToast }