jiachenlong/frontend/frontend_src/utils/api.js

231 lines
5.7 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 统一的 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