95 lines
3.5 KiB
TypeScript
Raw Normal View History

2026-05-21 15:52:36 +08:00
import axios from 'axios'
import { isWebView2 } from './env'
// WebView2 自定义协议前缀
const WEBVIEW2_BASE = 'app://api/'
// Vite 开发页走 5206 APIAPI 托管前端时使用同源地址。
const isViteDevServer = window.location.port === '51552'
const HTTP_ORIGIN = isViteDevServer
? `${window.location.protocol}//${window.location.hostname || 'localhost'}:5206`
: window.location.origin
const HTTP_BASE = `${HTTP_ORIGIN}/api/`
export const apiOrigin = (): string => HTTP_ORIGIN
export const apiUrl = (path: string): string => {
if (/^https?:\/\//i.test(path)) return path
const normalized = path.startsWith('/') ? path : `/${path}`
return `${isWebView2() ? '' : HTTP_ORIGIN}${normalized}`
}
2026-05-21 15:52:36 +08:00
// ─── axios 实例 ────────────────────────────────────────────────────────────────
const http = axios.create({
headers: { 'Content-Type': 'application/json' },
})
// 请求拦截器:仅在浏览器环境下注入鉴权 Token
// WebView2 本地运行,不需要鉴权
http.interceptors.request.use((config) => {
if (!isWebView2()) {
const token = localStorage.getItem('authToken')
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
}
return config
})
// 响应拦截器:统一解包 C# 返回的 { success, data/error } 结构
// C# BuildSuccessResponseBody 固定格式:{ "success": true, "data": ... }
// 错误格式:{ "success": false, "error": "..." }
// WebView2 桥接和 HTTP 两个环境返回结构相同,拦截器可统一处理
http.interceptors.response.use(
(response) => {
const payload = response.data as { success: boolean; data?: unknown; error?: string; message?: string }
2026-05-21 15:52:36 +08:00
if (payload?.success === false) {
return Promise.reject(new Error(payload.error ?? payload.message ?? '请求失败'))
2026-05-21 15:52:36 +08:00
}
return (payload?.data ?? payload) as never
},
(error) => {
const msg: string =
error.response?.data?.error ??
error.response?.data?.message ??
error.message ??
'网络错误'
return Promise.reject(new Error(msg))
},
)
// ─── 统一请求方法 ──────────────────────────────────────────────────────────────
interface RequestOptions {
method?: string
headers?: Record<string, string>
body?: unknown
}
export async function request<T = unknown>(endpoint: string, options: RequestOptions = {}): Promise<T> {
const url = (isWebView2() ? WEBVIEW2_BASE : HTTP_BASE) + endpoint
// WebView2直接走桥接 fetch桥接脚本已完整覆盖 window.fetch
if (isWebView2()) {
const res = await fetch(url, {
method: options.method ?? 'GET',
headers: { 'Content-Type': 'application/json', ...(options.headers ?? {}) },
body: options.body !== undefined ? JSON.stringify(options.body) : undefined,
})
const payload = await res.json() as { success: boolean; data?: T; error?: string; message?: string }
2026-05-21 15:52:36 +08:00
if (payload?.success === false) {
throw new Error(payload.error ?? payload.message ?? '请求失败')
2026-05-21 15:52:36 +08:00
}
return (payload?.data ?? payload) as T
}
// 普通浏览器:走 axios拦截器处理鉴权和响应解包
return http.request<T>({
url,
method: options.method ?? 'GET',
headers: options.headers,
data: options.body,
}) as Promise<T>
}