K线后台自动刷新并通过SSE推送到前端,移除轮询
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+93
-112
@@ -4,12 +4,10 @@
|
||||
var wrapEl = chartEl && chartEl.parentElement;
|
||||
var chart = null;
|
||||
var currentPeriod = '15m';
|
||||
var quoteTimer = null;
|
||||
var klineTimer = null;
|
||||
var klineSource = null;
|
||||
var streamActive = false;
|
||||
var reconnectTimer = null;
|
||||
var lastData = null;
|
||||
var klineLoading = false;
|
||||
|
||||
var FAST_PERIODS = ['timeshare', '1m', '2m', '5m', '15m', '1h', '2h', '4h'];
|
||||
|
||||
function getSymbol() {
|
||||
var hidden = document.getElementById('market-symbol-hidden');
|
||||
@@ -59,19 +57,6 @@
|
||||
return false;
|
||||
}
|
||||
|
||||
function klinePollMs() {
|
||||
if (!isTradingSession()) return 0;
|
||||
if (currentPeriod === 'timeshare' || FAST_PERIODS.indexOf(currentPeriod) >= 0) {
|
||||
return 1000;
|
||||
}
|
||||
if (currentPeriod === 'd' || currentPeriod === 'w') return 30000;
|
||||
return 5000;
|
||||
}
|
||||
|
||||
function quotePollMs() {
|
||||
return isTradingSession() ? 1000 : 10000;
|
||||
}
|
||||
|
||||
function initChart() {
|
||||
if (!chartEl || !window.echarts) return;
|
||||
chart = echarts.init(chartEl);
|
||||
@@ -254,9 +239,7 @@
|
||||
}
|
||||
|
||||
function hideEmptyOverlay() {
|
||||
if (emptyEl) {
|
||||
emptyEl.style.display = '';
|
||||
}
|
||||
if (emptyEl) emptyEl.style.display = '';
|
||||
if (wrapEl) wrapEl.classList.add('has-data');
|
||||
}
|
||||
|
||||
@@ -272,66 +255,106 @@
|
||||
var btn = document.getElementById('market-load-btn');
|
||||
if (btn) {
|
||||
btn.disabled = on;
|
||||
btn.textContent = on ? '加载中…' : '查看';
|
||||
}
|
||||
if (on) {
|
||||
showEmptyOverlay('加载中…');
|
||||
} else if (lastData) {
|
||||
hideEmptyOverlay();
|
||||
btn.textContent = on ? '连接中…' : '查看';
|
||||
}
|
||||
if (on) showEmptyOverlay('连接中…');
|
||||
else if (lastData) hideEmptyOverlay();
|
||||
}
|
||||
|
||||
function updateRefreshHint() {
|
||||
function updateRefreshHint(disconnected) {
|
||||
var el = document.getElementById('market-refresh-hint');
|
||||
if (!el) return;
|
||||
if (!getSymbol()) {
|
||||
el.textContent = '';
|
||||
return;
|
||||
}
|
||||
if (disconnected) {
|
||||
el.textContent = 'SSE 连接中断,正在重连…';
|
||||
return;
|
||||
}
|
||||
if (!streamActive) {
|
||||
el.textContent = '';
|
||||
return;
|
||||
}
|
||||
var src = lastData && lastData.source === 'local' ? ' · 本地缓存' : '';
|
||||
if (isTradingSession()) {
|
||||
var ms = klinePollMs();
|
||||
var src = lastData && lastData.source === 'local' ? ' · 本地缓存' : '';
|
||||
el.textContent = ms === 1000
|
||||
? '交易中 · 1秒刷新' + src
|
||||
: '交易中 · 自动刷新' + src;
|
||||
el.textContent = '交易中 · 后台刷新 · SSE 推送(约1秒)' + src;
|
||||
} else {
|
||||
el.textContent = '非交易时段 · 暂停高频刷新';
|
||||
el.textContent = 'SSE 推送 · 非交易时段低频刷新' + src;
|
||||
}
|
||||
}
|
||||
|
||||
function loadKline(silent) {
|
||||
function applyQuote(data) {
|
||||
var priceEl = document.getElementById('market-quote-price');
|
||||
var nameEl = document.getElementById('market-quote-name');
|
||||
if (nameEl && data.name) nameEl.textContent = data.name + ' ' + (data.symbol || '');
|
||||
if (priceEl) {
|
||||
priceEl.textContent = data.price != null ? Number(data.price).toFixed(2) : '—';
|
||||
}
|
||||
}
|
||||
|
||||
function stopKlineStream() {
|
||||
streamActive = false;
|
||||
if (reconnectTimer) {
|
||||
clearTimeout(reconnectTimer);
|
||||
reconnectTimer = null;
|
||||
}
|
||||
if (klineSource) {
|
||||
klineSource.close();
|
||||
klineSource = null;
|
||||
}
|
||||
}
|
||||
|
||||
function scheduleReconnect() {
|
||||
if (reconnectTimer) return;
|
||||
updateRefreshHint(true);
|
||||
reconnectTimer = setTimeout(function () {
|
||||
reconnectTimer = null;
|
||||
if (getSymbol()) startKlineStream(false);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function startKlineStream(showLoading) {
|
||||
stopKlineStream();
|
||||
var symbol = getSymbol();
|
||||
if (!symbol) {
|
||||
if (!silent) alert('请先选择或输入合约代码');
|
||||
alert('请先选择或输入合约代码');
|
||||
return;
|
||||
}
|
||||
if (klineLoading) return;
|
||||
klineLoading = true;
|
||||
if (!silent) setLoading(true);
|
||||
if (showLoading) setLoading(true);
|
||||
|
||||
var url = '/api/kline?symbol=' + encodeURIComponent(symbol) + '&period=' + encodeURIComponent(currentPeriod);
|
||||
fetch(url)
|
||||
.then(function (r) {
|
||||
return r.json().then(function (j) { return { ok: r.ok, data: j }; });
|
||||
})
|
||||
.then(function (res) {
|
||||
if (!res.ok) throw new Error(res.data.error || '加载失败');
|
||||
var codes = getMarketCodes();
|
||||
var q = 'symbol=' + encodeURIComponent(symbol) +
|
||||
'&period=' + encodeURIComponent(currentPeriod);
|
||||
if (codes.market_code) q += '&market_code=' + encodeURIComponent(codes.market_code);
|
||||
if (codes.sina_code) q += '&sina_code=' + encodeURIComponent(codes.sina_code);
|
||||
|
||||
klineSource = new EventSource('/api/kline/stream?' + q);
|
||||
streamActive = true;
|
||||
updateRefreshHint(false);
|
||||
|
||||
klineSource.addEventListener('kline', function (e) {
|
||||
try {
|
||||
var data = JSON.parse(e.data);
|
||||
if (!data.bars || !data.bars.length) return;
|
||||
hideEmptyOverlay();
|
||||
renderChart(res.data, silent);
|
||||
updateQuoteMeta(res.data);
|
||||
updateRefreshHint();
|
||||
if (!quoteTimer) startQuotePoll();
|
||||
if (!klineTimer) startKlinePoll();
|
||||
})
|
||||
.catch(function (err) {
|
||||
if (!silent) {
|
||||
showEmptyOverlay(err.message || '加载失败');
|
||||
}
|
||||
})
|
||||
.finally(function () {
|
||||
klineLoading = false;
|
||||
if (!silent) setLoading(false);
|
||||
});
|
||||
renderChart(data, lastData !== null);
|
||||
updateQuoteMeta(data);
|
||||
updateRefreshHint(false);
|
||||
setLoading(false);
|
||||
} catch (err) { /* ignore */ }
|
||||
});
|
||||
|
||||
klineSource.addEventListener('quote', function (e) {
|
||||
try {
|
||||
applyQuote(JSON.parse(e.data));
|
||||
} catch (err) { /* ignore */ }
|
||||
});
|
||||
|
||||
klineSource.onerror = function () {
|
||||
stopKlineStream();
|
||||
scheduleReconnect();
|
||||
};
|
||||
}
|
||||
|
||||
function updateQuoteMeta(data) {
|
||||
@@ -341,52 +364,13 @@
|
||||
}
|
||||
var nameEl = document.getElementById('market-quote-name');
|
||||
var hiddenName = document.getElementById('market-symbol-name');
|
||||
if (nameEl) {
|
||||
if (nameEl && !(nameEl.textContent && nameEl.textContent.trim())) {
|
||||
nameEl.textContent = (hiddenName && hiddenName.value) || data.symbol || '—';
|
||||
}
|
||||
}
|
||||
|
||||
function loadQuote() {
|
||||
var codes = getMarketCodes();
|
||||
if (!codes.symbol) return;
|
||||
var q = 'symbol=' + encodeURIComponent(codes.symbol);
|
||||
if (codes.market_code) q += '&market_code=' + encodeURIComponent(codes.market_code);
|
||||
if (codes.sina_code) q += '&sina_code=' + encodeURIComponent(codes.sina_code);
|
||||
fetch('/api/market_quote?' + q)
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (data) {
|
||||
var priceEl = document.getElementById('market-quote-price');
|
||||
var nameEl = document.getElementById('market-quote-name');
|
||||
if (nameEl && data.name) nameEl.textContent = data.name + ' ' + (data.symbol || '');
|
||||
if (priceEl) {
|
||||
priceEl.textContent = data.price != null ? Number(data.price).toFixed(2) : '—';
|
||||
}
|
||||
})
|
||||
.catch(function () { /* ignore */ });
|
||||
}
|
||||
|
||||
function startQuotePoll() {
|
||||
if (quoteTimer) clearInterval(quoteTimer);
|
||||
loadQuote();
|
||||
var ms = quotePollMs();
|
||||
if (ms > 0) quoteTimer = setInterval(loadQuote, ms);
|
||||
}
|
||||
|
||||
function startKlinePoll() {
|
||||
if (klineTimer) clearInterval(klineTimer);
|
||||
var ms = klinePollMs();
|
||||
if (ms > 0 && getSymbol()) {
|
||||
klineTimer = setInterval(function () {
|
||||
loadKline(true);
|
||||
updateRefreshHint();
|
||||
}, ms);
|
||||
}
|
||||
}
|
||||
|
||||
function restartPollers() {
|
||||
startQuotePoll();
|
||||
startKlinePoll();
|
||||
updateRefreshHint();
|
||||
function loadKline(showLoading) {
|
||||
startKlineStream(showLoading);
|
||||
}
|
||||
|
||||
function shiftDataZoom(delta) {
|
||||
@@ -421,8 +405,7 @@
|
||||
tabs.querySelectorAll('.period-tab').forEach(function (el) { el.classList.remove('active'); });
|
||||
btn.classList.add('active');
|
||||
currentPeriod = btn.getAttribute('data-period') || '15m';
|
||||
restartPollers();
|
||||
if (getSymbol()) loadKline(false);
|
||||
if (getSymbol()) loadKline(true);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -444,19 +427,17 @@
|
||||
if (active) currentPeriod = active.getAttribute('data-period') || '15m';
|
||||
|
||||
var loadBtn = document.getElementById('market-load-btn');
|
||||
if (loadBtn) loadBtn.addEventListener('click', function () {
|
||||
restartPollers();
|
||||
loadKline(false);
|
||||
});
|
||||
if (loadBtn) loadBtn.addEventListener('click', function () { loadKline(true); });
|
||||
|
||||
var hidden = document.getElementById('market-symbol-hidden');
|
||||
var input = document.getElementById('market-symbol-input');
|
||||
if (hidden && hidden.value) {
|
||||
if (input && !input.value) input.value = hidden.value;
|
||||
restartPollers();
|
||||
loadKline(false);
|
||||
loadKline(true);
|
||||
} else {
|
||||
updateRefreshHint();
|
||||
updateRefreshHint(false);
|
||||
}
|
||||
|
||||
window.addEventListener('beforeunload', stopKlineStream);
|
||||
});
|
||||
})();
|
||||
|
||||
Reference in New Issue
Block a user