// 统一的 API 客户端 const API_BASE = '' // 生产环境由 Nginx 代理 import { ErrorCodes, matchErrorCode } from './errorCodes.js' // 错误处理 class ApiError extends Error { constructor(message, status, data, code = null) { super(message) this.name = 'ApiError' this.status = status this.data = data this.code = code || matchErrorCode(status, message).code } // 获取格式化的错误信息 get formattedMessage() { return `${this.code}: ${this.message}` } } // 获取 Token const getToken = () => localStorage.getItem('token') // 统一请求方法 async function request(endpoint, options = {}) { const token = getToken() const defaultHeaders = { 'Content-Type': 'application/json', } if (token) { defaultHeaders['Authorization'] = `Bearer ${token}` } const config = { ...options, headers: { ...defaultHeaders, ...(options.headers || {}) } } try { const response = await fetch(`${API_BASE}${endpoint}`, config) const data = await response.json() if (!response.ok) { // 处理 401 未授权 if (response.status === 401) { localStorage.removeItem('token') localStorage.removeItem('user') window.location.hash = '#/login' throw new ApiError( data.detail || data.message || '登录已过期,请重新登录', 401, data, 'E00010' ) } // 处理 422 验证错误 if (response.status === 422 && data.detail) { const detail = Array.isArray(data.detail) ? data.detail[0] : data.detail const field = detail.loc ? detail.loc.join('.') : '' const msg = detail.msg || detail.message || data.message throw new ApiError( `${field}: ${msg}`, 422, data ) } // 其他错误 throw new ApiError( data.detail || data.message || data.error || '请求失败', response.status, data ) } return data } catch (error) { // 网络错误 if (error.name === 'TypeError' && error.message.includes('fetch')) { throw new ApiError('网络连接失败,请检查网络', 0, null, 'E00001') } throw error } } // API 模块 export const api = { // 认证 auth: { login: async (username, password) => { // 登录时不使用 token const params = new URLSearchParams() params.append('username', username) params.append('password', password) const response = await fetch(`${API_BASE}/api/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: params.toString() }) const data = await response.json() if (!response.ok) { // 优先使用 error.code 和 error.message(后端标准格式) const errorCode = data.error?.code || data.code const errorMsg = data.error?.message || data.detail || data.message || '登录失败' const error = new ApiError(errorMsg, response.status, data) if (errorCode) error.code = errorCode throw error } return data }, register: (data) => request('/api/auth/register', { method: 'POST', body: JSON.stringify(data) }) }, // 当前用户 user: { me: async () => { const token = getToken() if (!token) throw new ApiError('未登录', 401, null, 'E00010') const response = await fetch(`${API_BASE}/api/users/me`, { headers: { 'Authorization': `Bearer ${token}` } }) const data = await response.json() if (!response.ok) { throw new ApiError( data.detail || data.message || '获取用户信息失败', response.status, data ) } return data }, update: (data) => request('/api/users/me', { method: 'PUT', body: JSON.stringify(data) }) }, // 藏品 collections: { // 列表 list: (params = {}) => { const query = new URLSearchParams(params).toString() return request(`/api/collections${query ? '?' + query : ''}`) }, // 详情 get: (id) => request(`/api/collections/${id}`), // 创建 create: (data) => request('/api/collections', { method: 'POST', body: JSON.stringify(data) }), // 更新 update: (id, data) => request(`/api/collections/${id}`, { method: 'PUT', body: JSON.stringify(data) }), // 删除 delete: (id) => request(`/api/collections/${id}`, { method: 'DELETE' }), // 下一个编号 nextCode: () => request('/api/collections/next-code'), // 统计 stats: (params = {}) => { const query = new URLSearchParams(params).toString() return request(`/api/collections/stats${query ? '?' + query : ''}`) }, // 导出 export: (params) => request(`/api/collections/export?${new URLSearchParams(params)}`) }, // OCR ocr: { recognize: (file) => { const formData = new FormData() formData.append('image', file) return request('/api/ocr/recognize', { method: 'POST', body: formData }) } }, // 用户管理(仅管理员) admin: { users: { list: (page = 1, limit = 20) => request(`/api/admin/users?page=${page}&limit=${limit}`), get: (userId) => request(`/api/admin/users/${userId}`), collections: (userId) => request(`/api/admin/users/${userId}/collections`), count: (userId) => request(`/api/admin/users/${userId}/count`) } } } // 导出错误类 export { ApiError } // 默认导出 export default api