const API_BASE_STORAGE_KEY = 'x-financial-api-base-url' function normalizeApiBaseUrl(value) { return String(value || '/api/v1').replace(/\/$/, '') } 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 } function readStoredApiBaseUrl() { if (typeof window === 'undefined') { return '' } return resolveBrowserReachableApiBaseUrl(window.localStorage.getItem(API_BASE_STORAGE_KEY) || '') } let runtimeApiBaseUrl = normalizeApiBaseUrl('/api/v1') if (typeof window !== 'undefined') { window.localStorage.removeItem(API_BASE_STORAGE_KEY) } export function setRuntimeApiBaseUrl(value) { runtimeApiBaseUrl = resolveBrowserReachableApiBaseUrl(value) if (typeof window !== 'undefined') { window.localStorage.setItem(API_BASE_STORAGE_KEY, runtimeApiBaseUrl) } } export function getRuntimeApiBaseUrl() { return runtimeApiBaseUrl } function buildUrl(path) { if (!path.startsWith('/')) { return `${runtimeApiBaseUrl}/${path}` } return `${runtimeApiBaseUrl}${path}` } export async function apiRequest(path, options = {}) { let response try { response = await fetch(buildUrl(path), { headers: { 'Content-Type': 'application/json', ...(options.headers || {}) }, ...options }) } catch { throw new Error('无法连接 FastAPI 后端服务,请确认后端已启动且浏览器可访问后端端口。') } let payload = null try { payload = await response.json() } catch { payload = null } if (!response.ok) { throw new Error(payload?.detail || '接口请求失败,请稍后重试。') } return payload }