2026-07-20 13:02:26 +08:00
|
|
|
export type ApiSession = {
|
|
|
|
|
baseUrl: string
|
|
|
|
|
token: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type RequestOptions = {
|
|
|
|
|
method?: string
|
|
|
|
|
body?: unknown
|
|
|
|
|
token?: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let configuredBaseUrl = normalizeBaseUrl(import.meta.env.VITE_API_BASE_URL ?? 'http://127.0.0.1:18080')
|
|
|
|
|
|
|
|
|
|
export function setApiBaseUrl(baseUrl: string) {
|
|
|
|
|
configuredBaseUrl = normalizeBaseUrl(baseUrl)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getApiBaseUrl() {
|
|
|
|
|
return configuredBaseUrl
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function login(email: string, password: string): Promise<ApiSession> {
|
|
|
|
|
const response = await apiRequest<{ token: string }>('/api/auth/login', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: { email, password },
|
|
|
|
|
})
|
|
|
|
|
return { baseUrl: configuredBaseUrl, token: response.token }
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function apiRequest<T>(path: string, options: RequestOptions = {}): Promise<T> {
|
2026-07-20 21:45:36 +08:00
|
|
|
const isFormData = typeof FormData !== 'undefined' && options.body instanceof FormData
|
|
|
|
|
const requestBody = options.body === undefined ? undefined : isFormData ? options.body : JSON.stringify(options.body)
|
2026-07-20 13:02:26 +08:00
|
|
|
const response = await fetch(`${configuredBaseUrl}${path}`, {
|
|
|
|
|
method: options.method ?? 'GET',
|
|
|
|
|
headers: {
|
2026-07-20 21:45:36 +08:00
|
|
|
...(isFormData ? {} : { 'Content-Type': 'application/json' }),
|
2026-07-20 13:02:26 +08:00
|
|
|
...(options.token ? { Authorization: `Bearer ${options.token}` } : {}),
|
|
|
|
|
},
|
2026-07-20 21:45:36 +08:00
|
|
|
body: requestBody as BodyInit | undefined,
|
2026-07-20 13:02:26 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
let message = `请求失败:${response.status}`
|
|
|
|
|
try {
|
|
|
|
|
const payload = await response.json()
|
|
|
|
|
if (typeof payload.error === 'string') message = payload.error
|
|
|
|
|
} catch {
|
|
|
|
|
// Keep the status-based message when the server does not return JSON.
|
|
|
|
|
}
|
|
|
|
|
throw new Error(message)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 22:35:45 +08:00
|
|
|
if (response.status === 204) {
|
|
|
|
|
return undefined as T
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const text = await response.text()
|
|
|
|
|
if (text.trim() === '') {
|
|
|
|
|
return undefined as T
|
|
|
|
|
}
|
|
|
|
|
return JSON.parse(text) as T
|
2026-07-20 13:02:26 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeBaseUrl(value: string) {
|
|
|
|
|
const trimmed = value.trim()
|
|
|
|
|
return trimmed.endsWith('/') ? trimmed.slice(0, -1) : trimmed
|
|
|
|
|
}
|