166 lines
7.4 KiB
JavaScript
166 lines
7.4 KiB
JavaScript
|
|
// 错误码定义
|
|||
|
|
// 格式: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 }
|