2026-05-08 10:52:54 +08:00
|
|
|
const API_BASE_STORAGE_KEY = 'x-financial-api-base-url'
|
|
|
|
|
|
|
|
|
|
function normalizeApiBaseUrl(value) {
|
|
|
|
|
return String(value || '/api/v1').replace(/\/$/, '')
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 11:14:04 +08:00
|
|
|
function isLoopbackHost(hostname) {
|
|
|
|
|
const normalized = String(hostname || '').trim().toLowerCase()
|
|
|
|
|
return normalized === '127.0.0.1' || normalized === 'localhost' || normalized === '0.0.0.0' || normalized === '::1'
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resolveBrowserReachableApiBaseUrl(value) {
|
|
|
|
|
const normalized = normalizeApiBaseUrl(value)
|
|
|
|
|
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return normalized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
const apiUrl = new URL(normalized)
|
|
|
|
|
const browserHost = window.location.hostname
|
|
|
|
|
|
|
|
|
|
if (isLoopbackHost(apiUrl.hostname) && browserHost && !isLoopbackHost(browserHost)) {
|
|
|
|
|
apiUrl.hostname = browserHost
|
|
|
|
|
return normalizeApiBaseUrl(apiUrl.toString())
|
|
|
|
|
}
|
|
|
|
|
} catch {
|
|
|
|
|
return normalized
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return normalized
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 10:52:54 +08:00
|
|
|
function readStoredApiBaseUrl() {
|
|
|
|
|
if (typeof window === 'undefined') {
|
|
|
|
|
return ''
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-08 11:14:04 +08:00
|
|
|
return resolveBrowserReachableApiBaseUrl(window.localStorage.getItem(API_BASE_STORAGE_KEY) || '')
|
2026-05-08 10:52:54 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-09 09:29:34 +08:00
|
|
|
let runtimeApiBaseUrl = normalizeApiBaseUrl('/api/v1')
|
|
|
|
|
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
window.localStorage.removeItem(API_BASE_STORAGE_KEY)
|
|
|
|
|
}
|
2026-05-08 10:52:54 +08:00
|
|
|
|
|
|
|
|
export function setRuntimeApiBaseUrl(value) {
|
2026-05-08 11:14:04 +08:00
|
|
|
runtimeApiBaseUrl = resolveBrowserReachableApiBaseUrl(value)
|
2026-05-08 10:52:54 +08:00
|
|
|
|
|
|
|
|
if (typeof window !== 'undefined') {
|
|
|
|
|
window.localStorage.setItem(API_BASE_STORAGE_KEY, runtimeApiBaseUrl)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function getRuntimeApiBaseUrl() {
|
|
|
|
|
return runtimeApiBaseUrl
|
|
|
|
|
}
|
2026-05-07 11:50:10 +08:00
|
|
|
|
|
|
|
|
function buildUrl(path) {
|
|
|
|
|
if (!path.startsWith('/')) {
|
2026-05-08 10:52:54 +08:00
|
|
|
return `${runtimeApiBaseUrl}/${path}`
|
2026-05-07 11:50:10 +08:00
|
|
|
}
|
|
|
|
|
|
2026-05-08 10:52:54 +08:00
|
|
|
return `${runtimeApiBaseUrl}${path}`
|
2026-05-07 11:50:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function apiRequest(path, options = {}) {
|
|
|
|
|
let response
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
response = await fetch(buildUrl(path), {
|
|
|
|
|
headers: {
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
...(options.headers || {})
|
|
|
|
|
},
|
|
|
|
|
...options
|
|
|
|
|
})
|
|
|
|
|
} catch {
|
2026-05-08 11:14:04 +08:00
|
|
|
throw new Error('无法连接 FastAPI 后端服务,请确认后端已启动且浏览器可访问后端端口。')
|
2026-05-07 11:50:10 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let payload = null
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
payload = await response.json()
|
|
|
|
|
} catch {
|
|
|
|
|
payload = null
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!response.ok) {
|
|
|
|
|
throw new Error(payload?.detail || '接口请求失败,请稍后重试。')
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return payload
|
|
|
|
|
}
|