Fix chart viewport regressions from tail refresh and period switch.

Remove pendingViewportEpoch, fetch only 30 tail bars on poll, restore wasViewingTail logic, and fix left-scroll range shift from actual merge delta.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
dekun
2026-06-08 08:18:06 +08:00
parent 7ea51818f1
commit 35088be097
2 changed files with 25 additions and 15 deletions
+3
View File
@@ -652,6 +652,7 @@ def api_chart_ohlcv(
symbol: str = "", symbol: str = "",
timeframe: str = "1d", timeframe: str = "1d",
refresh: str = "", refresh: str = "",
tail: str = "",
limit: int = 0, limit: int = 0,
before_ms: str = "", before_ms: str = "",
): ):
@@ -665,6 +666,7 @@ def api_chart_ohlcv(
raise HTTPException(status_code=400, detail="请输入币种") raise HTTPException(status_code=400, detail="请输入币种")
ex_key = str(ex.get("key") or "").strip().lower() ex_key = str(ex.get("key") or "").strip().lower()
force = (refresh or "").strip().lower() in ("1", "true", "yes", "on") force = (refresh or "").strip().lower() in ("1", "true", "yes", "on")
tail_refresh = (tail or "").strip().lower() in ("1", "true", "yes", "on")
lim = int(limit) if int(limit or 0) > 0 else None lim = int(limit) if int(limit or 0) > 0 else None
bms_raw = (before_ms or "").strip() bms_raw = (before_ms or "").strip()
bms = None bms = None
@@ -690,6 +692,7 @@ def api_chart_ohlcv(
timeframe, timeframe,
remote_fetch, remote_fetch,
force_refresh=force, force_refresh=force,
tail_refresh=tail_refresh,
limit=lim, limit=lim,
before_ms=bms, before_ms=bms,
) )
+22 -15
View File
@@ -178,7 +178,7 @@
let chartRangeUserLocked = false; let chartRangeUserLocked = false;
let chartRangeLockTimer = null; let chartRangeLockTimer = null;
let suppressRangeUserLock = false; let suppressRangeUserLock = false;
let pendingViewportEpoch = -1; const CHART_TAIL_REFRESH_LIMIT = 30;
let priceTagTimer = null; let priceTagTimer = null;
let tfDigitBuf = ""; let tfDigitBuf = "";
let tfDigitTimer = null; let tfDigitTimer = null;
@@ -2033,7 +2033,6 @@
} }
function markChartRangeUserAdjusted() { function markChartRangeUserAdjusted() {
pendingViewportEpoch = -1;
chartRangeUserLocked = true; chartRangeUserLocked = true;
if (chartRangeLockTimer) clearTimeout(chartRangeLockTimer); if (chartRangeLockTimer) clearTimeout(chartRangeLockTimer);
chartRangeLockTimer = setTimeout(function () { chartRangeLockTimer = setTimeout(function () {
@@ -2168,6 +2167,7 @@
}); });
if (params.before_ms) qs.set("before_ms", String(params.before_ms)); if (params.before_ms) qs.set("before_ms", String(params.before_ms));
if (params.refresh) qs.set("refresh", "1"); if (params.refresh) qs.set("refresh", "1");
if (params.tail) qs.set("tail", "1");
const r = await fetch("/api/chart/ohlcv?" + qs.toString(), { credentials: "same-origin" }); const r = await fetch("/api/chart/ohlcv?" + qs.toString(), { credentials: "same-origin" });
const data = await r.json(); const data = await r.json();
if (!r.ok) { if (!r.ok) {
@@ -2197,8 +2197,10 @@
if (data.exhausted) exhaustedLeft = true; if (data.exhausted) exhaustedLeft = true;
const incoming = alignCandlesToTick(data.candles || []); const incoming = alignCandlesToTick(data.candles || []);
if (!incoming.length) return; if (!incoming.length) return;
const shift = incoming.length; const prevLen = lastCandles.length;
applyCandlesToChart(mergeCandles(lastCandles, incoming, { prepend: true }), shift); const merged = mergeCandles(lastCandles, incoming, { prepend: true });
const shift = merged.length - prevLen;
applyCandlesToChart(merged, shift);
if (elStatus && !elStatus.classList.contains("err")) { if (elStatus && !elStatus.classList.contains("err")) {
elStatus.textContent = elStatus.textContent =
"已加载 " + "已加载 " +
@@ -2230,12 +2232,15 @@
const candleCountBefore = lastCandles.length; const candleCountBefore = lastCandles.length;
let savedRange = null; let savedRange = null;
if (chart) savedRange = chart.timeScale().getVisibleLogicalRange(); if (chart) savedRange = chart.timeScale().getVisibleLogicalRange();
const wasViewingTail =
!savedRange || isViewingChartTail(savedRange, candleCountBefore);
try { try {
const data = await fetchChartChunk({ const data = await fetchChartChunk({
exchange_key: exKey, exchange_key: exKey,
symbol: sym, symbol: sym,
timeframe: tf, timeframe: tf,
limit: chartChunkLimit(tf), limit: CHART_TAIL_REFRESH_LIMIT,
tail: true,
}); });
if (myToken !== loadToken) return; if (myToken !== loadToken) return;
if (vKey !== lastViewKey) return; if (vKey !== lastViewKey) return;
@@ -2250,25 +2255,28 @@
applyChartPriceFormat(); applyChartPriceFormat();
} }
} }
const keepViewport = const shouldPreserve =
pendingViewportEpoch !== chartViewEpoch &&
savedRange && savedRange &&
isVisibleRangeValidForCandles(savedRange, candleCountBefore); isVisibleRangeValidForCandles(savedRange, candleCountBefore) &&
(chartRangeUserLocked || !wasViewingTail);
applyCandlesToChart( applyCandlesToChart(
mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }), mergeCandles(lastCandles, alignCandlesToTick(data.candles), { prepend: false }),
0, 0,
{ {
preserveRange: !!keepViewport, preserveRange: !!shouldPreserve,
skipAutoScale: chartRangeUserLocked, skipAutoScale: chartRangeUserLocked,
} }
); );
if (epochAtStart !== chartViewEpoch) return; if (epochAtStart !== chartViewEpoch) return;
const n = lastCandles.length; const n = lastCandles.length;
if (pendingViewportEpoch === chartViewEpoch) { const minorTailUpdate = Math.abs(n - candleCountBefore) <= CHART_TAIL_REFRESH_LIMIT + 5;
applyDefaultVisibleRange(); if (wasViewingTail && !chartRangeUserLocked) {
} else if (savedRange && isVisibleRangeValidForCandles(savedRange, n)) { restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n);
restoreVisibleLogicalRange(savedRange, n); } else if (shouldPreserve && minorTailUpdate) {
} else if (!chartRangeUserLocked) { if (!restoreVisibleLogicalRange(savedRange, n) && !chartRangeUserLocked) {
restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n);
}
} else if (!restoreVisibleLogicalRange(savedRange, n) && !chartRangeUserLocked) {
const curRange = chart && chart.timeScale().getVisibleLogicalRange(); const curRange = chart && chart.timeScale().getVisibleLogicalRange();
if (!curRange || !isVisibleRangeValidForCandles(curRange, n)) { if (!curRange || !isVisibleRangeValidForCandles(curRange, n)) {
restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n); restoreVisibleLogicalRange(tailVisibleLogicalRange(n), n);
@@ -2525,7 +2533,6 @@
chartDataLoading = true; chartDataLoading = true;
if (resetView) { if (resetView) {
chartViewEpoch += 1; chartViewEpoch += 1;
pendingViewportEpoch = chartViewEpoch;
chartRangeUserLocked = false; chartRangeUserLocked = false;
if (chartRangeLockTimer) { if (chartRangeLockTimer) {
clearTimeout(chartRangeLockTimer); clearTimeout(chartRangeLockTimer);