Files
InsightRadar/frontend/src/api/client.ts
T
2026-03-13 13:14:55 +08:00

108 lines
3.4 KiB
TypeScript

/**
* 通用 HTTP 客户端:自动注入 Bearer Token,统一处理错误
*/
import { useAuthStore } from '@/stores/auth'
import { pinia } from '@/stores'
import { fetchApi } from '@/config/apiBase'
// 后端返回的错误消息中英映射
const MESSAGE_MAP: Record<string, string> = {
'You can only operate your own resources': '只能操作自己的资源',
'Preference keyword already exists for this user': '该关键词已订阅',
'Keyword cannot be empty': '关键词不能为空',
'This delivery time already exists': '该推送时间已存在',
'This channel type already exists for the user': '该渠道类型已存在',
'Schedule not found': '推送时间不存在',
'Push endpoint not found': '推送渠道不存在',
'Preference not found': '偏好不存在',
'Invalid or expired token': '登录已过期,请重新登录',
'Authentication credentials were not provided': '请先登录',
}
function localizeMessage(msg: string): string {
return MESSAGE_MAP[msg] ?? msg
}
function getAuthHeaders(): Record<string, string> {
const authStore = useAuthStore(pinia)
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
if (authStore.accessToken) {
headers['Authorization'] = `Bearer ${authStore.accessToken}`
}
return headers
}
async function handleResponse<T>(response: Response): Promise<T> {
const raw = await response.text()
let data: Record<string, unknown> = {}
if (raw) {
try {
data = JSON.parse(raw) as Record<string, unknown>
} catch {
data = {}
}
}
if (!response.ok) {
const detail = data.detail
if (typeof detail === 'string') {
throw new Error(localizeMessage(detail))
}
throw new Error(`请求失败 (${response.status})`)
}
return data as T
}
/** GET 请求 */
export async function apiGet<T>(path: string, params?: Record<string, string | number>): Promise<T> {
let requestPath = path
if (params) {
const searchParams = new URLSearchParams()
for (const [key, value] of Object.entries(params)) {
searchParams.set(key, String(value))
}
const separator = requestPath.includes('?') ? '&' : '?'
requestPath += `${separator}${searchParams.toString()}`
}
const response = await fetchApi(requestPath, { method: 'GET', headers: getAuthHeaders() })
return handleResponse<T>(response)
}
/** POST 请求 */
export async function apiPost<T>(path: string, body?: unknown): Promise<T> {
const response = await fetchApi(path, {
method: 'POST',
headers: getAuthHeaders(),
body: body !== undefined ? JSON.stringify(body) : undefined,
})
return handleResponse<T>(response)
}
/** PATCH 请求 */
export async function apiPatch<T>(path: string, body: unknown): Promise<T> {
const response = await fetchApi(path, {
method: 'PATCH',
headers: getAuthHeaders(),
body: JSON.stringify(body),
})
return handleResponse<T>(response)
}
/** DELETE 请求 */
export async function apiDelete(path: string): Promise<void> {
const response = await fetchApi(path, {
method: 'DELETE',
headers: getAuthHeaders(),
})
if (!response.ok && response.status !== 204) {
const raw = await response.text()
let detail = `请求失败 (${response.status})`
try {
const data = JSON.parse(raw) as Record<string, unknown>
if (typeof data.detail === 'string') detail = localizeMessage(data.detail)
} catch { /* ignore */ }
throw new Error(detail)
}
}