mirror of
https://github.com/stardrophere/InsightRadar.git
synced 2026-06-05 23:07:51 +08:00
108 lines
3.4 KiB
TypeScript
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)
|
|
}
|
|
}
|