// ==================== 数据获取函数(带并发锁) ==================== let mexcFetching = false; let binanceFetching = false; async function fetchFromAPI(endpoint, source, cooldownSec, silentMode = false) { if ((source === 'mexc' && mexcFetching) || (source === 'binance' && binanceFetching)) { console.warn(`[自动获取] ${source.toUpperCase()} 上一次请求尚未完成,本次跳过`); return; } if (source === 'mexc') mexcFetching = true; else binanceFetching = true; if (infoCard && !silentMode) infoCard.classList.add('hide'); const startTime = Date.now(); if (!silentMode) { startCoolDown(source, cooldownSec); } if (!silentMode) { resultDiv.innerHTML = '
⏳ 请求中,请稍候 (拉取K线数据)...
'; } try { const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 45000); const response = await fetch(endpoint, { method: 'GET', headers: { 'Accept': 'application/json' }, signal: controller.signal }); clearTimeout(timeoutId); if (!response.ok) throw new Error(`HTTP ${response.status}`); const json = await response.json(); if (!json || Object.keys(json).length === 0) throw new Error('后端返回空对象'); const elapsed = ((Date.now() - startTime) / 1000).toFixed(1); const elapsedText = `✅ 加载完毕 ${elapsed}s`; if (source === 'mexc') { mexcElapsed = elapsedText; if (!silentMode) refreshCoolDownDisplay('mexc'); } else { binanceElapsed = elapsedText; if (!silentMode) refreshCoolDownDisplay('binance'); } cachedData = json; activeSource = source; cachedDynamicMap = null; // 清除旧的动态指标缓存 // 静默预加载动态指标(不阻塞界面) preloadDynamicIndicators(); // 如果是自动获取(silentMode = true),则静默保存到服务器 if (silentMode) { saveAllDataToServer(); } if (silentMode) { console.log(`[自动获取] ${source.toUpperCase()} 数据已更新,共 ${Object.keys(json).length} 个币种,耗时 ${elapsed}s`); if (currentMode !== null && cachedData) { updateView(); } } else { resultDiv.innerHTML = `
✅ ${source.toUpperCase()} 数据加载成功!共 ${Object.keys(json).length} 个币种
请点击上方标签查看数据
`; if (currentMode !== null && cachedData) { updateView(); } } } catch (err) { console.error(err); const errMsg = `❌ 错误: ${err.message}`; if (!silentMode) { if (source === 'mexc') { if (timerMEXC) timerMEXC.textContent = errMsg; setTimeout(() => { if (mexcCountdown > 0 && timerMEXC && timerMEXC.textContent === errMsg) refreshCoolDownDisplay('mexc'); }, 3000); } else { if (timerBinance) timerBinance.textContent = errMsg; setTimeout(() => { if (binanceCountdown > 0 && timerBinance && timerBinance.textContent === errMsg) refreshCoolDownDisplay('binance'); }, 3000); } resultDiv.innerHTML = `
❌ 请求失败: ${err.message}
请检查后端 ${endpoint} 是否正常
`; } else { console.warn(`[自动获取] ${source.toUpperCase()} 获取失败: ${err.message}`); } } finally { if (source === 'mexc') mexcFetching = false; else binanceFetching = false; } }