fix(web): 分析/自动刷新后保留 K 线视窗位置

拆分手动分析与自动刷新拉数路径;全量重建用 logical 优先恢复视窗,
增量 recent 用 scroll+barDelta;避免 barSpacing 重锚与重复冻结导致往右跳。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
jackyu66git
2026-08-25 23:38:20 +08:00
co-authored by Cursor
parent 8ee11317d3
commit 90499533fb
8 changed files with 449 additions and 310 deletions
+25 -51
View File
@@ -113,7 +113,6 @@ function updateTradingViewData(options) {
});
const newBarCount = candles.length;
const barDelta = (oldBarCount > 0 && newBarCount > 0) ? (newBarCount - oldBarCount) : 0;
const firstBarTime = newBarCount > 0 ? candles[0].time : null;
const lastBarTime = newBarCount > 0 ? candles[newBarCount - 1].time : null;
const clampedVisibleRange = clampVisibleRangeToBarTimes(savedVisibleRange, firstBarTime, lastBarTime);
@@ -336,58 +335,23 @@ function updateTradingViewData(options) {
].filter(Boolean);
const vr = clampedVisibleRange || savedVisibleRange;
const lr = savedLogicalRange;
const savedScroll = savedScrollPosition;
const mainChartRef = tvWidget.mainChart;
const viewSnap = {
logicalRange: savedLogicalRange,
visibleRange: vr,
scrollPosition: savedScrollPosition
};
const applyPosition = function (tag) {
let ok = false;
// scrollToPosition 最稳;setVisibleRange 的 to 常含右侧空白会锚到最右
if (typeof savedScroll === 'number' && mainChartRef) {
try {
const pos = savedScroll + (barDelta || 0);
mainChartRef.timeScale().scrollToPosition(pos, false);
const lrNow = mainChartRef.timeScale().getVisibleLogicalRange();
if (lrNow) {
charts.forEach(c => {
try { c.timeScale().setVisibleLogicalRange(lrNow); } catch (e) {}
});
ok = true;
console.log('🔄 恢复位置 scroll' + (tag || '') + ':', pos);
}
} catch (e) {}
}
if (!ok && lr && lr.from !== undefined && lr.to !== undefined && newBarCount > 0) {
const span = Math.max(1, lr.to - lr.from);
let to = lr.to;
let from = lr.from;
const maxTo = newBarCount - 1 + 8;
if (to > maxTo) {
to = maxTo;
from = to - span;
}
if (from < -8) {
from = -8;
to = from + span;
}
const clamped = { from: from, to: to };
charts.forEach(c => {
try {
c.timeScale().setVisibleLogicalRange(clamped);
ok = true;
} catch (e) {}
});
if (ok) console.log('🔄 恢复位置 logical' + (tag || '') + ':', clamped);
}
if (!ok && vr && vr.from !== undefined && vr.to !== undefined) {
charts.forEach(c => {
try {
c.timeScale().setVisibleRange(vr);
ok = true;
} catch (e) {}
});
if (ok) console.log('🔄 恢复位置 time' + (tag || '') + ':', vr);
}
if (typeof restoreChartViewState !== 'function') return;
restoreChartViewState(charts, viewSnap, {
incremental: true,
skipBarSpacing: true,
oldBarCount: oldBarCount,
newBarCount: newBarCount,
firstBarTime: firstBarTime,
lastBarTime: lastBarTime
});
if (tag) console.log('🔄 恢复位置' + tag);
};
const finishPreserve = function () {
@@ -404,8 +368,18 @@ function updateTradingViewData(options) {
applyPosition('');
setTimeout(function () { applyPosition('@0'); }, 0);
setTimeout(function () { applyPosition('@50'); }, 50);
// 增量 setData 常不触发可见时间范围回调,但价格轴会变:补刷分型竖边
var bumpFxVert = function () {
if (typeof window._redrawFxBoxVerticalOverlay === 'function') {
window._redrawFxBoxVerticalOverlay();
}
};
bumpFxVert();
setTimeout(bumpFxVert, 0);
setTimeout(bumpFxVert, 50);
setTimeout(function () {
applyPosition('@150');
bumpFxVert();
finishPreserve();
}, 150);
} else {