const API_KEY = 'your-external-api-key'; const API_BASE = '/api/v1'; const REQUEST_TIMEOUT = 30000; let pendingRequests = 0; let loadingToast = null; let loadingTimer = null; function showNonBlockingLoading(message = '加载中...') { pendingRequests++; updateLoadingUI(message); if (loadingTimer) clearTimeout(loadingTimer); loadingTimer = setTimeout(() => { console.warn('Some requests may be stuck, force clearing'); }, 60000); } function hideNonBlockingLoading() { pendingRequests = Math.max(0, pendingRequests - 1); updateLoadingUI(pendingRequests > 0 ? '处理中...' : null); } function updateLoadingUI(message) { if (!message) { if (loadingToast) { loadingToast.remove(); loadingToast = null; } return; } if (!loadingToast) { loadingToast = document.createElement('div'); loadingToast.id = 'loading-toast'; loadingToast.style.cssText = ` position: fixed; top: 20px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.8); color: white; padding: 12px 24px; border-radius: 8px; font-size: 14px; z-index: 10000; display: flex; align-items: center; gap: 10px; `; document.body.appendChild(loadingToast); } const spinner = '
'; const style = ''; if (!document.getElementById('loading-spinner-style')) { document.head.insertAdjacentHTML('beforeend', style); } loadingToast.innerHTML = spinner + `${message}(${pendingRequests})`; } async function api(endpoint, options = {}) { const method = options.method || 'GET'; const isMutation = ['POST', 'PUT', 'DELETE'].includes(method); const startMsg = `请求${isMutation ? '修改' : '获取'}中...`; showNonBlockingLoading(startMsg); const controller = new AbortController(); const timeoutId = setTimeout(() => { controller.abort(); showToast('请求超时,请稍后重试', 'error'); }, REQUEST_TIMEOUT); try { const headers = { 'X-API-Key': API_KEY, 'Content-Type': 'application/json', ...options.headers }; const response = await fetch(API_BASE + endpoint, { ...options, headers, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) { const error = `HTTP ${response.status}: ${response.statusText}`; if (isMutation) { showToast('操作失败: ' + error, 'error'); } return { success: false, error }; } const data = await response.json(); if (isMutation && data.success === false && data.error) { showToast(data.error, 'error'); } return data; } catch (err) { clearTimeout(timeoutId); if (err.name === 'AbortError') { return { success: false, error: '请求超时' }; } const errorMsg = err.message || '网络错误'; if (isMutation) { showToast(errorMsg, 'error'); } return { success: false, error: errorMsg }; } finally { hideNonBlockingLoading(); } } const apiClient = { get: (endpoint) => api(endpoint), post: (endpoint, data) => api(endpoint, { method: 'POST', body: JSON.stringify(data) }), put: (endpoint, data) => api(endpoint, { method: 'PUT', body: JSON.stringify(data) }), delete: (endpoint) => api(endpoint, { method: 'DELETE' }), nodes: { list: () => api('/nodes/'), get: (name) => api(`/nodes/${name}`), create: (data) => api('/nodes/', { method: 'POST', body: JSON.stringify(data) }), update: (name, data) => api(`/nodes/${name}`, { method: 'PUT', body: JSON.stringify(data) }), delete: (name) => api(`/nodes/${name}`, { method: 'DELETE' }), status: (name) => api(`/nodes/${name}/status`) }, messages: { send: (data) => api('/messages/send', { method: 'POST', body: JSON.stringify(data) }), broadcast: (data) => api('/messages/broadcast', { method: 'POST', body: JSON.stringify(data) }) }, config: { getMonitor: () => api('/config/monitor'), updateMonitor: (data) => api('/config/monitor', { method: 'POST', body: JSON.stringify(data) }), getWebhook: () => api('/config/webhook'), updateWebhook: (data) => api('/config/webhook', { method: 'POST', body: JSON.stringify(data) }) }, logs: { recent: (count = 50) => api(`/logs/recent?count=${count}`) } }; window.api = api; window.apiClient = apiClient;