87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
/**
|
||
* 统一 API 请求封装
|
||
* 规则:API 路径与现网完全一致,仅通过 baseUrl 区分环境(Next 或未来 Gin)
|
||
* 鉴权:管理端使用 JWT,自动带 Authorization: Bearer <token>(token 存 localStorage)
|
||
*/
|
||
|
||
import { getAdminToken } from './auth'
|
||
|
||
/** 未设置环境变量时使用的默认 API 地址(零配置部署) */
|
||
const DEFAULT_API_BASE = 'https://soulapi.quwanzhi.com'
|
||
|
||
const getBaseUrl = (): string => {
|
||
const url = import.meta.env.VITE_API_BASE_URL
|
||
if (typeof url === 'string' && url.length > 0) return url.replace(/\/$/, '')
|
||
return DEFAULT_API_BASE
|
||
}
|
||
|
||
/** 请求完整 URL:baseUrl + path,path 必须与现网一致(如 /api/orders) */
|
||
export function apiUrl(path: string): string {
|
||
const base = getBaseUrl()
|
||
const p = path.startsWith('/') ? path : `/${path}`
|
||
return base ? `${base}${p}` : p
|
||
}
|
||
|
||
export type RequestInitWithBody = RequestInit & { data?: unknown }
|
||
|
||
/**
|
||
* 发起请求。path 为与现网一致的 API 路径(如 /api/admin、/api/orders)。
|
||
* 若有 admin_token(JWT)则自动带 Authorization: Bearer;credentials: 'include' 保留以兼容需 Cookie 的接口。
|
||
*/
|
||
export async function request<T = unknown>(
|
||
path: string,
|
||
options: RequestInitWithBody = {}
|
||
): Promise<T> {
|
||
const { data, ...init } = options
|
||
const url = apiUrl(path)
|
||
const headers = new Headers(init.headers as HeadersInit)
|
||
const token = getAdminToken()
|
||
if (token) {
|
||
headers.set('Authorization', `Bearer ${token}`)
|
||
}
|
||
if (data !== undefined && data !== null && !headers.has('Content-Type')) {
|
||
headers.set('Content-Type', 'application/json')
|
||
}
|
||
const body = data !== undefined && data !== null ? JSON.stringify(data) : init.body
|
||
const res = await fetch(url, {
|
||
...init,
|
||
headers,
|
||
body,
|
||
credentials: 'include',
|
||
})
|
||
const contentType = res.headers.get('Content-Type') || ''
|
||
const json: T = contentType.includes('application/json')
|
||
? ((await res.json()) as T)
|
||
: (res as unknown as T)
|
||
if (!res.ok) {
|
||
const err = new Error((json as { error?: string })?.error || `HTTP ${res.status}`) as Error & {
|
||
status: number
|
||
data: T
|
||
}
|
||
err.status = res.status
|
||
err.data = json
|
||
throw err
|
||
}
|
||
return json
|
||
}
|
||
|
||
/** GET */
|
||
export function get<T = unknown>(path: string, init?: RequestInit): Promise<T> {
|
||
return request<T>(path, { ...init, method: 'GET' })
|
||
}
|
||
|
||
/** POST */
|
||
export function post<T = unknown>(path: string, data?: unknown, init?: RequestInit): Promise<T> {
|
||
return request<T>(path, { ...init, method: 'POST', data })
|
||
}
|
||
|
||
/** PUT */
|
||
export function put<T = unknown>(path: string, data?: unknown, init?: RequestInit): Promise<T> {
|
||
return request<T>(path, { ...init, method: 'PUT', data })
|
||
}
|
||
|
||
/** DELETE */
|
||
export function del<T = unknown>(path: string, init?: RequestInit): Promise<T> {
|
||
return request<T>(path, { ...init, method: 'DELETE' })
|
||
}
|